画像処理

画像を単色で塗りつぶす

画像を単色で塗りつぶす方法を示します. また,この例には示しませんが, 画像中の部分矩形を塗りつぶす場合には、ROIまたは cv::rectangle を利用します. 矩形を描く などを参考にしてください.

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  // 初期化時に塗りつぶす
  cv::Mat red_img(cv::Size(640, 480), CV_8UC3, cv::Scalar(0,0,255));
  cv::Mat white_img(cv::Size(640, 480), CV_8UC3, cv::Scalar::all(255));
  cv::Mat black_img = cv::Mat::zeros(cv::Size(640, 480), CV_8UC3); 

  // 初期化後に塗りつぶす
  cv::Mat green_img = red_img.clone();
  green_img = cv::Scalar(0,255,0);

  cv::namedWindow("red image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("white image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("black image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("green image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("red image", red_img);
  cv::imshow("white image", white_img);
  cv::imshow("black image", black_img);
  cv::imshow("green image", green_img);
  cv::waitKey(0);
}

実行結果:

_images/filled_red.png _images/filled_white.png _images/filled_black.png _images/filled_green.png

色空間を変換する

入力画像の型として可能なものは, CV_8UCV_16UCV_32F です. RGB画像のチャンネル順序は,変換コードで明示的に指定する必要があります.例えば,RBGからHSVならば, CV_RGB2HSV ,BGRからグレースケールならば CV_BGR2GRAY となります. また,すべての色空間が相互に変換可能なわけではないことに注意してください.

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat bgr_img = cv::imread("../../image/lenna.png", 1);
  if(bgr_img.empty()) return -1; 
  
  cv::Mat dst_img;

  // BGR -> HSV
  cv::cvtColor(bgr_img, dst_img, CV_BGR2HSV);
  // ... 何らかの処理
  
  // BGR -> Lab
  cv::cvtColor(bgr_img, dst_img, CV_BGR2Lab);
  // ... 何らかの処理

  // BGR -> YCrCb
  cv::cvtColor(bgr_img, dst_img, CV_BGR2YCrCb);
  // ... 何らかの処理  
}

画像サイズを変更する

