C#调用工业相机入门:USB/网口相机图像采集的基础实现 工业相机是机器视觉系统的核心组件广泛应用于缺陷检测、尺寸测量、条码识别、产品定位等工业场景。对于C#工业上位机开发者来说掌握工业相机的调用方法是必备技能。很多新手在刚接触工业相机时会被各种厂商SDK、不同接口类型搞得晕头转向。本文将从最基础的原理讲起详细讲解USB和GigE网口这两种最主流工业相机的C#调用方法所有代码均经过实际验证可直接用于项目开发。工业相机基础与选型在开始写代码之前我们先了解一下工业相机的基本分类和核心参数这对后续的开发和选型非常重要。主流接口类型对比目前工业现场最常用的是USB3.0和GigE网口两种接口的相机它们的优缺点和适用场景如下接口类型传输带宽最大传输距离供电方式易用性适用场景USB3.05Gbps5米USB总线供电高近距离、小体积设备、移动检测GigE1Gbps100米网线单独供电/PoE中远距离、多相机系统、工业现场核心参数解读分辨率决定图像的清晰度常见的有130万、200万、500万像素帧率每秒采集的图像数量决定系统的检测速度快门类型全局快门适合拍摄高速运动物体卷帘快门成本更低像素深度通常为8位灰度或24位彩色整体开发流程无论使用哪种接口的相机其调用流程基本一致都遵循连接-配置-采集-显示-释放的标准流程。枚举相机设备选择并连接相机配置相机参数设置采集模式启动采集接收图像数据图像格式转换显示/保存/处理停止采集释放资源USB相机通用实现DirectShow对于USB相机最通用的方式是使用DirectShow接口。几乎所有的USB工业相机和普通摄像头都支持DirectShow不需要安装任何厂商SDK非常适合入门和快速开发。环境搭建我们使用AForge.NET库它对DirectShow进行了很好的封装使用起来非常简单。Install-Package AForge.Video Install-Package AForge.Video.DirectShow核心代码实现publicclassUsbCamera:IDisposable{privateVideoCaptureDevice_videoDevice;privateBitmap_currentFrame;privatereadonlyobject_frameLocknewobject();// 枚举所有可用的USB相机publicstaticListstringEnumerateCameras(){varcamerasnewListstring();varvideoDevicesnewFilterInfoCollection(FilterCategory.VideoInputDevice);foreach(FilterInfodeviceinvideoDevices){cameras.Add(device.Name);}returncameras;}// 连接相机publicvoidConnect(intcameraIndex){varvideoDevicesnewFilterInfoCollection(FilterCategory.VideoInputDevice);if(cameraIndex0||cameraIndexvideoDevices.Count)thrownewArgumentException(无效的相机索引);_videoDevicenewVideoCaptureDevice(videoDevices[cameraIndex].MonikerString);// 设置分辨率和帧率根据相机支持的参数选择_videoDevice.VideoResolution_videoDevice.VideoCapabilities[0];// 注册新帧事件_videoDevice.NewFrameVideoDevice_NewFrame;}// 启动采集publicvoidStart(){if(_videoDevicenull||_videoDevice.IsRunning)return;_videoDevice.Start();}// 停止采集publicvoidStop(){if(_videoDevicenull||!_videoDevice.IsRunning)return;_videoDevice.SignalToStop();_videoDevice.WaitForStop();}// 获取当前帧publicBitmapGetCurrentFrame(){lock(_frameLock){return_currentFrame?.Clone()asBitmap;}}privatevoidVideoDevice_NewFrame(objectsender,NewFrameEventArgseventArgs){lock(_frameLock){_currentFrame?.Dispose();_currentFrame(Bitmap)eventArgs.Frame.Clone();}}publicvoidDispose(){Stop();_videoDevice?.NewFrame-VideoDevice_NewFrame;_currentFrame?.Dispose();}}使用示例WinFormprivateUsbCamera_camera;privatevoidForm1_Load(objectsender,EventArgse){// 枚举相机并添加到下拉框varcamerasUsbCamera.EnumerateCameras();comboBoxCameras.Items.AddRange(cameras.ToArray());if(cameras.Count0)comboBoxCameras.SelectedIndex0;}privatevoidbtnStart_Click(objectsender,EventArgse){_cameranewUsbCamera();_camera.Connect(comboBoxCameras.SelectedIndex);_camera.Start();// 定时器刷新显示timerDisplay.Start();}privatevoidtimerDisplay_Tick(objectsender,EventArgse){varframe_camera.GetCurrentFrame();if(frame!null){pictureBox.Image?.Dispose();pictureBox.Imageframe;}}常见问题图像卡顿不要在NewFrame事件中直接操作UI控件应该使用定时器异步刷新内存泄漏一定要及时释放Bitmap对象否则会导致内存快速增长分辨率设置必须选择相机支持的分辨率否则会抛出异常GigE网口相机实现海康MVS SDKGigE网口相机是工业现场的主流选择几乎所有的工业相机厂商都提供了自己的SDK。这里以国内最常用的海康威视MVS SDK为例讲解网口相机的调用方法。环境搭建下载并安装海康威视MVS客户端软件在项目中引用MVS SDK提供的MvCameraControl.Net.dll将SDK目录下的所有dll文件复制到项目输出目录核心代码实现publicclassHikGigeCamera:IDisposable{privateIntPtr_handleIntPtr.Zero;privateBitmap_currentFrame;privatereadonlyobject_frameLocknewobject();privatebool_isGrabbingfalse;// 枚举所有可用的GigE相机publicstaticListstringEnumerateCameras(){varcamerasnewListstring();vardeviceListnewMV_CC_DEVICE_INFO_LIST();varstatusMvCameraControl.MV_CC_EnumDevices_NET(MvCameraControl.MV_GIGE_DEVICE,refdeviceList);if(status!MvCameraControl.MV_OK)thrownewException($枚举相机失败错误码{status});for(inti0;ideviceList.nDeviceNum;i){vardeviceInfo(MV_CC_DEVICE_INFO)Marshal.PtrToStructure(deviceList.pDeviceInfo[i],typeof(MV_CC_DEVICE_INFO));varip${deviceInfo.SpecialInfo.stGigEInfo.nCurrentIp0xFF}.${(deviceInfo.SpecialInfo.stGigEInfo.nCurrentIp8)0xFF}.${(deviceInfo.SpecialInfo.stGigEInfo.nCurrentIp16)0xFF}.${(deviceInfo.SpecialInfo.stGigEInfo.nCurrentIp24)0xFF};cameras.Add(${deviceInfo.SpecialInfo.stGigEInfo.chModelName}({ip}));}returncameras;}// 连接相机publicvoidConnect(intcameraIndex){vardeviceListnewMV_CC_DEVICE_INFO_LIST();MvCameraControl.MV_CC_EnumDevices_NET(MvCameraControl.MV_GIGE_DEVICE,refdeviceList);if(cameraIndex0||cameraIndexdeviceList.nDeviceNum)thrownewArgumentException(无效的相机索引);// 创建句柄varstatusMvCameraControl.MV_CC_CreateDevice_NET(ref_handle,deviceList.pDeviceInfo[cameraIndex]);if(status!MvCameraControl.MV_OK)thrownewException($创建设备失败错误码{status});// 打开设备statusMvCameraControl.MV_CC_OpenDevice_NET(_handle);if(status!MvCameraControl.MV_OK)thrownewException($打开设备失败错误码{status});// 注册图像回调statusMvCameraControl.MV_CC_RegisterImageCallBack_NET(_handle,ImageCallBack,IntPtr.Zero);if(status!MvCameraControl.MV_OK)thrownewException($注册回调失败错误码{status});}// 设置触发模式publicvoidSetTriggerMode(boolenable){varmodeenable?1:0;MvCameraControl.MV_CC_SetEnumValue_NET(_handle,TriggerMode,(uint)mode);if(enable){// 设置触发源为软触发MvCameraControl.MV_CC_SetEnumValue_NET(_handle,TriggerSource,7);}}// 软触发一次publicvoidTriggerOnce(){if(!_isGrabbing)return;MvCameraControl.MV_CC_TriggerSoftwareExecute_NET(_handle);}// 启动采集publicvoidStartGrabbing(){if(_handleIntPtr.Zero||_isGrabbing)return;varstatusMvCameraControl.MV_CC_StartGrabbing_NET(_handle);if(statusMvCameraControl.MV_OK)_isGrabbingtrue;}// 停止采集publicvoidStopGrabbing(){if(_handleIntPtr.Zero||!_isGrabbing)return;MvCameraControl.MV_CC_StopGrabbing_NET(_handle);_isGrabbingfalse;}// 获取当前帧publicBitmapGetCurrentFrame(){lock(_frameLock){return_currentFrame?.Clone()asBitmap;}}privatevoidImageCallBack(IntPtrpData,refMV_FRAME_OUT_INFO_EXpFrameInfo,IntPtrpUser){lock(_frameLock){_currentFrame?.Dispose();// 将原始数据转换为Bitmapvardatanewbyte[pFrameInfo.nFrameLen];Marshal.Copy(pData,data,0,(int)pFrameInfo.nFrameLen);unsafe{fixed(byte*pDataPtrdata){_currentFramenewBitmap((int)pFrameInfo.nWidth,(int)pFrameInfo.nHeight,(int)pFrameInfo.nWidth*3,System.Drawing.Imaging.PixelFormat.Format24bppRgb,(IntPtr)pDataPtr);}}}}publicvoidDispose(){StopGrabbing();if(_handle!IntPtr.Zero){MvCameraControl.MV_CC_CloseDevice_NET(_handle);MvCameraControl.MV_CC_DestroyDevice_NET(_handle);_handleIntPtr.Zero;}_currentFrame?.Dispose();}}关键注意事项SDK版本匹配必须使用与MVS客户端相同版本的SDK否则会出现各种奇怪的问题网络配置GigE相机需要与工控机在同一网段建议使用静态IP巨帧设置为了提高传输效率建议在网卡属性中开启巨帧Jumbo Frame多线程安全图像回调是在SDK的线程中执行的访问共享资源时必须加锁图像显示与保存WinForm显示如前面的示例所示使用PictureBox控件显示图像即可。需要注意的是不要在UI线程中进行耗时的图像处理操作。WPF显示在WPF中显示Bitmap需要进行格式转换publicstaticBitmapSourceConvertToBitmapSource(Bitmapbitmap){varbitmapDatabitmap.LockBits(newRectangle(0,0,bitmap.Width,bitmap.Height),ImageLockMode.ReadOnly,bitmap.PixelFormat);varbitmapSourceBitmapSource.Create(bitmapData.Width,bitmapData.Height,bitmap.HorizontalResolution,bitmap.VerticalResolution,PixelFormats.Bgr24,null,bitmapData.Scan0,bitmapData.Stride*bitmapData.Height,bitmapData.Stride);bitmap.UnlockBits(bitmapData);returnbitmapSource;}图像保存publicvoidSaveImage(stringpath,Bitmapimage){image.Save(path,ImageFormat.Bmp);}工业级稳定性优化从demo到生产环境还需要进行以下几个方面的优化多线程采集将相机采集和图像处理放在单独的线程中避免阻塞UI线程异常处理为所有相机操作添加try-catch块记录详细的异常信息自动重连当相机连接断开时系统能够自动尝试重连内存管理及时释放所有IDisposable对象避免内存泄漏心跳检测定期检测相机的连接状态及时发现设备故障常见问题排查相机连接不上检查相机电源和网线是否连接正常检查工控机和相机是否在同一网段关闭防火墙或添加例外重启相机和工控机图像不显示检查是否调用了StartGrabbing方法检查图像回调是否正常触发检查图像格式是否正确采集卡顿或丢帧开启网卡巨帧降低相机分辨率或帧率优化图像处理代码使用性能更好的工控机内存泄漏确保所有Bitmap对象都被正确释放不要在循环中创建大量临时对象使用内存分析工具定位泄漏点总结本文详细讲解了USB和GigE网口两种主流工业相机的C#调用方法从基础原理到核心代码再到常见问题排查覆盖了入门开发的所有环节。需要注意的是不同厂商的SDK虽然API不同但调用流程基本一致。掌握了海康SDK的使用方法再去学习其他厂商如Basler、大华、大恒的SDK就会非常容易。在实际项目中我们还需要结合具体的应用场景进行相机参数优化、图像处理算法开发和系统稳定性测试才能打造出真正可用的工业视觉系统。