OpenCV是一個(gè)非常強(qiáng)大的圖形處理框架,可以運(yùn)行在Linux、Windows、Android和Mac OS操作系統(tǒng)上,在自動(dòng)駕駛、智能家居、人臉識(shí)別、圖片處理等方面提供了非常豐富且功能強(qiáng)大的api,在圖片處理方便,基本上可以滿足對(duì)圖片處理的所有需求。近期項(xiàng)目中有使用opencv作為圖片處理框架的需求,而且項(xiàng)目對(duì)圖片處理的需求并不是最常用的8bit色深圖片,而是16bit色深,所以在開發(fā)的過程中踩了很多坑,同時(shí)也對(duì)opencv的使用有了更深的理解,特此記錄回顧,也希望能給正在研究OpenCV的小伙伴提供一點(diǎn)思路。
本文簡單講解OpenCV的集成及Mat和UIImage互相轉(zhuǎn)化,下一篇文章會(huì)詳細(xì)記錄使用OpenCV對(duì)圖片進(jìn)行類似于美圖秀秀的各種處理功能。
一 集成OpenCV
OpenCV的集成有兩種方式
1.使用cocoapods進(jìn)行集成,在Podfile文件中使用
pod 'OpenCV', '~> 4.7.0'
即可集成opencv的4.7.0版本
2.手動(dòng)集成
需要去Opencv官網(wǎng)下載iOS端使用的框架,下載地址
https://opencv.org/releases/

選擇iOS端的包下載就好,然后將下載下來的文件夾整個(gè)導(dǎo)入項(xiàng)目中

