在ios下開發基於opencv的程序時經常會用到兩條接口,分別是UIImageToMat()和MatToUIImage(),這兩條接口是UIImage與Mat之間的轉換。關於這兩條api的信息opencv文檔裡面沒有給出太多的信息,所以,需要通過閱讀源碼來分析。
關於這條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通道如果轉換時沒有指定,則默認是有的。
下面來了解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通道
UIImageToMat輸出的mat默認帶Alpha通道,可以選擇不帶alpha通道,在UIImageToMat()的第三個參數指定就行,MatToUIImga輸出的UIImage默認為RGB類型