Корень этого вопроса: Каков побитовый формат свойства OpenCV IplImage-> imageData?
Справочная информация. Я использую ctypes Python, чтобы разрешить питоновский доступ к низкоуровневой библиотеке C, которая использует OpenCV. Я смог получить почти все функции, доступные из python, но я застрял на этом, который требует данных старой структуры OpenCV, известной как IplImage, в частности свойство imageData. Я не могу понять, как IplImage-> imageData организована по сравнению с типом типа python cv2.cv.LoadImage iplimage, который якобы имеет те же данные, что и структура C, но, похоже, он организован по-разному.
Так, например, у меня есть 4-пиксельное изображение размером 2x2 пикселя. Верхний левый пиксель на 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"), назначая его структуре IplImage с именем 'image'.
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 байта каждый). Когда я распечатываю свойство image-> imageSize из C, я получаю 16, а не 12. Так что что-то гнилое, я его не понимаю. Очевидно, что что-то не так с моей моделью того, что в imageData. Вы можете это объяснить?
В коде python, который я использовал, отсутствовала некоторая требуемая логика. Эта логика не применяется в интерфейсе Python, и в Python нет подсказки, как это работает в библиотеке C. В принципе, IplImage (и я считаю, что Mat тоже, C++, преемник старой структуры IplImage) выставляет строки пикселей в свойстве 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 заполняется правильной информацией из API python opencv. Обратите внимание, что это было бы одинаково, если вы использовали метод numpy_array.tostring() для данных и хотели использовать его для заполнения объекта Mat. Надеюсь, это поможет кому-то.