Core Image や vImage 等、iOS SDK の標準フレームワークの機能が充実してきたとはいえ、コンピュータビジョンの分野においてできることの幅広さではやはり OpenCV に軍配が上がります。そんな OpenCV を iOS アプリに導入する手順です。
下記の公式ドキュメントを参考に、自分でやってみて補足を加えました。
公式ドキュメント1 / 公式ドキュメント2 / 公式ドキュメント3
##準備編:フレームワークをビルドする
###1. Githubからソースコードを取得
githubからcloneします。1
cd ~/<my_working _directory>
git clone https://github.com/opencv/opencv.git
###2. ビルドの準備
ルートディレクトリに、Xcodeのバンドル内にあるDeveloperフォルダへのシンボリックリンクを作成します。(ビルドスクリプトが参照するため)
cd /
sudo ln -s /Applications/Xcode.app/Contents/Developer Developer
###3. ビルド
ビルドスクリプトを実行します。
cd ~/<my_working_directory>
python opencv/platforms/ios/build_framework.py ios
ビルドが成功すると、最後に ** INSTALL SUCCEEDED **
と出て、 ~/<my_working_directory>/ios/opencv2.framework
にフレームワークができています。
-
※1: もしスクリプト実行時に
sh: cmake: command not found
というエラーが出る場合は、cmake が入ってないのでインストールが必要です。homebrew の場合は$ brew install cmake
でインストールが始まります。
- ※2: pythonのエラーが出た場合はスクリプトを実行するPythonのバージョンが合ってない可能性があります。私はOpenCV 3.1のビルドスクリプトをPython v3.5.1で実行しようとして以下のようなエラーが出ました。
TypeError: cannot use a string pattern on a bytes-like object
pyenvでPython 2.7に切り替えてこのエラーは解消されました。
- ※3: OpenCV 3.1のビルドスクリプトがxcodebuildでコケました。マスターの最新コミット(2016.12.2現在)でも同様。
###バージョン確認方法
opencv2.framework/Versions/A/Resources/Info.plist
に書かれているフレームワークバンドルのバージョン=OpenCVのバージョンとなります。
たとえば 3.0.0 の場合は Info.plist はこうなっています。
<key>CFBundleVersion</key>
<string>3.0.0</string>
<key>CFBundleShortVersionString</key>
<string>3.0.0</string>
##導入編:Xcode プロジェクトにフレームワークを追加する
###1. フレームワークを追加
プロジェクトに opencv2.framework を追加します。
###2. 実装ファイルの拡張子を.mmにする
C++ のコードを書くため、実装ファイルの拡張子を .m から .mm に修正します。
3. ヘッダファイルをインポート
#ifdef __cplusplus
#import <opencv2/opencv.hpp>
#endif
(2018.10追記)ビルド時に"Expected identifier"エラーが発生する場合
OpenCV 3.2で、次のようなビルドエラーが発生しました。
Expected identifier
# warning
で次のようなメッセージが仕込まれていました。
Detected Apple 'NO' macro definition, it can cause build conflicts. Please, include this header before any Apple headers.
解決方法として、<opencv2/opencv.hpp>
のインポートを他のAppleヘッダのインポートよりも先に書く、とのこと。
次のようにすると解決しました。
#ifdef __cplusplus
#import <opencv2/opencv.hpp>
#endif
#import "xxxx.h"
##実践編:簡単な画像処理を実行してみる
1. UIImage 型の画像を cv::Mat 型に変換するメソッドを定義
UIImage 型の画像を cv::Mat 型に変換するメソッドを定義します。(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
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);
CGColorSpaceRelease(colorSpace);
return cvMat;
}
現行バージョンではopencv2/imgcodecs/ios.h
に、
void UIImageToMat(const UIImage* image,
cv::Mat& m, bool alphaExist = false);
が既に用意されています。変換関数を自分で実装する必要はなくなりました。
2. cv::Mat 型の画像を UIImage 型に変換するメソッドを定義
cv::Mat を 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;
}
現行バージョンではopencv2/imgcodecs/ios.h
に、
UIImage* MatToUIImage(const cv::Mat& image);
が既に用意されています。変換関数を自分で実装する必要はなくなりました。
3. グレースケール画像に変換する
色空間変換を行う cvtColor() というメソッドと、上記で実装した UIImage と cv::Mat を相互変換するメソッドを用いて、下記のように UIImage をグレースケールに変換して UIImage として出力する処理を実装できます。
cv::Mat srcMat;
UIImageToMat(srcImage, srcMat);
cv::Mat grayMat;
cv::cvtColor(srcMat, grayMat, CV_BGR2GRAY);
return MatToUIImage(grayMat);
応用編:漫画カメラ風に写真を加工する
下記記事に、OpenCVを用いて漫画カメラ風に加工する手順を書いたので、よろしければご参照ください。
たったの6ステップ!『漫画カメラ』風に写真を加工するiPhoneアプリの作り方
トラブルシューティング
画像が90度回転してしまう
UIImage
-> cv::Mat
の変換の際、UImage
のimageOrientation
がUIImageOrientationUp
の場合は問題ないが、UIImageOrientationLeft
だったりUIImageOrientationRight
だったりすると、90°(あるいは270°)回転してしまう。
解決方法としては、あらかじめ回転を画像に反映してから変換する。
- (UIImage *)normalizedImage {
if (self.imageOrientation == UIImageOrientationUp) return self;
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
[self drawInRect:(CGRect){0, 0, self.size}];
UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return normalizedImage;
}
cv::Mat srcMat;
// これだと結果が90°回転してしまう場合がある
// UIImageToMat(self, srcMat);
// こうするとOK
UIImageToMat([self normalizedImage], srcMat);
return MatToUIImage(srcMat);
参考
- https://stackoverflow.com/questions/5427656/ios-uiimagepickercontroller-result-image-orientation-after-upload/10611036#10611036
- https://stackoverflow.com/questions/14332687/converting-uiimage-to-cvmat
-
以前はItseezアカウントにあったのですが、いつの間にかOpenCVアカウントになってました ↩