前々回の記事でOpenCVの魚眼レンズのキャリブレーション関数がうまく動作しなかったため,fisheye::calibrate()関数をちょこちょこ改造したものを貼っていましたが,あとで必要になりそうな気がするので,キャリブレーションプログラム全体のソースを置いておきます.

fisheye::calibrate()に加えた変更点は,以下のような感じです.
1. ヤコビアンの逆行列の計算をEigen::fullPivLUに変更
2. 最急降下法の学習率を外部から設定できるように変更

プログラム全体としては,チェスコーナーの検出後,一度,カメラ内部パラメータのみの推定を行った後,それを初期値として歪みパラメータを含めた推定を行うようにしています.

学習率(α)は特にセンシティブなので,何回か変更しながら実行して,いい具合に収束する値を使うといいと思います.手持ちのPCでは0.4~0.6あたりが誤差の収束が良かったです.うまくいくと,下左の画像のように再投影誤差(error)がぐいぐい下がっていきます.収束は画像の枚数にもかなり依存しているようなので,多すぎない適当な枚数(30~40くらい?)で試すと良さそうです.

More »

3年位前にSURF特徴量を使ってARっぽいことをやってたんですが,最近見返してみたら直したいところが結構あったので作り直してみました.
ついでに,アクセス解析を見るとsolvePnPRansacについて検索してきている人が多いようなので,知っている範囲でこの関数などについて書いておこうと思います(といってもほぼ本家の英リファレンスのままですが).
3年前の記事:SURFとSolvePnPRansacで拡張現実

今回はマーカの画像とカメラから得られた現フレームの画像のそれぞれのORB特徴を抽出した後,FlannBasedMatcherでそれらの対応付けを取り,solvePnPRansacでカメラの位置・姿勢を推定しています.

orb_flann_pnpransac

More »

ちょっとCamShiftを使った物体追跡プログラムを書いてみました.

CamShiftはMeanShiftTrackingを改良したもので,物体の大きさや姿勢の変化も追跡することができます.
触った感じだと,CamShiftの結果はRotatedRectで帰ってくるのに対して入力には通常のRectを入れなければならないので,若干扱いづらい部分がありした.RotatedRectに外接する矩形をそのまま次の初期探索領域にすると,回転によって領域が大きくなりすぎがちだったため,今回は外接する矩形を少し小さくしたものを次の初期探索領域としています.
あと,ヒストグラムにも割りと敏感なので注意してやる必要がありそうです.

ソース(VS2013, OpenCV2.4.8)

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

/********************************
 * 初期矩形指定用マウスコールバック
********************************/
void onmouse(int event, int x, int y, int flags, void* userdata) {
	static bool lbutton_is_down = false;
	cv::Rect* rect = (cv::Rect*)userdata;

	switch (event) {
	case CV_EVENT_LBUTTONDOWN:
		lbutton_is_down = true;
		rect->x = x;
		rect->y = y;
		break;
	case CV_EVENT_LBUTTONUP:
		lbutton_is_down = false;
		break;
	case CV_EVENT_MOUSEMOVE:
		if (lbutton_is_down) {
			rect->width = x - rect->x;
			rect->height = y - rect->y;
		}
		break;
	}
}

/********************************
* 初期矩形設定
********************************/
cv::Rect getInitRect(const cv::Mat& frame) {
	cv::Rect init_rect(0, 0, 100, 100);
	cv::namedWindow("frame");
	cv::setMouseCallback("frame", onmouse, &init_rect);

	while (cv::waitKey(100) != ' ') {
		cv::Mat canvas = frame.clone();
		cv::rectangle(canvas, init_rect, cv::Scalar(0, 255, 0), 2);
		cv::imshow("frame", canvas);
	}
	return init_rect;
}

