

# 前提

SwiftUIとOpenCVを使って、スマホのカメラ映像（プレビュー画像）から特定色の輪郭を検出するプログラムを作成しました。

輪郭を検出してプレビューとして表示するところまではできたのですが、検出した輪郭の「面積」を求めるところで詰まってしまっています。

下図はアプリの実行画面で、持ち手が青色のハサミを映している様子です。  
[![画像の説明をここに入力](https://i.stack.imgur.com/z2QC4.png)](https://i.stack.imgur.com/z2QC4.png)  
アプリの「run」ボタンを押すとレンダリングを開始し、「stop」ボタンを押すとレンダリングを停止します。

# 実現したいこと

OpenCVで検出した輪郭の面積を求めたい

# 分からないこと

輪郭の面積を求めるために使う「Imgproc.contourArea()」関数にMat型のデータを渡す必要があるのですが、取得した輪郭のデータからMat型のデータを作成する方法が分かりません。

# 試したこと

輪郭の情報を取得するために「Imgproc.findContours()」関数を使用しています。  
各輪郭は「contours」に格納されます。printで中身（一部抜粋）を確認すると下図のようになっています。  
[![画像の説明をここに入力](https://i.stack.imgur.com/96wbD.png)](https://i.stack.imgur.com/96wbD.png)

輪郭の面積を計算するために「Imgproc.contourArea()」関数を使用しています。  
輪郭の面積を求めるcontourAreaにはMat型（○×○の行列型）のデータを渡す必要があるので、「findContours」で得られた輪郭のデータをMat型に変換する必要があると考えています。

OpenCVをSwiftで使用するための[ドキュメント](http://xtravision.stars.ne.jp/opencv-objc-doc-
test/docs/index.html)を参考に、[Matに変換する関数](http://xtravision.stars.ne.jp/opencv-
objc-doc-
test/docs/Classes/Converters.html#/c:objc\(cs\)Converters\(cm\)vector_Point_to_Mat:)をいくつか試してみたのですが、うまくいっていない状態です。

# 該当のソースコード

### 【ContentView.swift】

[code]

    import SwiftUI
    import AVFoundation
    import opencv2
    
    struct ContentView: View {
       let videoCapture = VideoCapture()
       @State var image: UIImage? = nil
       var body: some View {
           VStack {
               if let image = image {
                   Image(uiImage: image)
                       .resizable()
                       .scaledToFit()
               }
               else {
                   Spacer()
               }
               HStack {
                   Button("run") {
                       videoCapture.run { sampleBuffer in
                           if let convertImage = UIImageFromSampleBuffer(sampleBuffer) {
                               let src = Mat(uiImage: convertImage) // UIImageをMatに変換
                               Imgproc.cvtColor(src: src, dst: src, code: ColorConversionCodes.COLOR_RGB2BGR) // BGRに変換
                               Imgproc.cvtColor(src: src, dst: src, code: ColorConversionCodes.COLOR_BGR2HSV) // HSVに変換
                               Core.inRange(src: src, lowerb: Scalar(50, 50, 50), upperb: Scalar(255, 255, 255), dst: src) // HSVの検出範囲を指定
                               Imgproc.blur(src: src, dst: src, ksize: Size2i(width: 5, height: 5)) // フィルタでMatをぼかす
                               Imgproc.Canny(image: src, edges: src, threshold1: 360.0, threshold2: 360.0) // Canny法で輪郭を検出する
                               // 輪郭の情報を取得する
                               let contours: NSMutableArray = []
                               let hierarchy = Mat.zeros(Size(width: 5, height: 5), type: CvType.CV_8UC1)
                               Imgproc.findContours(image: src, contours: contours, hierarchy: hierarchy, mode: RetrievalModes.RETR_EXTERNAL, method: ContourApproximationModes.CHAIN_APPROX_SIMPLE)
                               // 輪郭の面積を求める
                               for contour in contours {
                                   print(contour)
                                   Imgproc.contourArea(contour: contour) // Matに変換する必要がある
                               }
                               let img = src.toUIImage() // MatをUIImageに変換
                               DispatchQueue.main.async {
                                   self.image = img
                               }
                           }
                       }
                   }
                   Button("stop") {
                       videoCapture.stop()
                   }
               }
               .font(.largeTitle)
           }
       }
    
       func UIImageFromSampleBuffer(_ sampleBuffer: CMSampleBuffer) -> UIImage? {
           guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil }
           let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
           let imageRect = CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer))
           guard let image = CIContext().createCGImage(ciImage, from: imageRect) else { return nil }
           
           return UIImage(cgImage: image)
       }
    
    }
    
    struct ContentView_Previews: PreviewProvider {
       static var previews: some View {
           ContentView()
       }
    }
    
[/code]

### 【VideoCapture.swift】

[code]

    import Foundation
    import AVFoundation
    
    class VideoCapture: NSObject {
       let captureSession = AVCaptureSession()
       var handler: ((CMSampleBuffer) -> Void)?
    
       override init() {
           super.init()
           setup()
       }
    
       func setup() {
           captureSession.beginConfiguration()
           let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
           guard
               let deviceInput = try? AVCaptureDeviceInput(device: device!),
               captureSession.canAddInput(deviceInput)
               else { return }
           captureSession.addInput(deviceInput)
    
           let videoDataOutput = AVCaptureVideoDataOutput()
           videoDataOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "mydispatchqueue"))
           videoDataOutput.alwaysDiscardsLateVideoFrames = true
    
           guard captureSession.canAddOutput(videoDataOutput) else { return }
           captureSession.addOutput(videoDataOutput)
           
           // アウトプットの画像を縦向きに変更（標準は横）
           for connection in videoDataOutput.connections {
               if connection.isVideoOrientationSupported {
                   connection.videoOrientation = .portrait
               }
           }
    
           captureSession.commitConfiguration()
       }
    
       func run(_ handler: @escaping (CMSampleBuffer) -> Void)  {
           if !captureSession.isRunning {
               self.handler = handler
               captureSession.startRunning()
           }
       }
    
       func stop() {
           if captureSession.isRunning {
               captureSession.stopRunning()
           }
       }
    }
    
    extension VideoCapture: AVCaptureVideoDataOutputSampleBufferDelegate {
       func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
           if let handler = handler {
               handler(sampleBuffer)
           }
       }
    }
    
