一、簡介
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 /// <summary> 2 /// 初始化新範例。 3 /// </summary> 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 /// <summary> 4 /// 該方法處理用於處理 Windows 表單執行緒引發的異常。 5 /// </summary> 6 /// <param name="sender"></param> 7 /// <param name="e"></param> 8 /// <exception cref="NotImplementedException"></exception> 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 /// <summary> 29 /// 使用該方法處理非UI執行緒所發生的異常。 30 /// </summary> 31 /// <param name="sender"></param> 32 /// <param name="e"></param> 33 /// <exception cref="NotImplementedException"></exception> 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 /// <summary> 2 /// 用於顯示系統的提示資訊,包括異常資訊、操作資訊等。 3 /// </summary> 4 /// <param name="message">需要顯示的資訊內容。</param> 5 /// <param name="stateCode">具體操作的狀態碼值,該值可有可無,不是所有操作都有狀態值的,預設值:0,表示沒有狀態值。</param> 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 /// <summary> 2 /// 獲取幀資料並儲存為影象的回撥函數。 3 /// </summary> 4 /// <param name="frameData">影象的幀資料。</param> 5 /// <param name="frameInfo">影象的幀資訊。</param> 6 /// <param name="cameraIndex">相機的索引,因為有多臺相機。</param> 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個相機註冊了四次回撥,但都是同一個回撥範例。這就是我發生錯誤的根本原因。
好了,問題解決了,又學了點技術,繼續努力,蒼天不負努力的人。