差不多一個月前,在嘗試解決將halcon的圖像顯示在Qt的窗口界面時,考慮過將halcon的HObject類型轉換成qt的QImage類型,結果因為太菜了(網上也找不到類似的例子)而沒能成功,具體見原來的文章qt窗口中顯示halcon的圖像
這兩天花了點時間,理清思路,參考網上資料加上自己摸索出來方法然后實踐了一下,能實現兩種格式的轉換(halcon to Qt)並能顯示出來,就是轉化耗時有點不盡人意。
先說說思路歷程:
雖然網上沒有找到Hobject和Qimage間轉化的例子,但是有很多HObject轉Vc或者轉opencv類型的例子,
如:
VC中bmp圖片和Halcon中圖片類型相互轉換
Halcon圖像與Opencv圖像相互轉換(C++代碼)
從這些例子中可以學到一些圖像轉換的思路:
先獲取Hobject類型圖像的圖像數據指針及尺寸大小,再根據目標圖像類型格式初始化一個相應大小的數據空間,然后按照一定的格式將HObject圖像數據copy到目標圖像數據空間,最后使用生成目標圖像的函數。(這里大致說下思路,細節在后面寫)
從halcon的Hobject圖像類型中獲取圖像數據指針比較簡單,問題是如何圖像數據里面的值給復制到目標圖像數據空間的對應位置,這就得弄清Hobject圖像數據的存儲格式和數據類型,以及QImage圖像數據的存儲格式和數據類型。
首先了解下HObject圖像(像素?)的數據類型:
有byte, (u)int1/2/4,real, complex, direction, cyclic, vector_field這些,但一般都是byte類型,這里做圖像轉換也是用的byte這種類型。
HObject圖像的byte類型的特點:(這里都是按自己理解編的)每個像素的一個通道的灰度值用byte類型來存儲(byte類型,即一個字節,為8bit,也就是說圖像的位深為8位,可以表示范圍0到255的值,所以bmp圖像灰度值的范圍也是0~255),比如三通道圖就是每個像素點都有rgb三個通道的灰度值,每個通道用一個byte來存值,單通道圖的話每個像素就一個byte來存值。
然后了解下生成QImage圖像需要的數據格式:
QImage 圖像格式小結 這篇文章對我的幫助很大,整篇文章內容都有用 ,看完對我啟發很大。
比如要用到的根據公式: W = ( w * bitcount + 31 )/32 * 4 計算得到的W是QImage圖像每行的字節數。
比如三通道和單通道需要不同的圖像格式:
QImage::Format_RGB888,存入格式為R, G, B 對應 0,1,2
QImage::Format_Indexed8,需要設定顏色表,QVector<QRgb>
以及,采用指針取值,行掃的方式對每個像素處理,還解決了數據補齊的問題。
然后是如何從圖像數據生成QImage:
參考了這篇文章【QT】處理圖像數據 中:
通過數據流讀取:
QImage::QImage(uchar *data, int width, int height, Format format, QImageCleanupFunction cleanupFunction = Q_NULLPTR, void *cleanupInfo = Q_NULLPTR) //連續內存的讀寫版本 QImage::QImage(const uchar *data, int width, int height, Format format, QImageCleanupFunction cleanupFunction = Q_NULLPTR, void *cleanupInfo = Q_NULLPTR) //連續內存的只讀版本 QImage::QImage(uchar *data, int width, int height, int bytesPerLine, Format format, QImageCleanupFunction cleanupFunction = Q_NULLPTR, void *cleanupInfo = Q_NULLPTR) //非連續內存的讀寫版本 QImage::QImage(const uchar *data, int width, int height, int bytesPerLine, Format format, QImageCleanupFunction cleanupFunction = Q_NULLPTR, void *cleanupInfo = Q_NULLPTR) //非連續內存的只讀版本
這里的數據流指的是圖像數據以二進制流的形式存放在內存中。
注意,QImage只支持8位深的單通道灰度圖像,此時需要設置colortabel或者將單通道轉3通到,對於彩色圖像則可以直接創建。
QImage內部數據按照行4字節對齊,即圖像一行的字節數必須整除4,如果圖像實際寬度w能整除4,則可以采用連續內存的構造函數創建QImage,如果圖像實際寬度不能整除4,則必須采用非連續內存的構造函數創建QImage,此時需要指定每行的實際字節數
示例:對於w* h=1000 * 1000的圖像,由於1000/4=250,可以采用連續內存的構造函數
//imgData的格式是RGB888 QImage qimage(imgData,1000,1000,QImage::Format_RGB888);
對於w* h=1050 * 1000的圖像,由於1050/4=262.5,必須采用非連續內存的構造函數
//imgData的格式是RGB888 QImage qimage(imgData,1050,1000,1050*3,QImage::Format_RGB888);
在創建QImage時,構造函數自動在每一行后面補零,由於w=1050,每行字節數=3150,需要補2個字節,即3152,實際qimage占用內存為3152*1000,比原來多了2000個字節。
下面是完成的代碼
1 void HObjectToQImage(HObject himage,QImage **qimage) 2 { 3 HTuple hChannels; 4 HTuple width,height; 5 width=height=0; 6 HTuple htype; 7 ConvertImageType(himage,&himage,"byte");//將圖片轉化成byte類型
8 CountChannels(himage,&hChannels); //判斷圖像通道數
9
10 if(hChannels[0].I()==1)//單通道圖
11 { 12 HTuple hv_pointer; 13 unsigned char *ptr; 14 GetImagePointer1(himage,&hv_pointer,&htype,&width,&height); 15
16 ptr=(unsigned char *)hv_pointer[0].L(); 17
18 *(*qimage)=QImage(ptr,width,height,width,QImage::Format_Indexed8);//不知道是否已自動4字節對齊
19 } 20 else if(hChannels[0].I()==3)//三通道圖
21 { 22 HTuple hv_ptrRed,hv_ptrGreen,hv_ptrBlue; 23 GetImagePointer3(himage,&hv_ptrRed,&hv_ptrGreen,&hv_ptrBlue,&htype,&width,&height); 24
25 uchar *ptrRed=(uchar*)hv_ptrRed[0].L(); 26 uchar *ptrGreen=(uchar*)hv_ptrGreen[0].L(); 27 uchar *ptrBlue=(uchar*)hv_ptrBlue[0].L(); 28 int bytesperline=(width*8*3+31)/32*4;//針對位深為8的三通道圖進行每行4字節對齊補齊
29 int bytecount=bytesperline*height;//整個圖像數據需要的字節數
30 uchar* data8=new uchar[bytecount]; 31 int lineheadid,pixid; 32 for(int i=0;i<height;i++) 33 { 34 lineheadid=bytesperline*i;//計算出圖像第i行的行首在圖像數據中的地址
35 for(int j=0;j<width;j++) 36 { 37 pixid=lineheadid+j*3;//計算坐標為(i,j)的像素id
38 data8[pixid]=ptrRed[width*i+j]; 39 data8[pixid+1]=ptrGreen[width*i+j]; 40 data8[pixid+2]=ptrBlue[width*i+j]; 41 } 42 } 43
44 *(*qimage)=QImage(data8,width,height,QImage::Format_RGB888); 45 } 46 }
單通道和三通道的數據傳輸部分有些不一樣,單通道直接獲取的一個數據指針,可以用指針賦值直接通過圖像數據生成QImage;三通道則是生成了三個通道的指針,所以需要通過遍歷每個像素數據,將三個通道的數據整合成符合Format_RGB888的圖像數據,再將數據生成QImage。
總結:因為三通道圖轉換過程中有遍歷圖像像素的過程,耗時明顯感覺較長,不知從何處改進。
在網上找到一個例子,有些地方實現方法不一樣,可以借鑒下
————————————————————————————————————————————————————
上面那個三通道圖像轉換的速度實在是太慢了,想尋找優化的方法。發現時間都耗在循環中的數組賦值,開始想的是字符數組的賦值效率問題,改進了下賦值方式
1 *(data8+pixid)=*(ptrRed+width*i+j); 2 *(data8+pixid+1)=*(ptrGreen+width*i+j); 3 *(data8+pixid+2)=*(ptrBlue+width*i+j);
將時間從兩秒多降到了一秒多,但是效率還是遠遠不夠。
想着如果halcon中三通道圖也能像單通道圖那樣獲取一個圖像數據指針就好了,后來還真找到了這個方法。
想起了以前看到過的一個halcon例程:gen_image_interleaved,內容大概是這樣
1 * This example program shows how to use gen_image_interleaved. It performs 2 * various transformations of an image matrix with interleaved pixels into 3 * a three-channel HALCON image. 4 *
5 * First of all an image matrix with interleaved pixels is calculated. 6 read_image (Image, 'claudia') 7 rgb3_to_interleaved (Image, ImageInterleaved) 8 *
9 get_image_pointer1 (ImageInterleaved, Pointer, TypeRGB, Width, Height) 10 dev_close_window () 11 dev_open_window (0, 0, Width / 3, Height, 'black', WindowHandle) 12 dev_set_part (0, 0, Height, Width / 3) 13 *
14 * A simple conversion. 15 gen_image_interleaved (BImageRGB1, Pointer, 'rgb', Width / 3, Height, -1, 'byte', 0, 0, 0, 0, -1, 0)
展示了如何用這個算子將一個像素交織着的圖像(直接照字面意思翻譯過來的)矩陣轉換成一個三通道的halcon圖像,為了展示,首先就通過
rgb3_to_interleaved
轉換出一個交織的像素(以前看到時候不懂什么是interleaved pixels,現在才知道就是我想要的rgb圖像格式)的圖像。
再通過獲取單通道圖像數據指針的方式獲取這個交織像素圖像數據的指針(這就是我們想要的),后面就是轉化成halcon圖像了。
然后就是實現:
將halcon代碼導出為c++文件,打開提取需要的部分,發現短短一行
rgb3_to_interleaved (Image, ImageInterleaved)
在c++文件里面是一大段函數實現的:
1 void rgb3_to_interleaved (HObject ho_ImageRGB, HObject *ho_ImageInterleaved); 2 void rgb3_to_interleaved (HObject ho_ImageRGB, HObject *ho_ImageInterleaved) 3 { 4 // Local iconic variables
5 HObject ho_ImageAffineTrans, ho_ImageRed, ho_ImageGreen; 6 HObject ho_ImageBlue, ho_RegionGrid, ho_RegionMoved, ho_RegionClipped; 7
8 // Local control variables
9 HTuple hv_PointerRed, hv_PointerGreen, hv_PointerBlue; 10 HTuple hv_Type, hv_Width, hv_Height, hv_HomMat2DIdentity; 11 HTuple hv_HomMat2DScale; 12
13 GetImagePointer3(ho_ImageRGB, &hv_PointerRed, &hv_PointerGreen, &hv_PointerBlue, 14 &hv_Type, &hv_Width, &hv_Height); 15 GenImageConst(&(*ho_ImageInterleaved), "byte", hv_Width*3, hv_Height); 16 // 17 HomMat2dIdentity(&hv_HomMat2DIdentity); 18 HomMat2dScale(hv_HomMat2DIdentity, 1, 3, 0, 0, &hv_HomMat2DScale); 19 AffineTransImageSize(ho_ImageRGB, &ho_ImageAffineTrans, hv_HomMat2DScale, "constant", 20 hv_Width*3, hv_Height); 21 // 22 Decompose3(ho_ImageAffineTrans, &ho_ImageRed, &ho_ImageGreen, &ho_ImageBlue); 23 GenGridRegion(&ho_RegionGrid, 2*hv_Height, 3, "lines", hv_Width*3, hv_Height+1); 24 MoveRegion(ho_RegionGrid, &ho_RegionMoved, -1, 0); 25 ClipRegion(ho_RegionMoved, &ho_RegionClipped, 0, 0, hv_Height-1, (3*hv_Width)-1); 26 //NOTE: Due to internal limitations, the images ImageRed, ImageGreen, and ImageBlue 27 //cannot be displayed by HDevelop.Trying to display one of these images results in the 28 //error message 'Internal error: number of chords too big for num_max'. However, this 29 //affects by no means the continuation or the results of this example program, and 30 //therefore, is no reason to be alarmed !
31 ReduceDomain(ho_ImageRed, ho_RegionClipped, &ho_ImageRed); 32 MoveRegion(ho_RegionGrid, &ho_RegionMoved, -1, 1); 33 ClipRegion(ho_RegionMoved, &ho_RegionClipped, 0, 0, hv_Height-1, (3*hv_Width)-1); 34 ReduceDomain(ho_ImageGreen, ho_RegionClipped, &ho_ImageGreen); 35 MoveRegion(ho_RegionGrid, &ho_RegionMoved, -1, 2); 36 ClipRegion(ho_RegionMoved, &ho_RegionClipped, 0, 0, hv_Height-1, (3*hv_Width)-1); 37 ReduceDomain(ho_ImageBlue, ho_RegionClipped, &ho_ImageBlue); 38 OverpaintGray((*ho_ImageInterleaved), ho_ImageRed); 39 OverpaintGray((*ho_ImageInterleaved), ho_ImageGreen); 40 OverpaintGray((*ho_ImageInterleaved), ho_ImageBlue); 41 return; 42 }
暫時還沒研究具體是什么原理,先用着。。。。
於是類型轉化函數變成了這樣:
1 void HObjectToQImage(HObject himage,QImage **qimage) 2 { 3 HTuple hChannels; 4 HTuple width,height; 5 width=height=0; 6 HTuple htype; 7 HTuple hpointer; 8
9 ConvertImageType(himage,&himage,"byte");//將圖片轉化成byte類型
10 CountChannels(himage,&hChannels); //判斷圖像通道數
11
12 if(hChannels[0].I()==1)//單通道圖
13 { 14 unsigned char *ptr; 15
16 GetImagePointer1(himage,&hpointer,&htype,&width,&height); 17
18 ptr=(unsigned char *)hpointer[0].L(); 19 *(*qimage)=QImage(ptr,width,height,width,QImage::Format_Indexed8);//不知道是否已自動4字節對齊
20 } 21 else if(hChannels[0].I()==3)//三通道圖
22 { 23 unsigned char *ptr3; 24 HObject ho_ImageInterleaved; 25 rgb3_to_interleaved(himage, &ho_ImageInterleaved); 26
27 GetImagePointer1(ho_ImageInterleaved, &hpointer, &htype, &width, &height); 28
29 ptr3=(unsigned char *)hpointer[0].L(); 30 *(*qimage)=QImage(ptr3,width/3,height,width,QImage::Format_RGB888); 31 } 32 }
注意其中
*(*qimage)=QImage(ptr3,width/3,height,width,QImage::Format_RGB888);
因為在rgb3_to_interleaved時,將圖像寬度拉長為原來的三倍,所以在GetImagePointer1生成圖像數據時獲取的圖像寬度是實際寬度的三倍,因此在給QImage傳參數時需要將width/3,而第四個參數行數據寬度就剛好是width了。
測了下轉換時間,已經降到了原來的百分之一左右(原來要1s現在只要0.01s),雖然還是不算快,不過已經進步了很多。
還有個小問題是轉換生成的圖像的前幾個像素有點問題,如這樣:
放大看是這樣,,(O_O)?
暫時不知道是怎么出現這種問題的,不過應該問題不大。