/********************************
 * main
********************************/
int main(int argc, char** argv) {
	cv::VideoCapture cap("mov03.mov");
	if (!cap.isOpened()) {
		std::cerr << "failed to open input file" << std::endl;
		std::cin.ignore(1);
		return 0;
	}

	cv::Mat frame, hist;
	cap >> frame;
	cv::Rect rect = getInitRect(frame);	// 初期探索矩形

	// ターゲットのヒストグラム生成
	int hist_size[] = { 64, 64, 64 };
	float range[] = { 0, 256 };
	const float* ranges[] = { range, range, range };
	int channels[] = { 0, 1, 2 };
	cv::Mat roi(frame, rect);
	cv::calcHist(&roi, 1, channels, cv::Mat(), hist, 3, hist_size, ranges);

	// メインループ, escが押されるまで
	while (cv::waitKey(66) != 0x1b) {
		cap >> frame;
		if (!frame.data) {
			continue;
		}

		// ヒストグラムのバックプロジェクション計算
		cv::Mat back_proj;
		cv::calcBackProject(&frame, 1, channels, hist, back_proj, ranges);
		// camshift
		cv::TermCriteria term_crit(cv::TermCriteria::EPS | cv::TermCriteria::COUNT, 30, 1);
		cv::RotatedRect rotated_rect = cv::CamShift(back_proj, rect, term_crit);

		// RotatedRectから次の探索矩形,boundingboxを少し小さくしたもの
		rect = rotated_rect.boundingRect();
		rect.x += rect.width / 8;
		rect.y += rect.height / 8;
		rect.width *= (6.0 / 8.0);
		rect.height *= (6.0 / 8.0);

		// RotatedRectから頂点座標
		std::vector<cv::Point2f> pts(4);
		rotated_rect.points(&pts[0]);
		std::vector<cv::Point> ptsi(4);
		std::copy(pts.begin(), pts.end(), ptsi.begin());

		// 描画
		cv::rectangle(frame, rect, cv::Scalar(255, 0, 0), 2);
		cv::polylines(frame, ptsi, true, cv::Scalar(0, 255, 0), 2);
		cv::imshow("frame", frame);
	}
}

ちょっとやってみたい事があるので,その準備として画像中の矩形検出処理を書いてみました.処理手順は単純に

  1. 画像を2値化
  2. 輪郭線検出
  3. 輪郭線を近似して,四角形になっているか判定

のような感じです.
四角形かどうかの判定は4点かつ,面積が一定以上かどうかで判定しています.
追加で凸形になっているかを調べるともっといいかもしれません.

sq sq2
緑が検出された輪郭線.青がその中で矩形っぽいもの.

2014/03/20 ソースちょっと修正

/***************************
* 矩形検出
* @author : tetro
***************************/
#include <vector>
#include <sstream>
#include <iostream>
#include <opencv2/opencv.hpp>

/***************************
* main
***************************/
int main(){
	cv::VideoCapture cap(0);
	// esc を押すまで
	while (cv::waitKey(5) != 0x1b){
		cv::Mat frame;
		cap >> frame;
		// キャプチャできていなければ処理を飛ばす
		if (!frame.data){
			continue;
		}

		// 2値化
		cv::Mat grayImage, binImage;
		cv::cvtColor(frame, grayImage, CV_BGR2GRAY);
		cv::threshold(grayImage, binImage, 128.0, 255.0, CV_THRESH_OTSU);
		cv::imshow("bin", binImage);

		// 輪郭抽出
		std::vector< std::vector< cv::Point > > contours;
		cv::findContours(binImage, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
		// 検出された輪郭線の描画
		for (auto contour = contours.begin(); contour != contours.end(); contour++){
			cv::polylines(frame, *contour, true, cv::Scalar(0, 255, 0), 2);
		}

		// 輪郭が四角形かの判定
		for (auto contour = contours.begin(); contour != contours.end(); contour++){
			// 輪郭を直線近似
			std::vector< cv::Point > approx;
			cv::approxPolyDP(cv::Mat(*contour), approx, 50.0, true);
			// 近似が4線かつ面積が一定以上なら四角形
			double area = cv::contourArea(approx);
			if (approx.size() == 4 && area > 1000.0){
				cv::polylines(frame, approx, true, cv::Scalar(255, 0, 0), 2);
				std::stringstream sst;
				sst << "area : " << area;
				cv::putText(frame, sst.str(), approx[0], CV_FONT_HERSHEY_PLAIN, 1.0, cv::Scalar(0, 128, 0));
			}
		}
		cv::imshow("frame", frame);
	}
}