iOS + OpenCVで輪郭検出
OpenCVを使って輪郭を検出したメモ。
今回はCannyアルゴリズムを使ってエッジを検出し、cv::findContoursで輪郭を検出してcv::drawContoursで表示してみました。
準備
iOSの画面を開いて画像をロードし、OpenCVを扱うメソッドに渡すところは前回と同じです(Swift + OpenCV, Swift + OpenCV2)。
今回はGrayスケール画像などを返すかわりに、検出した輪郭を表示した画像を返すことにします。
なお、OpenCVはver. 3.0を、画像は以下を使用しました。
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つ目の引数は線の太さを指定していて、値を大きくすると太くなります。また省略も可能です。