前言
OpenCV ,是一個開源的跨平臺計算機視覺和機器學習庫,通俗點的說,就是他給計算機提供了一雙眼睛,一雙可以從圖片中獲取資訊的眼鏡,從而完成人臉識別、去紅眼、追蹤移動物體等等的影像相關的功能。更多具體的說明可參見 OpenCV 官網。
匯入工程
匯入 OpenCV 到 Xcode 的工程中還是比較簡單的,從官網下載對應的 framework,直接丟到 Xcode 的工程中,然後在你想用 OpenCV 的地方引入 OpenCV 的標頭檔案:
#import <opencv2/opencv.hpp>
或者直接在 PCH 檔案中新增:
#ifdef __cplusplus
#import <opencv2/opencv.hpp>
#endif
然後把使用到 OpenCV 中 C++方法的實現檔案字尾名改成.mm
,就可以開始使用 OpenCV 的方法了。看起來很簡單,對吧?確實是很簡單,不過這裡有一些要注意的地方,我們一起來看看。
實際問題
首先說明,為何在 PCH 檔案中引入 OpenCV 的標頭檔案我們需要多加#ifdef __cpluseplus
這一部分呢?這是因為 PCH 檔案是一個會被所有的檔案引入的標頭檔案,而我們又希望 #import <opencv2/opencv.hpp>
這部分只會被一些 C++實現檔案編譯,所以我們加上#ifdef __cpluseplus
來表示這是 C++ 檔案才會編譯的,除了#ifdef __cpluseplus
,還有#ifdef __OBJC__
這樣的巨集來說明編譯規則(按照 OC 檔案編譯),這樣的巨集多出現於一些會被多種型別的實現檔案引用的標頭檔案中。
另外注意另一個問題:如果一個標頭檔案是C++型別的標頭檔案,那麼一定要保證所有直接或者間接引用這個標頭檔案的實現檔案都要為.mm
或者.cpp
,否則 Xcode 就不會把這個標頭檔案當做 C++標頭檔案來編譯,就會出現最基本的#include <iostream>
這種引用都會報出file not found
這樣的編譯錯誤的問題。我在編譯的過程中,某個C++標頭檔案 A.h 被 B.h 引用,然後 B.h 又被 C.m 引用,雖然 B 的實現檔案是 B.mm ,但是仍然報出了之前說的那個錯誤, 感謝 StackOberflow 讓我找到了問題發生的原因。所以對於 C++ 標頭檔案的引用一定要注意,但凡是引用了 A.h 的實現部分,都必須是.mm
或者.cpp
字尾名。(同時我們也可以知道,Xcode 是根據標頭檔案被引用的情況來判定標頭檔案的編譯 型別的)。
轉換 UIImage 和 cv::Mat
在 OpenCV 中同常用 cv::Mat 表示圖片,而 iOS 中則是 UIImage 來表示圖片,因此我們就需要一些轉換的方法,OpenCV 的官方教程中給吃了轉換的方法,這裡摘錄如下:
UIImage To cv::Mat:
- (cv::Mat)cvMatFromUIImage:(UIImage *)image
{
CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
CGFloat cols = image.size.width;
CGFloat rows = image.size.height;
cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels (color channels + alpha)
CGContextRef contextRef = CGBitmapContextCreate(cvMat.data, // Pointer to data
cols, // Width of bitmap
rows, // Height of bitmap
8, // Bits per component
cvMat.step[0], // Bytes per row
colorSpace, // Colorspace
kCGImageAlphaNoneSkipLast |
kCGBitmapByteOrderDefault); // Bitmap info flags
CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
CGContextRelease(contextRef);
return cvMat;
}
cv::Mat To UIImage:
-(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat
{
NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
CGColorSpaceRef colorSpace;
if (cvMat.elemSize() == 1) {//可以根據這個決定使用哪種
colorSpace = CGColorSpaceCreateDeviceGray();
} else {
colorSpace = CGColorSpaceCreateDeviceRGB();
}
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
// Creating CGImage from cv::Mat
CGImageRef imageRef = CGImageCreate(cvMat.cols, //width
cvMat.rows, //height
8, //bits per component
8 * cvMat.elemSize(), //bits per pixel
cvMat.step[0], //bytesPerRow
colorSpace, //colorspace
kCGImageAlphaNone|kCGBitmapByteOrderDefault,// bitmap info
provider, //CGDataProviderRef
NULL, //decode
false, //should interpolate
kCGRenderingIntentDefault //intent
);
// Getting UIImage from CGImage
UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
return finalImage;
}