vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

OpenCVでリアルタイム輪郭検出

前回作ったアプリに、以前トライしたOpenCVを使った輪郭検出の機能を追加してみました。

やったこと

今回はImageController.mmでUIImageを生成する部分を変更して、cv::Matを生成 -> 輪郭検出 -> UIImageに変換しています。
※ViewController.swiftは前回と同じなので省略します。

ImageController.mm

#import "ocvCameraImage-Bridging-Header.h"
#import < opencv2 / opencv.hpp >
#import "opencv2 / imgcodecs / ios.h"

@interface ImageController()
@property (nonatomic)CVImageBufferRef ibrImageBuffer;
@property (nonatomic)uint8_t *baseAddress;
@property (nonatomic)size_t sztWidth;
@property (nonatomic)size_t sztHeight;
@property (nonatomic)CGContextRef cnrContext;
@property (nonatomic, strong)UIImage *imgCreatedImage;
@end
@implementation ImageController

- (UIImage *) createImageFromBuffer:(CMSampleBufferRef) sbrBuffer
{
    _ibrImageBuffer = CMSampleBufferGetImageBuffer(sbrBuffer);
    // ピクセルバッファのベースアドレスをロックする.
    CVPixelBufferLockBaseAddress(_ibrImageBuffer, 0);
    // ベースアドレスの取得.
    _baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(_ibrImageBuffer, 0);

    // サイズの取得.
    _sztWidth = CVPixelBufferGetWidth(_ibrImageBuffer);
    _sztHeight = CVPixelBufferGetHeight(_ibrImageBuffer);

    
------------ここから変更---------------


    // Mat画像の生成
    cv::Mat matEdge((int)_sztHeight, (int)_sztWidth, CV_8UC4, (void*)_baseAddress);
    
    // 90°回転.
    cv::transpose(matEdge, matEdge);
    // 左右反転.
    cv::flip(matEdge, matEdge, 1);
    
    // グレイスケール化.
    cv::cvtColor(matEdge, matEdge, cv::COLOR_BGR2GRAY);
    
    std::vector< std::vector < cv::Point > > vctContours;
    std::vector< cv::Vec4i > vctHierarchy;
    
    // Canny アルゴリズムを使ったエッジ検出.
    Canny(matEdge, matEdge, 100, 100, 3);
    // 輪郭を取得する
    cv::findContours(matEdge, vctContours, vctHierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
    
    // 輪郭を表示する
    matEdge = cv::Mat::zeros(matEdge.size(), CV_8UC3);
    cv::drawContours(matEdge, vctContours, -1, cv::Scalar(255, 255, 255), 1);
    
    // UIImageへの変換.
    _imgCreatedImage = MatToUIImage(matEdge);
    
    // 解放.
    vctContours.clear();
    vctHierarchy.clear();
    matEdge.release();    

---------------------------------------


    // ベースアドレスのロックを解除.
    CVPixelBufferUnlockBaseAddress(_ibrImageBuffer, 0);
    
    return _imgCreatedImage;
}

線を引いた間の部分が変更点です。

cv::Matへの変換部分などは、なんだか前回よりすっきりした気がします。

AVFoundationで取得できる画像が90°反転している問題は、「cv::transpose」と「cv::flip」で対処しています(画面の回転に対応できるよう、変更が必要ですが)。

なお、前回のコードで生成していたUIImageを「UIImageToMat」でcv::Matに変換 -> エッジ検出 -> 「MatToUIImage」でUIImageに再変換しようとすると、実行中にエラーで止まってしまいました。
UIImageの生成またはUIImage <--> cv::Matの変換が重い、メモリの解放ができてないなどの問題があるのかもしれません。

また、輪郭を描画する「cv::drawContours」で、以前と同じくランダムな色を使う以下のコードを使うと、カメラが時々固まっていました。

  int intContourCount = (int)vctContours.size() - 1;
  
  for( int i = intContourCount; 0 <= i; i--)
  {
      // 輪郭に付ける色の指定.0~255の範囲で値を生成.
      sclColor = cv::Scalar(rngColor.uniform(0, 255), rngColor.uniform(0,255), rngColor.uniform(0,255) );
      // 輪郭の番号を指定して、色を付ける
      cv::drawContours(matDrawnContour, vctContours, i, sclColor, 1);
  }

毎フレーム実行するにはキツいのかもしれません。

これについては今後の課題ということで。

参考