如何将Python cv2.cv.LoadImage的字符串数据转换为C的IplImage->imageData结构
这个问题的核心是:OpenCV中的IplImage->imageData属性的位格式是什么样的?
背景:我正在使用Python的ctypes库来让Python能够访问一个使用OpenCV的低级C库。我几乎能从Python调用所有的函数,但在处理一个需要旧版OpenCV结构体IplImage的数据时遇到了麻烦,特别是imageData属性。我搞不清楚IplImage->imageData是如何组织的,而Python的cv2.cv.LoadImage的iplimage类型看起来有相同的数据,但组织方式却不一样。
举个例子,我有一张2x2像素的图像,总共4个像素。左上角的像素是100%红色,右上角是100%绿色,左下角是100%蓝色,右下角是100%白色。
在Python中,这些信息看起来是这样的:
import cv2
img = cv2.cv.LoadImage('rgbw.png')
pixels = []
for ch in img.tostring():
pixels.append(ord(ch))
print pixels
[0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255]
这让我觉得很合理:前面三个值[0, 0, 255]代表的是B:0, G:0, R:255,也就是红色像素。第二个是绿色,第三个是左下角的蓝色,最后一个是右下角的白色。
我把这些数据传入库中,库的表现正常,但它似乎“看不见”imageData里的内容(我得到的返回码表示“我什么都没看到”,而当我直接用C的API传入这些数据时,显然这些数据是可以理解的)。
所以我怀疑C的IplImage->imageData的数据组织方式完全不同。我在调试器中查看,惊讶地发现不仅数据不同,我也看不懂:这里是用cvLoadImage("rgbw.png")赋值给一个叫'image'的IplImage结构体的结果。
Breakpoint 1, main (argc=2, argv=0x7fffffffe418) at IplImageInfo.cpp:44
44 printf("imageData %s\n", image->imageData);
(gdb) x/16ub image->imageData
0x618c90: 0 0 255 0 255 0 0 0
0x618c98: 255 0 0 255 255 255 0 0
(gdb)
所以我逐字节比较,添加零以便比较:
Python:
000 000 255 | 000 255 000 | 255 000 000 | 255 255 255
C:(打印前16个字节,而不是我预期的12个,见下文)
000 000 255 | 000 255 000 | 000 000 255 | 000 000 255 | 255 255 000 | 000
注意前六个字节在两个数据中是相同的。但接下来发生了什么?我们又有两个红色像素,然后是一个青色像素?还有,这个文件的大小是12字节(4个像素,每个3字节)。当我从C中打印出image->imageSize属性时,我得到的是16,而不是12。所以这里面肯定有问题,我搞不懂。显然我对imageData的理解有误。你能解释一下吗?
1 个回答
我之前用的Python代码缺少了一些必要的逻辑。这些逻辑在Python接口中并不适用,而且在Python里也没有提示说明这在C库中是怎么工作的。简单来说,IplImage(我相信Mat也是;它是旧的IplImage结构的C++版本)会在imageData属性中填充像素行,使其能够被4整除,方法是添加相应数量的空字节(值为0的字节)。所以我之前的代码是这样的:
import cv2
img = cv2.cv.LoadImage('rgbw.png')
pixels = []
for ch in img.tostring():
pixels.append(ord(ch))
print pixels
[0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255]
但是缺少了这个逻辑。我是这样解决的:
import cv2
img = cv2.cv.LoadImage('rgbw.png')
height = img.height
width = img.width
raw_data = img.tostring()
# iplImage->imageData requires rows to be padded with zero bytes at the end
# so they be divisible by 4
pad_bytes_per_row = width % 4
# create the ctypes structure
ubyte_array_type = c_ubyte * (len(raw_data) + (height * pad_bytes_per_row))
ubyte_array = ubyte_array_type()
index = 0
for ch in raw_data:
ubyte_array[index] = ord(ch)
index += 1
if 0 == index % width: # end of row
pad_index = 0
while pad_index < pad_bytes_per_row:
ubyte_array[index] = 0
pad_index += 1
index += 1
现在,ubyte_array里填充了来自opencv的Python API的正确信息。请注意,如果你使用numpy_array.tostring()方法来获取数据,并想用它来填充Mat对象,结果也是一样的。希望这能帮助到某些人。