欢迎光临
我们一直在努力

Winform海康MV-CU系列视觉相机(C#)

⒈目的

基于海康官方提供的MVS案例进行简化至极简状态下的MV-CU系列相机的资源检索、打开、关闭以及PNG格式和JPG格式图片保存,为基于海康MV-CU系列相机的后续视觉开发提供基础。

⒉下载NuGet包

MvCameraControl.Net

推荐安装MvCameraControl.Net包,以便Winform在Debug和Release模式下均能进行模拟和发行测试。

联网状态下,右键解决方案-管理解决方案的NuGet程序包(N)…-浏览(MvCameraControl)-安装该NuGet包:

⒊界面设计

如上图参考海康MVS案例界面,针对MU-CU系列相机在视觉相关应用,截取相机的资源获取、打开、关闭、图像采集(PNG格式、JPG格式)、参数获取和设置等核心功能。

⒋代码编写

⑴命名空间、公共变量及初始化

using MvCameraControl;

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Threading;

using System.Threading.Tasks;

using System.Windows.Forms;

namespace WinformAreaScanCamera

{

    public partial class Form1: Form

    {

        //设备类型枚举,支持多种相机类型

        readonly DeviceTLayerType enumTLayerType = DeviceTLayerType.MvGigEDevice | DeviceTLayerType.MvUsbDevice

            | DeviceTLayerType.MvGenTLGigEDevice | DeviceTLayerType.MvGenTLCXPDevice | DeviceTLayerType.MvGenTLCameraLinkDevice | DeviceTLayerType.MvGenTLXoFDevice;

        List<IDeviceInfo> deviceInfoList = new List<IDeviceInfo>();//设备信息列表

        IDevice device = null;//设置当前操作设备为空

        bool isGrabbing = false;        //是否正在取图

        bool isRecord = false;          //是否正在录像

        Thread receiveThread = null;    //接收图像线程

        private IFrameOut frameForSave = null; //获取到的帧信息, 用于保存图像

        private readonly object saveImageLock = new object();//图像保存的锁对象

        public Form1()

        {

            InitializeComponent();

            SDKSystem.Initialize();//初始化SDK

            Control.CheckForIllegalCrossThreadCalls = false;//允许跨线程访问UI控件

        }

//内容代码

}

}

⑵错误信息处理

private void ShowErrorMsg(string message, int errorCode)//显示错误信息

{

     string errorMsg;

     if (errorCode == 0)

     {

         errorMsg = message;

     }

     else

     {

         errorMsg = message + ": Error =" + String.Format("{0:X}", errorCode);

     }

     switch (errorCode)

     {

         case MvError.MV_E_HANDLE: errorMsg += " 错误或无效的句柄 "; break;

         case MvError.MV_E_SUPPORT: errorMsg += " 不支持的功能 "; break;

         case MvError.MV_E_BUFOVER: errorMsg += " 缓存已满 "; break;

         case MvError.MV_E_CALLORDER: errorMsg += " 函数调用顺序错误 "; break;

         case MvError.MV_E_PARAMETER: errorMsg += " 参数不正确 "; break;

         case MvError.MV_E_RESOURCE: errorMsg += " 申请资源失败 "; break;

         case MvError.MV_E_NODATA: errorMsg += " 无数据 "; break;

         case MvError.MV_E_PRECONDITION: errorMsg += " 前置条件错误或运行环境已改变 "; break;

         case MvError.MV_E_VERSION: errorMsg += " 版本不匹配 "; break;

         case MvError.MV_E_NOENOUGH_BUF: errorMsg += " 内存不足 "; break;

         case MvError.MV_E_UNKNOW: errorMsg += " 未知错误 "; break;

         case MvError.MV_E_GC_GENERIC: errorMsg += " 通用错误 "; break;

         case MvError.MV_E_GC_ACCESS: errorMsg += " 节点访问条件错误 "; break;

         case MvError.MV_E_ACCESS_DENIED: errorMsg += " 无权限访问 "; break;

         case MvError.MV_E_BUSY: errorMsg += " 设备忙或网络断开 "; break;

         case MvError.MV_E_NETER: errorMsg += " 网络错误 "; break;

     }

     MessageBox.Show(errorMsg, "提示");

}

⑶索引设备

private void bnEnum_Click(object sender, EventArgs e)//索引列表

 {

     cbDeviceList.Items.Clear();//清除设备列表

     int nRet = DeviceEnumerator.EnumDevices(enumTLayerType, out deviceInfoList);

     if (nRet != MvError.MV_OK)

     {

         ShowErrorMsg("枚举设备失败!", nRet);//枚举设备失败

         return;

     }

     for (int i = 0; i < deviceInfoList.Count; i++)  //在窗体列表中显示设备名

     {

         IDeviceInfo deviceInfo = deviceInfoList[i];

         if (deviceInfo.UserDefinedName != "")//优先显示用户自定义名称(如果存在)

         {

             cbDeviceList.Items.Add(deviceInfo.TLayerType.ToString() + ": " + deviceInfo.UserDefinedName + " (" + deviceInfo.SerialNumber + ")");

         }

         else//否则显示制造商+型号+序列号

         {

             cbDeviceList.Items.Add(deviceInfo.TLayerType.ToString() + ": " + deviceInfo.ManufacturerName + " " + deviceInfo.ModelName + " (" + deviceInfo.SerialNumber + ")");

         }

     }

     if (deviceInfoList.Count > 0)//枚举设备数量大于0时,索引第1台设备(选择第一项)

     {

         cbDeviceList.SelectedIndex = 0;

     }

 }

⑷打开

private void bnOpen_Click(object sender, EventArgs e)//打开

{

    // 1. 设备选择验证

    if (deviceInfoList.Count == 0 || cbDeviceList.SelectedIndex == -1)

    {

        ShowErrorMsg("没有设备,请选择", 0);

        return;

    }

    // 2. 获取选择的设备信息

    IDeviceInfo deviceInfo = deviceInfoList[cbDeviceList.SelectedIndex];  //获取选择的设备信息

    // 3. 创建设备实例

    try

    {

        //打开设备

        device = DeviceFactory.CreateDevice(deviceInfo);

    }

    catch (Exception ex)

    {

        MessageBox.Show($"创建设备失败:" + ex.Message);

        return;

    }

    // 4. 打开物理设备

    int result = device.Open();

    if (result != MvError.MV_OK)

    {

        ShowErrorMsg("打开设备失败:", result);

        return;

    }

    // 5. GigE设备特殊配置

    if (device is IGigEDevice)//判断是否为gige设备

    {

        IGigEDevice gigEDevice = device as IGigEDevice; //转换为gigE设备

        int optionPacketSize;//探测网络最佳包大小(只对GigE相机有效)

        result = gigEDevice.GetOptimalPacketSize(out optionPacketSize);

        if (result != MvError.MV_OK)

        {

            ShowErrorMsg($"警告:网络包大小设置失败!", result);

        }

        else

        {

            result = device.Parameters.SetIntValue("GevSCPSPacketSize", (long)optionPacketSize);

            if (result != MvError.MV_OK)

            {

                ShowErrorMsg("Warning: Set Packet Size failed!", result);

            }

        }

    }

    device.Parameters.SetEnumValueByString("AcquisitionMode", "Continuous");//设置采集连续模式

    SetCtrlWhenOpen();//控件操作

    bnGetParam_Click(null, null);//获取参数

    bnStartGrabControl();//开始采集

}

private void SetCtrlWhenOpen()

{

    bnOpen.Enabled = false;

    bnClose.Enabled = true;

    tbExposure.Enabled = true;

    tbGain.Enabled = true;

    tbFrameRate.Enabled = true;

    cbPixelFormat.Enabled = true;

    bnGetParam.Enabled = true;

    bnSetParam.Enabled = true;

}

private void bnStartGrabControl()

{

    // 1. 启动图像采集线程

    try

    {

        isGrabbing = true; //设置采集标志位置位true

        receiveThread = new Thread(ReceiveThreadProcess);// 设为后台线程(防止程序退出时阻塞)

        receiveThread.Start();// 启动线程

    }

    catch (Exception ex)

    {

        MessageBox.Show($"线程启动失败:" + ex.Message);

        throw;// 重新抛出异常(可根据需求改为return)

    }

    // 2. 开始硬件采集

    int result = device.StreamGrabber.StartGrabbing();

    if (result != MvError.MV_OK)

    {

        // 采集失败时的清理工作

        isGrabbing = false; // 重置标志位

        receiveThread?.Join(500); // 等待线程结束(500ms超时)

        ShowErrorMsg($"开始采集失败!", result);

        return;

    }

    SetCtrlWhenStartGrab(); //更新控件操作

}

private void SetCtrlWhenStartGrab()

{

    cbPixelFormat.Enabled = false;

    bnSaveJpg.Enabled = true;

    bnSavePng.Enabled = true;

}

⑸关闭

private void bnClose_Click(object sender, EventArgs e)//关闭

{

    bnStopGrabControl(sender, e);//停止控制-传递sender和e参数给控制方法

    if (device != null) //关闭设备

    {

        this.BeginInvoke((MethodInvoker)delegate // 清除显示的图像

        {

            var oldImg1 = pictureBox1.Image; // 彻底清除图片

            pictureBox1.Image = null;

            oldImg1?.Dispose();

            device.Close();

            device.Dispose();

        });

    }

    SetCtrlWhenClose(); //控件操作

}

private void SetCtrlWhenClose()

{

    bnOpen.Enabled = true;

    bnClose.Enabled = false;

    bnSaveJpg.Enabled = false;

    bnSavePng.Enabled = false;

    tbExposure.Enabled = false;

    tbGain.Enabled = false;

    tbFrameRate.Enabled = false;

    bnGetParam.Enabled = false;

    bnSetParam.Enabled = false;

    cbPixelFormat.Enabled = false;

}

private void bnStopGrabControl(object sender, EventArgs e)//停止控制

{

    // 1. 设置采集标志位为false,注意:这将导致ReceiveThreadProcess循环终止

    isGrabbing = false;

    // 2. 停止设备采集流,说明:直接调用SDK的StopGrabbing方法,比依赖线程自然退出更可靠

    int result = device.StreamGrabber.StopGrabbing();

    // 3. 检查停止采集是否成功

    if (result != MvError.MV_OK)

    {

        ShowErrorMsg("停止采集失败!", result);// 显示错误信息(建议后续可改为记录日志)

    }

    // 4. 更新UI控件状态

    // 典型操作包括:

    // – 启用"开始采集"按钮

    // – 禁用"停止采集"按钮

    // – 禁用图像保存按钮

    SetCtrlWhenStopGrab();

}

private void SetCtrlWhenStopGrab()

{

    cbPixelFormat.Enabled = true;

    bnSaveJpg.Enabled = false;

    bnSavePng.Enabled = false;

}

⑹线程处理

        public void ReceiveThreadProcess()//线程处理

        {

            int nRet; // 存储SDK调用返回值

            while (isGrabbing) // 主采集循环:持续运行直到isGrabbing为false(停止采集)

            {

                IFrameOut frameOut;//存取获取到的图像帧

                nRet = device.StreamGrabber.GetImageBuffer(1000, out frameOut);//获取图像缓冲区,时间为1000ms

                if (MvError.MV_OK == nRet)//检查图像获取是否成功

                {

                    if (isRecord)//如果正在录像,输入帧到录像器

                    {

                        device.VideoRecorder.InputOneFrame(frameOut.Image);

                    }

                    lock (saveImageLock) //保存帧用于后续保存图片,使用lock确保多线程环境下frameForSave的安全访问

                    {

                        try

                        {

                            if (frameForSave != null)//释放之前帧资源,避免内存泄漏

                            {

                                frameForSave.Dispose();

                            }

                            frameForSave = frameOut.Clone() as IFrameOut;//克隆当前帧用于后续保存

                        }

                        catch (Exception e)

                        {

                            MessageBox.Show($"图像帧克隆失败:" + e.Message);//处理帧克隆失败的情况

                            return;//失败时退出线程

                        }

                    }

                    //条件编译:选择图像渲染方式

#if !GDI_RENDER//使用SDK自带的渲染器显示图像

                    device.ImageRender.DisplayOneFrame(pictureBox1.Handle, frameOut.Image);

#else//使用GDI绘制图像(备用方案)

                                        try

                                        {

                                            using (Bitmap bitmap = frameOut.Image.ToBitmap())

                                            {

                                                if (graphics == null)

                                                {

                                                    graphics = pictureBox1.CreateGraphics();

                                                }

                                                Rectangle srcRect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);

                                                Rectangle dstRect = new Rectangle(0, 0, pictureBox1.Width, pictureBox1.Height);

                                                graphics.DrawImage(bitmap, dstRect, srcRect, GraphicsUnit.Pixel);

                                            }

                                        }

                                        catch (Exception e)

                                        {

                                            device.StreamGrabber.FreeImageBuffer(frameOut);

                                            MessageBox.Show(e.Message);

                                            return;                   }

#endif

                    device.StreamGrabber.FreeImageBuffer(frameOut); // 释放图像缓冲区(必须调用,防止内存泄漏)

                }

            }

        }

⑺保存PNG/JPG格式图片

private void bnSavePng_Click(object sender, EventArgs e)//保存PNG格式图片

{

    int result;// 用于接收SDK操作结果

    try

    {

        // 1. 初始化图片格式参数

        ImageFormatInfo imageFormatInfo = new ImageFormatInfo();

        imageFormatInfo.FormatType = ImageFormatType.Png;// 设置为PNG格式

        // 2. 调用保存图片方法, 说明:SaveImage方法会处理线程安全和文件命名

        result = SaveImage(imageFormatInfo);

        // 3. 检查保存结果

        if (result != MvError.MV_OK)

        {

            ShowErrorMsg("保存PNG图片失败!", result);// 3.1 保存失败时显示错误详情(含错误码)

            return;

        }

        else

        {

            ShowErrorMsg("保存PNG图片成功!", 0);// 3.2 保存成功提示

        }

    }

    catch (Exception ex)

    {

        // 4. 捕获未处理的异常,注意:此处捕获的是非SDK异常(如内存不足、文件访问权限等)

        MessageBox.Show("保存PNG图片时发生异常:" + ex.Message);

        return;

    }

}

private int SaveImage(ImageFormatInfo imageFormatInfo)

{

    if (frameForSave == null)// 1. 空值检查,/ 说明:确保有有效的图像帧可供保存

    {

        throw new Exception("未获取到有效图像帧");

    }

    // 2. 生成文件名(包含图像属性信息),格式:Image_w{宽度}_h{高度}_fn{帧号}.{格式扩展名}

    string imagePath = "Image_w" + frameForSave.Image.Width.ToString() + "_h" + frameForSave.Image.Height.ToString() + "_fn" + frameForSave.FrameNum.ToString() + "." + imageFormatInfo.FormatType.ToString();

    lock (saveImageLock)// 3. 线程安全保护,说明:使用锁防止多个保存操作同时访问共享资源

    {

        // 4. 调用SDK保存图像

        // 参数说明:

        // imagePath: 保存路径; frameForSave.Image: 图像数据; imageFormatInfo: 图像格式参数; CFAMethod.Equilibrated: 采用平衡算法处理色彩

        return device.ImageSaver.SaveImageToFile(imagePath, frameForSave.Image, imageFormatInfo, CFAMethod.Equilibrated);

    }

}

