• 我在使用Winform7.0开发海康相机应用的时候系统悄无声息的退出


    一、简介
        1、说明一下
            最近,我在开发一个玻璃幕墙检测的项目,这个项目需要使用到海康的相机系统。业务是这样的,相机按着指定的坐标,扫描玻璃幕墙的每块玻璃,通过算法查看是否有损坏的,如果有就发出报警信息,告诉客户。这个项目是有一个同事写好的,我后来重构了一下,但是运行起来,运行若干次,就退悄无声息的退出软件,什么也不提示。通过这个现象,我知道肯定是和硬件交互的时候出了问题,而且抛出任何异常。海康的日志文件也没有显示是什么有价值的东西。
            但是,通过努力的查找,调试,排除,终于找到了问题的关键,我们有四个相机,就有四个回调函数,用于处理图像,但是每个相机注册了一个自己的回调,应该是四个相机,设置四个回调,但是回调函数的实例是一个,否则就会出现程序毫无征兆的崩溃。
        2、开发平台
            开发工具:Visual Studio2022
            开发语言:C#
            开发平台:Winform 7.0
            海康类库:MvCamCtrl.NET

    二、详细步骤
        这个问题搞了我两天才搞定,但是搞定了,心里舒服了。今天就把思路写下来,自己可以查找,也可以帮助大家。
        知道问题的关键点了,解决也就方便了。这也叫难着不会,会的不难。

        1、一定要把 MyCamera.cbOutputExdelegate 声明为类的成员。
            private MyCamera.cbOutputExdelegate _outputImageDelegate;

    复制代码
     1      #region 私有实例字段
     2 
     3         private MyCamera[]? _myCameras;//相机实例的数组。
     4         private MyCamera.MV_CC_DEVICE_INFO_LIST _deviceInformationList;//相机信息的列表
     5        
     7         //图片文件的存储路径:根目录/当前扫描时间(作为目录)/相机编号
     8         private string? _saveImageBaseDirectory;   //图像文件存储的根路径路径。
     9         private string? _saveImageOnceTimeDirectory;   //单次扫描图像文件的存储路径,目录结构:根目录+当前扫描时间
    10         private string[]? _saveImageForCameraDirectory;//最终存储图片文件的路径,目录结构:根目录+当前扫描时间+相机序列号(有多少台相机,就有多少个目录)
    11         private IntPtr[]? _imageDisplayHandles;//针对每台相机图像进行显示处理,应为有多台,所以是数据类型是数组。
    14         private MyCamera.cbOutputExdelegate _outputImageDelegate;
    17         #endregion
    复制代码


        2、一定要在构造函数里初始化,当然,如果你可以保证一个实例,就可以放在其他地方,我是为了方便,没有过多的设计。
            _outputImageDelegate = new MyCamera.cbOutputExdelegate(SaveImageCallBack);
          

    复制代码
     1     /// 
     2         /// 初始化新实例。
     3         /// 
     4         public frmMonitoring()
     5         {
     6             InitializeComponent();
     7             _outputImageDelegate = new MyCamera.cbOutputExdelegate(SaveImageCallBack);
     8             _patrolProcessor = new OrientalMotorPatrolProcessor();
     9           
    10 
    11             #region 全局异常处理
    12 
    13             AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
    14             Application.ThreadException += Application_ThreadException;
    15 
    16             #endregion
    17         }
    复制代码

          这是附送的方法,异常处理。

    复制代码
     1     #region 异常处理
     2 
     3         /// 
     4         /// 该方法处理用于处理 Windows 窗体线程引发的异常。
     5         /// 
     6         /// 
     7         /// 
     8         /// 
     9         private void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    10         {
    11             try
    12             {
    13                 ShowPromptMessage(e.Exception.Message);
    14             }
    15             catch (Exception)
    16             {
    17                 try
    18                 {
    19                     ShowPromptMessage("不可恢复的非 Windows 窗体线程异常,应用程序将退出!");
    20                 }
    21                 finally
    22                 {
    23                     Application.Exit();
    24                 }
    25             }
    26         }
    27 
    28         /// 
    29         /// 使用该方法处理非UI线程所发生的异常。
    30         /// 
    31         /// 
    32         /// 
    33         /// 
    34         private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    35         {
    36             try
    37             {
    38                 var exceptionObject = e.ExceptionObject as Exception;
    39                 if (exceptionObject != null)
    40                 {
    41                     ShowPromptMessage(exceptionObject.Message);
    42                 }
    43             }
    44             catch (Exception)
    45             {
    46                 try
    47                 {
    48                     ShowPromptMessage("不可恢复的非 Windows 窗体线程异常,应用程序将退出!");
    49                 }
    50                 finally
    51                 {
    52                     Application.Exit();
    53                 }
    54             }
    55 
    56         }
    57 
    58         #endregion 
    复制代码

          辅助方法。    

    复制代码
     1      /// 
     2         /// 用于显示系统的提示信息,包括异常信息、操作信息等。
     3         /// 
     4         /// 需要显示的信息内容。
     5         /// 具体操作的状态码值,该值可有可无,不是所有操作都有状态值的,默认值:0,表示没有状态值。
     6         private void ShowPromptMessage(string message, int stateCode = 0)
     7         {
     8             string resultMessage;
     9             if (stateCode == 0)
    10             {
    11                 resultMessage = $"{message}\r\n";
    12             }
    13             else
    14             {
    15                 resultMessage = $"{message}: Error ={string.Format("{0:X}", stateCode)}";
    16             }
    17 
    18             switch (stateCode)
    19             {
    20                 case MyCamera.MV_E_HANDLE: resultMessage += ",错误或无效句柄(Error or invalid handle)\r\n"; break;
    21                 case MyCamera.MV_E_SUPPORT: resultMessage += ",不支持的功能(Not supported function)\r\n"; break;
    22                 case MyCamera.MV_E_BUFOVER: resultMessage += ",缓存已满(Cache is full)\r\n"; break;
    23                 case MyCamera.MV_E_CALLORDER: resultMessage += ",函数调用顺序错误(Function calling order error)\r\n"; break;
    24                 case MyCamera.MV_E_PARAMETER: resultMessage += ",不正确的参数(Incorrect parameter)\r\n"; break;
    25                 case MyCamera.MV_E_RESOURCE: resultMessage += ",应用资源失败(Applying resource failed)\r\n"; break;
    26                 case MyCamera.MV_E_NODATA: resultMessage += ",没有数据(No data )\r\n"; break;
    27                 case MyCamera.MV_E_PRECONDITION: resultMessage += ",前提条件错误,或运行环境已更改(Precondition error, or running environment changed)\r\n"; break;
    28                 case MyCamera.MV_E_VERSION: resultMessage += ",版本不匹配(Version mismatches)\r\n"; break;
    29                 case MyCamera.MV_E_NOENOUGH_BUF: resultMessage += ",内存不足(Insufficient memory)\r\n"; break;
    30                 case MyCamera.MV_E_UNKNOW: resultMessage += ",未知错误(Unknown error)\r\n"; break;
    31                 case MyCamera.MV_E_GC_GENERIC: resultMessage += ",一般错误(General error)\r\n"; break;
    32                 case MyCamera.MV_E_GC_ACCESS: resultMessage += ",节点访问条件错误(Node accessing condition error)\r\n"; break;
    33                 case MyCamera.MV_E_ACCESS_DENIED: resultMessage += ",没有权限(No permission)\r\n"; break;
    34                 case MyCamera.MV_E_BUSY: resultMessage += ",设备正忙或网络断开连接(Device is busy, or network disconnected)\r\n"; break;
    35                 case MyCamera.MV_E_NETER: resultMessage += ",网络错误(Network error)\r\n"; break;
    36             }
    37 
    38             SetAsyncControlText(txtExceptionMessage, $">> {resultMessage}", true);
    39         }
    复制代码


        3、实例化相机,并初始化回调函数。
            

    复制代码
     1          MyCamera.MV_CC_DEVICE_INFO[] _deviceInfoArray = new MyCamera.MV_CC_DEVICE_INFO[localDeviceCount];
     2                 object? localDeviceInfo = null;
     3                 MyCamera.MV_CC_DEVICE_INFO device;                
     4 
     5                 for (int i = 0; i < localDeviceCount; ++i)
     6                 {
     7                     if (_deviceInformationList.pDeviceInfo != null && _deviceInformationList.pDeviceInfo.Length > 0)
     8                     {
     9                         localDeviceInfo = Marshal.PtrToStructure(_deviceInformationList.pDeviceInfo[i], typeof(MyCamera.MV_CC_DEVICE_INFO));
    10                         if (localDeviceInfo != null)
    11                         {
    12                             //获取选择的设备信息
    13                             device = (MyCamera.MV_CC_DEVICE_INFO)localDeviceInfo;
    14 
    15                             //打开设备
    16                             if (cameras[i] == null)
    17                             {
    18                                 cameras[i] = new MyCamera();
    19                             }
    20 
    21                             stateCode = cameras[i].MV_CC_CreateDevice_NET(ref device);
    22                             if (MyCamera.MV_OK != stateCode)
    23                             {
    24                                 return;
    25                             }
    26 
    27                             stateCode = cameras[i].MV_CC_OpenDevice_NET();
    28                             if (MyCamera.MV_OK != stateCode)
    29                             {
    30                                 return;
    31                             }
    32                             else
    33                             {
    34                                 _deviceInfoArray[i] = device;
    35                                 // 探测网络最佳包大小(只对GigE相机有效),我们是USB相机
    36                                 if (device.nTLayerType == MyCamera.MV_GIGE_DEVICE)
    37                                 {
    38                                     int nPacketSize = cameras[i].MV_CC_GetOptimalPacketSize_NET();
    39                                     if (nPacketSize > 0)
    40                                     {
    41                                         stateCode = cameras[i].MV_CC_SetIntValue_NET("GevSCPSPacketSize", (uint)nPacketSize);
    42                                         if (stateCode != MyCamera.MV_OK)
    43                                         {
    44                                             ShowPromptMessage("Warning: Set Packet Size failed {0:x8}", stateCode);
    45                                         }
    46                                     }
    47                                     else
    48                                     {
    49                                         ShowPromptMessage("Warning: Get Packet Size failed {0:x8}", stateCode);
    50                                     }
    51                                 }
    52 
    53                                 //1、开启设置触发模式                                
    54                                 SetCameraTriggerMode(cameras[i]);
    55                                 //2、设置具体的触发模式为软触发
    56                                 SetCameraTriggerSource(cameras[i]);
    57                                 cameras[i].MV_CC_RegisterImageCallBackEx_NET(_outputImageDelegate, i);
    58                             }
    59                         }
    60                     }
    61                 }
    复制代码

        4、回调方法的实现。
            回调的方法可以根据自己的需求编写,这是我的需求,我的具体编写方法,不能照抄。

    复制代码
     1      /// 
     2         /// 获取帧数据并保存为图像的回调函数。
     3         /// 
     4         /// 图像的帧数据。
     5         /// 图像的帧信息。
     6         /// 相机的索引,因为有多台相机。
     7         private void SaveImageCallBack(IntPtr frameData, ref MyCamera.MV_FRAME_OUT_INFO_EX frameInfo, IntPtr cameraIndex)
     8         {
     9             int _cameraIndex = (int)cameraIndex;
    10 
    11             if (_perCameraTotalFrames != null && _perCameraTotalFrames.Length > 0)
    12             {
    13                 //抓取的帧数
    14                 ++_perCameraTotalFrames[_cameraIndex];
    15 
    16                 //第一个相机数据,每台相机处理的数据是一样的,随机选择第一个,作为结果输出。
    17                 if (_cameraIndex == 0)
    18                 {
    19                     SetAsyncControlText(lblAcquisitionCountValue, _perCameraTotalFrames[_cameraIndex].ToString());
    20                 }
    21             }
    22 
    23             //显示图像 
    24             //将相机图像显示到对应的位置
    25             if (_imageDisplayHandles != null && _imageDisplayHandles.Length > 0)
    26             {
    27                 MyCamera.MV_DISPLAY_FRAME_INFO displayFrameInfo = new MyCamera.MV_DISPLAY_FRAME_INFO();
    28                 displayFrameInfo.hWnd = _imageDisplayHandles[_cameraIndex];
    29                 displayFrameInfo.pData = frameData;
    30                 displayFrameInfo.nDataLen = frameInfo.nFrameLen;
    31                 displayFrameInfo.nWidth = frameInfo.nWidth;
    32                 displayFrameInfo.nHeight = frameInfo.nHeight;
    33                 displayFrameInfo.enPixelType = frameInfo.enPixelType;
    34 
    35                 if (_myCameras != null && _myCameras.Length > 0)
    36                 {
    37                     CameraDisplayOneFrame(_myCameras[_cameraIndex], ref displayFrameInfo);
    38                 }
    39             }
    40 
    41             //判断当前相机是否允许保存图像
    42             if (_isPerCameraSaveImage != null && _isPerCameraSaveImage.Length > 0)
    43             {
    44                 if (_isPerCameraSaveImage[_cameraIndex] && _saveImageForCameraDirectory != null && _saveImageForCameraDirectory.Length > 0 && _perCameraSerialNumbers != null && _perCameraSerialNumbers.Length > 0)
    45                 {
    46                     MyCamera.MV_SAVE_IMG_TO_FILE_PARAM stSaveToFileParam = new MyCamera.MV_SAVE_IMG_TO_FILE_PARAM();
    47 
    48                     stSaveToFileParam.enPixelType = frameInfo.enPixelType;
    49                     stSaveToFileParam.pData = frameData;
    50                     stSaveToFileParam.nDataLen = frameInfo.nFrameLen;
    51                     stSaveToFileParam.nWidth = frameInfo.nWidth;
    52                     stSaveToFileParam.nHeight = frameInfo.nHeight;
    53 
    54                     stSaveToFileParam.enImageType = MyCamera.MV_SAVE_IAMGE_TYPE.MV_Image_Bmp;
    55                     stSaveToFileParam.nQuality = 100;
    56                     //图像文件名应包含采集时间、对应相机ID号、玻璃编号等信息
    57                     var saveImageFullPath = $"{_saveImageForCameraDirectory[_cameraIndex]}\\{DateTime.Now.ToString("yyyyMMddHHmmss")}_{_perCameraSerialNumbers[_cameraIndex]}_{_patrolFileGlassNumber}.bmp";
    58                     stSaveToFileParam.pImagePath = saveImageFullPath;
    59 
    60                     if (_myCameras != null && _myCameras.Length > 0)
    61                     {
    62                         _myCameras[_cameraIndex].MV_CC_SaveImageToFile_NET(ref stSaveToFileParam);
    63                         CameraSaveImageToFile(_myCameras[_cameraIndex], ref stSaveToFileParam);
    64                     }
    65                 }
    66             }
    67         }
    复制代码


    三、总结
        我已经实验过单相机回调没问题了,所以多相机就直接复制了多个回调,因此相机回调还没传到外面的实例,就已经崩了,肯定就是封装的问题。看了海康的多相机demo,相机有4个实例,回调函数只有一个实例,4个相机注册了四次回调,但都是同一个回调实例。这就是我发生错误的根本原因。
        好了,问题解决了,又学了点技术,继续努力,苍天不负努力的人。

  • 相关阅读:
    安卓原生项目工程结构说明
    Vue2【watch 侦听器、计算属性、axios、vue-cli、vue 组件】
    数字经济时代的开源数据库创新 | 2022开放原子全球开源峰会数据库分论坛圆满召开
    六、【计算】大数据Shuffle原理与实践(下) | 青训营笔记
    解决dev GridControl 刷新数据后,滚动条恢复原来位置
    Rust核心功能之一(所有权)
    【基础篇】三、Flink集群角色、系统架构以及作业提交流程
    iOS开发Swift-10-位置授权, cocoapods,API,天气获取,城市获取-和风天气App首页代码
    Java设计模式之状态模式
    后端编译与优化(JIT,即时编译器)
  • 原文地址:https://www.cnblogs.com/PatrickLiu/p/17513394.html