最近在自学Android开发,要用到OpenCV来进行有关的图像处理,在过程中遇到了一些小问题
小白刚开始学,自己总结了一些原因,可能有错误,还请大家赐教。
下面是我在用OpenCV中的灰度化函数对图片进行灰度化的时候遇到问题和解决办法
参考别人的资料,一般都是将java中的Bitmap转换为数组传入到JNI,然后JNI中将数组转换为Mat类型进行处理,再将处理好的图片通过数组形式传回java层;
下面是将Bitmap转换为数组的过程
//Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.cat);
bmp=selectbp;
int w = bmp.getWidth();
int h = bmp.getHeight();
int[] pixels = new int[w * h];
上面为JAVA层获取图片大小和每一个像素的代码,pixels数组中保存像素值。
下面是JNI中对图像灰度化的处理过程
Java_com_example_liuxiaodong_myphoto_NDKloader_getGrayImage(JNIEnv *env, jclass type,
jintArray buf, jint w, jint h) {
jint *cbuf;
cbuf = env->GetIntArrayElements(buf, NULL);
if (cbuf == NULL) {
return 0;
}
Mat image(h, w, CV_8UC4, (unsigned char*) cbuf);
cvtColor(image,image,COLOR_BGR2GRAY);
int* outImage = new int[w * h];
int n = 0;
for(int i = 0; i < h; i++) {
uchar* data = image.ptr(i);
for(int j = 0; j < w; j++) {
outImage[n++] = data[j];
}
}
int size = w * h;
jintArray result = env->NewIntArray(size);
env->SetIntArrayRegion(result, 0, size, outImage);
env->ReleaseIntArrayElements(buf, cbuf, 0);
return result;
执行如上操作进行灰度化之后,我发现输出的结果与实际不符合,如果生成的Bitmap为ARGB_8888,则输出是空白,如果是RGB_565,则是如下图所示。
原图:
处理后的图:
然后我在VS中调试发现,在执行完cvtColor(image,image,COLOR_BGR2GRAY)这个函数后,原本的4通道或者三通道的Mat对象变成了单通道,这时候在VS上的输出是没有影响的。但是当我们把image的数据传回的Bitmap类时,却发生了变化,原因如下:
在Bitmap中,不管是四通道还是三通道的图片,取得的pixels数组元素个数都为上面的w*h,以ARGB_8888为例,pixels数组中一个元素保存了该像素点的ARGB四个值的信息,以一个8位的16进制数表示,数据范围为0x00000000-0xffffffff,其中最高的两位表示透明度,然后往下依次是RGB三个通道的信息,每个通道取值都是0-255;所以我们直接输出pixels数组中元素的值的时候,会发现他为一个很大的负数,这是由于32位系统的第一位为符号位,一般图片透明度为255,也就是不透明,所以自然就变成了负数。当我们将数组pixels通过Mat image(h, w, CV_8UC4, (unsigned char*) cbuf)得到Mat对象的时候,由于这里的M at对象为四通道,所以原本的 w*h的矩阵变成了w*4*h的矩阵,这里我举一个简单的例子。假设有一个3x3像素的图片保存在Bitmap中如下:
0xffffffff | 0xffffffff | 0xffffffff |
0xffffffff | 0xffffffff | 0xffffffff |
0xffffffff | 0xffffffff | 0xffffffff |
那么它经过Mat image(h, w, CV_8UC4, (unsigned char*) cbuf)产生的四通道Mat对象image内的数据为:
255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 |
255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 |
255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 | 255 |
而将image执行完cvtColor(image,image,COLOR_BGR2GRAY)这个函数后,image内的数据变成了
255 | 255 | 255 |
255 | 255 | 255 |
255 | 255 | 255 |
所以当Bitmap接收到上面的输出时,如果接收的Bitmap对象是RGB_565格式生成的,那么就代表最低的2位有0-255的值,也就是蓝色通道有不同的值,所以上面的图片为蓝色,而当接收的Bitmap对象是ARGB_8888格式生成的,尽管蓝色通道有值,但是ALPHA通道的值为0.即图片完全透明,所以看到的是空白。
由于Bitmap中没有单通道的表示(可能有但是我不知道),所以要使得Android输出为我们所想要的灰度图,还需要将红色通道和绿色通道的值变成和蓝色通道一样,这样Bitmap就能得到我们想要的结果,这里以RGB_565的Bitmap为例;
在我们经过cvtColor(image,image,COLOR_BGR2GRAY)函数后,将原本的输出变为如下的函数
int* outImage = new int[w * h];
int n = 0;
for(int i = 0; i < h; i++) {
uchar* data = image.ptr(i);
for(int j = 0; j < w; j++) {
int a = 0;
a=trn(env,data[j]);
outImage[n++] =a;
}
}
trn的作用是把0x5f变为0x5f5f5f,即将红色通道和绿色通道的值变成和蓝色通道一样
jint trn(JNIEnv* env, jint in){
jint out;
out = (in/ 16) * 256*16 + (in % 16) * 256 + (in % 16) * 256*256+ (in / 16) * 256 * 16*256+in;
return out;
}
这样我们就可以得到如下所示的灰度图像:
总而言之,关键点在于把Mat和Bitmap中的数据保存方式弄清楚~