FFmpegをLGPLでビルドする

そういえばLGPLでビルドする方法を紹介すると昔の記事に書いていましたね。

すっかり忘れてました。


1. MinGWのインストール
 http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/
 からダウンロードしましょう。
download_mingw.jpg

 ダウンロードできたらインストールしましょう。
 mingw_install.jpg
C++ Compiler,MSYS Basic System,MinGW Developer Toolkitにチェックを入れるのを忘れずに。

2. NASMのインストール
 http://www.nasm.us/pub/nasm/releasebuilds/2.10.07/ からダウンロードしましょう。
download_nasm.jpg

Visual Studioのコマンドプロンプトを開いて
> C:\MinGW\msys\1.0\msys.bat
MinGWが開いたら
$ cd nasm-2.10.07
$ ./configure –prefix=/mingw
$ make
$ make install

3. FFmpegのインストール
 http://www.ffmpeg.org/download.html からダウンロードしましょう。
download_ffmpeg.jpg

Visual Studioのコマンドプロンプトを開いて
> C:\MinGW\msys\1.0\msys.bat
MinGWが開いたら
$ cd ffmpeg-1.2
$ ./configure --enable-shared --disable-gpl
$ make
$ make install
これで完了です。

ビルドしたファイルは「C:\MinGW\msys\1.0\local」に入っています。
スポンサーサイト



5点アルゴリズム使ってみた

OpenCVフリークの皆さん、こんにちは。

いつの間にか5点アルゴリズムが実装されたそうですよ!
3ヶ月ぐらい前らしいです全然知りませんでした。

基礎行列の計算方法はこれまで「cv::findFundamentalMat()で求めた基本行列からカメラ行列を掛ける」といったものでしたが(Mastering OpenCVにも書かれていますね)、

// 7点(RANSAC)アルゴリズムで基礎行列を計算
cv::Mat F = cv::findFundamentalMat(ptsA, ptsB, cv::FM_RANSAC, 0.1, 0.99);
cv::Mat_<double> K = cameraMatrix;
cv::Mat_<double> E = K.t() * F * K; //according to HZ (9.12)

キャリブレーション済みの画像を用いて、

// 5点アルゴリズムで基礎行列を計算
cv::Matx33d E = cv::findEssentialMat(ptsA, ptsB);
で出来るようになります。素敵!

ちょっとやってみましょう。

