如何将Python cv2.cv.LoadImage的字符串数据转换为C的IplImage->imageData结构

0 投票
1 回答
816 浏览
提问于 2025-04-17 23:28

这个问题的核心是: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 个回答

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对象,结果也是一样的。希望这能帮助到某些人。

撰写回答