補間手法を指定して画像サイズを変更することができます.
  • INTER_NEAREST 最近傍補間
  • INTER_LINEAR バイリニア補間(デフォルト)
  • INTER_AREA ピクセル領域の関係を利用したリサンプリング.画像を大幅に縮小する場合は,モアレを避けることができる良い手法です.しかし,画像を拡大する場合は, INTER_NEAREST メソッドと同様になります
  • INTER_CUBIC 4x4 の近傍領域を利用するバイキュービック補間
  • INTER_LANCZOS4 8x8 の近傍領域を利用する Lanczos法の補間
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1; 
  cv::Mat dst_img1;
  cv::Mat dst_img2(src_img.rows*0.5, src_img.cols*2.0, src_img.type());

  // INTER_LINER(バイリニア補間)でのサイズ変更
  cv::resize(src_img, dst_img1, cv::Size(), 0.5, 0.5);
  // INTER_CUBIC(バイキュービック補間)でのサイズ変更
  cv::resize(src_img, dst_img2, dst_img2.size(), cv::INTER_CUBIC);

  cv::namedWindow("resize image1", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("resize image2", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("resize image1", dst_img1);
  cv::imshow("resize image2", dst_img2);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果(縦横0.5倍,縦0.5倍+横2.0倍):

_images/lenna_resize_0505.png _images/lenna_resize_0520.png

画像を垂直・水平に反転する

2次元行列反転と同様です. 行列を反転する も参照してください.

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1;

  cv::Mat v_img, h_img, b_img;
  cv::flip(src_img, v_img, 0); // 水平軸で反転(垂直反転)
  cv::flip(src_img, h_img, 1); // 垂直軸で反転(水平反転)
  cv::flip(src_img, b_img, -1); // 両方の軸で反転

  cv::namedWindow("vertical flip image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("horizontal flip image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("both flip image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("vertical flip image", v_img);
  cv::imshow("horizontal flip image", h_img);
  cv::imshow("both flip image", b_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果(垂直反転,水平反転,垂直反転+水平反転):

_images/lenna_vflip.png _images/lenna_hflip.png _images/lenna_bflip.png

画像をネガポジ反転する

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 0);
  if(src_img.empty()) return -1;

  // NOT演算
  cv::Mat dst_img = ~src_img;

  cv::namedWindow("src image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("dst image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("src image", src_img);
  cv::imshow("dst image", dst_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna_gray.png

実行結果:

_images/sample_img_np.png

画像を2値化する

画素値を閾値処理して,画像の2値化を行います.閾値処理の手法には,以下のものがあります:

  • THRESH_BINARY:閾値以下の値は0に,それ以外は指定した値(maxVal)になります.
  • THRESH_BINARY_INV:上記とは逆に,閾値より大きい値が0に,それ以外は指定した値(maxVal)になります.
  • THRESH_TRUNC:閾値より大きい値は閾値まで切り詰められ,それ以外はそのまま残ります.
  • THRESH_TOZERO:閾値より大きい値はそのまま残り,それ以外は0になります.
  • THRESH_TOZERO_INV:上記とは逆に,閾値以下の値はそのまま残り,それ以外は0になります.

さらに,特殊な値 THRESH_OTSU を,上述のものと組み合わせて使うこともできます.この場合,関数は大津のアルゴリズムを用いて最適な閾値を決定し,それを引数 thresh で指定された値の代わりに利用します.つまり,自分で閾値を決める必要がありません.

また, adaptiveThreshold は適応的な閾値処理を行います.この適応的というのは,閾値が入力によって適応的に決まることを意味します.閾値決定の手法は,以下のものがあります:

  • ADAPTIVE_THRESH_MEAN_C:閾値 T(x,y) は,(x,y) の近傍 blockSize x blockSize の平均から C を引いた値
  • ADAPTIVE_THRESH_GAUSSIAN_C:閾値 T(x,y) は,(x,y) の近傍 blockSize x blockSize の(ガウス分布を用いた)加重平均から C を引いた値になります.このガウス分布の標準偏差は,blockSize から決定されます.
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat gray_img = cv::imread("../../image/lenna.png", 0);
  if(gray_img.empty()) return -1;
  
  // 固定の閾値処理
  cv::Mat bin_img, bininv_img, trunc_img, tozero_img, tozeroinv_img;
  // 入力画像,出力画像,閾値,maxVal,閾値処理手法
  cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY|cv::THRESH_OTSU);
  cv::threshold(gray_img, bininv_img, 0, 255, cv::THRESH_BINARY_INV|cv::THRESH_OTSU);
  cv::threshold(gray_img, trunc_img, 0, 255, cv::THRESH_TRUNC|cv::THRESH_OTSU);
  cv::threshold(gray_img, tozero_img, 0, 255, cv::THRESH_TOZERO|cv::THRESH_OTSU);
  cv::threshold(gray_img, tozeroinv_img, 0, 255, cv::THRESH_TOZERO_INV|cv::THRESH_OTSU);

  // 適応的な閾値処理
  cv::Mat adaptive_img;
  // 入力画像,出力画像,maxVal,閾値決定手法,閾値処理手法,blockSize,C
  cv::adaptiveThreshold(gray_img, adaptive_img, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 7, 8);

  // 結果画像表示
  cv::namedWindow("Binary", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Binary Inv", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Trunc", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("ToZero", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("ToZero Inv", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Adaptive", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Binary", bin_img);
  cv::imshow("Binary Inv", bininv_img);
  cv::imshow("Trunc", trunc_img);
  cv::imshow("ToZero", tozero_img);
  cv::imshow("ToZero Inv", tozeroinv_img);
  cv::imshow("Adaptive", adaptive_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_bin.png _images/lenna_bin_inv.png _images/lenna_trunc_bin.png _images/lenna_tozero_bin.png _images/lenna_tozero_bin_inv.png _images/lenna_adaptive_bin.png

2次元のアフィン変換を行う

A=
\begin{bmatrix}
a_{11} & a_{12} & a_{13} \\
a_{21} & a_{22} & a_{23} \\
\end{bmatrix}

変換元と変換先の3点を指定して,変換行列を決定する

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1;

  // 変換前の三点
  const cv::Point2f src_pt[] = { cv::Point2f(200, 200), cv::Point2f(250, 200), cv::Point2f(200, 100) };
  // 変換後の三点
  const cv::Point2f dst_pt[] = { cv::Point2f(300, 100), cv::Point2f(300, 50), cv::Point2f(200, 100) };
  // これらから,アフィン変換行列を計算
  const cv::Mat affine_matrix = cv::getAffineTransform(src_pt, dst_pt);

  std::cout << "affine_matrix=\n" << affine_matrix << std::endl;

  cv::Mat dst_img;
  cv::warpAffine(src_img, dst_img, affine_matrix, src_img.size());
  
  // 変換前後の座標を描画
  cv::line(src_img, src_pt[0], src_pt[1], cv::Scalar(255,255,0), 2);
  cv::line(src_img, src_pt[1], src_pt[2], cv::Scalar(255,255,0), 2);
  cv::line(src_img, src_pt[2], src_pt[0], cv::Scalar(255,255,0), 2);
  cv::line(src_img, dst_pt[0], dst_pt[1], cv::Scalar(255,0,255), 2);
  cv::line(src_img, dst_pt[1], dst_pt[2], cv::Scalar(255,0,255), 2);
  cv::line(src_img, dst_pt[2], dst_pt[0], cv::Scalar(255,0,255), 2);

  cv::namedWindow("src", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("dst", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("src", src_img);
  cv::imshow("dst", dst_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_affine_3points_input.png _images/lenna_affine_3points.png
affine_matrix=
[0, 1, 100;
  -1, -3.255497723249808e-17, 300.0000000000001]

変換パラメータを指定して,変換行列を決定する

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1; 

  // 回転: -40 [deg],  スケーリング: 1.0 [倍]
  float angle = -40.0, scale = 1.0;
  // 中心:画像中心
  cv::Point2f center(src_img.cols*0.5, src_img.rows*0.5);
  // 以上の条件から2次元の回転行列を計算
  const cv::Mat affine_matrix = cv::getRotationMatrix2D( center, angle, scale );

  std::cout << "affine_matrix=\n" << affine_matrix << std::endl;

  cv::Mat dst_img;
  cv::warpAffine(src_img, dst_img, affine_matrix, src_img.size());
  
  cv::namedWindow("src", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("dst", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("src", src_img);
  cv::imshow("dst", dst_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_affine_parameters.png
affine_matrix=
[0.766044443118978, -0.6427876096865394, 214.1616488863111;
  0.6427876096865394, 0.766044443118978, -108.4042944283088]

透視変換を行う

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1;

  cv::Point2f pts1[] = {cv::Point2f(150,150),cv::Point2f(150,300),cv::Point2f(350,300),cv::Point2f(350,150)};
  cv::Point2f pts2[] = {cv::Point2f(200,150),cv::Point2f(200,300),cv::Point2f(340,270),cv::Point2f(340,180)};
  // r5014以前ではノイズがのります.
  //cv::Point2f pts1[] = {cv::Point2f(150,150.),cv::Point2f(150,300.),cv::Point2f(350,300.),cv::Point2f(350,150.)};
  //cv::Point2f pts2[] = {cv::Point2f(200,200.),cv::Point2f(150,300.),cv::Point2f(350,300.),cv::Point2f(300,200.)};

  // 透視変換行列を計算
  cv::Mat perspective_matrix = cv::getPerspectiveTransform(pts1, pts2);
  cv::Mat dst_img;
  // 変換
  cv::warpPerspective(src_img, dst_img, perspective_matrix, src_img.size(), cv::INTER_LINEAR);


  // 変換前後の座標を描画
  cv::line(src_img, pts1[0], pts1[1], cv::Scalar(255,255,0), 2, CV_AA);
  cv::line(src_img, pts1[1], pts1[2], cv::Scalar(255,255,0), 2, CV_AA);
  cv::line(src_img, pts1[2], pts1[3], cv::Scalar(255,255,0), 2, CV_AA);
  cv::line(src_img, pts1[3], pts1[0], cv::Scalar(255,255,0), 2, CV_AA);
  cv::line(src_img, pts2[0], pts2[1], cv::Scalar(255,0,255), 2, CV_AA);
  cv::line(src_img, pts2[1], pts2[2], cv::Scalar(255,0,255), 2, CV_AA);
  cv::line(src_img, pts2[2], pts2[3], cv::Scalar(255,0,255), 2, CV_AA);
  cv::line(src_img, pts2[3], pts2[0], cv::Scalar(255,0,255), 2, CV_AA);

  cv::namedWindow("src", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("dst", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("src", src_img);
  cv::imshow("dst", dst_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_perspective_input.png _images/lenna_perspective.png

画像ピラミッドを作る

画像ピラミッドは,解像度の異なる同一画像の集合から構成されます. このような構造は,画像の拡大縮小表示,空間方向に関する極大点を求める処理の高速化,coarse-to-fine(最初に低解像度に対する荒い処理を行い,徐々に高精度化する)手法などに利用されます.

buildPyramid

元画像に対して,ダウンサンプリングするだけの画像ピラミッドの場合 buildPyramid() を利用します.

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1;

  // level2までの画像ピラミッドを作成
  std::vector<cv::Mat> dst_img;
  cv::buildPyramid(src_img, dst_img, 2);

  cv::namedWindow("level0", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("level1", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("level2", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);  
  cv::imshow("level0", dst_img[0]);
  cv::imshow("level1", dst_img[1]);
  cv::imshow("level2", dst_img[2]);
  cv::waitKey(0);
}

実行結果:

_images/lenna_level0.png _images/lenna_level1.png _images/lenna_level2.png

PyrDown/PyrUp

元画像に対して,ダウンサンプリング,アップサンプリングの両方を適用する場合は,それぞれ, PyrDown, PyrUp を利用します.

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  const char *imagename = argc > 1 ? argv[1] : "../../image/opencv-logomini.png";  
  cv::Mat src_img = cv::imread(imagename, 1);
  if(src_img.empty()) return -1;

  cv::Mat dst_imgUp, dst_imgDown;
  cv::pyrUp(src_img, dst_imgUp, cv::Size(src_img.cols*2, src_img.rows*2));
  cv::pyrDown(src_img, dst_imgDown, cv::Size(src_img.cols/2, src_img.rows/2));
  
  cv::namedWindow("Up", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Down", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Up", dst_imgUp);
  cv::imshow("Down", dst_imgDown);
  cv::waitKey(0);
}

入力画像:

_images/opencv-logomini.png

実行結果:

_images/opencv-logominiDown.png _images/opencv-logominiUp.png

画像の膨張・収縮処理を行う

マルチチャンネル画像の場合,各チャンネルが個別に処理されます.

デフォルトの構造要素で膨張・収縮

デフォルトの構造要素は,3x3の矩形です.

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.pbm", 1);
  //cv::Mat src_img = cv::imread(argv[1], 1);
  if(src_img.empty()) return -1; 

  cv::Mat d1_img, d3_img, e1_img, e3_img;
  cv::dilate(src_img, d1_img, cv::Mat(), cv::Point(-1,-1), 1);
  cv::dilate(src_img, d3_img, cv::Mat(), cv::Point(-1,-1), 3);
  cv::erode(src_img, e1_img, cv::Mat(), cv::Point(-1,-1), 1);
  cv::erode(src_img, e3_img, cv::Mat(), cv::Point(-1,-1), 3);
    
  //__//cv::namedWindow("Dilated image1", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  //__//cv::namedWindow("Dilated image2", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  //__//cv::namedWindow("Eroded image1", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  //__//cv::namedWindow("Eroded image2", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imwrite(__FILE__".Dilated image1.png", d1_img);//__//
std::cout<<__FILE__".Dilated image1.png"<<std::endl;//__//
  cv::imwrite(__FILE__".Dilated image2.png", d3_img);//__//
std::cout<<__FILE__".Dilated image2.png"<<std::endl;//__//
  cv::imwrite(__FILE__".Eroded image1.png", e1_img);//__//
std::cout<<__FILE__".Eroded image1.png"<<std::endl;//__//
  cv::imwrite(__FILE__".Eroded image2.png", e3_img);//__//
std::cout<<__FILE__".Eroded image2.png"<<std::endl;//__//
  //__//cv::waitKey(0);
}

入力画像(をpngに変換した画像):

_images/lenna_bw.png

実行結果(膨張1回,膨張3回,収縮1回,収縮3回):

_images/lenna_dilate1.png _images/lenna_dilate3.png _images/lenna_erode1.png _images/lenna_erode3.png

構造要素を指定して膨張・収縮

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.pbm", 1);
  if(src_img.empty()) return -1; 

  cv::Mat d17_img, d55_img, e17_img, e55_img;
  cv::Mat element = cv::Mat::ones(1,7,CV_8UC1);
  cv::dilate(src_img, d17_img, element, cv::Point(-1,-1), 1);
  cv::erode(src_img, e17_img, element, cv::Point(-1,-1), 1);

  element = cv::Mat::eye(5,5, CV_8UC1);
  cv::dilate(src_img, d55_img, element, cv::Point(-1,-1), 1);
  cv::erode(src_img, e55_img, element, cv::Point(-1,-1), 1);
    
  cv::namedWindow("Dilated image1", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Dilated image2", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Eroded image1", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Eroded image2", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Dilated image1", d17_img);
  cv::imshow("Dilated image2", d55_img);
  cv::imshow("Eroded image1", e17_img);
  cv::imshow("Eroded image2", e55_img);
  cv::waitKey(0);
}

入力画像(をpngに変換した画像):

_images/lenna_bw.png

実行結果(1x7行列+膨張1回,5x5単位行列+膨張1回,1x7行列+収縮1回,5x5単位行列+収縮1回):

_images/lenna_dilate_horizon.png _images/lenna_dilate_slant.png _images/lenna_erode_horizon.png _images/lenna_erode_slant.png

高度なモルフォロジー演算

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  const char *imagename = argc > 1 ? argv[1] : "../../image/ball.png";  
  cv::Mat src_img = cv::imread(imagename, 1);
  if(src_img.empty()) return -1; 

  // 構造要素
  cv::Mat element(3,3,CV_8U, cv::Scalar::all(255));
  
  cv::Mat open_img;
  cv::morphologyEx(src_img, open_img, cv::MORPH_OPEN, element, cv::Point(-1,-1), 3);
  // close
  cv::Mat close_img;
  cv::morphologyEx(src_img, close_img, cv::MORPH_CLOSE, element, cv::Point(-1,-1), 3);
  // gradient
  cv::Mat gradient_img;
  cv::morphologyEx(src_img, gradient_img, cv::MORPH_GRADIENT, element, cv::Point(-1,-1), 3);
  // tophat
  cv::Mat tophat_img;
  cv::morphologyEx(src_img, tophat_img, cv::MORPH_TOPHAT, element, cv::Point(-1,-1), 2);
  // blackhat
  cv::Mat blackhat_img;
  cv::morphologyEx(src_img, blackhat_img, cv::MORPH_BLACKHAT, element, cv::Point(-1,-1), 2);
  
  cv::namedWindow("open_img", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("close_img", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("gradient_img", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("tophat_img", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("blackhat_img", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("open_img", open_img);
  cv::imshow("close_img", close_img);
  cv::imshow("gradient_img", gradient_img);
  cv::imshow("tophat_img", tophat_img);
  cv::imshow("blackhat_img", blackhat_img);
  cv::waitKey(0);  
}

入力画像:

_images/ball.png

実行結果(Open, Close, Gradient, TopHat, BlackHat)

_images/ball_open.png _images/ball_close.png _images/ball_gradient.png _images/ball_tophat.png _images/ball_blackhat.png

画像を平滑化する(ぼかす)

ガウシアンフィルタを用いた平滑化を行います.

GaussianBlur

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1; 
  
  cv::Mat dst_img1, dst_img2;
  // ガウシアンを用いた平滑化
  // 入力画像,出力画像,カーネルサイズ,標準偏差x, y
  cv::GaussianBlur(src_img, dst_img1, cv::Size(11,11), 10, 10);
  cv::GaussianBlur(src_img, dst_img2, cv::Size(51,3), 80, 3);
  
  cv::namedWindow("Blur image1", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Blur image2", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Blur image1", dst_img1);
  cv::imshow("Blur image2", dst_img2);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_gaussianblur1.png _images/lenna_gaussianblur2.png

medianBlur

メディアンフィルタを用いた平滑化を行います.

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1; 
  
  cv::Mat dst_img1, dst_img2;
  // メディアンフィルタを用いた平滑化
  // 入力画像,出力画像,カーネルサイズ
  cv::medianBlur(src_img, dst_img1, 11);
  cv::medianBlur(src_img, dst_img2, 51);
  
  cv::namedWindow("Blur image1", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Blur image2", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Blur image1", dst_img1);
  cv::imshow("Blur image2", dst_img2);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_medianblur1.png _images/lenna_medianblur2.png

BilateralFilter

バイラテラルフィルタを用いた平滑化を行います.

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1;

  cv::Mat dst_img1, dst_img2;
  // 入力,出力,各ピクセルの近傍領域を表す直径,
  // 色空間におけるσ,座標空間におけるσ
  cv::bilateralFilter(src_img, dst_img1, 11, 40, 200);
  cv::bilateralFilter(src_img, dst_img2, 20, 90, 40);

  cv::namedWindow("Blur Image 1", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Blur Image 2", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Blur Image 1", dst_img1);
  cv::imshow("Blur Image 2", dst_img2);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_bilateral1.png _images/lenna_bilateral2.png

BoxFilter, Blur

ボックスフィルタを用いた平滑化を行います.

blur(src, dst, ksize, anchor, borderType);

という呼び出しは,

boxFilter(src, dst, src.type(), anchor, true, borderType);

と等価です.つまり,BoxFilterを必ず正規化するメソッドが blur と言えます. ビット深度=8 の画像で正規化を行わない場合,多くの画素が飽和する可能性があります.

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1;

  cv::Mat dst_img1, dst_img2;
  // 入力,出力,カーネルサイズ(,その他=default)
  cv::blur(src_img, dst_img1, cv::Size(5,5));
  cv::blur(src_img, dst_img2, cv::Size(2,100));

  cv::Mat dst_img3, dst_img4;
  // 入力,出力,カーネルサイズ,アンカー,正規化の有無
  cv::boxFilter(src_img, dst_img3, src_img.type(), cv::Size(5,5), cv::Point(-1,-1), true);
  cv::boxFilter(src_img, dst_img4, src_img.type(), cv::Size(2,2), cv::Point(-1,-1), false);

  cv::namedWindow("Blur Image 1", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Blur Image 2", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Blur Image 3", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Blur Image 4", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Blur Image 1", dst_img1);
  cv::imshow("Blur Image 2", dst_img2);
  cv::imshow("Blur Image 3", dst_img3);
  cv::imshow("Blur Image 4", dst_img4);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果(blur):

_images/lenna_blur1.png _images/lenna_blur2.png

実行結果(BoxFilter):

_images/lenna_BoxFilter1.png _images/lenna_BoxFilter2.png

微分画像・エッジ画像を求める

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main (int argc, char *argv[])
{
  // グレースケール画像として読み込む
  cv::Mat src_img = cv::imread("../../image/lenna.png", 0);
  if(src_img.empty()) return -1; 
  
  // Sobel
  cv::Mat tmp_img;
  cv::Mat sobel_img;
  cv::Sobel(src_img, tmp_img, CV_32F, 1, 1);
  cv::convertScaleAbs(tmp_img, sobel_img, 1, 0);

  // Laplacian
  cv::Mat laplacian_img;
  cv::Laplacian(src_img, tmp_img, CV_32F, 3);
  cv::convertScaleAbs(tmp_img, laplacian_img, 1, 0);
  
  // Canny
  cv::Mat canny_img;
  cv::Canny(src_img, canny_img, 50, 200);

  cv::namedWindow("Original(Grayscale)", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Sobel", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Laplacian", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Canny", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Original(Grayscale)", src_img);
  cv::imshow("Sobel", sobel_img);
  cv::imshow("Laplacian", laplacian_img);
  cv::imshow("Canny", canny_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna_gray.png

実行結果(Sobel,Laplacian,Canny):

_images/lenna_sobel.png _images/lenna_laplacian.png _images/lenna_canny.png

点座標集合に外接する図形を求める

外接矩形を求める

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Size img_size(500, 500);
  cv::Mat img = cv::Mat::zeros(img_size, CV_8UC3);

  // 一様分布乱数で座標を生成
  const int rand_num = 50;
  cv::Mat_<int> points(rand_num, 2);
  cv::randu(points, cv::Scalar(100), cv::Scalar(400));
  for(int i=0; i<rand_num; ++i) {
    // 座標に点を描画
    cv::circle(img, cv::Point(points(i,0), points(i,1)), 2, cv::Scalar(200,200,0), -1, CV_AA);
  }

  // CV_*C2型のMatに変換してから,外接矩形を計算
  cv::Rect brect = cv::boundingRect(cv::Mat(points).reshape(2));  
  // 外接矩形を描画
  cv::rectangle(img, brect.tl(), brect.br(), cv::Scalar(100, 100, 200), 2, CV_AA);
  
  cv::namedWindow("image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("image", img);
  cv::waitKey(0);
}

実行結果:

_images/sample_bounding_rect.png

回転を考慮した外接矩形を求める

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Size img_size(500, 500);
  cv::Mat img = cv::Mat::zeros(img_size, CV_8UC3);

  // 一様分布乱数で座標を生成
  const int rand_num = 50;
  cv::Mat_<int> points(rand_num, 2);
  cv::randu(points, cv::Scalar(100), cv::Scalar(400));
  for(int i=0; i<rand_num; ++i) {
    // 座標に点を描画
    cv::circle(img, cv::Point(points(i,0), points(i,1)), 2, cv::Scalar(200,200,0), -1, CV_AA);
  }

  // CV_*C2型のMatに変換してから,外接矩形(回転あり)を計算
  cv::Point2f center, vtx[4];
  float radius;
  cv::RotatedRect box = cv::minAreaRect(cv::Mat(points).reshape(2));
  // 外接矩形(回転あり)を描画
  box.points(vtx);
  for(int i=0; i<4; ++i)
    cv::line(img, vtx[i], vtx[i<3?i+1:0], cv::Scalar(100,100,200), 2, CV_AA);
  
  cv::namedWindow("image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("image", img);
  cv::waitKey(0);
}

実行結果:

_images/sample_bounding_box.png

外接円を求める

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Size img_size(500, 500);
  cv::Mat img = cv::Mat::zeros(img_size, CV_8UC3);

  // 一様分布乱数で座標を生成
  const int rand_num = 50;
  cv::Mat_<int> points(rand_num, 2);
  cv::randu(points, cv::Scalar(100), cv::Scalar(400));
  for(int i=0; i<rand_num; ++i) {
    // 座標に点を描画
    cv::circle(img, cv::Point(points(i,0), points(i,1)), 2, cv::Scalar(200,200,0), -1, CV_AA);
  }

  // CV_*C2型のMatに変換してから,外接円を計算
  cv::Point2f center;
  float radius;
  cv::minEnclosingCircle(cv::Mat(points).reshape(2), center, radius);
  // 外接円を描画
  cv::circle(img, center, radius, cv::Scalar(100,100,200), 2, CV_AA);
  
  cv::namedWindow("image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("image", img);
  cv::waitKey(0);
}

実行結果:

_images/sample_bounding_circle.png

凸包を求める

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Size img_size(500, 500);
  cv::Mat img = cv::Mat::zeros(img_size, CV_8UC3);

  // 一様分布乱数で座標を生成
  const int rand_num = 50;
  cv::Mat_<int> points(rand_num, 2);
  cv::randu(points, cv::Scalar(100), cv::Scalar(400));
  for(int i=0; i<rand_num; ++i) {
    // 座標に点を描画
    cv::circle(img, cv::Point(points(i,0), points(i,1)), 2, cv::Scalar(200,200,0), -1, CV_AA);
  }

  // CV_*C2型のMatに変換してから,凸包を計算
  std::vector<cv::Point> hull;
  cv::convexHull(cv::Mat(points).reshape(2), hull);
  // 凸包を描画
  int hnum = hull.size();
  for(int i=0; i<hnum; ++i)
    cv::line(img, hull[i], hull[i+1<hnum?i+1:0], cv::Scalar(100,100,200), 2, CV_AA);
  
  cv::namedWindow("image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("image", img);
  cv::waitKey(0);
}

実行結果:

_images/sample_convexhull.png

画像の修復・不要オブジェクトを除去する

以下のいずれかの方法で,ノイズや不要なオブジェクトを削除して画像を修復します.

  • INPAINT_NS:ナビエ・ストークス(Navier-Stokes)ベースの手法.
  • INPAINT_TELEA:Alexandru Telea による手法 Alexandru Telea, “An Image Inpainting Technique Based on the Fast Marching Method”. Journal of Graphics, GPU, and Game Tools 9 1, pp 23-34 (2004)
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

#if OPENCV_VERSION_CODE<OPENCV_VERSION(2,4,0)
#include <opencv2/imgproc/imgproc.hpp>
#else
#include <opencv2/photo/photo.hpp>
#endif

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna_inpaint.png", 1);
  if(src_img.empty()) return -1; 
  cv::Mat mask_img = cv::imread("../../image/inpaint_mask.png", 0);
  if(mask_img.empty()) return -1;   
  
  cv::Mat ns_img, telea_img;
  // 入力画像,マスク,出力画像,修正時に考慮される近傍範囲を表す半径,手法
  cv::inpaint(src_img, mask_img, ns_img, 3, cv::INPAINT_NS);
  cv::inpaint(src_img, mask_img, telea_img, 3, cv::INPAINT_TELEA);
  
  cv::namedWindow("inpainted image(NS)", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("inpainted image(TELEA)", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("inpainted image(NS)", ns_img);
  cv::imshow("inpainted image(TELEA)", telea_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna_inpaint.png _images/inpaint_mask.png

実行結果(INPAINT_NS, INPAINT_TELEA):

_images/lenna_inpaint_ns.png _images/lenna_inpaint_telea.png

直線を検出する

古典的Hough変換

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/building.png", 1);
  if(src_img.empty()) return -1;

  cv::Mat dst_img, work_img;
  dst_img = src_img.clone();
  cv::cvtColor(src_img, work_img, CV_BGR2GRAY);
  cv::Canny(work_img, work_img, 50, 200, 3);
  
  // (古典的)Hough変換
  std::vector<cv::Vec2f> lines;
  // 入力画像,出力,距離分解能,角度分解能,閾値,*,*
  cv::HoughLines(work_img, lines, 1, CV_PI/180, 200, 0, 0);

  std::vector<cv::Vec2f>::iterator it = lines.begin();
  for(; it!=lines.end(); ++it) {
    float rho = (*it)[0], theta = (*it)[1];
    cv::Point pt1, pt2;
    double a = cos(theta), b = sin(theta);
    double x0 = a*rho, y0 = b*rho;
    pt1.x = cv::saturate_cast<int>(x0 + 1000*(-b));
    pt1.y = cv::saturate_cast<int>(y0 + 1000*(a));
    pt2.x = cv::saturate_cast<int>(x0 - 1000*(-b));
    pt2.y = cv::saturate_cast<int>(y0 - 1000*(a));
    cv::line(dst_img, pt1, pt2, cv::Scalar(0,0,255), 3, CV_AA);
  }

  cv::namedWindow("HoughLines", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("HoughLines", dst_img);
  cv::waitKey(0);
}

入力画像:

_images/building.png

実行結果:

_images/building_houghlines.png

マルチスケールHough変換

確率的Hough変換

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/building.png", 1);
  if(src_img.empty()) return -1;

  cv::Mat dst_img, work_img;
  dst_img = src_img.clone();
  cv::cvtColor(src_img, work_img, CV_BGR2GRAY);
  cv::Canny(work_img, work_img, 50, 200, 3);
  
  // 確率的Hough変換
  std::vector<cv::Vec4i> lines;
  // 入力画像,出力,距離分解能,角度分解能,閾値,線分の最小長さ,
  // 2点が同一線分上にあると見なす場合に許容される最大距離
  cv::HoughLinesP(work_img, lines, 1, CV_PI/180, 50, 50, 10);

  std::vector<cv::Vec4i>::iterator it = lines.begin();
  for(; it!=lines.end(); ++it) {
    cv::Vec4i l = *it;
    cv::line(dst_img, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(0,0,255), 2, CV_AA);
  }

  cv::namedWindow("HoughLinesP", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("HoughLinesP", dst_img);
  cv::waitKey(0);
}

入力画像:

_images/building.png

実行結果:

_images/building_houghlinesP.png

円を検出する

直線検出の cv::HoughLinescv::HoughLinesP が,2値画像から直線を検出するのに対して, cv::HoughCircles はグレースケール画像から円を検出することに注意してください (内部的には2値画像に変換されていますが).

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/circles.png", 1);
  if(src_img.empty()) return -1;

  cv::Mat dst_img, work_img;
  dst_img = src_img.clone();
  cv::cvtColor(src_img, work_img, CV_BGR2GRAY);

  // Hough変換のための前処理(画像の平滑化を行なわないと誤検出が発生しやすい)
  cv::GaussianBlur(work_img, work_img, cv::Size(11,11), 2, 2);
  
  // Hough変換による円の検出と検出した円の描画
  std::vector<cv::Vec3f> circles;
  cv::HoughCircles(work_img, circles, CV_HOUGH_GRADIENT, 1, 100, 20, 50);

  std::vector<cv::Vec3f>::iterator it = circles.begin();
  for(; it!=circles.end(); ++it) {
    cv::Point center(cv::saturate_cast<int>((*it)[0]), cv::saturate_cast<int>((*it)[1]));
    int radius = cv::saturate_cast<int>((*it)[2]);
    cv::circle(dst_img, center, radius, cv::Scalar(0,0,255), 2);
  }

  cv::namedWindow("HoughCircles", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("HoughCircles", dst_img);
  cv::waitKey(0);
}

入力画像:

_images/circles.png

実行結果:

_images/circles_houghcircles.png

楕円フィッティングを行う

画像に対する楕円フィッティング

画像から輪郭を検出し,その輪郭に対して楕円フィッティングを行う.

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/stuff.jpg", 1);
  if(src_img.empty()) return -1; 

  cv::Mat gray_img, bin_img;
  cv::cvtColor(src_img, gray_img, CV_BGR2GRAY);

  std::vector<std::vector<cv::Point> > contours;
  // 画像の二値化
  cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY|cv::THRESH_OTSU);
  // 輪郭の検出
  cv::findContours(bin_img, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
  
  for(int i = 0; i < contours.size(); ++i) {
    size_t count = contours[i].size();
    if(count < 150 || count > 1000) continue; // (小さすぎる|大きすぎる)輪郭を除外

    cv::Mat pointsf;
    cv::Mat(contours[i]).convertTo(pointsf, CV_32F);
    // 楕円フィッティング
    cv::RotatedRect box = cv::fitEllipse(pointsf);
    // 楕円の描画
    cv::ellipse(src_img, box, cv::Scalar(0,0,255), 2, CV_AA);
  }

  cv::namedWindow("fit ellipse", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("bin image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("fit ellipse", src_img);
  cv::imshow("bin image", bin_img);
  cv::waitKey(0);
}

入力画像:

_images/stuff.jpg

実行結果(輪郭画像,フィッティング結果):

_images/stuff_bin.png _images/stuff_fit_ellipse.png

画像からテンプレート画像を探す

テンプレートマッチング

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  // 探索画像
  cv::Mat search_img = cv::imread("../../image/lenna.png", 1);
  if(search_img.empty()) return -1; 
  // テンプレート画像
  cv::Mat tmp_img = cv::imread("../../image/lenna_left_eye.png", 1);
  if(tmp_img.empty()) return -1; 

  cv::Mat result_img;
  // テンプレートマッチング
  cv::matchTemplate(search_img, tmp_img, result_img, CV_TM_CCOEFF_NORMED);

  // 最大のスコアの場所を探す
  cv::Rect roi_rect(0, 0, tmp_img.cols, tmp_img.rows);
  cv::Point max_pt;
  double maxVal;
  cv::minMaxLoc(result_img, NULL, &maxVal, NULL, &max_pt);
  roi_rect.x = max_pt.x;
  roi_rect.y = max_pt.y;
  std::cout << "(" << max_pt.x << ", " << max_pt.y << "), score=" << maxVal << std::endl;
  // 探索結果の場所に矩形を描画
  cv::rectangle(search_img, roi_rect, cv::Scalar(0,0,255), 3);

  cv::namedWindow("search image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("result image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("search image", search_img);
  cv::imshow("result image", result_img);
  cv::waitKey(0);
}

入力画像(探索対象画像,テンプレート画像):

_images/lenna.png _images/lenna_left_eye.png

実行結果(相関値マップ,最大相関の場所):

_images/lenna_matching_map.png _images/lenna_matched_place.png
(316, 245), score=1

K-meansによるピクセル値のクラスタリング(減色)

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1;

  const int cluster_count = 10; /* クラスタ数 */

  // 画像を1列の行列に変形
  cv::Mat points;
  src_img.convertTo(points, CV_32FC3);
  points = points.reshape(3, src_img.rows*src_img.cols);

  // RGB空間でk-meansを実行
  cv::Mat_<int> clusters(points.size(), CV_32SC1);
  cv::Mat centers;
  // クラスタ対象,クラスタ数,(出力)クラスタインデックス,
  // 停止基準,k-meansの実行回数,手法,(出力)クラスタ中心値

#if OPENCV_VERSION_CODE<OPENCV_VERSION(2,3,0)
  cv::kmeans(points, cluster_count, clusters, 
	     cvTermCriteria(CV_TERMCRIT_EPS|CV_TERMCRIT_ITER, 10, 1.0), 1, cv::KMEANS_PP_CENTERS, &centers);
#else
  cv::kmeans(points, cluster_count, clusters, 
             cvTermCriteria(CV_TERMCRIT_EPS|CV_TERMCRIT_ITER, 10, 1.0), 1, cv::KMEANS_PP_CENTERS, centers);
#endif

  // すべてのピクセル値をクラスタ中心値で置き換え
  cv::Mat dst_img(src_img.size(), src_img.type());
  cv::MatIterator_<cv::Vec3b> itd = dst_img.begin<cv::Vec3b>(), 
    itd_end = dst_img.end<cv::Vec3b>();
  for(int i=0; itd != itd_end; ++itd, ++i) {
    cv::Vec3f &color = centers.at<cv::Vec3f>(clusters(i), 0);
    (*itd)[0] = cv::saturate_cast<uchar>(color[0]);
    (*itd)[1] = cv::saturate_cast<uchar>(color[1]);
    (*itd)[2] = cv::saturate_cast<uchar>(color[2]);
  }

  cv::namedWindow("dst_img", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("dst_img", dst_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_kmeans.png

画像のヒストグラムを計算・描画する

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main (int argc, char **argv)
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 0);
  if(src_img.empty()) return -1; 
  
  // ヒストグラムを描画する画像割り当て
  const int ch_width = 260, ch_height=200;
  cv::Mat hist_img(cv::Size(ch_width, ch_height), CV_8UC3, cv::Scalar::all(255));

  cv::Mat hist;
  const int hdims[] = {256}; // 次元毎のヒストグラムサイズ
  const float hranges[] = {0,256};
  const float* ranges[] = {hranges}; // 次元毎のビンの下限上限
  double max_val = .0;

  // シングルチャンネルのヒストグラム計算
  // 画像(複数可),画像枚数,計算するチャンネル,マスク,ヒストグラム(出力),
  // ヒストグラムの次元,ヒストグラムビンの下限上限
  cv::calcHist(&src_img, 1, 0, cv::Mat(), hist, 1, hdims, ranges);
  
  // 最大値の計算
  cv::minMaxLoc(hist, 0, &max_val);

  // ヒストグラムのスケーリングと描画
  cv::Scalar color = cv::Scalar::all(100);
  // スケーリング
  hist = hist * (max_val? ch_height/max_val:0.);
  for(int j=0; j<hdims[0]; ++j) {
    int bin_w = cv::saturate_cast<int>((double)ch_width/hdims[0]);
    cv::rectangle(hist_img, 
		  cv::Point(j*bin_w, hist_img.rows),
		  cv::Point((j+1)*bin_w, hist_img.rows-cv::saturate_cast<int>(hist.at<float>(j))),
		  color, -1);
  }

  cv::namedWindow("Image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Histogram", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Image", src_img);
  cv::imshow("Histogram", hist_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna_gray.png

実行結果:

_images/lenna_histogram.png

複数の画像をつなげる

ROIを利用して複数の画像をつなげる

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  int img_num = argc > 1 ? argc-1 : 2;
  const std::string defaultfile[] = {"../../image/lenna.png", "../../image/fruit.png"};
  int total_width=0, max_height=0;  

  // ファイルを読み込み,結合後のサイズを決定
  std::vector<cv::Mat> src_img;
  for(int i=0; i<img_num; i++) {
    src_img.push_back(cv::imread(argc-1?argv[i+1]:defaultfile[i]));
    cv::Mat last = src_img.back();
    if(last.empty()) return -1;
    total_width += last.cols;
    max_height = cv::max(max_height, last.rows);
  }

  // ROIを利用して,実際に画像を結合
  cv::Mat combined_img(cv::Size(total_width, max_height), CV_8UC3);
  std::vector<cv::Mat>::iterator it = src_img.begin(), it_end = src_img.end();
  cv::Rect roi_rect;
  for(; it!=it_end; ++it) {
    roi_rect.width = it->cols;
    roi_rect.height = it->rows;
    cv::Mat roi(combined_img, roi_rect);
    it->copyTo(roi);
    roi_rect.x += it->cols;
  }

  cv::namedWindow("Combined Image", CV_WINDOW_AUTOSIZE);
  cv::imshow("Combined Image", combined_img);
  cv::waitKey(0);
}

入力画像(入力画像1,入力画像2):

_images/lenna.png _images/fruit.png

実行結果:

_images/lenna_fruit.png

サイズの等しい複数の画像をつなげる

幅または高さの等しい画像同士ならば,より簡単につなげることができます.

#include <opencv2/core/core.hpp>
#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  int img_num = 5;
  const char *imagename = argc > 1 ? argv[1] : "../../image/opencv-logomini.png";

  cv::Mat src_img[img_num];
  for(int i=0; i<img_num; i++) {
    src_img[i] = cv::imread(imagename, 1);
    if(src_img[i].empty()) return -1;
  }
  
  cv::Mat dst_img_h, dst_img_v;
  /// 画像をつなげる
  // 入力{Mat1, Mat2, ...}, Mat数, 出力Mat
  hconcat(src_img, img_num, dst_img_h);
  vconcat(src_img, img_num, dst_img_v);
  
  cv::namedWindow("Hconcat Image", CV_WINDOW_AUTOSIZE);
  cv::imshow("Hconcat Image", dst_img_h);
  cv::namedWindow("Vconcat Image", CV_WINDOW_AUTOSIZE);
  cv::imshow("Vconcat Image", dst_img_v);
  cv::waitKey(0);
}
_images/opencv-logomini.png

実行結果(横結合):

_images/opencv-logomini_vconcat.png

実行結果(縦結合):

_images/opencv-logomini_hconcat.png

画像の一部を切り抜いて保存する

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1; 

  // (x,y)=(200,200), (width,height)=(100,100)
  cv::Mat roi_img(src_img, cv::Rect(200, 200, 100, 100));

  cv::imwrite("lenna_clipped.png", roi_img);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_clipped.png

画像の一部のみを処理する

ROI(Region Of Interest) を指定すると,画像の一部を新たな画像のように扱うことができます. この際,例えば畳み込みなどで必要になる 「ROI の外側の画素」は,自動的に元の画像全体からのピクセルが利用されます.

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1; 
  cv::Mat dst_img = src_img.clone();

  cv::Rect roi_rect(200,200,100,100); // x,y,w,h
  cv::Mat src_roi = src_img(roi_rect);
  cv::Mat dst_roi = dst_img(roi_rect);

  // 何らかの処理...
  cv::blur(src_roi, dst_roi, cv::Size(30,30));

  cv::namedWindow("image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("image", dst_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_roi_blur.png

矩形領域のピクセル値をサブピクセル精度で取得する

ROIを利用して,部分矩形領域のピクセル値をそのまま参照したり,コピーしたりするのとは異なり, cv::getRectSubPix を用いると,バイリニア補間された浮動小数点座標のピクセル値も得ることができます.

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1; 
    
  cv::Size patch_sie(100.0, 100.0);
  cv::Point2f center(250.5, 250.8); // 実数値での座標指定
  cv::Mat dst_img;
  // 矩形領域ピクセル値をサブピクセル精度で取得
  cv::getRectSubPix(src_img, patch_sie, center, dst_img);
  
  cv::namedWindow("image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("image", dst_img);
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_getRectSubPix.png

顔を検出する

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  const char *imagename = argc > 1 ? argv[1] : "../../image/lenna.png";
  cv::Mat img = cv::imread(imagename, 1);
  if(img.empty()) return -1; 
  
  double scale = 4.0;
  cv::Mat gray, smallImg(cv::saturate_cast<int>(img.rows/scale), cv::saturate_cast<int>(img.cols/scale), CV_8UC1);
  // グレースケール画像に変換
  cv::cvtColor(img, gray, CV_BGR2GRAY);
  // 処理時間短縮のために画像を縮小
  cv::resize(gray, smallImg, smallImg.size(), 0, 0, cv::INTER_LINEAR);
  cv::equalizeHist( smallImg, smallImg);

  // 分類器の読み込み
  std::string cascadeName = "./haarcascade_frontalface_alt.xml"; // Haar-like
  //std::string cascadeName = "./lbpcascade_frontalface.xml"; // LBP
  cv::CascadeClassifier cascade;
  if(!cascade.load(cascadeName))
    return -1;

  std::vector<cv::Rect> faces;
  /// マルチスケール(顔)探索xo
  // 画像,出力矩形,縮小スケール,最低矩形数,(フラグ),最小矩形
  cascade.detectMultiScale(smallImg, faces,
                           1.1, 2,
                           CV_HAAR_SCALE_IMAGE,
                           cv::Size(30, 30));

  // 結果の描画
  std::vector<cv::Rect>::const_iterator r = faces.begin();
  for(; r != faces.end(); ++r) {
    cv::Point center;
    int radius;
    center.x = cv::saturate_cast<int>((r->x + r->width*0.5)*scale);
    center.y = cv::saturate_cast<int>((r->y + r->height*0.5)*scale);
    radius = cv::saturate_cast<int>((r->width + r->height)*0.25*scale);
    cv::circle( img, center, radius, cv::Scalar(80,80,255), 3, 8, 0 );
  }

  cv::namedWindow("result", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow( "result", img );    
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_face_detect.png

また,Haar-Like特徴の代わりにLBP(Local Binary Pattern)特徴を利用して学習した結果を用いて顔を検出することもできます.それぞれの特徴を利用した学習結果ファイルは,以下の場所に置かれます.

  • Haar-Like:(OpenCV install path)/data/haarcascades
  • LBP:(OpenCV install path)/data/lbpcascades

本サンプルでの利用法は,該当ファイルをバイナリと同じ場所にコピーして,対応するXMLファイル(上述のソースでコメントアウトされている箇所)を読み込むだけです.

実行結果:

_images/tiffany_face_detect.png

目を検出する

まず顔を検出して,その検出矩形の中から,さらに目を検出します.

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  const char *imagename = argc > 1 ? argv[1] : "../../image/lenna.png";
  cv::Mat img = cv::imread(imagename, 1);
  if(img.empty()) return -1; 
  
  double scale = 2.0;
  cv::Mat gray, smallImg(cv::saturate_cast<int>(img.rows/scale), cv::saturate_cast<int>(img.cols/scale), CV_8UC1);
  // グレースケール画像に変換
  cv::cvtColor(img, gray, CV_BGR2GRAY);
  // 処理時間短縮のために画像を縮小
  cv::resize(gray, smallImg, smallImg.size(), 0, 0, cv::INTER_LINEAR);
  cv::equalizeHist( smallImg, smallImg);

  // 分類器の読み込み
  std::string cascadeName = "./haarcascade_frontalface_alt.xml";
  cv::CascadeClassifier cascade;
  if(!cascade.load(cascadeName))
    return -1;
  std::vector<cv::Rect> faces;
  /// マルチスケール(顔)探索
  // 画像,出力矩形,縮小スケール,最低矩形数,(フラグ),最小矩形
  cascade.detectMultiScale(smallImg, faces,
                           1.1, 2,
                           CV_HAAR_SCALE_IMAGE, 
                           cv::Size(30, 30) );
  
  std::string nested_cascadeName = "./haarcascade_eye.xml";
  //std::string nested_cascadeName = "./haarcascade_eye_tree_eyeglasses.xml";
  cv::CascadeClassifier nested_cascade;
  if(!nested_cascade.load(nested_cascadeName))
    return -1;

  std::vector<cv::Rect>::const_iterator r = faces.begin();
  for(; r != faces.end(); ++r) {

    // 検出結果(顔)の描画
    cv::Point face_center;
    int face_radius;
    face_center.x = cv::saturate_cast<int>((r->x + r->width*0.5)*scale);
    face_center.y = cv::saturate_cast<int>((r->y + r->height*0.5)*scale);
    face_radius = cv::saturate_cast<int>((r->width + r->height)*0.25*scale);
    cv::circle( img, face_center, face_radius, cv::Scalar(80,80,255), 3, 8, 0 );


    cv:: Mat smallImgROI = smallImg(*r);
    std::vector<cv::Rect> nestedObjects;
    /// マルチスケール(目)探索
    // 画像,出力矩形,縮小スケール,最低矩形数,(フラグ),最小矩形
    nested_cascade.detectMultiScale(smallImgROI, nestedObjects,
                                    1.1, 3,
                                    CV_HAAR_SCALE_IMAGE, 
                                    cv::Size(10,10));
  

    // 検出結果(目)の描画
    std::vector<cv::Rect>::const_iterator nr = nestedObjects.begin();
    for(; nr != nestedObjects.end(); ++nr) {
      cv::Point center;
      int radius;
      center.x = cv::saturate_cast<int>((r->x + nr->x + nr->width*0.5)*scale);
      center.y = cv::saturate_cast<int>((r->y + nr->y + nr->height*0.5)*scale);
      radius = cv::saturate_cast<int>((nr->width + nr->height)*0.25*scale);
      cv::circle( img, center, radius, cv::Scalar(80,255,80), 3, 8, 0 );
    }
  }

  cv::namedWindow("result", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow( "result", img );    
  cv::waitKey(0);
}

入力画像:

_images/lenna.png

実行結果:

_images/lenna_eye_detect.png

また,メガネを掛けた人物の目を検出するための学習結果ファイルを利用することもできます.顔検出の場合と同様に,本サンプルでの利用法は,該当ファイルをバイナリと同じ場所にコピーして,対応するXMLファイル(上述のソースでコメントアウトされている箇所)を読み込むだけです.

実行結果:

_images/kate_eye_detect.png

複数の矩形をグループ化する

オブジェクト検出の際に,同程度に「オブジェクトらしい」箇所に複数の検出矩形が集まることがよくあります. 多くの場合,「オブジェクトらしい」場所の近隣も同様に「オブジェクトらしい」ためです. このような場合に,複数の矩形をグループ化して,同じような矩形は1つのグループに集めるための手法です.

#include <opencv2/core/core.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Size img_size(500, 500);
  cv::Mat img = cv::Mat::zeros(img_size, CV_8UC3);

  // 一様分布乱数で座標を生成
  const int rand_num = 5;
  cv::Mat_<int> p_tl(rand_num, 2), p_br(rand_num, 2);
  cv::randu(p_tl, cv::Scalar(150-5), cv::Scalar(150+5));
  cv::randu(p_br, cv::Scalar(350-5), cv::Scalar(350+5));
  std::vector<cv::Rect> rects;
  for(int i=0; i<rand_num; ++i) {
    rects.push_back(cv::Rect(cv::Point(p_tl(i,0), p_tl(i,1)), cv::Point(p_br(i,0),p_br(i,1))));
    // グループ化前の矩形を描画
    cv::rectangle(img, rects.back().tl(), rects.back().br(), cv::Scalar(200,200,0), 1, CV_AA);
  }

  /// 矩形のグループ化
  // 矩形の集合,1クラスタに最低限含まれる矩形数,マージに必要な矩形端点同士の差異
  cv::groupRectangles(rects, 2, 1.0);

  // グループ化された矩形を描画
  std::vector<cv::Rect>::iterator it = rects.begin();
  for(; it != rects.end(); ++it) {
    cv::rectangle(img, it->tl(), it->br(), cv::Scalar(0,0,200), 2, CV_AA);
  }

  cv::namedWindow("image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("image", img);
  cv::waitKey(0);
}

実行結果:

_images/group_rect.png

人を検出する

HOG

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  const char *imagename = argc > 1 ? argv[1] : "../../image/pedestrian.png";
  cv::Mat img = cv::imread(imagename, 1);
  if(img.empty()) return -1; 
  
  cv::HOGDescriptor hog;
  hog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector());

  std::vector<cv::Rect> found;
  // 画像,検出結果,閾値(SVMのhyper-planeとの距離),
  // 探索窓の移動距離(Block移動距離の倍数),
  // 画像外にはみ出た対象を探すためのpadding,
  // 探索窓のスケール変化係数,グルーピング係数
  hog.detectMultiScale(img, found, 0.2, cv::Size(8,8), cv::Size(16,16), 1.05, 2);

  std::vector<cv::Rect>::const_iterator it = found.begin();
  std::cout << "found:" << found.size() << std::endl;
  for(; it!=found.end(); ++it) {
    cv::Rect r = *it;
    // 描画に際して,検出矩形を若干小さくする
    r.x += cvRound(r.width*0.1);
    r.width = cvRound(r.width*0.8);
    r.y += cvRound(r.height*0.07);
    r.height = cvRound(r.height*0.8);
    cv::rectangle(img, r.tl(), r.br(), cv::Scalar(0,255,0), 3);
  }

  // 結果の描画
  cv::namedWindow("result", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow( "result", img );    
  cv::waitKey(0);
}

実行結果:

_images/people_detect1.png _images/people_detect2.png _images/people_detect3.png

特徴点を検出する

様々な手法で特徴点を検出することができます. detector( cv::FeatureDetector を継承した様々なクラス)を変えるだけで,検出手法を変更することができます.

固有値に基づく特徴点検出

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // 固有値に基づく特徴点検出
  // maxCorners=80, qualityLevel=0.01, minDistance=5, blockSize=3
  cv::GoodFeaturesToTrackDetector detector(80, 0.01, 5, 3);
  detector.detect(gray_img, keypoints);
  cv::Scalar color(0,200,255);
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 1, CV_AA);
    if(itk->angle>=0) {
      cv::Point pt2(itk->pt.x + cos(itk->angle)*itk->size, itk->pt.y + sin(itk->angle)*itk->size);
      cv::line(img, itk->pt, pt2, color, 1, CV_AA);
    }
  }

  cv::namedWindow("EigenValue Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("EigenValue Features", img);
  cv::waitKey(0);
}

実行結果:

_images/lenna_good_feature_to_track.png

Harris検出器に基づく特徴点検出

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // Harris 検出器に基づく特徴点検出
  // maxCorners=80, qualityLevel=0.01, minDistance=5, blockSize=3, useHarrisDetector=true
  cv::GoodFeaturesToTrackDetector detector(80, 0.01, 5, 3, true); 
  detector.detect(gray_img, keypoints);
  cv::Scalar color(255,200,0);
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 1, CV_AA);
    if(itk->angle>=0) {
      cv::Point pt2(itk->pt.x + cos(itk->angle)*itk->size, itk->pt.y + sin(itk->angle)*itk->size);
      cv::line(img, itk->pt, pt2, color, 1, CV_AA);
    }
  }

  cv::namedWindow("Harris Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Harris Features", img);
  cv::waitKey(0);  
}

実行結果:

_images/lenna_harris.png

FAST検出器に基づく特徴点検出

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1;

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // FAST 検出器に基づく特徴点検出
  // threashld=70, nonMaxSuppression=true
  cv::FastFeatureDetector detector(70, false);
  detector.detect(gray_img, keypoints);
  cv::Scalar color(100,255,100);
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 1, CV_AA);
    if(itk->angle>=0) {
      cv::Point pt2(itk->pt.x + cos(itk->angle)*itk->size, itk->pt.y + sin(itk->angle)*itk->size);
      cv::line(img, itk->pt, pt2, color, 1, CV_AA);
    }
  }
  
  cv::namedWindow("FAST Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("FAST Features", img);
  cv::waitKey(0);
}

実行結果:

_images/lenna_fast.png

Star検出器に基づく特徴点検出

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // FAST 検出器に基づく特徴点検出
  // maxSize=16, responseThreshold=40, lineThresholdProjected=10, lineThresholdBinarized=8, suppressNonMaxSize=5
  cv::StarFeatureDetector detector(16, 40);
  cv::Scalar color(255,100,50);
  detector.detect(gray_img, keypoints);
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 1, CV_AA);
    if(itk->angle>=0) {
      cv::Point pt2(itk->pt.x + cos(itk->angle)*itk->size, itk->pt.y + sin(itk->angle)*itk->size);
      cv::line(img, itk->pt, pt2, color, 1, CV_AA);
    }
  }

  cv::namedWindow("STAR Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("STAR Features", img);
  cv::waitKey(0);  
}

実行結果:

_images/lenna_star.png

SIFT検出器に基づく特徴点検出

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

#if OPENCV_VERSION_CODE>=OPENCV_VERSION(2,4,0)
#include <opencv2/nonfree/features2d.hpp>
#endif

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // SIFT 検出器に基づく特徴点検出
  // threshold=0.05, edgeThreshold=10.0
  cv::SiftFeatureDetector detector(0.05,10.0);
  cv::Scalar color(100,50,255);
  detector.detect(gray_img, keypoints);
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 1, CV_AA);
    if(itk->angle>=0) {
      cv::Point pt2(itk->pt.x + cos(itk->angle)*itk->size, itk->pt.y + sin(itk->angle)*itk->size);
      cv::line(img, itk->pt, pt2, color, 1, CV_AA);
    }
  }

  cv::namedWindow("SIFT Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("SIFT Features", img);
  cv::waitKey(0);  
}

実行結果:

_images/lenna_sift.png

SURF検出器に基づく特徴点検出

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

#if OPENCV_VERSION_CODE>=OPENCV_VERSION(2,4,0)
#include <opencv2/nonfree/features2d.hpp>
#endif

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // SURF 検出器に基づく特徴点検出
  // hessianThreshold=4500, 
  cv::SurfFeatureDetector detector(4500);
  cv::Scalar color(100,255,50);
  detector.detect(gray_img, keypoints);
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 1, CV_AA);
    if(itk->angle>=0) {
      cv::Point pt2(itk->pt.x + cos(itk->angle)*itk->size, itk->pt.y + sin(itk->angle)*itk->size);
      cv::line(img, itk->pt, pt2, color, 1, CV_AA);
    }
  }

  cv::namedWindow("SIFT Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("SIFT Features", img);
  cv::waitKey(0);  
}

実行結果:

_images/lenna_surf.png

MSER検出器に基づく特徴点検出

MSERは本来,領域抽出の手法ですが,OpenCVでは,検出された領域輪郭に楕円をフィッティングさせることで,サイズと方向を考慮する特徴点検出器として利用することもできます.

#include <iostream>
#include <cmath>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // MSER 検出器に基づく特徴点検出
  // default parameters
  cv::MserFeatureDetector detector;
  cv::Scalar color(0,200,200);
  detector.detect(gray_img, keypoints);
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 1, CV_AA);
    if(itk->angle>=0) {
      cv::Point pt2(itk->pt.x + cos(itk->angle)*itk->size, itk->pt.y + sin(itk->angle)*itk->size);
      cv::line(img, itk->pt, pt2, color, 1, CV_AA);
    }
  }

  cv::namedWindow("MSER Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("MSER Features", img);
  cv::waitKey(0);  
}

実行結果:

_images/lenna_mser.png

ORB検出器に基づく特徴点検出

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

int
main(int argc, char *argv[])
{
#if OPENCV_VERSION_CODE<OPENCV_VERSION(2,3,0)
    std::cout << "cannot support this opencv version:" 
              <<  CV_VERSION << std::endl;
    return -1;
#else

  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // ORB 検出器に基づく特徴点検出
  // n_features=300, params=default
  cv::OrbFeatureDetector detector(300);
  cv::Scalar color(200,250,255);
  detector.detect(gray_img, keypoints);
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 1, CV_AA);
  }

  cv::namedWindow("ORB Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("ORB Features", img);
  cv::waitKey(0);  
#endif
}

実行結果:

_images/lenna_ORB.png

FAST検出器+Gridアダプタ に基づく特徴点検出

入力画像をグリッド状に分割し,その中で特徴点を検出します.

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // FAST 検出器 + Grid アダプタ に基づく特徴点検出
  // maxTotalKeypoints=200, gridRows=10, gridCols=10
  cv::GridAdaptedFeatureDetector detector(new cv::FastFeatureDetector(10), 200, 10, 10);
  cv::Scalar color(100,255,100);
  detector.detect(gray_img, keypoints);
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 1, CV_AA);
    if(itk->angle>=0) {
      cv::Point pt2(itk->pt.x + cos(itk->angle)*itk->size, itk->pt.y + sin(itk->angle)*itk->size);
      cv::line(img, itk->pt, pt2, color, 1, CV_AA);
    }
  }

  cv::namedWindow("GridAdapted Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("GridAdapted Features", img);
  cv::waitKey(0);  
}

実行結果:

_images/lenna_fast_gridadapted.png

FAST検出器+Pyramidアダプタ に基づく特徴点検出

入力画像からガウシアンピラミッドを作成し,各レベルにおいて特徴点を検出します. 特徴点のスケールを指定できない検出器において役立ちます.

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // FAST 検出器 + Pyramid アダプタ に基づく特徴点検出
  // levels=3
  cv::PyramidAdaptedFeatureDetector detector(new cv::FastFeatureDetector(70), 3);
  cv::Scalar color(100,255,100);
  detector.detect(gray_img, keypoints);
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 1, CV_AA);
    if(itk->angle>=0) {
      cv::Point pt2(itk->pt.x + cos(itk->angle)*itk->size, itk->pt.y + sin(itk->angle)*itk->size);
      cv::line(img, itk->pt, pt2, color, 1, CV_AA);
    }
  }

  cv::namedWindow("GridAdapted Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("GridAdapted Features", img);
  cv::waitKey(0);  
}

実行結果:

_images/lenna_fast_pyramidadapted.png

FAST検出器+Dynamicアダプタ(+Adjusterアダプタ)に基づく特徴点検出

特徴点検出において,ある決まった個数範囲の特徴点を求めたい場合に,この方法が役にたちます. 求めたい特徴点個数の 最小値最大値 を指定すると,検出器のパラメータを変更しながら, 指定された 繰り返し数 範囲内で探索を行います.

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // threashold=20
  cv::FastFeatureDetector _detector(20);
  _detector.detect(gray_img, keypoints);
  std::cout << "keypoints(FastFeatureDetector): " << keypoints.size() << std::endl;

  // FAST 検出器 + Dynamic アダプタ に基づく特徴点検出
  // threashld=20, nonMaxSuppression=true, 
  // min_features=130, max_features=150, max_iters=40
  cv::DynamicAdaptedFeatureDetector detector(new cv::FastAdjuster(20),  130, 150, 40);
  cv::Scalar color(100,255,100);
  detector.detect(gray_img, keypoints);
  std::cout << "keypoints(DynamicAdaptedFeatureDetector): " << keypoints.size() << std::endl;
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 1, CV_AA);
    if(itk->angle>=0) {
      cv::Point pt2(itk->pt.x + cos(itk->angle)*itk->size, itk->pt.y + sin(itk->angle)*itk->size);
      cv::line(img, itk->pt, pt2, color, 1, CV_AA);
    }
  }

  cv::namedWindow("GridAdapted Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("GridAdapted Features", img);
  cv::waitKey(0);  
}

実行結果:

_images/lenna_fast_adjuster.png
keypoints(FastFeatureDetector): 1614
keypoints(DynamicAdaptedFeatureDetector): 145

SimpleBlob検出器に基づく特徴点検出

  1. 適応的閾値処理を行い,入力画像を二値化します.その際に,minThreasholdからmaxThresholdまで,thresholdStepステップで変化させた複数の閾値を利用します.
  2. 各二値画像から,findContours()を用いて連結成分を抽出し,その中心を求めます.
  3. 複数の二値画像の中心点を座標でグループ化します.近い中心同士は1つのblobを形成し,これは minDistBetweenBlobsパラメータで制御されます.
  4. グループの最終的な中心と半径を推定し,それをキーポイントの位置とサイズとして返します.
#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

int
main(int argc, char *argv[])
{
#if OPENCV_VERSION_CODE<OPENCV_VERSION(2,3,0)
    std::cout << "cannot support this opencv version:" 
              <<  CV_VERSION << std::endl;
    return -1;
#else

  cv::Mat img = cv::imread("../../image/BigBead.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;

  // SimpleBlob 検出器に基づく特徴点検出
  // thresholdStep=20, other params=default
  cv::SimpleBlobDetector::Params params;
  params.thresholdStep = 20;
  cv::SimpleBlobDetector detector(params);
  cv::Scalar color(200,255,100);
  detector.detect(gray_img, keypoints);
  for(itk = keypoints.begin(); itk!=keypoints.end(); ++itk) {
    cv::circle(img, itk->pt, 1, color, -1);
    cv::circle(img, itk->pt, itk->size, color, 3, CV_AA);
  }

  cv::namedWindow("SimpleBlob Features", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("SimpleBlob Features", img);
  cv::waitKey(0);  
#endif
}

実行結果:

_images/BigBead_SimpleBlob.png

局所特徴量を計算する

検出された特徴点に対して,それを記述する様々な局所特徴量を計算することができます.
detector と extractor( cv::DescriptorExtractor を継承した様々なクラス)を変えるだけで,計算する局所特徴量を変更することができます.

SIFT

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

#if OPENCV_VERSION_CODE>=OPENCV_VERSION(2,4,0)
#include <opencv2/nonfree/features2d.hpp>
#endif

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;
  cv::Mat descriptors;

  //
  // threshold=0.05, edgeThreshold=10.0
  cv::SiftFeatureDetector detector(0.05,10.0);
  detector.detect(gray_img, keypoints);
  // SIFT に基づくディスクリプタ抽出器
  cv::SiftDescriptorExtractor extractor;
  cv::Scalar color(100,255,100);
  extractor.compute(gray_img, keypoints, descriptors);

  // 128次元の特徴量 x keypoint数
  for(int i=0; i<descriptors.rows; ++i) {
    cv::Mat d(descriptors, cv::Rect(0,i,descriptors.cols,1));
    std::cout << i << ": " << d << std::endl;
  }
}

実行結果(最初の2行):

0: [0.021233672, 0.0014659651, 0.00054864027, 0.043791994, 0.15031554, 0.0081593897, 0.0052633695, 0.017900508, 0.14055812, 8.8222216e-05, 8.6816208e-06, 0.030280454, 0.079922713, 0.024566842, 0.014150854, 0.13550681, 0.084639646, 0.0055453554, 0.0002427475, 0.00017436311, 0.20782301, 0.12179199, 0.017845433, 0.051384307, 0.24379671, 0.04320721, 0.0031827968, 0.0067592184, 0.017057467, 0.0070692343, 0.00028205666, 0.010635576, 0.026519295, 0.0020695599, 0.00010507141, 0.051817521, 0.24379671, 0.011494503, 0.012467382, 0.022929478, 0.24379671, 0.015562179, 0.00014225872, 0.0075872038, 0.093277723, 0.030935662, 0.0084859077, 0.17412668, 0.086305462, 0.0027722607, 0.00011851717, 0.0001424334, 0.24379671, 0.18022627, 0.022019938, 0.10054632, 0.24379671, 0.020836202, 0.0073310034, 0.014498728, 0.030319119, 0.023211006, 0.012665867, 0.17109598, 0.027040122, 0.015344816, 0.0038383096, 0.053639598, 0.24379671, 0.015584219, 0.00036631722, 0.00097804808, 0.24379671, 0.17657502, 0.0014261669, 0.010763198, 0.062686205, 0.0095831826, 0.001129471, 0.014588176, 0.10341847, 0.05432947, 0.0017211817, 0.0058160201, 0.19489776, 0.12657295, 0.036803395, 0.10844494, 0.05650235, 0.0056605069, 0.0058878455, 0.0046394193, 0.035608321, 0.043195184, 0.032221042, 0.2320625, 0.031031983, 0.019548343, 0.0016009037, 0.0021222478, 0.13484018, 0.10258325, 0.0028180839, 0.0019360944, 0.24379671, 0.082535937, 0.0010450779, 0.001452325, 0.032737724, 0.042230025, 0.0076545845, 0.012181766, 0.11940889, 0.078639783, 0.02931896, 0.031542189, 0.01965417, 0.01233358, 0.0052560121, 0.01419207, 0.02527442, 0.080087811, 0.056695677, 0.011795605, 0.011991151, 0.0063652727, 0.0025214576, 0.017665019]
1: [0.063618973, 0.27865481, 0.19649969, 0.008179483, 0.00023455996, 0.00014861442, 0.00023793994, 0.0001030515, 0.13486731, 0.17437772, 0.084160596, 0.070562989, 0.016319536, 0.032444309, 0.036570705, 0.023056991, 0.075900875, 0.069621712, 0.04741209, 0.01626209, 0.0072119543, 0.069958389, 0.068665504, 0.022338104, 0.2045857, 0.098395817, 0.050180946, 0.003105673, 0.00020554618, 0.0014919095, 0.0046132631, 0.099711582, 0.26318488, 0.087078199, 0.011536893, 0.0062746275, 0.0005164505, 0.00090169167, 0.074670583, 0.045949377, 0.27865481, 0.087247498, 0.0035000516, 0.0051924065, 0.0061974553, 0.031058798, 0.054185811, 0.11179995, 0.067107782, 0.01578919, 0.0089584077, 0.042678677, 0.13425377, 0.17923307, 0.10109202, 0.046970662, 0.037586223, 0.09692584, 0.11265471, 0.015591307, 0.028214036, 0.010593625, 0.0073529598, 0.0073716887, 0.066582181, 0.0010976094, 0, 0, 1.1674022e-06, 0.0073210392, 0.27865481, 0.1735087, 0.27865481, 0.06770201, 0.030971454, 0.029228326, 0.0092106732, 0.023255298, 0.16263069, 0.1806239, 0.040729184, 0.030285547, 0.042511113, 0.1631007, 0.12234834, 0.014959278, 0.012759373, 0.014949872, 0.0036240586, 0.0050182575, 0.0067629395, 0.010970328, 0.024487708, 0.018909603, 0.065328762, 0.022244232, 0.050587136, 0.08213155, 0.012370801, 0.0037464912, 0.0066909697, 0.03632981, 0.24200492, 0.091015652, 0.0084979935, 0.0094920862, 0.0079585062, 0.022813687, 0.11294786, 0.16293645, 0.12560333, 0.023835396, 0.0090456782, 0.0010426415, 0.0040089954, 0.018021021, 0.037192263, 0.027802264, 0.011082977, 0.022885716, 0.019266251, 0.00074749964, 7.8681638e-05, 0.00014372123, 0.00019890479, 0.0030645328, 0.020529719, 0.038005192]

SURF

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

#if OPENCV_VERSION_CODE>=OPENCV_VERSION(2,4,0)
#include <opencv2/nonfree/features2d.hpp>
#endif

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;
  cv::Mat descriptors;

  //
  // threshold=4500
  cv::SurfFeatureDetector detector(4500);
  detector.detect(gray_img, keypoints);
  // SURF に基づくディスクリプタ抽出器
  cv::SurfDescriptorExtractor extractor;
  cv::Scalar color(100,255,50);
  extractor.compute(gray_img, keypoints, descriptors);

  // 64次元の特徴量 x keypoint数
  for(int i=0; i<descriptors.rows; ++i) {
    cv::Mat d(descriptors, cv::Rect(0,i,descriptors.cols,1));
    std::cout << i << ": " << d << std::endl;
  }
}

実行結果(最初の2行):

0: [0.0005440524, 0.00019080873, 0.0011156601, 0.00060204417, 0.031112911, -0.0049687005, 0.03507245, 0.0080976002, -0.0028718193, 0.00090316747, 0.003157127, 0.0027868869, -0.00011331915, -0.00025480194, 0.0001945437, 0.00049618207, -0.0012214067, -0.00029818181, 0.0054713301, 0.0025845629, 0.36256874, 0.071781643, 0.40608761, 0.11346382, -0.065762296, 0.21537137, 0.065762296, 0.22915401, -0.0069807679, 0.029208595, 0.0077104745, 0.031328633, 0.00089156715, 0.0006404923, 0.0038368064, 0.0031534194, 0.51476336, -0.042307768, 0.52824235, 0.075963326, -0.022579186, 0.036412533, 0.02337856, 0.046764888, -0.0039506666, 0.011458799, 0.0040103844, 0.011458799, -0.00056651817, 7.803012e-05, 0.00057466829, 0.00047578587, 0.061804887, -0.043759901, 0.063265294, 0.044868838, 0.0088353241, -0.051317215, 0.0097551076, 0.051738929, 0.00096447999, -0.0053662988, 0.0010464506, 0.0053944737]
1: [-0.00072802493, -0.00041244976, 0.0017245135, 0.0011232586, -0.0067925481, -0.0030960809, 0.0079159923, 0.0044780653, 0.043065421, 0.00064619089, 0.061223406, 0.0055225501, 0.0086779324, 0.00014670294, 0.0088232476, 0.00087578868, 0.00012487371, 0.0264633, 0.016818596, 0.051180985, -0.14471763, 0.028734764, 0.17976919, 0.14659522, 0.31568813, -0.0027507769, 0.41184184, 0.058844961, 0.047577083, -0.0039710426, 0.049062785, 0.0097013526, 0.017899623, -0.016191542, 0.028678821, 0.046525974, -0.074518472, 0.10378933, 0.30330241, 0.49421933, 0.17636862, 0.060175549, 0.43175146, 0.21572286, 0.051260743, 0.00054621755, 0.054715548, 0.006727933, 0.0031269097, -0.00389991, 0.0033542607, 0.0039988817, -0.026279358, -0.0058076805, 0.032108702, 0.025418459, 0.033053506, -0.023869021, 0.050510518, 0.030669102, 0.011189211, -0.00067684194, 0.012327171, 0.0011812798]

Calonder

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

#if OPENCV_VERSION_CODE>=OPENCV_VERSION(2,4,0)
#include <opencv2/nonfree/features2d.hpp>
#include <opencv2/legacy/legacy.hpp>
#endif

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;
  cv::Mat descriptors;

  //
  // threshold=4500
  cv::SurfFeatureDetector detector(4500);
  detector.detect(gray_img, keypoints);
  // Calonder に基づくディスクリプタ抽出器(あらかじめ学習させておく必要があります)
  cv::CalonderDescriptorExtractor<float> extractor("calonder.dat");
  cv::Scalar color(100,255,100);
  extractor.compute(gray_img, keypoints, descriptors);

  // 
  for(int i=0; i<descriptors.rows; ++i) {
    cv::Mat d(descriptors, cv::Rect(0,i,descriptors.cols,1));
    std::cout << i << ": " << d << std::endl;
    }
}

実行結果(最初の5行):

[OK] Loaded RTC, quantization=4 bits
[OK] RTC: overall 55673/4325376 (1.287%) zeros in float leaves
          overall 243423/4325376 (5.628%) zeros in uint8 leaves
0: [-0.00066007086, -0.0024703937, 0.00051066413, -0.002032117, -0.0016254914, 0.0022725903, -2.1952466e-05, -0.0011181739, 0.0012997336, 0.00014707487, -4.5135701e-05, -0.0015029697, 0.00031392116, -0.0022969458, 0.001976727, -0.00099447055, 0.0012108093, -0.00047411732, -2.6497488e-05, -0.0010783827, 0.00087404321, 0.00072360033, -0.0006009338, 0.001890304, 0.00029500175, 0.003113484, -0.0006059141, 0.00074204541, 0.00057215668, 0.00049296627, -0.00030687905, 0.0017618984, 0.00082324166, 0.0016948842, -0.0041480018, 0.002844325, 3.8008115e-05, -0.0020949445, -0.001950997, 0.00029717243, -0.0020662977, 0.0027430113, -0.0015814503, -0.00027710691, 0.00061151962, -5.7207752e-05, -0.00075050228, 0.001434573, -0.0036784874, 0.00064557674, 0.00088262529, -0.0025574949, 0.00076234736, 0.0016572556, -0.0023775462, 0.00016016405, -0.00079171243, 0.00030554456, 0.00092991948, 0.00064085104, 0.0024300502, 0.00046147266, -0.00022175124, 0.00079395209, 0.00030632596, -0.00040124182, -8.6227868e-05, 0.0014025657, 0.0010878899, -0.00035935585, -1.9277368e-05, -5.9232112e-05, -0.0010884214, -0.00034759883, -0.00017398712, 0.0018306236, -0.0023302666, 0.00063621835, 8.5777523e-05, 0.000631298, -4.7389778e-05, 0.0020115257, 0.00070277468, -0.0018638497, -0.0010511409, -0.00015567684, -0.0011067932, -0.0027190733, -0.00093306886, 0.0016994989, 0.0036870835, -0.0007537643, -0.00056488416, 0.002259993, -0.0016289778, 7.040504e-05, 0.00089445151, -0.0004169695, -0.0024700069, -0.0022734238, 4.4834451e-07, 1.1578998e-05, 0.0012584596, -0.0015955415, -0.00021057886, 0.003623195, -0.00065472699, -0.00067710812, -0.00040601785, 0.0016859263, 0.00081306492, -0.00084803178, 0.00089693139, 0.001117954, -0.0010808489, -0.0015704372, 0.0016996716, 0.0002969857, -0.00021493435, -0.0019525024, -0.00044437489, -0.00054735062, -0.0042922478, 0.0022557213, -0.00042609987, -0.00078931608, -0.00027628333, 6.4732005e-05, -0.00091591617, -0.00025169147, -0.0014460358, -0.0024987783, 0.0026095982, 0.0035187399, -0.0032224783, 0.0011347263, -0.0021197691, 0.0014817567, -0.0015274514, 0.0013261451, 0.0020656122, 0.0019335005, -3.3104869e-05, 0.0030435326, -0.0014547043, -0.0014690449, -0.00033831145, -4.2241649e-05, -0.0015512211, 0.00060716557, -0.0040006777, -0.0017807428, 0.00076706358, 0.0010047187, 0.0011281611, -0.001454946, 0.0019023614, 0.0015685922, -0.0027419019, 0.00063813577, -0.00085954246, 0.00061636593, 0.0021170245, 0.00099111756, -0.00044671181, -0.00091111677, -0.0010112411, -0.00020761949, 0.00062171422, -0.0012111417, -0.0021619105, 0.0015374956, -0.00019102466, -0.00041628841, 0.0019997358, 0.0018624921]
1: [-0.0017131848, -0.0017694887, 0.0038728307, -0.0011573619, -0.001508927, 0.00055965217, 0.0027727291, -0.0001537947, -0.00061144569, -0.00027270502, -0.00077621639, -0.0018652329, -9.9153061e-05, -0.002982822, 0.00042818589, 0.00075839396, 0.0037067428, 4.4658614e-07, 0.00076624664, 0.00089692342, -0.00014199778, 0.00091094914, -0.0019691263, -0.00047743542, -0.0023056534, 0.00037580857, 0.00031019296, 0.0019658417, 0.0015228369, 0.00041580017, 0.00013475995, 0.0010534819, -0.0010911704, 0.0003334959, -0.0027049223, 0.0013364753, 0.00016094309, -0.0002824782, -0.00096225593, 0.0025072019, -0.00040832502, 0.0028687126, -0.00045640478, 0.0010744483, 4.2792712e-05, -0.0015350333, -0.0034802575, 0.0021858169, -0.00078705518, -0.001551567, 0.0017340543, 0.0012833631, -0.00094231707, 0.0010057853, -0.0036348205, 0.00053347909, 0.0027175115, -0.00019107746, 0.0026801985, -0.0005192922, 0.00057908299, -0.0011273101, -0.0026315066, -0.0018757157, 0.0020011256, -0.00055518252, -0.00087493024, 0.0028906113, 0.0006187135, -0.00099275506, -0.0010975956, -0.00032995982, 0.0010934341, 0.0017741012, 0.00026472489, 0.00025876681, 0.0011092479, -0.00022165776, -0.00033365621, -0.00044268343, 0.001081433, -0.00026227822, -0.00081609882, -0.0029998657, -0.0010666655, -0.00013648646, 0.00015024703, 0.00090111612, 0.00013877143, -0.00086062384, 0.0020823823, -0.0005365744, -0.0013149502, 0.00044973768, 0.00083720469, -0.00015207315, -5.1880746e-05, 0.001652954, -0.0013588372, -0.003022237, 0.00086524105, 0.0010019534, 0.0017731227, -3.6405643e-05, -0.0017166324, 0.0026721063, -0.0011735077, -0.00072048139, 0.00032740657, 0.00048561511, 0.0017567994, -0.0013743151, 0.00090102694, 0.0011765239, 0.00048251209, -0.0023282918, 0.0017043905, -0.00025485293, -0.00069832691, -0.00069154584, 0.0010746883, 0.00082947873, -0.0003351222, -0.001322386, 0.0018752591, -0.00035994544, 2.0959653e-05, 0.00076145661, -6.0290142e-05, 0.00061863399, -0.00048026783, -0.0011446426, 0.0024944434, 0.0026537599, -0.00067963422, -0.0024214219, -0.0027384192, -0.00013052628, -0.00054970017, -7.778986e-05, 0.0023211818, 0.0029513522, 0.00062964443, -4.4173023e-05, -0.0019382994, -0.0017759009, 0.00143599, 0.0011939075, 0.00077985041, -0.00087809726, -0.0023401917, -0.00020799012, 0.0025424829, 0.0020968353, -0.0020334772, 0.001881829, -0.00044259927, 0.0024173013, -0.0023374304, 0.0013871035, -0.0023284138, -0.00060242362, 0.00013056258, -0.00079482776, -0.00054403808, -0.00039382861, 0.0018237618, 0.00063636556, 0.0015730833, -0.0015360246, -0.0010277134, 0.00059501949, -0.00040481714, -0.0010504216, -0.00044775088, 0.002655538]

ORB

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

int
main(int argc, char *argv[])
{
#if OPENCV_VERSION_CODE<OPENCV_VERSION(2,3,0)
  std::cout << "cannot support this opencv version:" 
            <<  CV_VERSION << std::endl;
  return -1;
#else

  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;
  cv::Mat descriptors;

  //
  // n_features=300, params=default
  cv::OrbFeatureDetector detector(300);
  detector.detect(gray_img, keypoints);
  // ORB に基づくディスクリプタ抽出器
  cv::OrbDescriptorExtractor extractor;
  cv::Scalar color(200,250,255);
  extractor.compute(gray_img, keypoints, descriptors);

  // 32次元の特徴量 x keypoint数
  for(int i=0; i<descriptors.rows; ++i) {
    cv::Mat d(descriptors, cv::Rect(0,i,descriptors.cols,1));
    std::cout << i << ": " << d << std::endl;
  }
#endif
}

実行結果(最初の2行):

0: [180, 137, 181, 234, 55, 126, 183, 253, 99, 155, 106, 4, 14, 190, 91, 118, 178, 110, 239, 152, 201, 31, 246, 132, 242, 214, 175, 35, 227, 55, 43, 159]
1: [252, 215, 78, 130, 207, 217, 231, 175, 222, 53, 46, 119, 80, 17, 107, 45, 124, 21, 99, 62, 107, 31, 52, 171, 124, 26, 160, 103, 230, 97, 83, 163]

Brief

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

#if OPENCV_VERSION_CODE>=OPENCV_VERSION(2,4,0)
#include <opencv2/nonfree/features2d.hpp>
#endif

int
main(int argc, char *argv[])
{
  cv::Mat img = cv::imread("../../image/lenna.png", 1);
  if(img.empty()) return -1; 

  cv::Mat gray_img;
  cv::cvtColor(img, gray_img, CV_BGR2GRAY);
  cv::normalize(gray_img, gray_img, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints;
  std::vector<cv::KeyPoint>::iterator itk;
  cv::Mat descriptors;

  //
  // threshold=0.05, edgeThreshold=10.0
  cv::SiftFeatureDetector detector(0.05,10.0);
  detector.detect(gray_img, keypoints);
  // Brief に基づくディスクリプタ抽出器
  cv::BriefDescriptorExtractor extractor;
  cv::Scalar color(50,50,155);
  extractor.compute(gray_img, keypoints, descriptors);

  // 32次元の特徴量 x keypoint数
  for(int i=0; i<descriptors.rows; ++i) {
    cv::Mat d(descriptors, cv::Rect(0,i,descriptors.cols,1));
    std::cout << i << ": " << d << std::endl;
  }
}

実行結果(最初の2行):

0: [72, 198, 7, 190, 7, 54, 94, 100, 25, 243, 31, 128, 247, 240, 129, 40, 94, 130, 80, 30, 102, 47, 128, 45, 13, 43, 174, 59, 44, 56, 162, 215]
1: [25, 198, 32, 84, 33, 59, 42, 84, 31, 10, 218, 40, 112, 137, 177, 11, 166, 250, 90, 189, 136, 155, 223, 33, 249, 93, 37, 179, 168, 61, 44, 93]

局所特徴量の集合同士を比較する

ある画像から局所特徴量の集合が計算されると,それらを複数の画像間で比較することができます. matcher( cv::DescriptorMatcher または cv::GenericDescriptorMatcher を継承した様々なクラス)を変えるだけで,比較手法を変更することができます.

FlannBasedMatcher

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

#if OPENCV_VERSION_CODE>=OPENCV_VERSION(2,4,0)
#include <opencv2/nonfree/features2d.hpp>
#endif

int
main(int argc, char *argv[])
{
  cv::Mat img1 = cv::imread("../../image/lenna.png", 1);
  if(img1.empty()) return -1;
  cv::Mat img2 = cv::imread("../../image/lenna_rotated.png", 1);
  if(img2.empty()) return -1; 

  cv::Mat gray_img1, gray_img2;
  cv::cvtColor(img1, gray_img1, CV_BGR2GRAY);
  cv::cvtColor(img2, gray_img2, CV_BGR2GRAY);
  cv::normalize(gray_img1, gray_img1, 0, 255, cv::NORM_MINMAX);
  cv::normalize(gray_img2, gray_img2, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints1, keypoints2;
  std::vector<cv::KeyPoint>::iterator itk;
  cv::Mat descriptors1, descriptors2;

  // SURF
  // threshold=2000
  cv::SurfFeatureDetector detector(2000);
  detector.detect(gray_img1, keypoints1);
  detector.detect(gray_img2, keypoints2);
  // SURF に基づくディスクリプタ抽出器
  cv::SurfDescriptorExtractor extractor;
  cv::Scalar color(100,255,50);
  extractor.compute(gray_img1, keypoints1, descriptors1);
  extractor.compute(gray_img2, keypoints2, descriptors2);

  // FlannBasedMatcher によるマッチ
  cv::FlannBasedMatcher matcher;
  std::vector<cv::DMatch> matches;
  matcher.match(descriptors1, descriptors2, matches);

  // マッチング結果の描画
  cv::Mat dst_img;
  cv::drawMatches(img1, keypoints1, img2, keypoints2, matches, dst_img);

  cv::namedWindow("Match", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Match", dst_img);
  cv::waitKey(0);
}

入力画像(入力画像1,入力画像2):

_images/lenna.png _images/lenna_rotated.png

実行結果:

_images/lenna_DescriptorMatcher_flannBased.png

FlannBasedMatcher+クロスチェック

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

#if OPENCV_VERSION_CODE>=OPENCV_VERSION(2,4,0)
#include <opencv2/nonfree/features2d.hpp>
#endif

int
main(int argc, char *argv[])
{
  cv::Mat img1 = cv::imread("../../image/lenna.png", 1);
  if(img1.empty()) return -1; 
  cv::Mat img2 = cv::imread("../../image/lenna_rotated.png", 1);
  if(img2.empty()) return -1; 

  cv::Mat gray_img1, gray_img2;
  cv::cvtColor(img1, gray_img1, CV_BGR2GRAY);
  cv::cvtColor(img2, gray_img2, CV_BGR2GRAY);
  cv::normalize(gray_img1, gray_img1, 0, 255, cv::NORM_MINMAX);
  cv::normalize(gray_img2, gray_img2, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints1, keypoints2;
  std::vector<cv::KeyPoint>::iterator itk;
  cv::Mat descriptors1, descriptors2;

  // SURF
  // threshold=2000
  cv::SurfFeatureDetector detector(2000);
  detector.detect(gray_img1, keypoints1);
  detector.detect(gray_img2, keypoints2);
  // SURF に基づくディスクリプタ抽出器
  cv::SurfDescriptorExtractor extractor;
  cv::Scalar color(100,255,50);
  extractor.compute(gray_img1, keypoints1, descriptors1);
  extractor.compute(gray_img2, keypoints2, descriptors2);

  // FlannBasedMatcher によるKNNマッチを利用したクロスチェック
  cv::FlannBasedMatcher matcher;
  std::vector<cv::DMatch> matches;  
  std::vector<std::vector<cv::DMatch> > matches12, matches21;
  int knn = 1;
  matcher.knnMatch(descriptors1, descriptors2, matches12, knn);
  matcher.knnMatch(descriptors2, descriptors1, matches21, knn);
  // KNN探索で,1->2と2->1が一致するものだけがマッチしたとみなされる
  for( size_t m = 0; m < matches12.size(); m++ ) {
    bool findCrossCheck = false;
    for( size_t fk = 0; fk < matches12[m].size(); fk++ ) {
      cv::DMatch forward = matches12[m][fk]; 
      for( size_t bk = 0; bk < matches21[forward.trainIdx].size(); bk++ ) {
	cv::DMatch backward = matches21[forward.trainIdx][bk];
	if( backward.trainIdx == forward.queryIdx ) {
	  matches.push_back(forward);
	  findCrossCheck = true;
	  break;
	}
      }
      if( findCrossCheck ) break;
    }
  }

  // マッチング結果の描画
  cv::Mat dst_img;
  cv::drawMatches(img1, keypoints1, img2, keypoints2, matches, dst_img);

  cv::namedWindow("Match", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Match", dst_img);
  cv::waitKey(0);
}

入力画像(入力画像1,入力画像2):

_images/lenna.png _images/lenna_rotated.png

実行結果:

_images/lenna_DescriptorMatcher_flannBased_crossCheck.png

BruteForceMatcher

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

#if OPENCV_VERSION_CODE>=OPENCV_VERSION(2,4,0)
#include <opencv2/nonfree/features2d.hpp>
#include <opencv2/legacy/legacy.hpp>
#endif

int
main(int argc, char *argv[])
{
  cv::Mat img1 = cv::imread("../../image/lenna.png", 1);
  if(img1.empty()) return -1; 
  cv::Mat img2 = cv::imread("../../image/lenna_rotated.png", 1);
  if(img2.empty()) return -1; 

  cv::Mat gray_img1, gray_img2;
  cv::cvtColor(img1, gray_img1, CV_BGR2GRAY);
  cv::cvtColor(img2, gray_img2, CV_BGR2GRAY);
  cv::normalize(gray_img1, gray_img1, 0, 255, cv::NORM_MINMAX);
  cv::normalize(gray_img2, gray_img2, 0, 255, cv::NORM_MINMAX);

  std::vector<cv::KeyPoint> keypoints1, keypoints2;
  std::vector<cv::KeyPoint>::iterator itk;
  cv::Mat descriptors1, descriptors2;

  // SURF
  // threshold=2000
  cv::SurfFeatureDetector detector(2000);
  detector.detect(gray_img1, keypoints1);
  detector.detect(gray_img2, keypoints2);
  // SURF に基づくディスクリプタ抽出器
  cv::SurfDescriptorExtractor extractor;
  cv::Scalar color(100,255,50);
  extractor.compute(gray_img1, keypoints1, descriptors1);
  extractor.compute(gray_img2, keypoints2, descriptors2);

  // BruteForceMatcher によるマッチ
  // 比較にL2距離を指定
  // 他には L1<class T>, Hamming, HammingLUT など
  cv::BruteForceMatcher<cv::L2<float> > matcher;
  std::vector<cv::DMatch> matches;
  matcher.match(descriptors1, descriptors2, matches);

  // マッチング結果の描画
  cv::Mat dst_img;
  cv::drawMatches(img1, keypoints1, img2, keypoints2, matches, dst_img);

  cv::namedWindow("Match", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Match", dst_img);
  cv::waitKey(0);
}

入力画像(入力画像1,入力画像2):

_images/lenna.png _images/lenna_rotated.png

実行結果:

_images/lenna_DescriptorMatcher_bruteForce.png

画像のチャンネルの合成と分離

画像のチャンネル(R,G,Bなど)の合成と分離も,行列の場合と同様です. cv::Mat のチャンネルの合成と分離 を参照してください.

画像に境界領域を追加する

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("../../image/lenna.png", 1);
  if(src_img.empty()) return -1;

  // 画像境界の値を複製してコピー aaaaaa|abcdefgh|hhhhhhh
  cv::Mat repBD_img;
  copyMakeBorder(src_img, repBD_img, 20,20,20,20, cv::BORDER_REPLICATE);
  // 画像境界で反射するようにコピー fedcba|abcdefgh|hgfedcb
  cv::Mat refBD_img;
  copyMakeBorder(src_img, refBD_img, 20,20,20,20, cv::BORDER_REFLECT);
  // 画像境界で反射するようにコピー(境界を共有) gfedcb|abcdefgh|gfedcba
  cv::Mat ref101BD_img;
  copyMakeBorder(src_img, ref101BD_img, 20,20,20,20, cv::BORDER_REFLECT_101);
  // 画像自身を繰り返しコピー cdefgh|abcdefgh|abcdefg
  cv::Mat wrapBD_img;
  copyMakeBorder(src_img, wrapBD_img, 20,20,20,20, cv::BORDER_WRAP);
  // 固定値をコピーして埋める
  cv::Mat constBD_img;
  copyMakeBorder(src_img, constBD_img, 20,20,20,20, cv::BORDER_CONSTANT, cv::Scalar(200,100,0));
  
  cv::namedWindow("replicate border", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("replicate border", repBD_img);
  cv::namedWindow("reflect border", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("reflect border", refBD_img);
  cv::namedWindow("reflect101 border", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("reflect101 border", ref101BD_img);
  cv::namedWindow("wrap border", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("wrap border", wrapBD_img);
  cv::namedWindow("constant border", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("constant border", constBD_img);
  cv::waitKey(0);
}

入力画像(入力画像1,入力画像2):

_images/lenna.png

実行結果:

_images/lenna_border_rep.png _images/lenna_border_ref.png _images/lenna_border_ref101.png _images/lenna_border_wrap.png _images/lenna_border_const.png

輪郭の検出

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  const char *imagename = argc > 1 ? argv[1] : "../../image/star_objects.png";
  const int contour_index = argc > 2 ? atoi(argv[2]) : 1;
  const int max_level = argc > 3 ? atoi(argv[3]) : 0;
  cv::Mat src_img = cv::imread(imagename, 1);
  if(src_img.empty()) return -1; 
 
  // 2値化
  cv::Mat gray_img, bin_img;
  cv::cvtColor(src_img, gray_img, CV_BGR2GRAY);
  cv::threshold(gray_img, bin_img, 0, 255, cv::THRESH_BINARY|cv::THRESH_OTSU);
  
  /// 輪郭の検出
  std::vector<std::vector<cv::Point> > contours;
  std::vector<cv::Vec4i> hierarchy;
  // 2値画像,輪郭(出力),階層構造(出力),輪郭抽出モード,輪郭の近似手法
  cv::findContours(bin_img, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

  std::cout << "contour index = " << contour_index << std::endl;
  std::cout << "max level = " << max_level << std::endl;
  std::cout << "num of contours = " << contours.size() << std::endl;

  /// 輪郭の描画
  // 画像,輪郭,描画輪郭指定インデックス,色,太さ,種類,階層構造,描画輪郭の最大レベル
  cv::drawContours(src_img, contours, contour_index, cv::Scalar(0, 0, 200), 3, CV_AA, hierarchy, max_level);
  
  cv::namedWindow("Contours", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Contours", src_img);
  cv::waitKey(0);
}

入力画像:

_images/star_objects.png

実行結果(index=0, max_level=0):

./sample_contours ../../image/star_objects.png 0 0
contour index = 0
max level = 0
num of contours = 8
_images/sample_contours_0_0.png

実行結果(index=2, max_level=0):

./sample_contours ../../image/star_objects.png 2 0
contour index = 2
max level = 0
num of contours = 8
_images/sample_contours_2_0.png

実行結果(index=2, max_level=1):

./sample_contours ../../image/star_objects.png 2 1
contour index = 2
max level = 1
num of contours = 8
_images/sample_contours_2_1.png

実行結果(index=-1, max_level=1):

./sample_contours ../../image/star_objects.png -1 1
contour index = -1
max level = 1
num of contours = 8
_images/sample_contours_-1_1.png

オプティカルフローを計算する

OpenCVのC++インタフェースには,オプティカルフローを計算する二種類のメソッドが用意されています (CやPythonインタフェースからは,以前の cvCalcPoticalFlowBM や cvCalcPoticalFlowHS, cvCalcPoticalFlowLK なども利用できます).

PyrLK

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/video/tracking.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

int
main(int argc, char *argv[])
{
  // image1
  cv::Mat img1 = cv::imread("../../image/blocks1.png", 1);
  if(img1.empty()) return -1; 
  // image2
  cv::Mat img2 = cv::imread("../../image/blocks2.png", 1);
  if(img2.empty()) return -1; 

  cv::Mat prev, next;
  cv::cvtColor(img1, prev, CV_BGR2GRAY);
  cv::cvtColor(img2, next, CV_BGR2GRAY);

  std::vector<cv::Point2f> prev_pts;
  std::vector<cv::Point2f> next_pts;
  
  // 初期化
  cv::Size flowSize(30,30);
  cv::Point2f center = cv::Point(prev.cols/2., prev.rows/2.);
  for(int i=0; i<flowSize.width; ++i) {
    for(int j=0; j<flowSize.width; ++j) {
      cv::Point2f p(i*float(prev.cols)/(flowSize.width-1), 
                    j*float(prev.rows)/(flowSize.height-1));
      prev_pts.push_back((p-center)*0.9f+center);
    }
  }

  // Lucas-Kanadeメソッド+画像ピラミッドに基づくオプティカルフロー
  // parameters=default
#if OPENCV_VERSION_CODE > OPENCV_VERSION(2,3,0)
  cv::Mat status, error;
#else
  std::vector<uchar> status;
  std::vector<float> error;
#endif
  cv::calcOpticalFlowPyrLK(prev, next, prev_pts, next_pts, status, error);

  // オプティカルフローの表示
  std::vector<cv::Point2f>::const_iterator p = prev_pts.begin();
  std::vector<cv::Point2f>::const_iterator n = next_pts.begin();
  for(; n!=next_pts.end(); ++n,++p) {
    cv::line(img1, *p, *n, cv::Scalar(150,0,0),2);
  }
  
  cv::namedWindow("optical flow", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("optical flow", img1);
  cv::waitKey(0);
}

入力画像:

_images/blocks1.png _images/blocks2.png

実行結果:

_images/of_PyrLK.png

Farneback

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/video/tracking.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  // image1
  cv::Mat img1 = cv::imread("../../image/blocks1.png", 1);
  if(img1.empty()) return -1; 
  // image2
  cv::Mat img2 = cv::imread("../../image/blocks2.png", 1);
  if(img2.empty()) return -1; 

  cv::Mat prev, next;
  cv::cvtColor(img1, prev, CV_BGR2GRAY);
  cv::cvtColor(img2, next, CV_BGR2GRAY);

  std::vector<cv::Point2f> prev_pts;
  
  // 初期化
  cv::Size flowSize(30,30);
  cv::Point2f center = cv::Point(prev.cols/2., prev.rows/2.);
  for(int i=0; i<flowSize.width; ++i) {
    for(int j=0; j<flowSize.width; ++j) {
      cv::Point2f p(i*float(prev.cols)/(flowSize.width-1), 
                    j*float(prev.rows)/(flowSize.height-1));
      prev_pts.push_back((p-center)*0.9f+center);
    }
  }

  // Gunnar Farnebackのアルゴリズムに基づくオプティカルフロー
  // 画像ピラミッド作成のスケール(<1),ピラミッドの層数,
  // 平均化窓サイズ,各層での繰り返し数,
  // 各ピクセルの隣接領域サイズ.通常は 5 or 7,
  // ガウシアンの標準偏差,フラグ
  cv::Mat flow;
  std::vector<float> error;
  cv::calcOpticalFlowFarneback(prev, next, flow, 0.5, 3, 15, 3, 5, 1.1, 0);

  // オプティカルフローの表示
  std::vector<cv::Point2f>::const_iterator p = prev_pts.begin();
  for(; p!=prev_pts.end(); ++p) {
    const cv::Point2f& fxy = flow.at<cv::Point2f>(p->y, p->x);
    cv::line(img1, *p, *p+fxy, cv::Scalar(150,0,0),2);
  }
  
  cv::namedWindow("optical flow", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("optical flow", img1);
  cv::waitKey(0);
}

入力画像:

_images/blocks1.png _images/blocks2.png

実行結果:

_images/of_Farneback.png

アルファチャンネル付PNG画像の読み書き

アルファチャンネル付PNG画像を読み込む

OpenCV2.3.1で,アルファチャンネル付きPNG画像がサポートされました. ただし,現時点ではUndocumentedなので,正式なサポートか否かは不明です. アルファチャンネル付き画像を読み込む際に指定するフラグ(-1)は,highgui.hpp で

enum
{
    ...
    IMREAD_UNCHANGED  =-1,
    ...
}

と定義されている値です.

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

int
main(int argc, char *argv[])
{
#if OPENCV_VERSION_CODE<OPENCV_VERSION(2,3,1)
    std::cout << "cannot support this opencv version:" 
              <<  CV_VERSION << std::endl;
    return -1;
#else
  // -1 を指定してアルファチャンネルを読み込む
  cv::Mat img1 = cv::imread("../../image/cvlogo.png", -1);
  if(img1.empty()) return -1;
  cv::Mat img2 = cv::imread("../../image/lenna.png", 1);
  if(img2.empty()) return -1;

  // BGRAチャンネルに分離
  std::vector<cv::Mat> mv;
  cv::split(img1, mv);
  
  /// 合成処理
  std::vector<cv::Mat> tmp_a;
  cv::Mat alpha, alpha32f;
  // 4番目のチャンネル=アルファ
  alpha = mv[3].clone();
  mv[3].convertTo(alpha32f, CV_32FC1);
  cv::normalize(alpha32f, alpha32f, 0., 1., cv::NORM_MINMAX);
  for(int i=0; i<3; i++) tmp_a.push_back(alpha32f);
  cv::Mat alpha32fc3, beta32fc3;
  cv::merge(tmp_a, alpha32fc3);
  cv::Mat tmp_ones = cv::Mat::ones(cv::Size(img2.rows, img2.cols*3), CV_32FC1);
  beta32fc3 = tmp_ones.reshape(3,img2.rows) - alpha32fc3;
  cv::Mat img1_rgb, img1_32f, img2_32f;
  mv.resize(3);
  cv::merge(mv, img1_rgb);
  img1_rgb.convertTo(img1_32f, CV_32FC3);
  img2.convertTo(img2_32f, CV_32FC3);
  // 二つの画像の重み付き和
  cv::Mat blend32f = img1_32f.mul(alpha32fc3) + img2_32f.mul(beta32fc3);
  cv::Mat blend;
  blend32f.convertTo(blend, CV_8UC3);

  // 表示
  cv::namedWindow("Alpha channel", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::namedWindow("Blend", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("Alpha channel", alpha);
  cv::imshow("Blend", blend);
  cv::waitKey(0);
#endif
}

入力画像:

_images/cvlogo.png _images/lenna.png

実行結果(アルファチャンネル,合成画像):

_images/cvlogo_a.png _images/cvblend_lenna.png

アルファチャンネル付PNG画像を書き出す

目次

前のトピックへ

線形代数

次のトピックへ

描画処理

このページ