private void bnGetParam_Click(object sender, EventArgs e)//保存参数

{

    IFloatValue floatValue;// 定义浮点参数接收对象

    // 1. 获取曝光时间参数

    int result = device.Parameters.GetFloatValue("ExposureTime", out floatValue);

    if (result == MvError.MV_OK)

    {

        tbExposure.Text = floatValue.CurValue.ToString("F1");// 将曝光时间显示到文本框(保留1位小数)

    }

    // 2. 获取增益参数

    result = device.Parameters.GetFloatValue("Gain", out floatValue);

    if (result == MvError.MV_OK)

    {

        tbGain.Text = floatValue.CurValue.ToString("F1");// 将增益值显示到文本框(保留1位小数)

    }

    // 3. 获取实际帧率参数

    result = device.Parameters.GetFloatValue("ResultingFrameRate", out floatValue);

    if (result == MvError.MV_OK)

    {

        tbFrameRate.Text = floatValue.CurValue.ToString("F1");// 将帧率显示到文本框(保留1位小数)

    }

    // 4. 获取像素格式参数

    cbPixelFormat.Items.Clear();//清空下拉列表

    IEnumValue enumValue;

    result = device.Parameters.GetEnumValue("PixelFormat", out enumValue);

    if (result == MvError.MV_OK)

    {

        foreach (var item in enumValue.SupportEnumEntries)// 遍历所有支持的像素格式

        {

            cbPixelFormat.Items.Add(item.Symbolic);// 添加像素格式到下拉列表

            if (item.Symbolic == enumValue.CurEnumEntry.Symbolic)// 设置当前选中项

            {

                cbPixelFormat.SelectedIndex = cbPixelFormat.Items.Count – 1;

            }

        }

    }

}