即可正常使用
二 Mat和UIImage的互相轉(zhuǎn)化
Mat是OpenCV中提供的一個(gè)重要的類,Mat中包含了圖片的很多信息,比如圖片的像素寬高、通道數(shù)量,iOS端使用opencv框架對(duì)圖片的處理基本上也都需要轉(zhuǎn)化為Mat對(duì)象之后才可以正常進(jìn)行。
注意:轉(zhuǎn)化方法使用c++代碼,所以在代碼的編寫文件以及使用該文件的地方,都需要將.m改為.mm,以告訴編譯器以c++的形式來編譯這些文件,否則會(huì)報(bào)錯(cuò)。
代碼里特意標(biāo)明了清晰的注釋,幫助小伙伴理解。想深入梳理的務(wù)必閱讀,伸手黨直接復(fù)制粘貼就好,互相轉(zhuǎn)化的方法對(duì)8位及16位的RGB及RGBA圖片都做了兼容,可以愉快使用。其他特殊格式如16bpp的圖片,請照葫蘆畫瓢,單獨(dú)處理,思路和方法是一樣的。
1.UIImage轉(zhuǎn)Mat
+(cv::Mat)cvMatFromUIImage:(UIImage *)image
{
//獲取圖片的CGImageRef結(jié)構(gòu)體
CGImageRef imageRef = CGImageCreateCopy([image CGImage]);
//獲取圖片尺寸
CGSize size = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
//獲取圖片寬度
CGFloat cols = size.width;
//獲取圖高度
CGFloat rows = size.height;
//獲取圖片顏色空間,創(chuàng)建圖片對(duì)應(yīng)Mat對(duì)象,需要使用同樣的顏色空間
CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
//判斷圖片的通道位深及通道數(shù) 默認(rèn)使用8位4通道格式
int type = CV_16UC4;
//獲取bitmpa位數(shù)
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
//獲取通道位深
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
//獲取通道數(shù)
size_t channels = bitsPerPixel/bitsPerComponent;
if(channels == 3 || channels == 4){ // 因?yàn)閝uartz框架只支持處理帶有alpha通道的數(shù)據(jù),所以3通道的圖片采取跟4通道的圖片一樣的處理方式,轉(zhuǎn)化的時(shí)候alpha默認(rèn)會(huì)賦最大值,歸一化的數(shù)值位1.0,這樣即使給圖片增加了alpha通道,也并不會(huì)影響圖片的展示
if(bitsPerComponent == 8){
//8位3通道 因?yàn)閕OS端只支持
type = CV_8UC4;
}else if(bitsPerComponent == 16){
//16位3通道
type = CV_16UC4;
}else{
printf("圖片格式不支持");
abort();
}
}else{
printf("圖片格式不支持");
abort();
}
//創(chuàng)建位圖信息 根據(jù)通道位深及通道數(shù)判斷使用的位圖信息
CGBitmapInfo bitmapInfo;
if(bitsPerComponent == 8){
if(channels == 3){
bitmapInfo = kCGImageAlphaNone | kCGImageByteOrderDefault;
}else if(channels == 4){
bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrderDefault;
}else{
printf("圖片格式不支持");
abort();
}
}else if(bitsPerComponent == 16){
if(channels == 3){ //雖然是三通道,但是iOS端的CGBitmapContextCreate方法不支持16位3通道的創(chuàng)建,所以仍然作為4通道處理
bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrder16Little;
}else if(channels == 4){
bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrder16Little;
}else{
printf("圖片格式不支持");
abort();
}
}else{
printf("圖片格式不支持");
abort();
}
//使用獲取到的寬高創(chuàng)建mat對(duì)象CV_16UC4 為傳入的矩陣類型
cv::Mat cvMat(rows, cols, type); // 每通道8bit 共有4通道(RGB + Alpha通道 RGBA格式)
CGContextRef contextRef = CGBitmapContextCreate(cvMat.data, // 數(shù)據(jù)源
cols, // 每行像素?cái)?shù)
rows, // 列數(shù)(高度)
bitsPerComponent, // 每個(gè)通道bit數(shù)
cvMat.step[0], // 每行字節(jié)數(shù)
colorSpace, // 顏色空間
bitmapInfo); // 位圖信息(alpha通道信息,字節(jié)讀取信息)
//將圖片繪制到上下文中mat對(duì)象中
CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
//釋放imageRef對(duì)象
CGImageRelease(imageRef);
//釋放顏色空間
CGColorSpaceRelease(colorSpace);
//釋放上下文環(huán)境
CGContextRelease(contextRef);
return cvMat;
}
2.Mat轉(zhuǎn)Image
+(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat
{
//獲取矩陣數(shù)據(jù)
NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
//判斷矩陣使用的顏色空間
CGColorSpaceRef colorSpace;
if (cvMat.elemSize() == 1) {
colorSpace = CGColorSpaceCreateDeviceGray();
} else {
colorSpace = CGColorSpaceCreateDeviceRGB();
}
//創(chuàng)建數(shù)據(jù)privder
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
//獲取bitmpa位數(shù)
size_t bitsPerPixel = cvMat.elemSize()*8;
//獲取通道數(shù)
size_t channels = cvMat.channels();
//獲取通道位深
size_t bitsPerComponent = bitsPerPixel/channels;
//創(chuàng)建位圖信息 根據(jù)通道位深及通道數(shù)判斷使用的位圖信息
CGBitmapInfo bitmapInfo;
if(bitsPerComponent == 8){
if(channels == 3){
bitmapInfo = kCGImageAlphaNone | kCGImageByteOrderDefault;
}else if(channels == 4){
bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrderDefault;
}else{
printf("圖片格式不支持");
abort();
}
}else if(bitsPerComponent == 16){
if(channels == 3){
bitmapInfo = kCGImageAlphaNone | kCGImageByteOrder16Little;
}else if(channels == 4){
bitmapInfo = kCGImageAlphaPremultipliedLast | kCGImageByteOrder16Little;
}else{
printf("圖片格式不支持");
abort();
}
}else{
printf("圖片格式不支持");
abort();
}
//根據(jù)矩陣及相關(guān)信息創(chuàng)建CGImageRef結(jié)構(gòu)體
CGImageRef imageRef = CGImageCreate(cvMat.cols, //矩陣寬度
cvMat.rows, //矩陣列數(shù)
bitsPerComponent, //通道位深
8 * cvMat.elemSize(), //每個(gè)像素位深
cvMat.step[0], //每行占用字節(jié)數(shù)
colorSpace, //使用的顏色空間
bitmapInfo,//通道排序、大小端讀取順序信息
provider, //數(shù)據(jù)源
NULL, //解碼數(shù)組 一般傳null
true, //是否抗鋸齒
kCGRenderingIntentDefault //使用默認(rèn)的渲染方式
);
// 通過cgImage轉(zhuǎn)化出來UIImage對(duì)象
UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
//釋放imageRef
CGImageRelease(imageRef);
//釋放provider
CGDataProviderRelease(provider);
//釋放顏色空間
CGColorSpaceRelease(colorSpace);
return finalImage;
}
3.一個(gè)小工具,使用Mat打印圖片詳細(xì)信息,方便核對(duì)數(shù)據(jù)
//獲取圖片信息
+(void)readInfoWithImage:(UIImage*)inputImage{
Mat inputMat = [CVTools matFromImage:inputImage];
printf("圖片寬度 = %d \n",inputMat.cols);
printf("圖片高度 = %d \n",inputMat.rows);
printf("通道位深 = %zu \n",inputMat.elemSize()*8/inputMat.channels());
printf("通道數(shù) %d \n",inputMat.channels());
printf("每個(gè)像素bit數(shù) = %zu \n",inputMat.elemSize()*8);
printf("每行元素的字節(jié)數(shù) = %zu \n",inputMat.step[0]);
}
三 常見問題
1.提示不支持的參數(shù)組合
因?yàn)閝uarzt 2D框架對(duì)于圖片的處理有著嚴(yán)格的規(guī)定,所以對(duì)于Bitmapinfo內(nèi)的alpha通道和讀取順序組合有著明確的規(guī)則,報(bào)錯(cuò)如下

解決方法
第一種方式是通過官網(wǎng)查閱quartz允許的組合搭配,官網(wǎng)截圖如下:

第二種方法是根據(jù)提示去設(shè)置環(huán)境變量在Log窗口打印支持的組合搭配,設(shè)置方式如下


增加“CGBITMAP_CONTEXT_LOG_ERRORS”位圖環(huán)境錯(cuò)誤log信息的打印,然后再運(yùn)行Log窗口輸出如下:

可以看到,對(duì)于8Bit和16Bit通道位深的圖片,quartz只支持帶有alpha通道的,通道的讀取方式也有明確規(guī)定,根據(jù)自己的圖片格式采取相應(yīng)的配置就可以了。
因?yàn)閝uartz框架只支持處理帶有alpha通道的數(shù)據(jù),所以3通道的圖片采取跟4通道的圖片一樣的處理方式,轉(zhuǎn)化的時(shí)候alpha默認(rèn)會(huì)賦最大值,歸一化的數(shù)值位1.0,這樣即使給圖片增加了alpha通道,也并不會(huì)影響圖片的展示
這個(gè)地方很坑,以16位圖片來說,即使明知圖片是含有alpha通道的,而且alpha通道的位置在最后,也并不能使用kCGImageAlphaLast的圖片通道信息,而是要使用kCGImageAlphaPremultipliedLast的枚舉來約束,但是如果是8位的圖片卻并沒有這個(gè)限制,而且字節(jié)讀取順序需要額外注明使用16位小端讀取kCGImageByteOrder16Little,做16位圖片處理的小伙伴一定要注意,深坑啊。
2.在導(dǎo)入頭文件的時(shí)候,一定要將oencv用到的頭文件放在所有OC的文件引用之前引用,否則會(huì)出現(xiàn)函數(shù)重定義沖突
以該測試工程里的文件為例,頭文件引用方式為:
#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>
#include <math.h>
#include <iostream>
using namespace cv;
using namespace std;
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
使用的命名空間也需要額外聲明。
有想法歡迎交流,等你。