前言随着工业自动化与机器视觉技术的快速发展图像采集设备在高精度检测、自动化控制、安防监控等领域的应用日益广泛。海康威视Hikvision作为行业领先的安防设备制造商其高性能工业相机产品及配套的软件开发者工具包Software Development Kit简称 SDK为程序开发者提供了强大的硬件控制能力和丰富的图像采集接口。在进行图像处理类应用程序特别是需要高度定制化的视觉系统项目开发时利用海康威视官方提供的底层SDK进行二次开发能够实现对相机关键参数如曝光时间、增益、触发模式、采集分辨率、ROI区域等的精确控制直接获取原始图像数据流并支持多种网络传输协议如USB3 Vision, GigE Vision。本示例主要围绕海康SDK在.NET环境C#语言下的集成与二次开发实践进行阐述。C#凭借其丰富的框架如Windows窗体-WinForms、Windows Presentation Foundation-WPF及标准的类库支持结合面向对象的开发特性能够高效地实现对海康相机各项高级功能的调用和管理构建直观的用户操作界面如实时视频预览窗口、参数调节面板、状态监控面板、图像保存及处理等。通过本简介及后续相关的示例代码与开发经验分享希望能为项目中基于海康工业相机SDK进行C#二次开发的工作提供基础起点指导工程师快速熟悉SDK结构及接口调用流程同时适配项目实际需求完成功能定制工作。在二次开发过程中请务必谨慎阅读海康官网发布的最新SDK文档开发环境参考信息SDK版本号示例MVS_{版本号}开发工具示例Visual Studio 2022相机型号适用性USB3.0工业相机系列/GigE接口工业相机系列初始化注意项集成开发中初始阶段主要包含SDK动态链接库DLL的引用设置如MvCameraControl.Net.dll设备连接初始化网络协议接入/设备索引枚举相机资源状态检查与资源占用管理策略调试参考主要包括SDK日志系统的初始化位置设定异常监控管理网络状态与中断重连机制预备示例代码标注简要示例若有代码需作标注可用于高速采集模式设定初始接口示例// 初始化设备上下文 int nRet MV_CC_CreateDevice_NET(ref device); if (MV_SDK.ErrorCode.MV_OK ! nRet) { MessageBox.Show($初始化失败,错误码[{nRet:X8}]); return false; }Dll一般在MVS的文件夹里找到对应的系统然后放到对应项目的Debug下界面示例:using MvCamCtrl.NET; using OpenCvSharp; using System; using System.Drawing; using System.Drawing.Imaging; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; using OpenCvSharp.Extensions; namespace HikvisionUserControl { public class HKCameraControl : UserControl { private MyCamera _camera null; private MyCamera.MV_CC_DEVICE_INFO_LIST _deviceList; private ComboBox cmbCameraList; private Button btnFindCamera, btnConnect, btnDisconnect; private Button btnStartPreview, btnStopPreview, btnSaveImage; private PictureBox pictureBoxPreview; private Label lblStatus; private MyCamera.cbOutputExdelegate _imageCallback; public bool IsPreviewing _isGrabbing; private bool _isConnected false; private bool _isGrabbing false; private Bitmap _currentFrame null; private readonly object _frameLock new object(); private Panel panel1; private Panel panel2; private Label label1; private Label label2; private Panel panel3; private SynchronizationContext _syncContext; public HKCameraControl() { InitializeComponent(); _syncContext SynchronizationContext.Current ?? new SynchronizationContext(); this.Resize HKCameraControl_Resize; } private void InitializeComponent() { this.cmbCameraList new System.Windows.Forms.ComboBox(); this.btnFindCamera new System.Windows.Forms.Button(); this.btnConnect new System.Windows.Forms.Button(); this.btnDisconnect new System.Windows.Forms.Button(); this.btnStartPreview new System.Windows.Forms.Button(); this.btnStopPreview new System.Windows.Forms.Button(); this.btnSaveImage new System.Windows.Forms.Button(); this.pictureBoxPreview new System.Windows.Forms.PictureBox(); this.lblStatus new System.Windows.Forms.Label(); this.panel1 new System.Windows.Forms.Panel(); this.label1 new System.Windows.Forms.Label(); this.panel2 new System.Windows.Forms.Panel(); this.label2 new System.Windows.Forms.Label(); this.panel3 new System.Windows.Forms.Panel(); ((System.ComponentModel.ISupportInitialize)(this.pictureBoxPreview)).BeginInit(); this.panel1.SuspendLayout(); this.panel2.SuspendLayout(); this.panel3.SuspendLayout(); this.SuspendLayout(); // // cmbCameraList // this.cmbCameraList.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(60)))), ((int)(((byte)(65))))); this.cmbCameraList.DrawMode System.Windows.Forms.DrawMode.OwnerDrawFixed; this.cmbCameraList.DropDownHeight 150; this.cmbCameraList.DropDownStyle System.Windows.Forms.ComboBoxStyle.DropDownList; this.cmbCameraList.FlatStyle System.Windows.Forms.FlatStyle.Flat; this.cmbCameraList.Font new System.Drawing.Font(微软雅黑, 9F); this.cmbCameraList.ForeColor System.Drawing.Color.White; this.cmbCameraList.IntegralHeight false; this.cmbCameraList.Location new System.Drawing.Point(119, 30); this.cmbCameraList.Name cmbCameraList; this.cmbCameraList.Size new System.Drawing.Size(220, 28); this.cmbCameraList.TabIndex 1; this.cmbCameraList.DrawItem new System.Windows.Forms.DrawItemEventHandler(this.cmbCameraList_DrawItem_1); // // btnFindCamera // this.btnFindCamera.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204))))); this.btnFindCamera.FlatAppearance.BorderSize 0; this.btnFindCamera.FlatStyle System.Windows.Forms.FlatStyle.Flat; this.btnFindCamera.Font new System.Drawing.Font(微软雅黑, 9F, System.Drawing.FontStyle.Bold); this.btnFindCamera.ForeColor System.Drawing.Color.White; this.btnFindCamera.Location new System.Drawing.Point(11, 27); this.btnFindCamera.Name btnFindCamera; this.btnFindCamera.Size new System.Drawing.Size(100, 32); this.btnFindCamera.TabIndex 0; this.btnFindCamera.Text 查找相机; this.btnFindCamera.UseVisualStyleBackColor false; this.btnFindCamera.Click new System.EventHandler(this.btnFindCamera_Click_1); // // btnConnect // this.btnConnect.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(150)))), ((int)(((byte)(80))))); this.btnConnect.FlatAppearance.BorderSize 0; this.btnConnect.FlatStyle System.Windows.Forms.FlatStyle.Flat; this.btnConnect.Font new System.Drawing.Font(微软雅黑, 9F, System.Drawing.FontStyle.Bold); this.btnConnect.ForeColor System.Drawing.Color.White; this.btnConnect.Location new System.Drawing.Point(349, 27); this.btnConnect.Name btnConnect; this.btnConnect.Size new System.Drawing.Size(90, 32); this.btnConnect.TabIndex 2; this.btnConnect.Text 连接相机; this.btnConnect.UseVisualStyleBackColor false; this.btnConnect.Click new System.EventHandler(this.btnConnect_Click_1); // // btnDisconnect // this.btnDisconnect.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(57)))), ((int)(((byte)(43))))); this.btnDisconnect.FlatAppearance.BorderSize 0; this.btnDisconnect.FlatStyle System.Windows.Forms.FlatStyle.Flat; this.btnDisconnect.Font new System.Drawing.Font(微软雅黑, 9F, System.Drawing.FontStyle.Bold); this.btnDisconnect.ForeColor System.Drawing.Color.White; this.btnDisconnect.Location new System.Drawing.Point(449, 27); this.btnDisconnect.Name btnDisconnect; this.btnDisconnect.Size new System.Drawing.Size(80, 32); this.btnDisconnect.TabIndex 3; this.btnDisconnect.Text 断开; this.btnDisconnect.UseVisualStyleBackColor false; this.btnDisconnect.Click new System.EventHandler(this.btnDisconnect_Click_1); // // btnStartPreview // this.btnStartPreview.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204))))); this.btnStartPreview.FlatAppearance.BorderSize 0; this.btnStartPreview.FlatStyle System.Windows.Forms.FlatStyle.Flat; this.btnStartPreview.Font new System.Drawing.Font(微软雅黑, 9F, System.Drawing.FontStyle.Bold); this.btnStartPreview.ForeColor System.Drawing.Color.White; this.btnStartPreview.Location new System.Drawing.Point(539, 27); this.btnStartPreview.Name btnStartPreview; this.btnStartPreview.Size new System.Drawing.Size(90, 32); this.btnStartPreview.TabIndex 4; this.btnStartPreview.Text 开始预览; this.btnStartPreview.UseVisualStyleBackColor false; this.btnStartPreview.Click new System.EventHandler(this.btnStartPreview_Click_1); // // btnStopPreview // this.btnStopPreview.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(169)))), ((int)(((byte)(68)))), ((int)(((byte)(66))))); this.btnStopPreview.FlatAppearance.BorderSize 0; this.btnStopPreview.FlatStyle System.Windows.Forms.FlatStyle.Flat; this.btnStopPreview.Font new System.Drawing.Font(微软雅黑, 9F, System.Drawing.FontStyle.Bold); this.btnStopPreview.ForeColor System.Drawing.Color.White; this.btnStopPreview.Location new System.Drawing.Point(639, 27); this.btnStopPreview.Name btnStopPreview; this.btnStopPreview.Size new System.Drawing.Size(90, 32); this.btnStopPreview.TabIndex 5; this.btnStopPreview.Text 停止预览; this.btnStopPreview.UseVisualStyleBackColor false; this.btnStopPreview.Click new System.EventHandler(this.btnStopPreview_Click_1); // // btnSaveImage // this.btnSaveImage.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204))))); this.btnSaveImage.FlatAppearance.BorderSize 0; this.btnSaveImage.FlatStyle System.Windows.Forms.FlatStyle.Flat; this.btnSaveImage.Font new System.Drawing.Font(微软雅黑, 9F, System.Drawing.FontStyle.Bold); this.btnSaveImage.ForeColor System.Drawing.Color.White; this.btnSaveImage.Location new System.Drawing.Point(739, 27); this.btnSaveImage.Name btnSaveImage; this.btnSaveImage.Size new System.Drawing.Size(90, 32); this.btnSaveImage.TabIndex 6; this.btnSaveImage.Text 保存图片; this.btnSaveImage.UseVisualStyleBackColor false; this.btnSaveImage.Click new System.EventHandler(this.btnSaveImage_Click_1); // // pictureBoxPreview // this.pictureBoxPreview.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(20)))), ((int)(((byte)(20)))), ((int)(((byte)(25))))); this.pictureBoxPreview.BorderStyle System.Windows.Forms.BorderStyle.FixedSingle; this.pictureBoxPreview.Dock System.Windows.Forms.DockStyle.Fill; this.pictureBoxPreview.Location new System.Drawing.Point(0, 0); this.pictureBoxPreview.Name pictureBoxPreview; this.pictureBoxPreview.Size new System.Drawing.Size(845, 543); this.pictureBoxPreview.SizeMode System.Windows.Forms.PictureBoxSizeMode.Zoom; this.pictureBoxPreview.TabIndex 7; this.pictureBoxPreview.TabStop false; this.pictureBoxPreview.Paint new System.Windows.Forms.PaintEventHandler(this.pictureBoxPreview_Paint_1); // // lblStatus // this.lblStatus.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(28)))), ((int)(((byte)(28)))), ((int)(((byte)(32))))); this.lblStatus.Dock System.Windows.Forms.DockStyle.Top; this.lblStatus.Font new System.Drawing.Font(宋体, 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); this.lblStatus.ForeColor System.Drawing.Color.Red; this.lblStatus.Location new System.Drawing.Point(0, 0); this.lblStatus.Name lblStatus; this.lblStatus.Size new System.Drawing.Size(845, 24); this.lblStatus.TabIndex 8; this.lblStatus.Text 就绪; this.lblStatus.TextAlign System.Drawing.ContentAlignment.MiddleLeft; // // panel1 // this.panel1.Controls.Add(this.btnSaveImage); this.panel1.Controls.Add(this.label1); this.panel1.Controls.Add(this.btnStartPreview); this.panel1.Controls.Add(this.btnFindCamera); this.panel1.Controls.Add(this.btnStopPreview); this.panel1.Controls.Add(this.cmbCameraList); this.panel1.Controls.Add(this.btnDisconnect); this.panel1.Controls.Add(this.btnConnect); this.panel1.Dock System.Windows.Forms.DockStyle.Top; this.panel1.Location new System.Drawing.Point(0, 0); this.panel1.Name panel1; this.panel1.Size new System.Drawing.Size(845, 61); this.panel1.TabIndex 9; // // label1 // this.label1.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(128)))), ((int)(((byte)(0))))); this.label1.Dock System.Windows.Forms.DockStyle.Top; this.label1.Location new System.Drawing.Point(0, 0); this.label1.Name label1; this.label1.Size new System.Drawing.Size(845, 1); this.label1.TabIndex 7; // // panel2 // this.panel2.Controls.Add(this.pictureBoxPreview); this.panel2.Dock System.Windows.Forms.DockStyle.Fill; this.panel2.Location new System.Drawing.Point(0, 61); this.panel2.Name panel2; this.panel2.Size new System.Drawing.Size(845, 543); this.panel2.TabIndex 10; // // label2 // this.label2.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(128)))), ((int)(((byte)(0))))); this.label2.Dock System.Windows.Forms.DockStyle.Bottom; this.label2.Location new System.Drawing.Point(0, 26); this.label2.Name label2; this.label2.Size new System.Drawing.Size(845, 1); this.label2.TabIndex 8; // // panel3 // this.panel3.Controls.Add(this.label2); this.panel3.Controls.Add(this.lblStatus); this.panel3.Dock System.Windows.Forms.DockStyle.Bottom; this.panel3.Location new System.Drawing.Point(0, 577); this.panel3.Name panel3; this.panel3.Size new System.Drawing.Size(845, 27); this.panel3.TabIndex 9; // // HKCameraControl // this.BackColor System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48))))); this.Controls.Add(this.panel3); this.Controls.Add(this.panel2); this.Controls.Add(this.panel1); this.Font new System.Drawing.Font(微软雅黑, 9F); this.MinimumSize new System.Drawing.Size(640, 480); this.Name HKCameraControl; this.Size new System.Drawing.Size(845, 604); ((System.ComponentModel.ISupportInitialize)(this.pictureBoxPreview)).EndInit(); this.panel1.ResumeLayout(false); this.panel2.ResumeLayout(false); this.panel3.ResumeLayout(false); this.ResumeLayout(false); } private string GetDeviceDisplayName(MyCamera.MV_CC_DEVICE_INFO deviceInfo) { try { if (deviceInfo.nTLayerType MyCamera.MV_GIGE_DEVICE) { var gigeInfo (MyCamera.MV_GIGE_DEVICE_INFO)MyCamera.ByteToStruct(deviceInfo.SpecialInfo.stGigEInfo, typeof(MyCamera.MV_GIGE_DEVICE_INFO)); return ${gigeInfo.chUserDefinedName} - {gigeInfo.chSerialNumber}; } else if (deviceInfo.nTLayerType MyCamera.MV_USB_DEVICE) { var usbInfo (MyCamera.MV_USB3_DEVICE_INFO)MyCamera.ByteToStruct(deviceInfo.SpecialInfo.stUsb3VInfo, typeof(MyCamera.MV_USB3_DEVICE_INFO)); return ${usbInfo.chUserDefinedName} - {usbInfo.chSerialNumber}; } } catch { } return 未知设备; } private void DisconnectCamera() { if (!_isConnected || _camera null) return; if (_isGrabbing) { _camera.MV_CC_StopGrabbing_NET(); _isGrabbing false; } _camera.MV_CC_CloseDevice_NET(); _camera.MV_CC_DestroyDevice_NET(); _camera null; _isConnected false; lock (_frameLock) { _currentFrame?.Dispose(); _currentFrame null; } pictureBoxPreview.Image?.Dispose(); pictureBoxPreview.Image null; pictureBoxPreview.Invalidate(); SetStatus(相机已断开。。。); } private void ImageCallbackEx(IntPtr pData, ref MyCamera.MV_FRAME_OUT_INFO_EX pFrameInfo, IntPtr pUser) { if (pData IntPtr.Zero || pFrameInfo.nWidth 0 || pFrameInfo.nHeight 0) return; try { Bitmap bitmap null; if (pFrameInfo.enPixelType MyCamera.MvGvspPixelType.PixelType_Gvsp_RGB8_Packed) { int stride ((pFrameInfo.nWidth * 3 3) / 4) * 4; bitmap new Bitmap(pFrameInfo.nWidth, pFrameInfo.nHeight, stride, PixelFormat.Format24bppRgb, pData); bitmap new Bitmap(bitmap); } else if (pFrameInfo.enPixelType MyCamera.MvGvspPixelType.PixelType_Gvsp_BGR8_Packed) { int stride ((pFrameInfo.nWidth * 3 3) / 4) * 4; using (Bitmap temp new Bitmap(pFrameInfo.nWidth, pFrameInfo.nHeight, stride, PixelFormat.Format24bppRgb, pData)) { bitmap new Bitmap(temp); } } else if (pFrameInfo.enPixelType MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8) { bitmap new Bitmap(pFrameInfo.nWidth, pFrameInfo.nHeight, PixelFormat.Format24bppRgb); var rect new Rectangle(0, 0, pFrameInfo.nWidth, pFrameInfo.nHeight); BitmapData bmpData bitmap.LockBits(rect, ImageLockMode.WriteOnly, bitmap.PixelFormat); unsafe { byte* src (byte*)pData; byte* dst (byte*)bmpData.Scan0; int strideDst bmpData.Stride; for (int y 0; y pFrameInfo.nHeight; y) for (int x 0; x pFrameInfo.nWidth; x) { byte gray src[y * pFrameInfo.nWidth x]; dst[y * strideDst x * 3] gray; dst[y * strideDst x * 3 1] gray; dst[y * strideDst x * 3 2] gray; } } bitmap.UnlockBits(bmpData); } else return; if (bitmap ! null) { lock (_frameLock) { var old _currentFrame; _currentFrame bitmap; old?.Dispose(); } _syncContext.Post(_ { if (pictureBoxPreview ! null !pictureBoxPreview.IsDisposed _currentFrame ! null) { var oldImg pictureBoxPreview.Image; pictureBoxPreview.Image null; oldImg?.Dispose(); lock (_frameLock) pictureBoxPreview.Image new Bitmap(_currentFrame); } }, null); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($回调异常: {ex.Message}); } } private void btnFindCamera_Click_1(object sender, EventArgs e) { try { if (_isConnected) DisconnectCamera(); _deviceList new MyCamera.MV_CC_DEVICE_INFO_LIST(); int nRet MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref _deviceList); if (nRet ! MyCamera.MV_OK) { SetStatus($枚举设备失败错误码0x{nRet:X8}); return; } cmbCameraList.Items.Clear(); if (_deviceList.nDeviceNum 0) { SetStatus(未找到任何相机设备。。。); return; } for (uint i 0; i _deviceList.nDeviceNum; i) { IntPtr pDevice _deviceList.pDeviceInfo[i]; var deviceInfo (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(pDevice, typeof(MyCamera.MV_CC_DEVICE_INFO)); string displayName GetDeviceDisplayName(deviceInfo); cmbCameraList.Items.Add(displayName); } if (cmbCameraList.Items.Count 0) { cmbCameraList.SelectedIndex 0; SetStatus($找到 {_deviceList.nDeviceNum} 个相机设备); } } catch (Exception ex) { SetStatus($查找相机异常{ex.Message}); } } private void btnConnect_Click_1(object sender, EventArgs e) { if (cmbCameraList.SelectedIndex 0) { SetStatus(请先查找并选择相机。。。); return; } if (_isConnected) return; try { int idx cmbCameraList.SelectedIndex; IntPtr pDevice _deviceList.pDeviceInfo[idx]; var deviceInfo (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(pDevice, typeof(MyCamera.MV_CC_DEVICE_INFO)); _camera new MyCamera(); int nRet _camera.MV_CC_CreateDevice_NET(ref deviceInfo); if (nRet ! MyCamera.MV_OK) { SetStatus($创建设备失败错误码0x{nRet:X8}); _camera null; return; } nRet _camera.MV_CC_OpenDevice_NET(); if (nRet ! MyCamera.MV_OK) { SetStatus($打开设备失败错误码0x{nRet:X8}); _camera.MV_CC_DestroyDevice_NET(); _camera null; return; } _isConnected true; SetStatus($已连接相机{cmbCameraList.Text}); _camera.MV_CC_SetEnumValue_NET(TriggerMode, 0); } catch (Exception ex) { SetStatus($连接相机异常{ex.Message}); } } private void btnDisconnect_Click_1(object sender, EventArgs e) { DisconnectCamera(); } private void btnStartPreview_Click_1(object sender, EventArgs e) { if (!_isConnected || _camera null) { SetStatus(请先连接相机。。。); return; } if (_isGrabbing) return; _imageCallback new MyCamera.cbOutputExdelegate(ImageCallbackEx); int nRet _camera.MV_CC_RegisterImageCallBackEx_NET(_imageCallback, IntPtr.Zero); if (nRet ! MyCamera.MV_OK) { SetStatus($注册图像回调失败错误码0x{nRet:X8}); return; } nRet _camera.MV_CC_StartGrabbing_NET(); if (nRet ! MyCamera.MV_OK) { SetStatus($开始取流失败错误码0x{nRet:X8}); return; } _isGrabbing true; SetStatus(实时预览已启动); } private void btnStopPreview_Click_1(object sender, EventArgs e) { if (!_isGrabbing || _camera null) return; _camera.MV_CC_StopGrabbing_NET(); _isGrabbing false; SetStatus(预览已停止); pictureBoxPreview.Invalidate(); } private void btnSaveImage_Click_1(object sender, EventArgs e) { if (!_isGrabbing) { SetStatus(未预览无法保存图片。。。); return; } Bitmap snap null; lock (_frameLock) if (_currentFrame ! null) snap new Bitmap(_currentFrame); if (snap null) { SetStatus(没有图像数据。。。); return; } using (SaveFileDialog sfd new SaveFileDialog()) { sfd.Filter JPEG图片|*.jpg|PNG图片|*.png|BMP图片|*.bmp; sfd.FileName $Snapshot_{DateTime.Now:yyyyMMdd_HHmmss}; if (sfd.ShowDialog() DialogResult.OK) { try { var fmt sfd.FilterIndex 1 ? ImageFormat.Jpeg : (sfd.FilterIndex 2 ? ImageFormat.Png : ImageFormat.Bmp); snap.Save(sfd.FileName, fmt); SetStatus($图片已保存{sfd.FileName}); } catch (Exception ex) { SetStatus($保存失败{ex.Message}); } finally { snap.Dispose(); } } else snap.Dispose(); } } private void cmbCameraList_DrawItem_1(object sender, DrawItemEventArgs e) { if (e.Index 0) return; e.DrawBackground(); bool isSelected (e.State DrawItemState.Selected) DrawItemState.Selected; using (var backBrush new SolidBrush(isSelected ? Color.FromArgb(0, 122, 204) : Color.FromArgb(60, 60, 65))) using (var textBrush new SolidBrush(isSelected ? Color.White : Color.LightGray)) { e.Graphics.FillRectangle(backBrush, e.Bounds); string text cmbCameraList.Items[e.Index].ToString(); e.Graphics.DrawString(text, e.Font, textBrush, e.Bounds.X 4, e.Bounds.Y (e.Bounds.Height - e.Font.Height) / 2); } e.DrawFocusRectangle(); } private void pictureBoxPreview_Paint_1(object sender, PaintEventArgs e) { if (pictureBoxPreview.Image null) { using (var brush new SolidBrush(Color.FromArgb(180, 255, 255, 255))) using (var font new Font(微软雅黑, 12)) { string text 暂无视频流\n请连接相机并开始预览; SizeF textSize e.Graphics.MeasureString(text, font); float x (pictureBoxPreview.Width - textSize.Width) / 2; float y (pictureBoxPreview.Height - textSize.Height) / 2; e.Graphics.DrawString(text, font, brush, x, y); } } } private void HKCameraControl_Resize(object sender, EventArgs e) { if (pictureBoxPreview ! null) pictureBoxPreview.Size new System.Drawing.Size(this.ClientSize.Width - 24, this.ClientSize.Height - 110); if (lblStatus ! null) { lblStatus.Top this.ClientSize.Height - 38; lblStatus.Width this.ClientSize.Width - 24; } } private void SetStatus(string msg) { if (lblStatus.IsDisposed) return; if (InvokeRequired) { Invoke(new Actionstring(SetStatus), msg); return; } lblStatus.Text msg; } protected override void Dispose(bool disposing) { if (disposing) DisconnectCamera(); base.Dispose(disposing); } /// summary /// 从当前帧捕获图像并测量物体尺寸 /// /summary /// returns(宽度mm, 高度mm, 深度mm)/returns public (double width, double height, double depth) CaptureAndMeasure() { // 确保有图像数据 if (_currentFrame null) throw new InvalidOperationException(没有图像数据请先预览); // 克隆当前帧 Bitmap bmp; lock (_frameLock) bmp new Bitmap(_currentFrame); try { // Bitmap 转 MatOpenCV 图像对象 using (Mat src BitmapConverter.ToMat(bmp)) { //图像预处理 轮廓检测 var (widthPx, heightPx) MeasureObject(src); //像素转实际尺寸需要标定 double pixelToMm GetPixelToMmRatio(); // 标定系数 double widthMm widthPx * pixelToMm; double heightMm heightPx * pixelToMm; // 深度若无法测量可返回 0 或从其他方式获取 double depthMm 0; return (widthMm, heightMm, depthMm); } } finally { bmp.Dispose(); // 释放克隆的图像 } } /// summary /// 使用 OpenCVSharp 检测图像中最大物体的外接矩形 /// /summary private (int width, int height) MeasureObject(Mat src) { // 转为灰度图 using (Mat gray new Mat()) using (Mat binary new Mat()) { Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY); // 高斯模糊降噪 Cv2.GaussianBlur(gray, gray, new OpenCvSharp.Size(5, 5), 0); // 二值化OTSU 自动阈值 Cv2.Threshold(gray, binary, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu); // 查找轮廓 Cv2.FindContours(binary, out OpenCvSharp.Point[][] contours, out HierarchyIndex[] hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple); if (contours.Length 0) throw new InvalidOperationException(未检测到任何物体轮廓); // 找到面积最大的轮廓 var maxContour contours.OrderByDescending(c Cv2.ContourArea(c)).First(); // 计算最小外接矩形[reference:3] RotatedRect rotatedRect Cv2.MinAreaRect(maxContour); // 或使用轴对齐矩形Rect rect Cv2.BoundingRect(maxContour); // 返回矩形尺寸宽、高 int width (int)rotatedRect.Size.Width; int height (int)rotatedRect.Size.Height; return (width, height); } } public void StartPreview() { if (!_isConnected || _camera null) throw new InvalidOperationException(相机未连接请先连接相机); if (_isGrabbing) return; _imageCallback new MyCamera.cbOutputExdelegate(ImageCallbackEx); int nRet _camera.MV_CC_RegisterImageCallBackEx_NET(_imageCallback, IntPtr.Zero); if (nRet ! MyCamera.MV_OK) throw new Exception($注册图像回调失败错误码0x{nRet:X8}); nRet _camera.MV_CC_StartGrabbing_NET(); if (nRet ! MyCamera.MV_OK) throw new Exception($开始取流失败错误码0x{nRet:X8}); _isGrabbing true; SetStatus(实时预览已启动); } /// summary /// 获取像素到毫米的转换系数 /// /summary private double GetPixelToMmRatio() { // 方法1使用已知尺寸的标定物 // 例如拍摄一个已知宽度为 10mm 的物体测量其像素宽度为 200px // 则 pixelToMm 10 / 200 0.05 // 方法2从配置文件或用户输入读取 // 这里先用一个示例值实际使用时需要标定 return 0.05; // 每像素 0.05mm } } }_camera.MV_CC_SetEnumValue_NET(TriggerMode, 0);这代表TriggerMode 0连续模式即 Free Run。需要改为TriggerMode 1触发模式并设置触发源为Software。修改触发模式为软触发btnConnect_Click_1 中修改触发配置 csharp // 连接成功后设置触发模式为软触发 _camera.MV_CC_SetEnumValue_NET(TriggerMode, 1); // 开启触发模式 _camera.MV_CC_SetEnumValue_NET(TriggerSource, 0); // 0 Software // 可选设置触发一次后续通过命令触发 2. 添加触发拍照的公共方法 csharp public void TriggerSoftware() { if (!_isConnected || _camera null) throw new InvalidOperationException(相机未连接); int nRet _camera.MV_CC_SetCommandValue_NET(TriggerSoftware); if (nRet ! MyCamera.MV_OK) throw new Exception($软件触发失败错误码0x{nRet:X8}); } 3. 确保在触发后获取最新帧 由于触发后图像通过回调更新您需要在测量前等待新帧到达。可增加一个 帧计数器 或 事件等待 机制。例如 csharp private int _frameCount 0; // 在回调中递增 private readonly object _frameCountLock new object(); // 在 ImageCallbackEx 末尾 lock (_frameCountLock) { _frameCount; } // 触发并等待新帧 public void TriggerAndWaitForNewFrame(int timeoutMs 1000) { int oldCount; lock (_frameCountLock) { oldCount _frameCount; } TriggerSoftware(); DateTime start DateTime.Now; while ((DateTime.Now - start).TotalMilliseconds timeoutMs) { lock (_frameCountLock) { if (_frameCount ! oldCount) return; // 有新帧 } Thread.Sleep(10); } throw new TimeoutException(触发后未收到新图像); } 然后在 Form2 的 BtnStartWork_Click 中电机到位后调用 csharp cameraControl.TriggerAndWaitForNewFrame(); var (width, height, depth) await Task.Run(() cameraControl.CaptureAndMeasure()); 4. 修改 CaptureAndMeasure 读取最新帧 因为已经等待了新帧_currentFrame 就是触发后的图像直接使用即可无需再克隆但克隆仍是安全的。 五、其他注意事项 标定GetPixelToMmRatio 必须根据实际工作距离和镜头参数标定否则测量尺寸不准确。 深度若需真实三维尺寸需额外硬件如激光测距或使用双目视觉目前代码只返回0。 相机触发模式枚举值建议查阅海康SDK文档确认 TriggerMode 和 TriggerSource 的具体数值通常 TriggerMode1 开启TriggerSource0 为软件。 异常处理触发失败或超时需捕获并反馈给用户避免程序卡死。