⑻设置相机参数

private void bnSetParam_Click(object sender, EventArgs e)//设置相机参数

{

    // 1. 参数有效性验证, 说明:检查用户输入的曝光、增益、帧率是否为有效浮点数

    try

    {

        float.Parse(tbExposure.Text);  // 曝光时间

        float.Parse(tbGain.Text);      // 增益值

        float.Parse(tbFrameRate.Text); // 帧率

    }

    catch

    {

        ShowErrorMsg("请输入有效的数值!", 0);// 输入格式错误时提示用户

        return;

    }

    //2.设置曝光参数,说明:关闭自动曝光,设置固定曝光时间

    device.Parameters.SetEnumValue("ExposureAuto", 0);// 禁用自动曝光

    int result = device.Parameters.SetFloatValue("ExposureTime", float.Parse(tbExposure.Text));

    if (result != MvError.MV_OK)

    {

        ShowErrorMsg("设置曝光时间失败!", result);

    }

    // 3. 设置增益参数, 说明:关闭自动增益,设置固定增益值

    device.Parameters.SetEnumValue("GainAuto", 0);// 禁用自动增益

    result = device.Parameters.SetFloatValue("Gain", float.Parse(tbGain.Text));

    if (result != MvError.MV_OK)

    {

        ShowErrorMsg("设置增益失败!", result);

    }

    // 4. 设置帧率参数,说明:启用帧率控制并设置目标帧率

    result = device.Parameters.SetBoolValue("AcquisitionFrameRateEnable", true);

    if (result != MvError.MV_OK)

    {

        ShowErrorMsg("启用帧率控制失败!", result);

    }

    else

    {

        result = device.Parameters.SetFloatValue("AcquisitionFrameRate", float.Parse(tbFrameRate.Text));

        if (result != MvError.MV_OK)

        {

            ShowErrorMsg("设置帧率失败!", result);

        }

    }

}

