最新のOpenCVをiOSアプリで使いたい! opencv2.xcframework を自分でビルドする

2024.01.09

OpenCVは、画像処理やコンピュータビジョンのための強力なオープンソースライブラリである。iOSアプリにOpenCVを組み込むことで高度な画像処理や分析を実現できる。

先日、CocoaPodsを使ってOpenCVをインストールする方法について記事を書いたが、のちにこの方法でインストールできるOpenCVのバージョンが4.3.0 と古いことに気付いた。

2024年1月4日現在、OpenCVの最新バージョンは 4.9.0 である。CocoaPodsを使ってインストールすると、OpenCVのバージョンが 4.3.0 になる。SwiftコードからOpenCVを扱えるようになる「Obj-C / Swift bindings」のサポートは 4.4.0 以降のため、最新のOpenCVをiOSアプリで使用したいと考えた。

この記事では、iOSアプリで最新のOpenCVを利用するため、MacBook Pro (M1 Pro)でopencv2.xcframeworkをビルドし、画像をグレースケール変換する方法を紹介する。

検証環境

  • MacBook Pro (16-inch, 2021)
  • Apple M1 Pro
  • CMake 3.81
  • Python 3.8.10
  • Xcode 15.1
  • macOS 14.1
  • OpenCV 4.9.0

注意点としては、OpenCV、macOS、Xcodeのバージョンの組み合わせが重要なようで、異なる組み合わせの場合にはうまくビルドできないかもしれない。

なぜ opencv2.xcframework が必要か

OpenCV公式はGitHubのリリースページでopencv2.frameworkを配信しているが、XCFramework(opencv2.xcframework)は配信していない。

XCFrameworkは、Appleが提供するフレームワークフォーマットで、iOS、macOS、watchOS、tvOSなどの異なるAppleプラットフォームとアーキテクチャ用のバイナリを一つのパッケージにまとめたもので、XCFrameworkを使用することで、複数のアーキテクチャや異なるターゲットのサポートが容易になる。

opencv2.frameworkを利用した場合、MacBook Pro (M1 Pro) でデバッグ実行すると以下のような警告が表示される。

Build failed because opencv2.swiftmodule is missing a required architecture. Would you like to build for Rosetta instead?
Ensure all targets are configured to build for standard architectures. If your project uses external dependencies, contact those vendors to provide updated copies built to support all architectures.
You can control the visibility of architecture-specific run destinations in the Product > Destination menu.

opencv2.frameworkにはiOSシミュレータ向けのi386,x86_64、iPhone実機向けのarmv7,arm64のバイナリが含まれていているが、iOSシミュレータ向けのarm64バイナリが含まれていない。そのため、Appleシリコン搭載のMacBookではそのままiOSシミュレータで実行できない。

iOSシミュレータをRosettaで実行するか、XCFrameworkをビルドする必要がある。私は後者のOpenCVをXCFrameworkで扱うことにした。

最新のOpenCVのxcframeworkをビルドする

OpenCVのXCFrameworkをビルドする手順は以下のとおり。

  1. Xcodeコマンドラインツールのインストール
  2. OpenCVのソースコードをGitHubからダウンロードし、build_xcframework.pyスクリプトを実行する

この build_xcframework.py スクリプトは、iOSデバイス用、iOSシミュレーター用、Mac Catalyst用のビルドをそれぞれおこない、それらをひとつのXCFrameworkにまとめる。作成された opencv2.xcframework は、異なるアーキテクチャをサポートするフレームワークである。

本記事では opencv2.xcframework の作成方法と使い方を説明するが、OpenCVの公式ページにXCFramework作成方法の詳細が掲載されているので参照して欲しい。

1. Xcodeコマンドラインツールのインストール

Xcodeコマンドラインツールがインストールされていることを確認する。

xcode-select --install

2. OpenCVをビルドして opencv2.xcframework を生成する

私と同一の環境であれば、以下のスクリプトを実行すればXCFrameworkを生成できるだろう。このスクリプトは、iOSデバイスとシミュレーター用のarm64バイナリを含むopencv2.xcframeworkを生成する。WORKSPACE_DIR には私の作業ディレクトリのパスを指定している。読み替えて欲しい。

また --without パラメータで不要なモジュールを除外することができる。不要なモジュールを除外することで、ビルド時間を短縮し、生成されるファイルのサイズをコンパクトに収めることができる。利用する機能に応じて、必要なモジュールのみを含めるように調整してほしい。

OPENCV_VERSION=4.9.0
SOURCE_URL=https://github.com/opencv/opencv/archive/refs/tags/$OPENCV_VERSION.zip
WORKSPACE_DIR=~/works/opencv

mkdir -p "$WORKSPACE_DIR/opencv-$OPENCV_VERSION"

curl -L $SOURCE_URL -o "$WORKSPACE_DIR/$OPENCV_VERSION.zip"
unzip "$WORKSPACE_DIR/$OPENCV_VERSION.zip" -d "$WORKSPACE_DIR"

cd "$WORKSPACE_DIR/opencv-$OPENCV_VERSION"

python ./platforms/apple/build_xcframework.py --out ./build --iphoneos_archs arm64 --iphonesimulator_archs arm64 --iphoneos_deployment_target 14 --build_only_specified_archs --without videoio --without video --without ts --without stitching --without photo --without ml --without highgui --without gapi

