你的位置:首页 > 操作系统

[操作系统]opencv源码阅读之——iOS的两条接口UIImageToMat()和MatToUIImage()

在ios下开发基于opencv的程序时经常会用到两条接口,分别是UIImageToMat()和MatToUIImage(),这两条接口是UIImage和cv::Mat之间的互转。关于这两条api的信息opencv文档里面没有给出太多的信息,所以,需要通过阅读源码来分析。

1.UIImageToMat的细节

关于这条api,我们总想知道返回的mat的一些细节,比如是几通道的,是否带alpha通道,色彩空间是RGBA还BGR的,我们都不清楚,带着这几个问题,我们一起来阅读源码:

void UIImageToMat(const UIImage* image,             cv::Mat& m, bool alphaExist) {  CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);  CGFloat cols = image.size.width, rows = image.size.height;  CGContextRef contextRef;  CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast;  if (CGColorSpaceGetModel(colorSpace) == 0)  {    m.create(rows, cols, CV_8UC1); // 8 bits per component, 1 channel    bitmapInfo = kCGImageAlphaNone;    if (!alphaExist)      bitmapInfo = kCGImageAlphaNone;    contextRef = CGBitmapContextCreate(m.data, m.cols, m.rows, 8,                      m.step[0], colorSpace,                      bitmapInfo);  }  else  {    m.create(rows, cols, CV_8UC4); // 8 bits per component, 4 channels    if (!alphaExist)      bitmapInfo = kCGImageAlphaNoneSkipLast |                kCGBitmapByteOrderDefault;    contextRef = CGBitmapContextCreate(m.data, m.cols, m.rows, 8,                      m.step[0], colorSpace,                      bitmapInfo);  }  CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows),            image.CGImage);  CGContextRelease(contextRef);}

这个源码是在文件夹modules/imgcodecs/src/ ios_conversions.mm里面的。从源码可以看出,opencv会先把UIImage类型的image转化为CGImga,这是一个位图bitmap图像

@property(nullable, nonatomic,readonly) CGImageRef CGImage; // returns underlying CGImageRef or nil if CIImage based

The CGImageRef opaque type represents bitmap images and bitmap image masks, based on sample data that you supply. A bitmap (or sampled) image is a rectangular array of pixels, with each pixel representing a single sample or data point in a source image.

CGImageGetColorSpace会获取指定图像的色彩空间,如果指定的图像(也不能叫图像)是一个图像蒙板(image mask),则返回NULL,图像蒙板其实就是ps里面的蒙板,蒙板指定的区域才会显示,具体可以看ios文档说明。opencv的c++版不同于java版,java版里面的图像有width和height两个对象,c++里面是cols和rows,分别代表宽和高,其实就是Mat的列数和行数,表示不同。

然后获取图像的色彩模式CGColorSpaceGetModel,得到一个枚举值:

typedef CF_ENUM (int32_t, CGColorSpaceModel) {  kCGColorSpaceModelUnknown = -1,  kCGColorSpaceModelMonochrome,  kCGColorSpaceModelRGB,  kCGColorSpaceModelCMYK,  kCGColorSpaceModelLab,  kCGColorSpaceModelDeviceN,  kCGColorSpaceModelIndexed,  kCGColorSpaceModelPattern};

kCGColorSpaceModelMonochrome是单色图,也就是黑白的灰度图,如果是灰度图则通过CGBitmapContextCreate往cv::Mat类型的m里面的data写数据,

大小和原图一样,色彩空间也是和原图一样,这里就是单色图,没有alpha通道。如果不是灰度图,则把原来图片的色彩空间的色彩写进cv::Mat的data里,得到转换的m。

通过阅读源码可以知道,如果传进来的是单色灰度图,则返回的也是单色灰度图,没有alpha通道。如果传进来的是RGB 或者BGRA的,则会返回原图的色彩空间RGB或者 BGRA,其中A通道如果转换时没有指定,则默认是有的。

2.MatToUIImage的转换细节

下面来了解MatToUIImage的转换细节,源码:

UIImage* MatToUIImage(const cv::Mat& image) {  NSData *data = [NSData dataWithBytes:image.data                 length:image.elemSize()*image.total()];  CGColorSpaceRef colorSpace;  if (image.elemSize() == 1) {    colorSpace = CGColorSpaceCreateDeviceGray();  } else {    colorSpace = CGColorSpaceCreateDeviceRGB();  }  CGDataProviderRef provider =      CGDataProviderCreateWithCFData((__bridge CFDataRef)data);  // Preserve alpha transparency, if exists  bool alpha = image.channels() == 4;  CGBitmapInfo bitmapInfo = (alpha ? kCGImageAlphaLast : kCGImageAlphaNone) | kCGBitmapByteOrderDefault;  // Creating CGImage from cv::Mat  CGImageRef imageRef = CGImageCreate(image.cols,                    image.rows,                    8,                    8 * image.elemSize(),                    image.step.p[0],                    colorSpace,                    bitmapInfo,                    provider,                    NULL,                    false,                    kCGRenderingIntentDefault                    );  // Getting UIImage from CGImage  UIImage *finalImage = [UIImage imageWithCGImage:imageRef];  CGImageRelease(imageRef);  CGDataProviderRelease(provider);  CGColorSpaceRelease(colorSpace);  return finalImage;}

首先会获取传入的矩阵的data原始数据,长度为image.elemSize()*image.total(),elemSize()会返回元素的大小(单位:字节bytes),详情可以看opencv的文档说明(其实就是通道数*通道元素大小,例如元素类型为CV_8UC3,它的大小其实就是3字节,每个通道一字节8位bit,三个通道)。image.total()返回图像的所有元素数量。

然后判断image的元素大小,如果为1字节,那么就设置色彩空间是单色灰度图gray(CV_8UC1),如果不是1字节,那么就设置色彩空间为RGB(所以通过MatToUIImage()转换的图像色彩空间都是RGB的),然后判断元素的大小是否为4字节,如果为4字节则是带alpha通道的,因为RGB各占一个通道,也就是各一个字节,第四个字节就是alpha通道。

CGDataProviderCreateWithCFData创建一个CGDataProviderRef对象(_bridge标记是xcode 在Core Foundation和Foundation对象之间的转换要通过Toll-Free bridge来对内存管理进行转换,只在ARC下有效)

CGImageCreate会通过参数创建一个bitmap位图图像,各参数可以看说明文档。再通过bitmap创建UIImage,所以通过阅读源码可以知道,图像的色彩空间是如果原图是单色灰度图,则UIImage的色彩空间为单色灰度图,如果不是,则默认为RGB,如果cv::Mat是带alpha通道,则转换的UIImage带Alpha通道

 

3.总结

UIImageToMat输出的mat默认带Alpha通道,可以选择不带alpha通道,在UIImageToMat()的第三个参数指定就行,MatToUIImga输出的UIImage默认为RGB类型