private void cbPixelFormat_SelectionChangeCommitted(object sender, EventArgs e)

{

    // 1. 获取当前选择的像素格式并设置到设备参数中,说明:通过字符串形式设置像素格式参数(如"Mono8", "BayerRG8"等)

    int result = device.Parameters.SetEnumValueByString("PixelFormat", cbPixelFormat.Text);

    // 2. 检查设置是否成功

    if (result != MvError.MV_OK)

    {

        // 3. 如果设置失败,显示错误信息,注意:这里使用的是原始错误码,ShowErrorMsg方法会将其转换为中文描述

        ShowErrorMsg("设置像素格式失败!", result);

    }

}

⑼资源释放

private void Form1_FormClosing(object sender, FormClosingEventArgs e)//程序关闭事件,释放SDK资源

 {

     // 1. 程序关闭事件处理,说明:当窗体即将关闭时触发,用于释放所有系统资源

     // 2. 释放SDK全局资源,注意:必须在设备关闭前调用,否则可能导致资源泄漏

     SDKSystem.Finalize();

     if (device != null) //关闭设备

     {

         this.BeginInvoke((MethodInvoker)delegate // 清除显示的图像

         {

             var oldImg1 = pictureBox1.Image; // 彻底清除图片

             pictureBox1.Image = null;

             oldImg1?.Dispose();

             device.Close();

             device.Dispose();

         });

     }

}

⒌应用参数

如下图运行测试,点击“检索”,“打开”和“关闭”相机,保存PNG和JPG格式图片,对相机参数进行读取和设置。

赞(0)
未经允许不得转载:171主机测评 » Winform海康MV-CU系列视觉相机(C#)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址