前述したとおり、build_xcframework.py のスクリプトは、複数のframeworkをビルドしてから、ひとつのxcframeworkにまとめている。ひとつひとつのフレームワークのビルドにそれなりに時間がかかるようで、Apple M1 Pro搭載のMacBook Proでもビルドに20分かかった。

============================================================
Building ./build/opencv2.xcframework
============================================================
Executing: ['xcodebuild', '-create-xcframework', '-output', './build/opencv2.xcframework', '-framework', './build/iphoneos/opencv2.framework', '-framework', './build/iphonesimulator/opencv2.framework', '-framework', './build/macos/opencv2.framework', '-framework', './build/catalyst/opencv2.framework'] in /Users/ch3cooh/works/opencv/opencv-4.9.0
Executing: xcodebuild -create-xcframework -output ./build/opencv2.xcframework -framework ./build/iphoneos/opencv2.framework -framework ./build/iphonesimulator/opencv2.framework -framework ./build/macos/opencv2.framework -framework ./build/catalyst/opencv2.framework
xcframework successfully written out to: /Users/ch3cooh/works/opencv/opencv-4.9.0/build/opencv2.xcframework

============================================================
Finished building ./build/opencv2.xcframework
============================================================

このログが出力されれば、XCFrameworkの生成に成功している。実際にiOSプロジェクトに組み込み、簡単な画像処理(グレースケール変換)を実装しよう。

OpenCVを使った画像処理クラスを作成する

iOSアプリでは画像をUIImageオブジェクトとして扱うが、OpenCVでは直接UIImageオブジェクトを扱えない。OpenCVを使って画像処理を行うには、UIImageからMatへ変換し、画像処理をおこない、MatからUIImageへ再変換する。下図の手順で変換をおこなう。

OpenCVを使ってグレースケール変換を行うステップは以下のとおり。

  1. Assets.xcassetsにサンプル画像の追加
  2. opencv2.xcframework の追加
  3. 画像をグレースケール変換する処理の実装
  4. SwiftUI(画面)で画像処理関数を実行

1. Assets.xcassetsにサンプル画像を追加

Assets.xcassetsにサンプル画像を追加した。ここでは名前を sample とした。

2. opencv2.xcframework の追加

生成したopencv2.xcframeworkをiOSプロジェクトにドラッグアンドドロップする。以下のフレームワークを追加する。

  • libc++.tbd
  • CoreMedia
  • CoreImage
  • CoreGraphics

Frameworksグループの表示が、下図のようにフレームワークが追加されていれば良い。

さらに Other Linker Flags に -all_load を追加しておく。

3. 画像をグレースケール変換する処理の実装

ImageProcessorクラスを追加してgrayscale関数を実装した。この関数は、UIImageオブジェクトをグレースケール画像に変換して返す。この関数は以下の手順で処理を行います。

import opencv2
import UIKit

enum ImageProcessor {
    static func grayscale(image: UIImage?) -> UIImage? {
        guard let image = image else {
            return nil
        }

        let mat = Mat(uiImage: image)
        Imgproc.cvtColor(
            src: mat, 
            dst: mat,
            code: ColorConversionCodes.COLOR_RGB2GRAY
        )
        return mat.toUIImage()
    }
}

先日紹介した「CocoaPodsでOpenCVをiOSアプリに導入する」では、グレースケール変換の処理をObjective-C++で実装していたが、OpenCV 4.9.0を利用しているため、Obj-C / Swift bindings機能が使える。

Swiftコードのみで実装できており、Obj-C/Swiftが混在したコードと比較すると、シンプルになったのがわかる。

4. 画面側で画像処理を実行する

このサンプルでは、SwiftUIで画面を実装している。「to grayscale」ボタンをタップすると、ImageProcessor#convertToGrayscaleを実行し、加工後の画像を表示する。

import SwiftUI

struct ContentView: View {
    @State private var image = UIImage(named: "sample")
    @State private var text: String?

    var body: some View {
        VStack {
            if let image = image {
                Image(uiImage: image)
                    .resizable()
                    .frame(width: 200, height: 200)
                    .scaledToFit()
            }
            if let text = text {
                Text(text)
            }
            Button("to grayscale") {
                action()
            }
            Button("clear") {
                image = UIImage(named: "sample")
                text = nil
            }
        }
        .padding()
    }

    private func action() {
        guard let source = UIImage(named: "sample") else {
            text = "画像の取得に失敗"
            return
        }
        guard let image = ImageProcessor.grayscale(image: source) else {
            text = "画像の変換に失敗"
            return
        }

        self.image = image
        text = nil
    }
}

動作確認

サンプルアプリを実行し、to grayscaleボタンをタップすると下図のように変化する。

まとめ

以上で、opencv2.xcframeworkをビルドして、iOSアプリにOpenCVを導入し、画像をグレースケール化する方法を紹介した。このコードをベースにすることで、さらに高度な画像処理や解析機能の開発が可能となるだろう。

この記事で解説したサンプルコードについては、以下のリポジトリで公開している。

参照リンク

ビルド時のパラメータによって iOSシミュレータでは実行できない opencv2.xcframework ができたりと、試行錯誤の記録と残すのと再現性を高めるため、シェルスクリプトを書いて対応していった。のちほど知ったことだが、最終的にSansan Tech Blogさんの「社内ライブラリを Swift Package Manager に対応させた話 その2 ~OpenCV に依存したライブラリ編~」で紹介されている方法に似た内容となった。私が書いたスクリプトよりも完成度が高いので是非併せて読んでいただきたい。