Junpei Qawasaki

博報堂アイ・スタジオという会社でテクノロジーをベースに新規事業とかインタラクティブなものとかを色々とやっております

【OpenCV】OpenCVで画像2値化(エッジ抽出)サンプルを試してみた

OpenCVを使ってやってみたかった事の一つに画像の二値化(エッジ抽出)があってそのサンプルを入手して試してみました。二値化って僕も専門外なので最近知ったのですがある決まった値(閾値)を元にその上か下かで分けてしまうという、白黒はっきりさせます的な処理です。
これをやるとどんな画像が出来るのかというと、こんな感じになります。
f:id:jkawasaki:20140105221516p:plain

では、中身を見てみましょう。
■サンプルコード

#include <iostream>
#include <opencv2/opencv.hpp>

#define IMAGE(x, y) img.at<float>(y, x)
#define	THRESHOLD	85		// 閾値

void Thinning(cv::Mat &img)
{
    const int width = img.cols;
    const int height = img.rows;
    cv::rectangle(img, cv::Point(0, 0), cv::Point(width - 1, height - 1),
                  CV_RGB(0, 0, 0));
    cv::Mat four_neighbor = cv::getStructuringElement(cv::MORPH_CROSS,
                                                      cv::Size(3, 3));
    
    int step = 0;
    while (1) {
        step++;
        cv::Mat candidate = img.clone();
        cv::erode(candidate, candidate, four_neighbor);
        candidate = img - candidate;
        
        int	num_delete = 0;
        for (int y = 1; y < height - 1; y++) {
            for (int x = 1; x < width - 1; x++) {
                if (candidate.at<uchar>(y, x) == 0) {
                    continue;	                }
 
                int dx[8] = {1,  1,  0, -1, -1, -1,  0,  1};
                int dy[8] = {0, -1, -1, -1,  0,  1,  1,  1};
                unsigned char n[8];
                int num = 0;
                for (int i = 0; i < 8; i++) {
                    n[i] = img.at<uchar>(y + dy[i], x + dx[i]) ? 1 : 0;
                    num += n[i];
                }
                if (num < 3) {
                    continue;
                }
                //連結数の確認
                int m = 0;
                for (int i = 0; i < 4; i++) {
                    int k0 = i * 2;
                    int k1 = (k0 + 1) % 8;
                    int k2 = (k1 + 1) % 8;
                    m += n[k0] * (1 - n[k1] * n[k2]);
                }
                if (m == 1) {
                    img.at<uchar>(y, x) = 0;
                    num_delete++;
                }
            }
        }
        // 終了判定
        if (num_delete == 0) {
            break;
        }
    }
}
int main(int argc, char **argv)
{
    char *window_capture = "capture";
    cv::namedWindow(window_capture, CV_WINDOW_AUTOSIZE);
    char *window_output = "output";
    cv::namedWindow(window_output, CV_WINDOW_AUTOSIZE);
	
    cv::VideoCapture cap;
    cap.open(0);
    if (!cap.isOpened()) {
        std::cerr << "cannot find camera or file" << std::endl;
        return -1;
    }
    while (1) {
        cv::Mat frame;
        cap >> frame;
        
        cv::Mat gray, sobel, edge, edge_x, edge_y, result;
        cv::cvtColor(frame, gray, CV_BGR2GRAY);
        cv::Sobel(gray, sobel, CV_16S, 1, 0, 3);
        cv::convertScaleAbs(sobel, edge_x);
        cv::Sobel(gray, sobel, CV_16S, 0, 1, 3);
        cv::convertScaleAbs(sobel, edge_y);
        edge = edge_x + edge_y;
        cv::threshold(edge, result, THRESHOLD, 255, CV_THRESH_TOZERO);
        
        Thinning(result);
        cv::imshow(window_capture, frame);
        cv::imshow(window_output, result);
        
        int key = cv::waitKey(5);
        if (key == 3 || key == 27 || key == 'q') {
            break;
        }
    }   
    return 0;
}

今回はちょっとサンプルを変更して閾値として定義している値をTHRESHOLDを85にしてみました。
ちょうど半分だと白の割合が多くなっちゃったので。
色味についてはcvtColorの引数になっているCV_BGR2GRAYを変更すると代わりますが、そもそもRGBとかになってしまうと二値じゃなくなるので今回はCV_BGR2GRAYだけで試します。閾値も85のまま設定だけ変更してみます。

それ以外にthreshold関数の引数を変更する事で風合いが変わります。設定出来るパラメータと内容は下記の通りです。
————————
THRESH_BINARY : 閾値を超えるピクセルは MAXに,それ以外のピクセルは 0 になります.
THRESH_BINARY_INV : 閾値を超えるピクセルは 0 に,それ以外のピクセルは MAXになります.
THRESH_TRUNC : 閾値を超えるピクセルは threshold に,それ以外のピクセルは変更されません.
THRESH_TOZERO : 閾値を超えるピクセルは変更されず,それ以外のピクセルは 0 になります.
THRESH_TOZERO_INV : 閾値を超えるピクセルは 0 に,それ以外のピクセルは変更されません.
————————

実際に試すとこんな感じ。
f:id:jkawasaki:20140105220846p:plain

画像自体がちょっと怖めになったり、カメラ画像そのまま映すと動きがヌルヌルして見えるのでなんだか色々使い道があるような気がします。
静止画だと伝わらない気がしたので今回は動画をUPしてみました。

設定によってはレンダリングに時間がかかってカクついたりしてしまうのが難点です。
プログラム自体はシンプルなものですが使い方次第では色々使えるような気がしてます。

ただ、白黒反転したかったけどまだやり方分かんないので引き続きOpenCV、修行します。