3 画像処理

3.1 画像を単色で塗りつぶす

#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

3.2 色空間を変換する

#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("./lenna.png", 1);
  if(!bgr_img.data) 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);
  // ... 何らかの処理  
}

3.3 画像サイズを変更する

#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("./lenna.png", 1);
  if(!src_img.data) 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

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

#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("./lenna.png", 1);
  if(!src_img.data) 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

3.5 画像をネガポジ反転する

#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("./lenna.png", 0);
  if(!src_img.data) 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

3.6 画像を2値化する

#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("./lenna.png", 0);
  if(!gray_img.data) 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

3.7 画像ピラミッドを作る

1 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("./lenna.png", 1);
  if(!src_img.data) 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

2 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] : "./opencv-logomini.png";  
  cv::Mat src_img = cv::imread(imagename, 1);
  if(!src_img.data) 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

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

1 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("./lenna.png", 1);
  if(!src_img.data) 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

2 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("./lenna.png", 1);
  if(!src_img.data) 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

3 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.data) 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 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_bilateral1.png _images/lenna_bilateral2.png

4 BoxFilter, Blur

#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("./lenna.png", 1);
  if(!src_img.data) 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

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

1 外接矩形を求める

#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

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

#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

3 外接円を求める

#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

4 凸包を求める

#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

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

#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("./lenna_inpaint.png", 1);
  if(!src_img.data) return -1; 
  cv::Mat mask_img = cv::imread("./inpaint_mask.png", 0);
  if(!mask_img.data) 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

3.11 直線を検出する

1 古典的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("./building.png", 1);
  if(!src_img.data) 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

2 確率的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("./building.png", 1);
  if(!src_img.data) 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

3.12 円を検出する

#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("./circles.png", 1);
  if(!src_img.data) 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

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

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

#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("./stuff.jpg", 1);
  if(!src_img.data) 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

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

#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("./lenna.png", 0);
  if(!src_img.data) 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

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

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

int
main(int argc, char *argv[])
{
  cv::Mat src_img = cv::imread("./lenna.png", 1);
  if(!src_img.data) 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

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

#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("./lenna.png", 1);
  if(!src_img.data) 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

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

#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("./lenna.png", 1);
  if(!src_img.data) return -1; 

  cv::Size patch_sie(100, 100);
  cv::Point2f center(250.0, 250.0);
  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

3.18 顔を検出する

#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] : "./lenna.png";
  cv::Mat img = cv::imread(imagename, 1);
  if(!img.data) 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;
  // マルチスケール(顔)探索
  // 画像,出力矩形,縮小スケール,最低矩形数,(フラグ),最小矩形
  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ファイル(上述のソースでコメントアウトされている箇所)を読み込むだけです.

実行結果:(LBP)

_images/tiffany_face_detect.png

3.19 目を検出する

#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] : "./lenna.png";
  cv::Mat img = cv::imread(imagename, 1);
  if(!img.data) 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

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

#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

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

#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("./lenna.png", 1);
  if(!src_img.data) 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