// --------------------------------------------------------------------------
// main(Number of arguments, Argument values)
// Description : This is the entry point of the program.
// Return value : SUCCESS:0 ERROR:-1
// --------------------------------------------------------------------------
int main(int argc, char **argv)
{
// キャリブレーションデータ
cv::Mat cameraMatrix, distCoeffs;
cv::FileStorage fs("camera.xml", CV_STORAGE_READ);
fs["intrinsic"] >> cameraMatrix;
fs["distortion"] >> distCoeffs;

// 読み込み
cv::Mat imgA, imgB;
cv::undistort(cv::imread("A.bmp"), imgA, cameraMatrix, distCoeffs);
cv::undistort(cv::imread("B.bmp"), imgB, cameraMatrix, distCoeffs);

// ORB
cv::OrbFeatureDetector detector;
cv::OrbDescriptorExtractor extractor;

// 特徴点検出
cv::Mat descriptorsA, descriptorsB;
std::vector<cv::KeyPoint> keypointsA, keypointsB;
detector.detect(imgA, keypointsA);
detector.detect(imgB, keypointsB);
extractor.compute(imgA, keypointsA, descriptorsA);
extractor.compute(imgB, keypointsB, descriptorsB);

// マッチング
std::vector<cv::DMatch> matches;
cv::BFMatcher matcher(cv::NORM_HAMMING, true);
matcher.match(descriptorsA, descriptorsB, matches);

// 最大・最小距離
double max_dist = 0; double min_dist = DBL_MAX;
for (int j = 0; j < (int)matches.size(); j++){
double dist = matches[j].distance;
if (dist < min_dist) min_dist = dist;
if (dist > max_dist) max_dist = dist;
}

// 良いペアのみ残す
double cutoff = 5.0 * (min_dist + 1.0);
std::set<int> existing_trainIdx;
std::vector<cv::DMatch> matches_good;
for (int j = 0; j < (int)matches.size(); j++) {
if (matches[j].trainIdx <= 0) matches[j].trainIdx = matches[j].imgIdx;
if (matches[j].distance > 0.0 && matches[j].distance < cutoff) {
if (existing_trainIdx.find(matches[j].trainIdx) == existing_trainIdx.end() && matches[j].trainIdx >= 0 && matches[j].trainIdx < (int)keypointsB.size()) {
matches_good.push_back(matches[j]);
existing_trainIdx.insert(matches[j].trainIdx);
}
}
}

// 表示
cv::Mat matchImage;
cv::drawMatches(imgA, keypointsA, imgB, keypointsB, matches_good, matchImage, cv::Scalar::all(-1), cv::Scalar::all(-1), std::vector<char>(), cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
cv::imshow("match", matchImage);

// 十分な対応点 (5ペア以上は必要)
if (matches_good.size() > 100) {
// ステレオペア
std::vector<cv::Point2f>ptsA, ptsB;
for (int j = 0; j < (int)matches_good.size(); j++) {
ptsA.push_back(keypointsA[matches_good[j].queryIdx].pt);
ptsB.push_back(keypointsB[matches_good[j].trainIdx].pt);
}

// 焦点距離とレンズ主点
double fovx, fovy, focal, pasp;
cv::Point2d pp;
cv::calibrationMatrixValues(cameraMatrix, cv::Size(imgA.cols, imgA.rows), 0.0, 0.0, fovx, fovy, focal, pp, pasp);

// 5点アルゴリズムで基礎行列を計算
cv::Matx33d E = cv::findEssentialMat(ptsA, ptsB, focal, pp);
std::cout << E << std::endl;
}

cv::waitKey(0);

return 0;
}


使い方これであってるのかな?まあいいか。

ORB使ってみた

特徴点の検出器といえば、SIFT(Scale-invariant feature transform)やSURF(Speed-Upped Robust Feature)が有名です。

どちらもスケール回転不偏特徴量として優れていますが、CV Drone付属のOpenCVライブラリはNON_FREE無効でビルドしているため、これらが使えません。

ORB(Oriented-BRIEF)はOpenCV2.3から実装されたフリーの特徴検出器・記述子で、SIFTやSURFのようにスケール・回転不偏でありながら、SURFの10倍、SIFTの100倍 高速と言われています。リアルタイムで動かすにはもってこいですね。

SIFT
sift_good_match.jpg
detection: 0.417645 s
description: 0.397645 s

SURF (hessianThreshold = 2000)
surf_good_match.jpg
detection: 0.119403 s
description: 0.106793 s

ORB
orb_good_match.jpg
detection: 0.0145967 s
description: 0.0151162 s

元画像(OpenCV.jpより) lenna.png lenna_rotated.png


SURFの結果があまりよろしくない。なんででしょう?

おまけ (今回のプログラム)

続きを読む

AR.Droneで物体追跡(パーティクルフィルタ編)

今回はパーティクルフィルタ(ConDensation)を使ったトラッキングを紹介します。

パーティクルフィルタは、モデルの状態を多数の粒子で近似する方法です。

ゴリ押しです。

OpenCV.jpのサンプルでは、尤度(ゆうど)関数がRGB色空間でのユークリッド距離でしたが、
任意色をとるには使いにくいので、本サンプルでは「指定した色の重心との距離」を尤度としています。


#include "ardrone/ardrone.h"
#include "opencv2/legacy/legacy.hpp"
#include "opencv2/legacy/compat.hpp"

#define KEY_DOWN(key) (GetAsyncKeyState(key) & 0x8000)
#define KEY_PUSH(key) (GetAsyncKeyState(key) & 0x0001)

// --------------------------------------------------------------------------
// main(引数の数、引数リスト)
// メイン関数です
// 戻り値 正常終了:0 エラー:-1
// --------------------------------------------------------------------------
int main(int argc, char **argv)
{
// AR.Droneクラス
ARDrone ardrone;

// 初期化
if (!ardrone.open()) {
printf("Failed to initialize.\n");
return -1;
}

// パーティクルフィルタ
CvConDensation *con = cvCreateConDensation(4, 0, 3000);

// フィルタの設定
CvMat *lowerBound = cvCreateMat(4, 1, CV_32FC1);
CvMat *upperBound = cvCreateMat(4, 1, CV_32FC1);
cvmSet(lowerBound, 0, 0, 0);
cvmSet(lowerBound, 1, 0, 0);
cvmSet(lowerBound, 2, 0, -10);
cvmSet(lowerBound, 3, 0, -10);
cvmSet(upperBound, 0, 0, ardrone.getImage()->width);
cvmSet(upperBound, 1, 0, ardrone.getImage()->height);
cvmSet(upperBound, 2, 0, 10);
cvmSet(upperBound, 3, 0, 10);

// 初期化
cvConDensInitSampleSet(con, lowerBound, upperBound);

// 等速直線運動モデル
con->DynamMatr[0] = 1.0; con->DynamMatr[1] = 0.0; con->DynamMatr[2] = 1.0; con->DynamMatr[3] = 0.0;
con->DynamMatr[4] = 0.0; con->DynamMatr[5] = 1.0; con->DynamMatr[6] = 0.0; con->DynamMatr[7] = 1.0;
con->DynamMatr[8] = 0.0; con->DynamMatr[9] = 0.0; con->DynamMatr[10] = 1.0; con->DynamMatr[11] = 0.0;
con->DynamMatr[12] = 0.0; con->DynamMatr[13] = 0.0; con->DynamMatr[14] = 0.0; con->DynamMatr[15] = 1.0;

// ノイズパラメータの設定
cvRandInit(&(con->RandS[0]), -25, 25, (int)cvGetTickCount());
cvRandInit(&(con->RandS[1]), -25, 25, (int)cvGetTickCount());
cvRandInit(&(con->RandS[2]), -5, 5, (int)cvGetTickCount());
cvRandInit(&(con->RandS[3]), -5, 5, (int)cvGetTickCount());

// 閾値
int minH = 0, maxH = 255;
int minS = 0, maxS = 255;
int minV = 0, maxV = 255;

// ウィンドウ
cvNamedWindow("binalized");
cvCreateTrackbar("H max", "binalized", &maxH, 255);
cvCreateTrackbar("H min", "binalized", &minH, 255);
cvCreateTrackbar("S max", "binalized", &maxS, 255);
cvCreateTrackbar("S min", "binalized", &minS, 255);
cvCreateTrackbar("V max", "binalized", &maxV, 255);
cvCreateTrackbar("V min", "binalized", &minV, 255);
cvResizeWindow("binalized", 0, 0);

// メインループ
while (!GetAsyncKeyState(VK_ESCAPE)) {
// 更新
if (!ardrone.update()) break;

// 画像を取得
IplImage *image = ardrone.getImage();

// HSVに変換
IplImage *hsv = cvCloneImage(image);
cvCvtColor(image, hsv, CV_RGB2HSV_FULL);

// 2値化画像
IplImage *binalized = cvCreateImage(cvGetSize(image), IPL_DEPTH_8U, 1);

// 2値化
CvScalar lower = cvScalar(minH, minS, minV);
CvScalar upper = cvScalar(maxH, maxS, maxV);
cvInRangeS(hsv, lower, upper, binalized);

// 結果を表示
cvShowImage("binalized", binalized);

// ノイズの除去
cvMorphologyEx(binalized, binalized, NULL, NULL, CV_MOP_CLOSE);

// 輪郭検出
CvSeq *contour = NULL, *maxContour = NULL;
CvMemStorage *contourStorage = cvCreateMemStorage();
cvFindContours(binalized, contourStorage, &contour, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

// 一番大きな輪郭を抽出
double max_area = 0.0;
while (contour) {
double area = fabs(cvContourArea(contour));
if ( area > max_area) {
maxContour = contour;
max_area = area;
}
contour = contour->h_next;
}

// 検出できた
if (maxContour) {
// 輪郭の描画
cvZero(binalized);
cvDrawContours(binalized, maxContour, cvScalarAll(255), cvScalarAll(255), 0, CV_FILLED);

// 重心を求める
CvMoments moments;
cvMoments(binalized, &moments, 1);
int my = (int)(moments.m01/moments.m00);
int mx = (int)(moments.m10/moments.m00);
cvCircle(image, cvPoint(mx, my), 10, CV_RGB(255,0,0));

// 各パーティクルの尤度(ゆうど)を求める
for (int i = 0; i < con->SamplesNum; i++) {
// サンプル
float x = (con->flSamples[i][0]);
float y = (con->flSamples[i][1]);

// サンプルが画像範囲内にある
if (x > 0 && x < image->width && y > 0 && y < image->height) {
// ガウス分布とみなす
double sigma = 50.0;
double dist = hypot(x - mx, y - my); // 重心に近いほど尤度が高い
con->flConfidence[i] = 1.0 / (sqrt (2.0 * CV_PI) * sigma) * expf (-dist*dist / (2.0 * sigma*sigma));
}
else con->flConfidence[i] = 0.0;
cvCircle(image, cvPointFrom32f(cvPoint2D32f(x, y)), 3, CV_RGB(0,128,con->flConfidence[i] * 50000));
}
}

// 更新
cvConDensUpdateByTime(con);

// 重み付き平均用
double sumX = 0, sumY = 0, sumConf = 0;
for (int i = 0; i < con->SamplesNum; i++) {
sumX += con->flConfidence[i] * con->flSamples[i][0];
sumY += con->flConfidence[i] * con->flSamples[i][1];
sumConf += con->flConfidence[i];
}

// 推定値を計算
if (sumConf > 0.0) {
float x = sumX / sumConf;
float y = sumY / sumConf;
cvCircle(image, cvPointFrom32f(cvPoint2D32f(x, y)), 10, CV_RGB(0,255,0));
}

// 表示
cvShowImage("camera", image);
cvWaitKey(1);

// メモリ解放
cvReleaseImage(&hsv);
cvReleaseImage(&binalized);
cvReleaseMemStorage(&contourStorage);
}

// メモリ解放
cvReleaseMat(&lowerBound);
cvReleaseMat(&upperBound);
cvReleaseConDensation(&con);

// さようなら
ardrone.close();

return 0;
}
結果はこの通りです。
condens_tracking.jpg

尤度が高いとパーティクルが青くなり低いとコジマ粒子になります。

AR.Droneで物体追跡(カルマンフィルタ編)

カルマンフィルタは、ノイズの乗った観測値から真値を推定する際によく使われるものです。

OpenCVにはCvKalman、cv::KalmanFilterとして実装されています。

OpenCVに同梱されているサンプルを動かすのもいいと思います。
ちょっとゴチャゴチャしていますが。

前回のサンプルにカルマンフィルタを入れたので、これを使って実際にどう使うか解説していきます。

1. カルマンフィルタの初期化
 状態をこのように定義します。xyは画面上のマーカーの位置です。
kalman_state_mat.jpg

観測されるのは位置なので、観測行列は
kalman_measurement_mat.jpg
となります。

cvCreateKalmanに状態ベクトルの次元数(=4)と観測ベクトルの次元数(=2)を入れて初期化します。

// 初期化
CvKalman *kalman = cvCreateKalman(4, 2);
cvSetIdentity(kalman->measurement_matrix, cvRealScalar(1.0));
cvSetIdentity(kalman->process_noise_cov, cvRealScalar(1e-5));
cvSetIdentity(kalman->measurement_noise_cov, cvRealScalar(0.1));
cvSetIdentity(kalman->error_cov_post, cvRealScalar(1.0));
measurement_noise_covは応答が遅いな、と思ったら小さくしましょう。
他はテキトーでOKです。

2. 運動モデルの定義
 大事な部分です。今回は等速直線運動なのでこうなります。
 ※OpenCVのサンプルから持ってきただけなのでΔtが1になっています。
kalman_transition_mat.jpg

// 等速直線運動モデル
kalman->DynamMatr[0] = 1.0; kalman->DynamMatr[1] = 0.0; kalman->DynamMatr[2] = 1.0; kalman->DynamMatr[3] = 0.0;
kalman->DynamMatr[4] = 0.0; kalman->DynamMatr[5] = 1.0; kalman->DynamMatr[6] = 0.0; kalman->DynamMatr[7] = 1.0;
kalman->DynamMatr[8] = 0.0; kalman->DynamMatr[9] = 0.0; kalman->DynamMatr[10] = 1.0; kalman->DynamMatr[11] = 0.0;
kalman->DynamMatr[12] = 0.0; kalman->DynamMatr[13] = 0.0; kalman->DynamMatr[14] = 0.0; kalman->DynamMatr[15] = 1.0;
3. 観測値の取得
 特に書くことはありません。

4. フィルタ修正(観測値が得られた場合のみ)
 cvKalmanCorrectを使って、現在のモデルを修正します。

// 観測値
float m[] = {mx, my};
CvMat measurement = cvMat(2, 1, CV_32FC1, m);

// 修正フェーズ
const CvMat *correction = cvKalmanCorrect(kalman, &measurement);

5. フィルタ更新
 cvKalmanPredictで次のモデル状態を推定します。
// 更新フェーズ
const CvMat *prediction = cvKalmanPredict(kalman);
毎ループ呼び出しておけば常に予測が働くので、追跡しやすくなると思います。


#include "ardrone/ardrone.h"

#define KEY_DOWN(key) (GetAsyncKeyState(key) & 0x8000)
#define KEY_PUSH(key) (GetAsyncKeyState(key) & 0x0001)

// --------------------------------------------------------------------------
// main(引数の数、引数リスト)
// メイン関数です
// 戻り値 正常終了:0 エラー:-1
// --------------------------------------------------------------------------
int main(int argc, char **argv)
{
// AR.Droneクラス
ARDrone ardrone;

// 初期化
if (!ardrone.open()) {
printf("初期化に失敗しました\n");
return -1;
}

// カルマンフィルタ
CvKalman *kalman = cvCreateKalman(4, 2);

// 共分散行列の設定
cvSetIdentity(kalman->measurement_matrix, cvRealScalar(1.0));
cvSetIdentity(kalman->process_noise_cov, cvRealScalar(1e-5));
cvSetIdentity(kalman->measurement_noise_cov, cvRealScalar(0.1));
cvSetIdentity(kalman->error_cov_post, cvRealScalar(1.0));

// 等速直線運動モデル
kalman->DynamMatr[0] = 1.0; kalman->DynamMatr[1] = 0.0; kalman->DynamMatr[2] = 1.0; kalman->DynamMatr[3] = 0.0;
kalman->DynamMatr[4] = 0.0; kalman->DynamMatr[5] = 1.0; kalman->DynamMatr[6] = 0.0; kalman->DynamMatr[7] = 1.0;
kalman->DynamMatr[8] = 0.0; kalman->DynamMatr[9] = 0.0; kalman->DynamMatr[10] = 1.0; kalman->DynamMatr[11] = 0.0;
kalman->DynamMatr[12] = 0.0; kalman->DynamMatr[13] = 0.0; kalman->DynamMatr[14] = 0.0; kalman->DynamMatr[15] = 1.0;

// 閾値
int minH = 0, maxH = 255;
int minS = 0, maxS = 255;
int minV = 0, maxV = 255;

// ウィンドウ
cvNamedWindow("binalized");
cvCreateTrackbar("H max", "binalized", &maxH, 255);
cvCreateTrackbar("H min", "binalized", &minH, 255);
cvCreateTrackbar("S max", "binalized", &maxS, 255);
cvCreateTrackbar("S min", "binalized", &minS, 255);
cvCreateTrackbar("V max", "binalized", &maxV, 255);
cvCreateTrackbar("V min", "binalized", &minV, 255);
cvResizeWindow("binalized", 0, 0);

// メインループ
while (!GetAsyncKeyState(VK_ESCAPE)) {
// 更新
if (!ardrone.update()) break;

// 画像を取得
IplImage *image = ardrone.getImage();

// HSVに変換
IplImage *hsv = cvCloneImage(image);
cvCvtColor(image, hsv, CV_RGB2HSV_FULL);

// 2値化画像
IplImage *binalized = cvCreateImage(cvGetSize(image), IPL_DEPTH_8U, 1);

// 2値化
CvScalar lower = cvScalar(minH, minS, minV);
CvScalar upper = cvScalar(maxH, maxS, maxV);
cvInRangeS(hsv, lower, upper, binalized);

// 結果を表示
cvShowImage("binalized", binalized);

// ノイズの除去
cvMorphologyEx(binalized, binalized, NULL, NULL, CV_MOP_CLOSE);

// 輪郭検出
CvSeq *contour = NULL, *maxContour = NULL;
CvMemStorage *contourStorage = cvCreateMemStorage();
cvFindContours(binalized, contourStorage, &contour, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

// 一番大きな輪郭を抽出
double max_area = 0.0;
while (contour) {
double area = fabs(cvContourArea(contour));
if ( area > max_area) {
maxContour = contour;
max_area = area;
}
contour = contour->h_next;
}

// 検出できた
if (maxContour) {
// 輪郭の描画
cvZero(binalized);
cvDrawContours(binalized, maxContour, cvScalarAll(255), cvScalarAll(255), 0, CV_FILLED);

// 重心を求める
CvMoments moments;
cvMoments(binalized, &moments, 1);
int my = (int)(moments.m01/moments.m00);
int mx = (int)(moments.m10/moments.m00);

// 観測値
float m[] = {mx, my};
CvMat measurement = cvMat(2, 1, CV_32FC1, m);

// 修正フェーズ
const CvMat *correction = cvKalmanCorrect(kalman, &measurement);
}

// 予測フェーズ
const CvMat *prediction = cvKalmanPredict(kalman);

// 表示
cvCircle(image, cvPointFrom32f(cvPoint2D32f(prediction->data.fl[0], prediction->data.fl[1])), 10, CV_RGB(0,255,0));
cvShowImage("camera", image);
cvWaitKey(1);

// メモリ解放
cvReleaseImage(&hsv);
cvReleaseImage(&binalized);
cvReleaseMemStorage(&contourStorage);
}

// メモリ解放
cvReleaseKalman(&kalman);

// さようなら
ardrone.close();

return 0;
}

今回は標準的なカルマンフィルタを用いた線形モデルの推定でしたが、cvKalmanは拡張カルマンフィルタもサポートしており、これによって非線形なモデルも扱うことができるそうです。

非線形モデルの推定といえばパーティクルフィルタ(ConDensataion)もありますね。
プロフィール

puku

Author:puku
暇な時はゲームかプログラミングしてる人だよ。
だいたい月1更新。
CV Drone はこちら(GitHub)

最近はQiitaでOnsenUI2で遊んでいる。

最新記事
最新コメント
最新トラックバック
検索フォーム
カレンダー
02 | 2013/03 | 04
- - - - - 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31 - - - - - -
月別アーカイブ
カテゴリ
スポンサードリンク
RSSリンクの表示
FC2カウンター
リンク
ブロとも申請フォーム

この人とブロともになる

アクセスランキング
[ジャンルランキング]
コンピュータ
607位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
120位
アクセスランキングを見る>>
FC2ブログランキング

FC2Blog Ranking

QRコード
QR