LoginSignup
267

More than 5 years have passed since last update.

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アプリの作り方

manga

トラブルシューティング

画像が90度回転してしまう

UIImage -> cv::Matの変換の際、UImageimageOrientationUIImageOrientationUpの場合は問題ないが、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);

参考


  1. 以前はItseezアカウントにあったのですが、いつの間にかOpenCVアカウントになってました 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
267