iOSアプリでOpenCVを使って輪郭の境界線を検出する

2024.02.13

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

この記事では、iOSアプリでOpenCVを使って輪郭の境界線を検出する方法を紹介する。

iOSプロジェクトへOpenCVを導入する

iOSプロジェクトにOpenCVを導入する方法はいくつかある。

それぞれ一長一短あるため、好みの方法を選んで欲しい。

  • 前者は、導入は簡単だけどObjective-CからのみOpenCVを扱える。
  • 後者は、導入方法が難しいがSwiftからもOpenCVを扱える。

OpenCV for iOSでは、v4.4.0 からObj-C / Swift bindingsに対応しているが、CocoaPodsで公開されているOpenCVは非公式なもので、現在はメンテナンスされておらずバージョンが v4.3.0 と若干古いのが原因である。Obj-C / Swift bindings の詳細については、変更履歴を参照のこと。

私のおすすめは、自分でOpenCVをビルドして扱う方法である。以下の記事で、最新のOpenCVを扱うための方法を紹介しているので参考にして欲しい。

本記事では後者の「自前でOpenCVをビルドして手動でインストールする」を採用している。アプリのプロジェクトに opencv2.xcframework を組み込み、SwiftからOpenCVを利用できる状態にしていることを前提としている。

OpenCVを使って輪郭の境界線を取得する

OpenCVを使って輪郭の境界線を取得するには、以下の手順で画像処理をおこなう。輪郭検出までに色々と前処理をしているが、これは輪郭を抽出しやすくするための準備である。

  1. グレースケール変換
  2. ぼかし処理
  3. 二値化
  4. 輪郭検出

オリジナル画像

画像処理前のオリジナル画像である。かわいいインコネコがたくさん並んでいる。

グレースケール変換

画像を単色調(グレースケール)に変換する。色情報を排除し、画像処理をより単純化する。グレースケール画像では、各ピクセルが輝度(明るさの値)を持っている。

ぼかし処理

次に画像をぼかす。ぼかし処理によって、画像のノイズや細かいテクスチャを減少させて、重要でない情報を除去する。この処理によって、画像が滑らかになり二値化によって画像のノイズや細かいテクスチャを減少させる(不要な情報を取り除く)ことができる。

このサンプル画像のように、くっきりしたイラストの場合にはぼかし処理は不要かもしれない。

二値化

画像を黒と白の2つの値のみを持つ二値画像に変換する。二値化により、画像内のピクセルを「白(高い値)」または「黒(低い値)」に変換する。背景と前景の区別が容易になって、輪郭やエッジの検出が簡単になる。

以上で、輪郭検出のための前準備は完了である。輪郭検出がうまくいかない場合は、二値化の閾値を調整するなどしたい。

輪郭検出

最後に OpenCVの findContours 関数を用いて画像から輪郭を検出する。この関数では、二値化された画像を分析して、連続する点を追跡して輪郭を検出する。

輪郭描画

輪郭が検出された後、オリジナル画像に輪郭を描画して、検出結果が正しいかどうかを視覚的に確認する。

サンプルコード

実装したサンプルコードは以下の通りだ。

import opencv2
import UIKit

enum ImageProcessor {
    static func findContours(image: UIImage?, withThreshold threshold: Double) -> UIImage? {
        guard let image = image else {
            return nil
        }

        let imgColor = Mat(uiImage: image)
        let imgGray = imgColor.clone()
        let imgBlur = imgGray.clone()

        // グレースケール変換
        Imgproc.cvtColor(src: imgColor, dst: imgGray, code: .COLOR_BGR2GRAY)

        // ぼかし処理
        Imgproc.blur(src: imgGray, dst: imgBlur, ksize: Size2i(width: 9, height: 9))

        // 二値化
        let imgBinary = Mat()
        Imgproc.threshold(
            src: imgBlur,
            dst: imgBinary,
            thresh: threshold,
            maxval: 255,
            type: .THRESH_BINARY
        )

        // 輪郭検出
        let contoursNSMutableArray: NSMutableArray = []
        let hierarchy = Mat()
        Imgproc.findContours(
            image: imgBinary,
            contours: contoursNSMutableArray,
            hierarchy: hierarchy,
            mode: RetrievalModes.RETR_TREE,
            method: ContourApproximationModes.CHAIN_APPROX_SIMPLE
        )

        // 輪郭描画
        var contours: [[Point2i]] = []
        for contourObj in contoursNSMutableArray {
            if let contour = contourObj as? [Point2i] {
                contours.append(contour)
            }
        }
        Imgproc.drawContours(
            image: imgColor,
            contours: contours,
            contourIdx: -1,
            color: Scalar(0, 255, 0, 255),
            thickness: 2
        )

        return imgColor.toUIImage()
    }
}

動作確認

実際にiOSシミュレータで動かしてみると、下図のような結果が得られる。

まとめ

この記事ではiOSアプリでOpenCVを使って輪郭の境界線を検出する方法を紹介した。

輪郭の境界線を検出する方法については「【Python】画像のノイズを減らして二値化し、輪郭を抽出する - LabCode」を参考にした。輪郭の検出は同じ手段を使っている人が多いようだ。しかし、パラメータについてはターゲットによってそれぞれで「Python: OpenCVで画像から輪郭を検出する」も参考にしながら値を決めた。