[/code]

### 【swiftui_avfoundationApp.swift】

[code]

    import SwiftUI
    
    @main
    struct swiftui_avfoundationApp: App {
       var body: some Scene {
           WindowGroup {
               ContentView()
           }
       }
    }
    
[/code]

# 補足情報①（FW/ツールのバージョンなど）

iOS：15.7  
Xcode：13.1  
OpenCV：opencv-4.6.0-ios-framework  
[OpenCV4.6.0ダウンロードサイト](https://github.com/opencv/opencv/releases)

# 補足情報②（Android版について）

以前に、Androidでも同様の機能を持ったアプリを作成しました。  
その際は、下記のプログラムで輪郭の面積を出すことができていました。

[code]

    // 認識した色に応じた処理を実行
    ...
       private fun detectColor(img:Mat):Boolean {
           Core.inRange(img, Scalar(0.0, 0.0, 0.0)
               , Scalar(255.0, 255.0, 255.0)
               , img)
           // 色の輪郭の面積に応じた処理を実行
           return compareContourColor(detectContour(img))
       }
    
       private fun detectContour(img:Mat): Int {
           // ガウシアンフィルタで画像をぼかす
           Imgproc.GaussianBlur(img, img, Size(5.0, 5.0), 5.0)
           // 輪郭を取得
           Imgproc.Canny(img, img, 10.0, 360.0)
           // 輪郭の情報を取得
           val contours: List<MatOfPoint> = ArrayList()
           val hierarchy = Mat.zeros(Size(5.0, 5.0), CvType.CV_8UC1)
           Imgproc.findContours(img,
               contours,  // 輪郭情報を格納
               hierarchy, // 階層構造を格納
               Imgproc.RETR_EXTERNAL, // 輪郭抽出モードを指定
               Imgproc.CHAIN_APPROX_SIMPLE // 輪郭の表示方法を指定
           )
           return contours.size
       }
    
       private fun compareContourColor(size:Int):Boolean {
           ... // 取得した輪郭面積に応じた処理を実行
       }
    
       // カメラ映像の各フレームに対する処理
       override fun onCameraFrame(inputFrame: CameraBridgeViewBase.CvCameraViewFrame): Mat? {
           // カメラプレビューの各フレームをフルカラーで取得
           mMat = inputFrame.rgba()
           Imgproc.cvtColor(mMat, mMatChange, Imgproc.COLOR_RGBA2BGR) // MatをRGBAからBGRに変換
           Imgproc.cvtColor(mMatChange, mMatChange, Imgproc.COLOR_BGR2HSV) // MatをBGRからHSVに変換
           detectColor(mMatChange!!) // 輪郭面積に応じた処理を実行
           return mMat // プレビュー画像を返す
       }
    ...
    
[/code]

# 参考サイト

[OpenCVのSwift用ドキュメント](http://xtravision.stars.ne.jp/opencv-objc-doc-
test/docs/index.html)  
[SwiftUIでAVFoundationを使ってフレームバッファを取得する](https://zenn.dev/yorifuji/articles/swiftui-
avfoundation)  
[OpenCVのサンプルをSwiftUIだけで作成する](https://takumi-
oda.com/blog/2022/03/06/post-2143/)


CC BY-SA 4.0
2022-10-21T06:09:44.183
91720
2022-10-21T06:09:44.183
46924
question
swift ios opencv swiftui
OpenCVで検出した色の面積を求めたい