文章目錄
- 1 在回調函數中實現
- 2 獨立封裝調用
- 2.1 獲取圖像寬、高、pBuffer、channel
- 2.2 內存圖像數據截取ROI并顯示
- 2.3 回調函數調用
- 3 for循環嵌套 方法2
- 4 for循環嵌套 方法3
- 5 按行復制數據提高效率,但很耗內存
- 6 unsafe代碼 解釋及注意事項 看我另一篇文章
- 7 ConvertToRGB24詳細解釋 、示例、注意事項 看我另一篇文章
- 8 問題與反思
- 8.1 被反復創建和使用,需手動釋放嗎?
- 8.2 創建一個全局Bitmap bitma,多線程訪問會沖突嗎?
當我們只要顯示ROI區域的畫面(顯示局部細節),而不是相機整個畫面時,就需要對圖像數據進行截取。
對于工業相機原圖像數據IFrameData 裁剪ROI,并不像 OpenCV中截取ROI那么簡單, 下面提供3種方法實現(效率不同),并將ROI畫面實時顯示在pictureBox中,
1 在回調函數中實現
public IntPtr pBuffer = IntPtr.Zero;private void __OnFrameCallbackFun(object objUserParam, IFrameData objIFrameData)
{int channel = 0;try{if (null != objIFrameData){//************************************************************//獲取圖像信息,為圖像處理等功能做準備//*************************************************************int imgHeight = (int)objIFrameData.GetHeight();int imgWidth = (int)objIFrameData.GetWidth();//************************************************************// 對圖像進行裁剪并顯示在 PictureBox 中//*************************************************************Rectangle ROI = new Rectangle(1323, 212, 2200, 1500);//獲取圖像bufferGX_VALID_BIT_LIST emValidBits = GX_VALID_BIT_LIST.GX_BIT_0_7;emValidBits = __GetBestValudBit(objIFrameData.GetPixelFormat());// 判斷圖像是否成功接收if (GX_FRAME_STATUS_LIST.GX_FRAME_STATUS_SUCCESS == objIFrameData.GetStatus()){// 在關鍵部分的代碼前加鎖lock (this){if (m_bIsColor){// 彩色圖像pBuffer = objIFrameData.ConvertToRGB24(emValidBits, GX_BAYER_CONVERT_TYPE_LIST.GX_RAW2RGB_NEIGHBOUR, false);//最后一個參數是否垂直翻轉圖像channel = 3;}else{// 黑白圖像if (__IsPixelFormat8(objIFrameData.GetPixelFormat())){pBuffer = objIFrameData.GetBuffer();}else{pBuffer = objIFrameData.ConvertToRaw8(emValidBits);}channel = 1;}PixelFormat format = new PixelFormat();format = channel == 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format8bppIndexed;//若3通道彩色圖像,否則黑白圖像//創建ROI 空圖像Bitmap bitmap = new Bitmap(ROI.Width, ROI.Height, format);BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, ROI.Width, ROI.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);int srcStride = imgWidth * channel;// 原圖每行數據長度int destStride = bmpData.Stride;// 新圖每行數據長度int srcStartX = (ROI.X - 1) * channel; // ROI 起始 X 坐標在原圖數據中的位置(二維視角)int srcStartY = ROI.Y - 1; // ROI 起始 Y 坐標在原圖數據中的位置(二維視角)int srcOffset = srcStartY * srcStride + srcStartX; // ROI 起始位置在原圖數據中的偏移量(原圖數據在內存中是一行,一維數組)unsafe{byte* srcPtr = (byte*)pBuffer + srcOffset;byte* destPtr = (byte*)bmpData.Scan0;for (int y = 0; y < ROI.Height; y++){for (int x = 0; x < ROI.Width * channel; x++){destPtr[x] = srcPtr[x];}srcPtr += srcStride;destPtr += destStride;}}bitmap.UnlockBits(bmpData);// 在這里處理 bitmap 圖像,下面是顯示舉例m_bitmap = bitmap;//將其賦值給全局的m_bitmap,從而可以在外部調用ShowProcessedImage(m_nOperateID, bitmap);//傳給函數,進行處理顯示,//pictureBox1.Image = bitmap;//傳給pictureBox1直接顯示}}}m_objCFps.IncreaseFrameNum();}catch (Exception ex){MessageBox.Show(ex.Message);}
}
2 獨立封裝調用
2.1 獲取圖像寬、高、pBuffer、channel
private void getImgInfo(IFrameData objIFrameData, ref IntPtr pBuffer){try{if (null != objIFrameData){//獲取圖像寬高SrcImgHeight = (int)objIFrameData.GetHeight();SrcImgWidth = (int)objIFrameData.GetWidth();//獲取圖像bufferGX_VALID_BIT_LIST emValidBits = GX_VALID_BIT_LIST.GX_BIT_0_7;emValidBits = __GetBestValudBit(objIFrameData.GetPixelFormat());// 判斷圖像是否成功接收if (GX_FRAME_STATUS_LIST.GX_FRAME_STATUS_SUCCESS == objIFrameData.GetStatus()){// 在關鍵部分的代碼前加鎖lock (this){if (colorFlag){// 彩色圖像pBuffer = objIFrameData.ConvertToRGB24(emValidBits, GX_BAYER_CONVERT_TYPE_LIST.GX_RAW2RGB_NEIGHBOUR, false);//最后一個參數是否垂直翻轉圖像,true則翻轉channel = 3;}else{// 黑白圖像if (__IsPixelFormat8(objIFrameData.GetPixelFormat())){pBuffer = objIFrameData.GetBuffer();}else{pBuffer = objIFrameData.ConvertToRaw8(emValidBits);}channel = 1;}}}}}catch (Exception ex){MessageBox.Show(ex.Message);}}
2.2 內存圖像數據截取ROI并顯示
private void getRoiBmpData(Rectangle ROI, IntPtr pBuffer, ref Bitmap ROI_bitmap){try{if (null != pBuffer){ //判斷像素格式PixelFormat format = new PixelFormat();// format = channels == 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format8bppIndexed;//若3通道彩色圖像,否則黑白圖像switch (channel){case 1:format = PixelFormat.Format8bppIndexed;break;case 3:format = PixelFormat.Format24bppRgb;break;case 4:format = PixelFormat.Format32bppArgb;break;}// 在關鍵部分的代碼前加鎖lock (this){//創建ROI 空圖像Bitmap bitmap = new Bitmap(ROI.Width, ROI.Height, format);BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, ROI.Width, ROI.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);int srcStride = SrcImgWidth * channel;// 原圖每行數據長度int destStride = bmpData.Stride;// 新圖每行數據長度int srcStartX = (ROI.X - 1) * channel; // ROI 起始 X 坐標在原圖數據中的位置(二維視角)int srcStartY = ROI.Y - 1; // ROI 起始 Y 坐標在原圖數據中的位置(二維視角)int srcOffset = srcStartY * srcStride + srcStartX; // ROI 起始位置在原圖數據中的偏移量(原圖數據在內存中是一行,一維數組)unsafe{byte* srcPtr = (byte*)pBuffer + srcOffset;byte* destPtr = (byte*)bmpData.Scan0;for (int y = 0; y < ROI.Height; y++){for (int x = 0; x < ROI.Width * channel; x++){destPtr[x] = srcPtr[x];}srcPtr += srcStride;destPtr += destStride;}}bitmap.UnlockBits(bmpData);// 在這里處理 bitmap 圖像//ShowProcessedImage(m_nOperateID, bitmap);ROI_bitmap = bitmap;}}}catch (Exception ex){MessageBox.Show(ex.Message);}}
2.3 回調函數調用
public static int SrcImgHeight, SrcImgWidth;int channel = 0;bool isShowSrcImg = true;bool camImg_isProcess = false; //是否圖像處理,不執行圖像處理的時候,默認開啟預覽模式。bool isPreviewRoiImg = false; //是否圖像處理,不執行圖像處理的時候,默認開啟預覽模式。Rectangle ROI = new Rectangle(1323, 212, 2200, 1500);Bitmap bitmapB6;
代碼中未出現的變量為 全局變量,或者庫中的變量
private void __OnFrameCallbackFun_1(object objUserParam, IFrameData objIFrameData){try{if (isShowSrcImg) //當開始處理圖像時原視頻要暫停,否則buffer中的數據會變化,圖像上下顛倒交替出現(因Show(objIFrameData)中顯示實現對圖像數據做了垂直翻轉){//************************************************************//顯示相機獲取的原圖//************************************************************ m_objGxBitmap1.Show(objIFrameData);}//獲取圖像寬、高、pBuffer、channel等信息getImgInfo2(objIFrameData, ref pBuffer1);//************************************************************// 對圖像進行裁剪并顯示在 PictureBox 中//*************************************************************if (isPreviewRoiImg)// 開啟預覽模式。{getRoiBmpData2(ROI, pBuffer1, ref bitmapB6);//ImageShow1.Image = bitmapB6;}//統計幀率m_objCFps1.IncreaseFrameNum();}catch (Exception ex){MessageBox.Show("回調函數2" + ex.Message);}}
3 for循環嵌套 方法2
private void __OnFrameCallbackFun(object objUserParam, IFrameData objIFrameData){try{if (null != objIFrameData){// 獲取圖像信息int srcImgWidth = (int)objIFrameData.GetWidth();int srcImgHeight = (int)objIFrameData.GetHeight();GX_VALID_BIT_LIST emValidBits = GX_VALID_BIT_LIST.GX_BIT_0_7;emValidBits = __GetBestValudBit(objIFrameData.GetPixelFormat());// 判斷圖像是否成功接收if (GX_FRAME_STATUS_LIST.GX_FRAME_STATUS_SUCCESS == objIFrameData.GetStatus()){// 在關鍵部分的代碼前加鎖lock (this){IntPtr pBuffer = IntPtr.Zero;if (m_bIsColor){// 彩色圖像pBuffer = objIFrameData.ConvertToRGB24(emValidBits, GX_BAYER_CONVERT_TYPE_LIST.GX_RAW2RGB_NEIGHBOUR, false);}else{// 黑白圖像if (__IsPixelFormat8(objIFrameData.GetPixelFormat())){pBuffer = objIFrameData.GetBuffer();}else{pBuffer = objIFrameData.ConvertToRaw8(emValidBits);}}// 創建ROI BitmapBitmap bitmap = new Bitmap(ROI.Width, ROI.Height, PixelFormat.Format24bppRgb);BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, ROI.Width, ROI.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);// 計算每行字節數int stride = bmpData.Stride;int bytes = Math.Abs(stride) * bitmap.Height;// 創建一個 byte 數組來存儲圖像數據byte[] imageData = new byte[bytes];// 計算ROI在原始圖像中的偏移位置int roiOffsetX = ROI.X;int roiOffsetY = ROI.Y;// 將 pBuffer 中的圖像數據復制到數組中,考慮ROI在原始圖像中的偏移位置for (int y = 0; y < ROI.Height; y++){int srcY = roiOffsetY + y;int srcIndex = srcY * srcImgWidth * 3; // 假設是24位RGB圖像for (int x = 0; x < ROI.Width; x++){int srcX = roiOffsetX + x;int dstIndex = (y * ROI.Width + x) * 3;imageData[dstIndex] = Marshal.ReadByte(pBuffer, srcIndex + srcX * 3);imageData[dstIndex + 1] = Marshal.ReadByte(pBuffer, srcIndex + srcX * 3 + 1);imageData[dstIndex + 2] = Marshal.ReadByte(pBuffer, srcIndex + srcX * 3 + 2);}}// 復制圖像數據到 Bitmap 中Marshal.Copy(imageData, 0, bmpData.Scan0, bytes);bitmap.UnlockBits(bmpData);// 顯示圖像ShowProcessedImage(m_nOperateID, bitmap);}}}m_objCFps.IncreaseFrameNum();}catch (Exception ex){MessageBox.Show(ex.Message);}}
4 for循環嵌套 方法3
private void __OnFrameCallbackFun(object objUserParam, IFrameData objIFrameData){try{if (null != objIFrameData){// 獲取圖像信息SrcImgWidth = (int)objIFrameData.GetWidth();SrcImgHeight = (int)objIFrameData.GetHeight();GX_VALID_BIT_LIST emValidBits = GX_VALID_BIT_LIST.GX_BIT_0_7;emValidBits = __GetBestValudBit(objIFrameData.GetPixelFormat());PixelFormat format;// 判斷圖像是否成功接收if (GX_FRAME_STATUS_LIST.GX_FRAME_STATUS_SUCCESS == objIFrameData.GetStatus()){// 在關鍵部分的代碼前加鎖lock (this){IntPtr pBuffer = IntPtr.Zero;int channel;if (m_bIsColor){// 彩色圖像pBuffer = objIFrameData.ConvertToRGB24(emValidBits, GX_BAYER_CONVERT_TYPE_LIST.GX_RAW2RGB_NEIGHBOUR, false); // 最后一個參數是否垂直翻轉圖像,true則翻轉channel = 3;}else{// 黑白圖像if (__IsPixelFormat8(objIFrameData.GetPixelFormat())){pBuffer = objIFrameData.GetBuffer();}else{pBuffer = objIFrameData.ConvertToRaw8(emValidBits);}channel = 1;}// 判斷像素格式format = channel == 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format8bppIndexed;// 創建ROI 空圖像m_bitmap = new Bitmap(ROI.Width, ROI.Height, format);BitmapData bmpData = m_bitmap.LockBits(new Rectangle(0, 0, ROI.Width, ROI.Height), ImageLockMode.WriteOnly, m_bitmap.PixelFormat);int srcStride = SrcImgWidth * channel; // 原圖每行數據長度int destStride = bmpData.Stride; // 新圖每行數據長度int srcStartX = (ROI.X - 1) * channel; // ROI 起始 X 坐標在原圖數據中的位置(二維視角)int srcStartY = ROI.Y - 1; // ROI 起始 Y 坐標在原圖數據中的位置(二維視角)int srcOffset = srcStartY * srcStride + srcStartX; // ROI 起始位置在原圖數據中的偏移量(原圖數據在內存中是一行,一維數組)// 將 pBuffer 中的圖像數據復制到數組中,考慮ROI在原始圖像中的偏移位置for (int y = 0; y < ROI.Height; y++){int srcY = srcStartY + y;int srcIndex = srcOffset + srcY * srcStride;for (int x = 0; x < ROI.Width * channel; x++){int srcX = srcStartX + x;int dstIndex = y * destStride + x;// 復制像素數據Marshal.WriteByte(bmpData.Scan0, dstIndex, Marshal.ReadByte(pBuffer, srcIndex + x));}}m_bitmap.UnlockBits(bmpData);// 顯示圖像ShowProcessedImage(m_nOperateID, m_bitmap);}}m_objCFps.IncreaseFrameNum();}}catch (Exception ex){MessageBox.Show(ex.Message);}}
5 按行復制數據提高效率,但很耗內存
private unsafe void __OnFrameCallbackFun111(object objUserParam, IFrameData objIFrameData){try{if (null != objIFrameData){// 獲取圖像信息SrcImgWidth = (int)objIFrameData.GetWidth();SrcImgHeight = (int)objIFrameData.GetHeight();GX_VALID_BIT_LIST emValidBits = GX_VALID_BIT_LIST.GX_BIT_0_7;emValidBits = __GetBestValudBit(objIFrameData.GetPixelFormat());PixelFormat format;// 判斷圖像是否成功接收if (GX_FRAME_STATUS_LIST.GX_FRAME_STATUS_SUCCESS == objIFrameData.GetStatus()){// 在關鍵部分的代碼前加鎖lock (this){if (m_bIsColor){// 彩色圖像pBuffer = objIFrameData.ConvertToRGB24(emValidBits, GX_BAYER_CONVERT_TYPE_LIST.GX_RAW2RGB_NEIGHBOUR, false); // 最后一個參數是否垂直翻轉圖像,true則翻轉channel = 3;}else{// 黑白圖像if (__IsPixelFormat8(objIFrameData.GetPixelFormat())){pBuffer = objIFrameData.GetBuffer();}else{pBuffer = objIFrameData.ConvertToRaw8(emValidBits);}channel = 1;}// 判斷像素格式format = channel == 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format8bppIndexed;// 創建ROI 空圖像m_bitmap = new Bitmap(ROI.Width, ROI.Height, format);BitmapData bmpData = m_bitmap.LockBits(new Rectangle(0, 0, ROI.Width, ROI.Height), ImageLockMode.WriteOnly, m_bitmap.PixelFormat);int srcStride = SrcImgWidth * channel; // 原圖每行數據長度 int destStride = bmpData.Stride;// 新圖每行數據長度int srcStartX = (ROI.X - 1) * channel; // ROI 起始 X 坐標在原圖數據中的位置(二維視角) int srcStartY = ROI.Y - 1; // ROI 起始 Y 坐標在原圖數據中的位置(二維視角)// ROI 起始位置在原圖數據中的偏移量(原圖數據在內存中是一行,一維數組)int srcOffset = srcStartY * srcStride + srcStartX;unsafe{// 計算 ROI 區域總字節數//int bytes = Math.Abs(destStride) * m_bitmap.Height;byte* bmpPtr = (byte*)bmpData.Scan0; // 指向 ROI Bitmap 的掃描行指針 byte* srcPtr = (byte*)pBuffer + srcOffset; // 指向原始圖像數據的指針 for (int y = 0; y < ROI.Height; y++) // 逐行復制ROI區域的圖像數據{// 復制當前行的圖像數據到ROI Bitmap中//從源地址 srcPtr開始,每次復制destStride個字節(一整行的數據)到目標地址 bmpPtr 處,第四個參數偏移量Buffer.MemoryCopy(srcPtr, bmpPtr, destStride, destStride);bmpPtr += destStride;srcPtr += srcStride;}}m_bitmap.UnlockBits(bmpData);// 顯示圖像ShowProcessedImage(m_nOperateID, m_bitmap);}}m_objCFps.IncreaseFrameNum();}}catch (Exception ex){MessageBox.Show(ex.Message);}}
6 unsafe代碼 解釋及注意事項 看我另一篇文章
7 ConvertToRGB24詳細解釋 、示例、注意事項 看我另一篇文章
8 問題與反思
C# 自定義函數getRoiBmpData,該函數功能“對相機獲取到的 IFrameData 轉換為pBuffer ,然后進行ROI裁剪操作,將ROI圖像bitmap傳給ShowProcessedImage函數進行處理”。getRoiBmpData函數被5個回調函數同時調用,因為有5個相機,每個相機調用一個回調函數。那么我為什么5個相機要用5個回調呢,因為這樣每個回調函數只負責一個相機,這樣看起來分工更明確。
現在有兩個問題:
問題1.
這行代碼“ Bitmap bitmap = new Bitmap(ROI.Width, ROI.Height, format);”中bitmap 被反復創建和使用,在該函數中需要手動釋放嗎?
問題2.
這行代碼“ Bitmap bitmap = new Bitmap(ROI.Width, ROI.Height, format);”中bitmap 被反復創建和使用,那么我可以創建一個全局變量Bitmap bitmap ,這樣就不用每次進入函數都會創建一次bitmap ,但是呢我擔心5個回調函數共享這一個bitmap 會沖突嗎?當然ShowProcessedImage函數和getRoiBmpData函數也只有一個,都是同時被5個回調函數調用的。
8.1 被反復創建和使用,需手動釋放嗎?
在這段代碼中,雖然 bitmap 被反復創建和使用,但在每次使用后都被正確地釋放了。這是因為在使用完畢后調用了 bitmap.UnlockBits(bmpData); 來釋放 BitmapData 對象。所以不需要手動釋放 bitmap,因為在 bitmap 超出范圍時會被自動回收。
bitmap 對象是在函數內部創建的,它會在函數執行完畢后自動離開作用域,從而被垃圾回收機制回收。因為它的生命周期受到函數作用域的限制,一旦函數執行完畢就會被銷毀。
8.2 創建一個全局Bitmap bitma,多線程訪問會沖突嗎?
問題2:創建一個全局變量 Bitmap bitmap 是一種有效的方法,可以避免在每次函數調用時都重新創建 bitmap 對象。然而,如果多個回調函數同時訪問和修改同一個全局 bitmap 對象,就可能會發生競態條件或數據競爭,導致程序行為不確定或產生錯誤。因此,你需要確保在對 bitmap 進行讀寫操作時進行適當的線程同步,以避免沖突。
為了解決這個問題,可以采取以下方法之一:
-
在訪問全局 bitmap 對象時使用線程同步機制(如鎖),以確保在任何時候只有一個回調函數可以訪問或修改 bitmap 對象。這樣可以避免并發訪問導致的問題。一個簡單的方法是在訪問 bitmap 之前使用 lock 關鍵字來確保線程安全,就像你在代碼中對關鍵部分加鎖一樣。這樣可以確保每次只有一個線程能夠訪問 bitmap,從而避免并發沖突。
-
將 bitmap 對象作為函數參數傳遞給每個回調函數,這樣每個函數都有自己的 bitmap 對象實例,不會相互干擾。
選擇哪種方法取決于你的具體需求和代碼結構。如果需要在多個回調函數之間共享相同的 bitmap 對象,并且需要確保線程安全,則使用第一種方法;如果每個回調函數都需要獨立的 bitmap 對象,則使用第二種方法。