ちょっと話題の記事

AVFoundation+OpenCVで矩形検出(「名刺撮影用カメラ」みたいなやつ作ってみました)

2017.02.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

AVFoundationで、AVCaptureVideoDataOutputを出力に指定すると、毎秒30フレームほどの画像が取得できます。 この画像から矩形を検出し、元の長方形に整形する「名刺撮影用のカメラ」みたいなものを作ってみました。

人が、画像から名刺の位置を識別するのは簡単ですが、プログラムでやるとなると、やはり少し色々な工夫が必要でした。

本記事では、その辺を紹介させて頂きたいと思います。なお、AVFounndationや、OpenCVの使用方法については、以前の記事と重複するため、詳しくはそちらをご参照下さい。


[iOS] AVFundationを使用して、「ビデオ録画」や「連写カメラ」や「QRコードリーダー」や「バーコードリーダー」を作ってみた
[iOS] AVFoundation+OpenCVで劇画調カメラを作ってみた

2 2値化

OpenCVでは、findContoursというメソッドで画像の中で輪郭検出が可能です。このメソッドの入力は、2値画像が対象となっています。そこで、まずは、最初に2値化を行います。

(1) グレースケール

画像の色空間を変換するcvtColorに、CV_BGR2GRAYを指定することで、グレースケールに変換できます。

cv::Mat gray; // グレースケール出力用
cv::cvtColor(mat,gray,CV_BGR2GRAY);

006 001

この状態でも、findContoursを使用することは可能ですが、境界が不鮮明のため輪郭検出は誤差が多すぎます。そこで、thresholdを使用して閾値処理を行います。

cv::threshold(gray, gray, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

002

明るい場所や、机の色が明るい場合、机の光った部分などの影響で輪郭検出に支障が出ることがあります。そのような場合は、CV_THRESH_TOZERO_INVで、いったん閾値よりも上を飛ばしてしまい、白黒反転してからfindContoursで処理すると良いかもしれません。

cv::threshold(gray, gray, 200, 255, CV_THRESH_TOZERO_INV );
cv::bitwise_not(gray, gray); // 白黒の反転
cv::threshold(gray, gray, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

003 004 005

3 輪郭検出

2値化が完了したので、いよいよfindContoursを使用します。 下記のコードは、検出した輪郭を赤色で表示したものです。

std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(gray, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_L1);

int max_level = 0;
for(int i = 0; i < contours.size(); i++){
    cv::drawContours(mat, contours, i, cv::Scalar(255, 0, 0, 255), 3, CV_AA, hierarchy, max_level);
}

007

ここまででも、一応、名刺の輪郭が取れていますが、まだ千個以上のエリアの集合なので、後の処理に無理があります。 そこで、contourAreaを使用して、検出した各エリアの面積を求め、ある程度以下のもの排除し、approxPolyDPで輪郭を直線近似化します。最後に、頂点が4つのものだけを選出すると、矩形が検出できたことになります。

int max_level = 0;
for(int i = 0; i < contours.size(); i++) {
    // ある程度の面積が有るものだけに絞る
    double a = contourArea(contours[i],false);
    if(a > 15000) {
        //輪郭を直線近似する
        std::vector<cv::Point> approx;
        cv::approxPolyDP(cv::Mat(contours[i]), approx, 0.01 * cv::arcLength(contours[i], true), true);
        // 矩形のみ取得
        if (approx.size() == 4) {
            cv::drawContours(mat, tmpContours, i, cv::Scalar(255, 0, 0, 255), 3, CV_AA, hierarchy, max_level);
        }
    }
}

下記は、2つの矩形が検出できているようすです。

008

4 透視変換

カメラの画像は、真上から正確に撮らない限り、長方形にはなりません、そこで、warpPerspectiveを使用して画像の透視変換を行いました。

009

変換元は、先の輪郭検出で取得した矩形の頂点です。そして、変換先は、その外接矩形の頂点です。とりあえず、歪みだけをOpenCVwarpPerspectiveで修正し、縦横の比率については、予め名刺の比率に合わたUIImageScale To Fillで表示することで合わせました(すいません、実は、ちょっと適当)。

cv::Point2f src[4]; // 変換元 
cv::Point2f dst[4]; // 変換先
cv::Mat perspective_matrix = cv::getPerspectiveTransform(src, dst);
cv::warpPerspective(mat, mat, perspective_matrix, mat.size(), cv::INTER_LINEAR);

5 最後に

今回は、OpenCVを使用させて頂いて、矩形の検出をしてみました。

OpenCVの画像処理では、直線の検出なども可能なので、この他にも色々なものを検出して面白いものが作れそうです。

6 参考リンク


とりあえずSquare
OpenCV関係
opencv/squares.cpp at master · opencv/opencv · GitHub
画像処理 — OpenCV-CookBook
動的配列クラス std::vector の操作
[iOS] AVFundationを使用して、「ビデオ録画」や「連写カメラ」や「QRコードリーダー」や「バーコードリーダー」を作ってみた
[iOS] AVFoundation+OpenCVで劇画調カメラを作ってみた