最近在使用JNI,设法从Java中调用OpenCV的函数。在OpenCV中图像是以IplImage的形式封装的。IplImage是一个Header,定义了图像的各种属性,通过查看IplImage结构体我们可以发现,图像数据实际上也是在一段连续的内存中分配的,IplImage.imageData就是指向这块数据的指针。随便访问一个结构体的成员是不好的,所以OpenCV提供了访问原始数据RawData的方法
void cvGetRawData( const CvArr* arr, uchar** data, int* step=NULL, CvSize* roi_size=NULL );
典型的,对于一个三通道,颜色深度为256的图像img,假设我们知道它的width,height,为了取得其RawData,可以定义一个数组data,并调用cvGetRawData()函数,将RawData存放到data中:
int raw_data_length = width*height*3;
byte* data= (byte *)malloc(raw_data_length * sizeof(byte));
cvGetRawData( img, (uchar**)&data, NULL, NULL );
那么,这个时候,data内部是如何组织的呢?按照一般的逻辑,它应该是这样
{{34,123,90},{34,122,87},{21,123,88},......}
按照从左到右,从上到下的顺序,每个像素占三个byte。
如果把这个数据通过JNI传给Java程序,然后再按照上述逻辑重新“组装”成一幅图像,问题就来了。
为了便于调试,我使用了Matlab来“组装”并观察这幅Raw图像(height=13, width=17):
a=[34,123,90,34,122,];
m=1;
for i=1:13
for j=1:17
for k=1:3
if a(m)<0
a(m)=a(m)+256;
end
d(i,j,k)=a(m);m=m+1;
end
end
end
figure,imshow(uint8(d));
应该是图像RawData的Size,Step之类的错误。
最终我没有找到具体错在那里,但是用下面的代码可以得到正确的数据。
// reconstruct image data
char *data;
int i, j, step;
CvSize size;
cvGetRawData( rstImg, (uchar**)&data, &step, &size );
step /= sizeof(data[0]);
int k = 0, offset = 0;
for ( i=0; i<size.height; i++, offset=i*step) {
for ( j=0; j<size.width; j++ ) {
result[k++] = data[offset+2];
result[k++] = data[offset+1];
result[k++] = data[offset];
offset += 3;
}
}
注意,Java里面的RGB图像和OpenCV的通道顺序正好是反过来的,即一个是RGB,一个是BGR。
至此,总算可以把OpenCV的图像和Java的图像对应起来了。至于如何在两者之间传递图像数据,请参见@todo: