OpenCV在iOS端的集成及Mat和UIImage互相轉(zhuǎn)化(附源碼)

OpenCV是一個非常強大的圖形處理框架颖系,可以運行在Linux、Windows辩越、Android和Mac OS操作系統(tǒng)上嘁扼,在自動駕駛、智能家居黔攒、人臉識別趁啸、圖片處理等方面提供了非常豐富且功能強大的api,在圖片處理方便督惰,基本上可以滿足對圖片處理的所有需求不傅。近期項目中有使用opencv作為圖片處理框架的需求,而且項目對圖片處理的需求并不是最常用的8bit色深圖片,而是16bit色深赏胚,所以在開發(fā)的過程中踩了很多坑访娶,同時也對opencv的使用有了更深的理解,特此記錄回顧觉阅,也希望能給正在研究OpenCV的小伙伴提供一點思路崖疤。

本文簡單講解OpenCV的集成及Mat和UIImage互相轉(zhuǎn)化,下一篇文章會詳細(xì)記錄使用OpenCV對圖片進(jìn)行類似于美圖秀秀的各種處理功能典勇。

一 集成OpenCV

OpenCV的集成有兩種方式

1.使用cocoapods進(jìn)行集成劫哼,在Podfile文件中使用

pod 'OpenCV', '~> 4.7.0'

即可集成opencv的4.7.0版本

2.手動集成

需要去Opencv官網(wǎng)下載iOS端使用的框架,下載地址
https://opencv.org/releases/

d9e0ca17c4fb4496abfcc22d057678aa.png

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


7616f7b1448146b384bd75a4a9c119a7.png

即可正常使用

二 Mat和UIImage的互相轉(zhuǎn)化

Mat是OpenCV中提供的一個重要的類权烧,Mat中包含了圖片的很多信息,比如圖片的像素寬高伤溉、通道數(shù)量豪嚎,iOS端使用opencv框架對圖片的處理基本上也都需要轉(zhuǎn)化為Mat對象之后才可以正常進(jìn)行。

注意:轉(zhuǎn)化方法使用c++代碼谈火,所以在代碼的編寫文件以及使用該文件的地方侈询,都需要將.m改為.mm,以告訴編譯器以c++的形式來編譯這些文件糯耍,否則會報錯扔字。

代碼里特意標(biāo)明了清晰的注釋,幫助小伙伴理解温技。想深入梳理的務(wù)必閱讀革为,伸手黨直接復(fù)制粘貼就好,互相轉(zhuǎn)化的方法對8位及16位的RGB及RGBA圖片都做了兼容舵鳞,可以愉快使用震檩。其他特殊格式如16bpp的圖片,請照葫蘆畫瓢,單獨處理抛虏,思路和方法是一樣的博其。

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)建圖片對應(yīng)Mat對象迂猴,需要使用同樣的顏色空間
    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){  // 因為quartz框架只支持處理帶有alpha通道的數(shù)據(jù)慕淡,所以3通道的圖片采取跟4通道的圖片一樣的處理方式,轉(zhuǎn)化的時候alpha默認(rèn)會賦最大值沸毁,歸一化的數(shù)值位1.0峰髓,這樣即使給圖片增加了alpha通道,也并不會影響圖片的展示
        if(bitsPerComponent == 8){
            //8位3通道 因為iOS端只支持
            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對象CV_16UC4 為傳入的矩陣類型
    cv::Mat cvMat(rows, cols, type); // 每通道8bit 共有4通道(RGB + Alpha通道 RGBA格式)
    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // 數(shù)據(jù)源
                                                    cols,                       // 每行像素數(shù)
                                                    rows,                       // 列數(shù)(高度)
                                                    bitsPerComponent,                          // 每個通道bit數(shù)
                                                    cvMat.step[0],              // 每行字節(jié)數(shù)
                                                    colorSpace,                 // 顏色空間
                                                    bitmapInfo); // 位圖信息(alpha通道信息,字節(jié)讀取信息)
    //將圖片繪制到上下文中mat對象中
    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    //釋放imageRef對象
    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(),  //每個像素位深
                                        cvMat.step[0],  //每行占用字節(jié)數(shù)
                                        colorSpace,    //使用的顏色空間
                                        bitmapInfo,//通道排序搂誉、大小端讀取順序信息
                                        provider, //數(shù)據(jù)源
                                        NULL,   //解碼數(shù)組 一般傳null
                                        true, //是否抗鋸齒
                                        kCGRenderingIntentDefault   //使用默認(rèn)的渲染方式
                                        );
    // 通過cgImage轉(zhuǎn)化出來UIImage對象
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    //釋放imageRef
    CGImageRelease(imageRef);
    //釋放provider
    CGDataProviderRelease(provider);
    //釋放顏色空間
    CGColorSpaceRelease(colorSpace);
    return finalImage;
}

3.一個小工具徐紧,使用Mat打印圖片詳細(xì)信息,方便核對數(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("每個像素bit數(shù) = %zu \n",inputMat.elemSize()*8);

    printf("每行元素的字節(jié)數(shù) = %zu \n",inputMat.step[0]);
}

三 常見問題

1.提示不支持的參數(shù)組合

因為quarzt 2D框架對于圖片的處理有著嚴(yán)格的規(guī)定勒葱,所以對于Bitmapinfo內(nèi)的alpha通道和讀取順序組合有著明確的規(guī)則浪汪,報錯如下


1b69d955b3cd467a93e4a07a5e2117a0.png

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

782f2d2601ba4f9182c70f04efd6dbd1.png

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


939cdd79342b4fb885b71da73884c77d.png
50aebc2e6cd846e3881dd6ce3974e91e.png

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


f1944feaead744f680cd4731dd78ad9d.png

可以看到,對于8Bit和16Bit通道位深的圖片凯旋,quartz只支持帶有alpha通道的呀潭,通道的讀取方式也有明確規(guī)定,根據(jù)自己的圖片格式采取相應(yīng)的配置就可以了至非。
因為quartz框架只支持處理帶有alpha通道的數(shù)據(jù)钠署,所以3通道的圖片采取跟4通道的圖片一樣的處理方式,轉(zhuǎn)化的時候alpha默認(rèn)會賦最大值荒椭,歸一化的數(shù)值位1.0谐鼎,這樣即使給圖片增加了alpha通道,也并不會影響圖片的展示

這個地方很坑趣惠,以16位圖片來說狸棍,即使明知圖片是含有alpha通道的,而且alpha通道的位置在最后味悄,也并不能使用kCGImageAlphaLast的圖片通道信息草戈,而是要使用kCGImageAlphaPremultipliedLast的枚舉來約束,但是如果是8位的圖片卻并沒有這個限制侍瑟,而且字節(jié)讀取順序需要額外注明使用16位小端讀取kCGImageByteOrder16Little唐片,做16位圖片處理的小伙伴一定要注意,深坑啊。

2.在導(dǎo)入頭文件的時候费韭,一定要將oencv用到的頭文件放在所有OC的文件引用之前引用茧球,否則會出現(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>

使用的命名空間也需要額外聲明揽思。

有想法歡迎交流袜腥,等你见擦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钉汗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鲤屡,更是在濱河造成了極大的恐慌损痰,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酒来,死亡現(xiàn)場離奇詭異卢未,居然都是意外死亡,警方通過查閱死者的電腦和手機堰汉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門辽社,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人翘鸭,你說我怎么就攤上這事滴铅。” “怎么了就乓?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵汉匙,是天一觀的道長。 經(jīng)常有香客問我生蚁,道長噩翠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任邦投,我火速辦了婚禮伤锚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘志衣。我一直安慰自己屯援,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布蠢涝。 她就那樣靜靜地躺著玄呛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪和二。 梳的紋絲不亂的頭發(fā)上徘铝,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音,去河邊找鬼惕它。 笑死怕午,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的淹魄。 我是一名探鬼主播郁惜,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼甲锡!你這毒婦竟也來了兆蕉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤缤沦,失蹤者是張志新(化名)和其女友劉穎虎韵,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缸废,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡包蓝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了企量。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片测萎。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖届巩,靈堂內(nèi)的尸體忽然破棺而出硅瞧,到底是詐尸還是另有隱情,我是刑警寧澤姆泻,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布零酪,位于F島的核電站,受9級特大地震影響拇勃,放射性物質(zhì)發(fā)生泄漏四苇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一方咆、第九天 我趴在偏房一處隱蔽的房頂上張望月腋。 院中可真熱鬧,春花似錦瓣赂、人聲如沸榆骚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妓肢。三九已至,卻和暖如春苫纤,著一層夾襖步出監(jiān)牢的瞬間碉钠,已是汗流浹背纲缓。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留喊废,地道東北人祝高。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像污筷,于是被迫代替她去往敵國和親工闺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容