Halcon HImage 與 Qt QImage 的相互轉換
以前一直是用 OpenCV 開發機器視覺算法,最近由於某些機緣開始接觸學習 Halcon。Halcon 確實是功能強大,用 Halcon 寫算法比 OpenCV 方便了太多。但是 Halcon 與OpenCV一樣,專注於視覺算法,如果要開發軟件界面或者與其他程序交互,Halcon 就不是很擅長了。所以我還是決定用 C++ Qt 做我的程序,只在需要視覺算法的地方用Halcon 提供的算子。今天花了半天時間研究了如何在 Halcon 和 Qt 間傳遞圖像數據。
原理講解
Qt 中用 QImage 來存放圖像數據,Halcon 中則是 HImage。對於灰度圖像,這兩個類的存儲方式類似,都是一個大數組。但是對於彩色圖像,存儲方式就有很大不同了。
QImage 中彩色圖像的各個分量是交替存儲的。比如 RGB32 格式,RGB 三個顏色分量打包成一個 32 bits 的dword,然后像素點按照 row major 的方式依次存儲在內存中。QImage 支持的彩色圖像的種類很多,比如 RGB888、RGB555、RGB565、RGB32 等。我的代碼實現中只支持 RGB888 和 RGB32 這兩種。其余的因為很少能用到,所以暫時先不考慮。
HImage 中彩色圖像的各個分量是單獨存儲的,簡單的說就是 RGB 分別存到 3 個數組中。而且 HImage 不支持 Alpha 通道。由於 HImage 與 QImage 在內存中存儲彩色圖像時的巨大區別,因此這兩個類之間很難共享同一個圖像數據。所以在下面的代碼中都對圖像數據進行了拷貝操作。當然如果只是灰度圖像的話,是可以共享數據的。在本文的最后我也會簡單介紹一下如何共享數據。
HImage -> QImage
我的實現方式是先給 QImage 分配合適的數據空間。然后把 HImage 里面的數據拷貝到 QImage 中。如果是 灰度圖像可以用 HImage::GetImagePointer1 獲得圖像數據的首地址。
void* HImage::GetImagePointer1(HString* Type, Hlong* Width, Hlong* Height)
如果是彩色圖像則是用 HImage::GetImagePointer3 來獲得圖像數據各個顏色分量的首地址。
void HImage::GetImagePointer3(void** PointerRed, void** PointerGreen, void** PointerBlue, HString* Type, Hlong* Width, Hlong* Height)
得到了HImage 中數據的地址之后拷貝就很簡單了。獲得 QImage 中圖像數據的地址可以用下面兩個函數:
1 uchar *QImage::bits() 2 uchar *QImage::scanLine(int i)
下面直接貼出實現代碼:
1 /** 2 * @brief HImage2QImage 將 Halcon 的 HImage 轉換為 Qt 的 QImage 3 * @param from HImage ,暫時只支持 8bits 灰度圖像和 8bits 的 3 通道彩色圖像 4 * @param to QImage ,這里 from 和 to 不共享內存。如果 to 的內存大小合適,那么就不用重新分配內存。所以可以加快速度。 5 * @return true 表示轉換成功,false 表示轉換失敗 6 */
7 bool HImage2QImage(HalconCpp::HImage &from, QImage &to) 8 { 9 Hlong width; 10 Hlong height; 11 from.GetImageSize(&width, &height); 12
13 HTuple channels = from.CountChannels(); 14 HTuple type = from.GetImageType(); 15
16 if( strcmp(type[0].S(), "byte" )) // 如果不是 byte 類型,則失敗
17 { 18 return false; 19 } 20
21 QImage::Format format; 22 switch(channels[0].I()) 23 { 24 case 1: 25 format = QImage::Format_Grayscale8; 26 break; 27 case 3: 28 format = QImage::Format_RGB32; 29 break; 30 default: 31 return false; 32 } 33
34 if(to.width() != width || to.height() != height || to.format() != format) 35 { 36 to = QImage(static_cast<int>(width), 37 static_cast<int>(height), 38 format); 39 } 40 HString Type; 41 if(channels[0].I() == 1) 42 { 43 unsigned char * pSrc = reinterpret_cast<unsigned char *>( from.GetImagePointer1(&Type, &width, &height) ); 44 memcpy( to.bits(), pSrc, static_cast<size_t>(width) * static_cast<size_t>(height) ); 45 return true; 46 } 47 else if(channels[0].I() == 3) 48 { 49 uchar *R, *G, *B; 50 from.GetImagePointer3(reinterpret_cast<void **>(&R), 51 reinterpret_cast<void **>(&G), 52 reinterpret_cast<void **>(&B), &Type, &width, &height); 53
54 for(int row = 0; row < height; row ++) 55 { 56 QRgb* line = reinterpret_cast<QRgb*>(to.scanLine(row)); 57 for(int col = 0; col < width; col ++) 58 { 59 line[col] = qRgb(*R++, *G++, *B++); 60 } 61 } 62 return true; 63 } 64
65 return false; 66 }
QImage ->HImage
這里的知識點就是拿到 QImage 的數據后如何構造 HImage。對於彩色圖像來說,可以用如下的函數:
void HImage::GenImageInterleaved(void* PixelPointer, const char* ColorFormat, Hlong OriginalWidth, Hlong OriginalHeight, Hlong Alignment, const char* Type, Hlong ImageWidth, Hlong ImageHeight, Hlong StartRow, Hlong StartColumn, Hlong BitsPerChannel, Hlong BitShift)
對於灰度圖像,如下面的函數:
void HImage::GenImage1(const char* Type, Hlong Width, Hlong Height, void* PixelPointer)
對於彩色圖像,用如下的函數:
void HImage::GenImageInterleaved(void* PixelPointer, const char* ColorFormat, Hlong OriginalWidth, Hlong OriginalHeight, Hlong Alignment, const char* Type, Hlong ImageWidth, Hlong ImageHeight, Hlong StartRow, Hlong StartColumn, Hlong BitsPerChannel, Hlong BitShift)
這里多說一句,如果我們的圖像數據的各個顏色分量本來就是分離的,那么可以用下面的函數:
void HImage::GenImage3(const char* Type, Hlong Width, Hlong Height, void* PixelPointerRed, void* PixelPointerGreen, void* PixelPointerBlue)
有了這兩個函數就夠了,下面是我的實現代碼:
1 /** 2 * @brief QImage2HImage 將 Qt QImage 轉換為 Halcon 的 HImage 3 * @param from 輸入的 QImage 4 * @param to 輸出的 HImage ,from 和 to 不共享內存數據。 每次都會為 to 重新分配內存。 5 * @return true 表示轉換成功,false 表示轉換失敗。 6 */
7 bool QImage2HImage(QImage &from, HalconCpp::HImage &to) 8 { 9 if(from.isNull()) return false; 10
11 int width = from.width(), height = from.height(); 12 QImage::Format format = from.format(); 13
14 if(format == QImage::Format_RGB32 ||
15 format == QImage::Format_ARGB32 ||
16 format == QImage::Format_ARGB32_Premultiplied) 17 { 18 to.GenImageInterleaved(from.bits(), "rgbx", width, height, 0, "byte", width, height, 0, 0, 8, 0); 19 return true; 20 } 21 else if(format == QImage::Format_RGB888) 22 { 23 to.GenImageInterleaved(from.bits(), "rgb", width, height, 0, "byte", width, height, 0, 0, 8, 0); 24 return true; 25 } 26 else if(format == QImage::Format_Grayscale8 || format == QImage::Format_Indexed8) 27 { 28 to.GenImage1("byte", width, height, from.bits()); 29 return true; 30 } 31 return false; 32 }
灰度圖像如何共享內存
這里需要我們知道兩個函數,一個是 HImage 的:
void HImage::GenImage1Extern(const char* Type, Hlong Width, Hlong Height, void* PixelPointer, void* ClearProc)
利用這個函數生成的 HImage 不拷貝圖像數據,在析構時通過 ClearProc 來刪除圖像數據,如果 ClearProc 傳個 null 進去,那么就不負責刪除了。
QImage 中也有類似的構造函數:
QImage::QImage(uchar *data, int width, int height, Format format, QImageCleanupFunction cleanupFunction = Q_NULLPTR, void *cleanupInfo = Q_NULLPTR)
用這個構造函數構造QImage 時不復制圖像數據。
有這兩個函數,就可以實現 QImage 與 HImage 灰度圖像的數據共享了。具體的實現代碼大家自己寫吧。
但是彩色圖像似乎是沒有辦法共享的。