将Halcon HObject类型转为Qt QImage类型

将Halcon HObject类型转为Qt QImage类型

差不多一个月前,在尝试解决将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),虽然还是不算快,不过已经进步了很多。

还有个小问题是转换生成的图像的前几个像素有点问题,如这样:将Halcon HObject类型转为Qt QImage类型

放大看是这样,将Halcon HObject类型转为Qt QImage类型,(O_O)?

暂时不知道是怎么出现这种问题的,不过应该问题不大。