読者です 読者をやめる 読者になる 読者になる

vaguely

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

iOS + OpenCVで輪郭検出

OpenCVを使って輪郭を検出したメモ。

今回はCannyアルゴリズムを使ってエッジを検出し、cv::findContoursで輪郭を検出してcv::drawContoursで表示してみました。

準備

iOSの画面を開いて画像をロードし、OpenCVを扱うメソッドに渡すところは前回と同じです(Swift + OpenCV, Swift + OpenCV2)。

今回はGrayスケール画像などを返すかわりに、検出した輪郭を表示した画像を返すことにします。

なお、OpenCVはver. 3.0を、画像は以下を使用しました。

f:id:mslGt:20140903235424p:plain

swImageViewer-Bridging-Header.h

#import < Foundation / Foundation.h >
#import < UIKit / UIKit.h >

@interface ImageController : NSObject
- (void)initOpenCv:(UIImage *)imgSource;
- (UIImage *)getImgContour;
@end

ImageController.mm

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

@interface ImageController()
@property (strong, nonatomic)UIImage *imgContour;
@end
@implementation ImageController
- (void)initOpenCv:(UIImage *)imgSource
{
  cv::Mat matSource;
  // UIImageをcv::Matに変換する
  UIImageToMat(imgSource, matSource);
  // Gray scale
  cv::cvtColor(matSource, matSource, cv::COLOR_BGR2GRAY);
  // Blur
  cv::blur(matSource, matSource, cv::Size(3,3));
  
  cv::Mat matCanny;
  std::vector< std::vector < cv::Point > > vctContours;
  std::vector< cv::Vec4i > hierarchy;
  cv::Scalar sclColor;
  // 乱数生成器
  cv::RNG rngColor;
  
  // Cannyアルゴリズムを使ったエッジ検出
  Canny(matTarget, matCanny, 100, 100, 3);
  // 輪郭を取得する
  cv::findContours(matCanny, vctContours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
  
  int intContourCount = (int)vctContours.size() - 1;
  
  // 輪郭を表示する
  cv::Mat matDrawnContour = cv::Mat::zeros(matCanny.size(), CV_8UC3 );
  
  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);
  }
  
  _imgContour = MatToUIImage(matDrawnContour);
}
- (UIImage *)getImgContour
{
  return _imgContour;
}

Blur処理はなくても問題なく輪郭が取れますが、あった方がノイズが少なくて良いです。

※「std:: vector < std:: vector < cv:: Point > > vctContours;」で、「std:: vector < cv:: Point >」を「std:: vector < Point >」とすると実行時にエラーになります。

Canny

Cannyによるエッジ検出の時点で、以前使用したSobelフィルタのような画像が取得できます。

  • 3, 4つ目の引数を小さくするほど細かいエッジも検出し、1000など大きくしすぎると真っ黒になります(5つ目の引数が3の場合)。
  • 5つ目の引数はapertureSizeということなのですが、指定できるのは3, 5, 7で、値を大きくするほど細かいエッジを検出していました。また、値を大きくすると3, 4つ目の引数を大きくしても真っ黒な画像にはなりませんでした。

findContours

輪郭を取得しているfindContoursで、4つ目の引数(輪郭抽出のモード)に「cv::RETR_EXTERNAL」を指定すると、外側の輪郭のみを抽出するとのことなのですが、使用した画像だと「cv::RETR_TREE」との違いはいまいち良くわかりませんでした。

drawContours

上記のコードでは、取得した輪郭をループで一つずつ指定して色を付けていますが、3つ目の引数を負の値にすると、すべての輪郭に処理を行うことができます。

cv::drawContours(matDrawnContour, vctContours, -1, cv::Scalar(0, 0, 255), 1);

なお、5つ目の引数は線の太さを指定していて、値を大きくすると太くなります。また省略も可能です。

参考