質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

1回答

3294閲覧

OpenCV 鳥瞰図の座標がわかりません

koomint

総合スコア3

OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2021/08/22 14:58

編集2021/08/23 07:41

OpenCVの鳥瞰図用のホモグラフィを計算するために渡す点の座標がわかりません。
参考にしたのは「詳解OpenCV3」という本のP672の例19-1のプログラムです。

##問題点
本に書いてあることをほぼそのまま写して実行しました。
元の画像は次の画像です。(解像度を少し下げました)
イメージ説明
鳥瞰図の結果は次のようになりました。
イメージ説明
少し見づらいですが、左上に元の画像の鳥瞰画像が出ています。
コードは次に示します。

C++

1#include <iostream> 2#include <fstream> 3#include <opencv2/opencv.hpp> 4#include <stdlib.h> 5 6const int WIDTH = 600; 7const int HEIGHT = 600; 8 9//入力は[board_w] = 6, [board_h] = 9, [intinsics.xml] = intrinsics.xml, [checker_image] = image.png 10void help(char *argv[]){ 11 std::cout << "Call: " /Users/shimizumiwa/coyomi/apps/bird/main.cpp<< argv[0] << "[board_w] [board_h] [intrinsics.xml] [checker_image]" << std::endl; 12 //std::cout << "Demonstrates Pyramid Lucas-Kanade optical flow." << std::endl; 13} 14 15//引数:[board_w], [board_h], [intrinsics.xml], [checker_image] 16//[board_w]:チェスボードの横のマス数 17//[board_h]:チェスボードの盾のマス数 18//[intrinsics.xml]:カメラ定数とかが入ってるファイル 19//[checker_image]:入力画像 20int main(int argc, char *argv[]){ 21 22 //argcはプログラムの引数の数 23 //./bird image.jpgで実行したとしたらargv[0] = bird,argv[1] = image.jpgでargcは2 24 if(argc != 5){ 25 std::cout << "\nERROR: too few parameters\n"; 26 //help(argv); 27 return -1; 28 } 29 30 //atoiは文字列を数値に変換 31 int board_w = atoi(argv[1]); 32 int board_h = atoi(argv[2]); 33 int board_n = board_w * board_h; 34 cv::Size board_sz = cv::Size2i(board_w, board_h); 35 //cv::Size board_sz(board_w, board_h); 36 //読み込みでファイルを開く 37 cv::FileStorage fs(argv[3], cv::FileStorage::READ); 38 cv::Mat intrinsic, distortion; 39 40 fs["camera_matrix"] >> intrinsic; 41 fs["distortion_coefficients"] >> distortion; 42 if(!fs.isOpened() || intrinsic.empty() || distortion.empty()){ 43 std::cout << "Error: Couldn't load intrinsic parameters from " 44 << argv[3] << std::endl; 45 return -1; 46 } 47 fs.release(); 48 49 cv::Mat gray_image, image, image0 = cv::imread(argv[4], 1); 50 if(image0.empty()){ 51 std::cout << "Error: Couldn't load image" << argv[4] << std::endl; 52 return -1; 53 } 54 55 //画像の歪みを補正する 56 cv::undistort(image0, image, intrinsic, distortion, intrinsic); 57 //入力画像、出力画像、グレーにする定数 58 cv::cvtColor(image0, gray_image, cv::COLOR_BGR2GRAY); 59 60 //その平面上のチェスボードを得る 61 std::vector<cv::Point2f> corners; 62 //チェスボードの内側コーナー位置を求める 63 bool found = cv::findChessboardCorners( 64 image, 65 //内側コーナーの個数 66 board_sz, 67 //コーナーの出力配列 68 corners/*, 69 //白黒画像に変換する際に固定閾値の代わりに適応的閾値を利用 70 //輪郭抽出の際の誤った四角を除外するための基準追加(あると逆に上手くいかない) 71 cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FILTER_QUADS*/); 72 if(!found){ 73 std::cout << "Couldn't acquire checkerboard on " << argv[4] 74 << ", only found " << corners.size() << " of " << board_n 75 << " corners\n"; 76 return -1; 77 } 78 //これらのコーナーをサブピクセルの精度で得る 79 std::vector<std::vector<cv::Point2f>> img_points; 80 cv::Mat src_gray = cv::Mat(image.size(), CV_8UC1); 81 cv::cvtColor(image, src_gray, cv::COLOR_BGR2GRAY); 82 cv::find4QuadCornerSubpix(src_gray, corners, cv::Size(3,3)); 83 cv::drawChessboardCorners(image, board_sz, corners, found); 84 img_points.emplace_back(corners); 85 86 87 //画像と物体の点を得る 88 //物体の点(r,c) 89 //(0, 0), (board_w-1, 0), (0, board_h-1), (board_w-1, board_h-1) 90 //これはコーナーがcorners[r*board_w + c]であることを意味するらしい 91 92 cv::Point2f objPts[4], imgPts[4]; 93 objPts[0].x = 0; objPts[0].y = 0; 94 objPts[1].x = board_w - 1; objPts[1].y = 0; 95 objPts[2].x = 0; objPts[2].y = board_h - 1; 96 objPts[3].x = board_w - 1; objPts[3].y = board_h - 1; 97 imgPts[0] = corners[0]; 98 imgPts[1] = corners[board_w - 1]; 99 imgPts[2] = corners[(board_h - 1) * board_w]; 100 imgPts[3] = corners[(board_h - 1) * board_w + board_w - 1]; 101 102 103 //点をB,G,R,YELLOWの順で描画する 104 cv::circle(image, imgPts[0], 9, cv::Scalar(255, 0, 0), 3); 105 cv::circle(image, imgPts[1], 9, cv::Scalar(0, 255, 0), 3); 106 cv::circle(image, imgPts[2], 9, cv::Scalar(0, 0, 255), 3); 107 cv::circle(image, imgPts[3], 9, cv::Scalar(0, 255, 255), 3); 108 109 //発見したチェスボードを描画する 110 cv::drawChessboardCorners(image, board_sz, corners, found); 111 cv::imshow("Checkers", image); 112 cv::imwrite("./apps/bird/Checkers.png", image); 113 114 //ホモグラフィを求める 115 116 cv::Mat H = cv::getPerspectiveTransform(objPts, imgPts); 117 118 //このビューの高さzをユーザーが調整できるようにする 119 double Z = 1; 120 cv::Mat birds_image = cv::Mat::zeros(HEIGHT + 100, WIDTH + 100, CV_8UC3); 121 //Escキーで止まる 122 for(;;){ 123 H.at<double>(2, 2) = Z; 124 //ホモグラフィを用いて再マッピングする 125 126 cv::warpPerspective( 127 image, 128 birds_image, 129 //変換配列 130 H, 131 //出力画像の配列 132 birds_image.size(), 133 cv::WARP_INVERSE_MAP | cv::INTER_LINEAR, 134 cv::BORDER_CONSTANT, 135 //境界を黒で埋める 136 cv::Scalar::all(0) 137 ); 138 139 cv::imshow("Birds_Eye", birds_image); 140 int key = cv::waitKey() & 255; 141 if(key == 'u') Z += 0.5; 142 if(key == 'd') Z -= 0.5; 143 if(key == 27) break; 144 } 145 146 cv::imwrite("./apps/bird/Birds_Eye.png", birds_image); 147 // cv::moveWindow("./apps/bird/Birds_Eye.png", 3000, 3000); 148 149 //回転と平行移動ベクトルを表示 150 std::vector<cv::Point2f> image_points; 151 std::vector<cv::Point3f> object_points; 152 for(int i = 0; i < 4; ++i){ 153 image_points.push_back(imgPts[i]); 154 object_points.push_back( 155 cv::Point3f(objPts[i].x, objPts[i].y, 0) 156 ); 157 } 158 159 cv::Mat rvec, tvec, rmat; 160 cv::solvePnP( 161 //オブジェクト座標系の3次元座標系 162 object_points, 163 //画像座標系の2次元座標点 164 image_points, 165 //カメラ行列 166 intrinsic, 167 //最初に歪みを補正したので歪み係数はゼロになる 168 cv::Mat(), 169 //回転ベクトルを出力 170 rvec, 171 //平行移動ベクトルを出力 172 tvec 173 ); 174 175 cv::Rodrigues(rvec, rmat); 176 177 //出力と終了 178 std::cout << "rotation matrix: " << rmat << std::endl; 179 std::cout << "translation vector: " << tvec << std::endl; 180 std::cout << "homography matrix: " << H << std::endl; 181 std::cout << "inverted homography matrix: " << H.inv() << std::endl; 182 183 return 1; 184 185} 186

自分のやりたいのは、この元の画像を回転せずにそのままの向きで鳥瞰図にすることです。
また、本に載っていた鳥瞰図取得の結果では、ウィンドウの下の真ん中あたりからから上に画像が広がっていくような鳥瞰図になっていました。

##特に知りたいこと
ホモグラフィを求めるための座標(objPts,imgPts)が間違っているのではないかと考えています。ですが、本のサンプルコードに書いてあるようにその座標がどうしてそう求められるのか理解していません。どのような考え方でこのobjPtsとimgPtsがこの求め方で出るのか教えていただきたいです。

##お陰様で出来ました
イメージ説明

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

cv::Mat H = cv::getPerspectiveTransform(objPts, imgPts);

これで何を求めているのかを把握していますか?

  • 引数の順序はこれで正しいですか?

(→後段で WARP_INVERSE_MAP を使っているから,これは正しい模様)

  • objPts と imgPts の4点の対応関係は正しいですか?

→像が回転する要因になっていないか?

  • objPts の値は実行時のコマンドライン引数由来であるようですが,一体どんな値を与えているのですか?

→コマンドライン引数では「何を」与える仕様としているのですか?
コマンドライン引数の使われ方を見るとちょっと謎です(コーナーの点の個数として使われているが,objPtsの座標値にも用いられている.これは正しいの?)


参考までに,最も単純なコードを示す.

  • 入力画像はサイズがでかかったので適当に縮小した
  • チェスボードパターンの四隅座標は,当該縮小した画像上で手作業(目視)で求めたものをハードコーディングしてある
  • warpPerspective で WARP_INVERSE_MAP を使ってない

C++

1int main() 2{ 3 //※"Board.png"は質問に貼られていた画像を縦横半分に縮小したものを使用した 4 cv::Mat SrcImg = cv::imread( "Board.png" ); 5 if( SrcImg.empty() )return 0; 6 7 //SrcImg 上のチェスボードパターンの四隅の座標 8 cv::Point2f ImgPts[4] = { 9 { 218, 267 }, { 385, 267 }, { 196, 375 }, { 406, 373 } 10 }; 11 12 //↑の4点の変換結果目標座標. 13 //320*240サイズの結果画像の四隅から少し(10[pixel])だけ内側. 14 cv::Mat ResultImg = cv::Mat::zeros( 240, 320, CV_8UC3 ); 15 cv::Point2f ObjPts[4] = { 16 { 10,10 }, { 310, 10, }, { 10, 230 }, { 310, 230 } 17 }; 18 19 cv::Mat H = cv::getPerspectiveTransform( ImgPts, ObjPts ); 20 cv::warpPerspective( 21 SrcImg, ResultImg, 22 H, ResultImg.size(), 23 cv::INTER_LINEAR, cv::BORDER_CONSTANT, 24 cv::Scalar::all(0) 25 ); 26 cv::imshow( "Result", ResultImg ); 27 cv::waitKey(); 28 return 0; 29}

結果:
イメージ説明

投稿2021/08/23 01:10

編集2021/08/23 01:30
fana

総合スコア11632

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fana

2021/08/23 01:41 編集

簡単なサンプルを示した. 質問コードでは,objPts の座標値に「パターン個数」の値をそのまま使っているから1桁の値になっており,結果として,変換結果画像でチェスボードの部分が数pixelな大きさになっている. この点は後段での「Z の値がどうの」で倍率調整する想定なのだと見えるが,その調整方法で大丈夫なのか? っていう. (Zを 0.01 とかそういう値にできる必要があると思うのだが,今の質問コードだとそれができないのでは. →Zの初期値を 100 とか 0.01 とか,いろいろ変えて何が起こるのかを見てみると良いのではないかと. ) また,変換結果の像の位置(オフセット)はまともに考えられていない様子に見える. (常にチェスパターンの4隅のうちのどこかが結果画像の(0,0)に来てしまうであろう)
fana

2021/08/23 01:50 編集

getPerspectiveTransform()でホモグラフィ行列を求めるとき imgPts は入力画像上での位置(pixel)であり, objPts はそれが出力画像上のどこに行くべきかという位置(pixel)である. (ホモグラフィ行列を求む,というそのものズバリな名前の findHomography() も存在する)
fana

2021/08/23 01:55

本のサンプルがどんなものかは知らないけど, ここに示したように,最もシンプルな状態(その関数を使ってみるのに必要最低限な要素しか無い状態)を最初はやってみると良いんじゃないかな. (何故か「サンプル」って 中途半端にアプリケーションっぽく なってたりするけど,そういうの邪魔だよね)
koomint

2021/08/23 05:25

ご回答ありがとうございます。 実は私も、objPtsでチェッカーボードのマスの数がそのまま用いられているのに疑問を持ちました。このobjPtsとimgPtsの座標の代入値は本に書いてあったものをそのまま写したので、自分もなぜこの計算方法で出すのかわかりません。間違えているのでは?と思いました。 Zの値については0.01とか小さい値になる程画像が左上に向かって小さくなって、0では全面真っ黒になることを確認しています。100のように大きくなるほど、画像が左上から右下まで拡大されていくようになります。 私が知りたいのは、なぜこのobjPtsとimgPtsの座標計算方法で本では上手くいってるのか、計算方法の理屈をもしご存知であればお伺いしたかったのです。 また、一つの画像に対してだけ鳥瞰図を得たいわけではなく、いろんな画像が入ってきたときに全てに対してプログラム一つで鳥瞰図をそれぞれ出したいので、objPtsやimgPtsの座標情報をcornersなどを使って固定値ではなくて変数として出したいです。
koomint

2021/08/23 05:40

一応、cornersなど難しい要素を入れないで座標の値を自分で入れてやったものはうまくいったので、やっぱり座標の求め方が問題なんだなというのは把握しました。
fana

2021/08/23 06:38 編集

objPts の座標として「コーナー点の個数-1」を用いるというのは, 要は,チェスボードパターンの1個の四角形のサイズを 1x1 とする世界を考えていることに相当します. (単位を「ボードの四角形の辺の長さ」という形での正規化を考えた世界,という感じ) おそらくそういう意図があるのでしょう. (その本に,そういう意味合いに関して何も説明が書いてないんですかね?) ↓ しかし,OpenCVの関数 warpPerspective は出力側の単位として[pixel]が用いられたものを期待しているので, そのような正規化が成された世界に関して計算された行列 H をそのまま使っても,座標の単位に齟齬があるため,既述したように,めちゃくちゃ小さな結果画像が得られるわけです. (「1x1」の「1」が,1[pixel]として扱われるから) で,結果画像のズーム具合を Z なる値(H[2][2]の値)で後から調整しているわけですね. (同次座標で計算した第三の要素の値を増減させることによって最終結果のスケールを変えている……のだと思うけど,もしもそういう意味なのであれば本来は H の第3行目の成分全てを同じ倍率でスケーリングするべきだと思う)
fana

2021/08/23 06:12

> いろんな画像が入ってきたときに全てに対してプログラム一つで鳥瞰図をそれぞれ出したい 結局のところ,あなたが「最終的な結果をどういう絵にしたいか?」に依存するでしょう. * チェスボードが左上らへんに写っている入力画像 * チェスボードが右下らへんに写っている入力画像 * … などが存在し得る中で,チェスボードのコーナー点を用いて演算を行えば,objPtsの側が同じであれば(その画像でのチェスボードのコーナー点の位置がいつも結果画像でobjPtsの場所にくるので)「結果画像内で,入力画像の絵が占める部分」が異なる形になるでしょう.
koomint

2021/08/23 07:47 編集

試しに、変換前の点の座標にはcornersを用いた本に書いてある通りの座標情報を、変換後の点の座標にはfanaさんのように適当に表示したい場所の座標を入れてみました。 また、fanaさんの送ってくださったコードを見て、勘違いしていたところ(cv::warpPerspectiveのcv::WARP_INVERSE_MAPがいらなかった)が気づけたのでそこも変えてみたところ、下のようにうまくいきました。 (すいません画像が載せられないので質問の一番最後のとこに載せます) 全部が表示されているわけではないのでそこは調整が必要ですが、重要なとこは解決しました。
koomint

2021/08/23 07:46

詳しく教えていただいてありがとうございました。 今回はZの変更についてはしないつもりではありましたが、あっても良いと思うので、スケーリングについてはこの後考えてみたいと思います。Hの三行目の成分を全て定数倍するなどはまだHの中身が理解しきれていないので調べていきたいと思っています。 本当にありがとうございました。助かりました。
fana

2021/08/23 08:06 編集

OpenCVのリファレンスの getPerspectiveTransform のところに H に関する数式が書いてあるハズです. 入力画像の座標(x,y) に対する,Hを用いた出力画像の座標が,こんな感じの式で書いてあるハズ(一部,記号が違うかもだけど): (k*x' k*y' k)^t = H * (x y 1)^t  (※ここで ^t は転置の意味) 左辺の第三成分で他の成分を割ることで,結果画像上の座標(x', y')が得られる仕組み.(割るのが「同次座標」の約束事.詳しくは「同次座標」で調べてください) で,ここでもしもHの三行目の成分を全部2倍に改ざんしたら,左辺の第三成分だけが2倍になる→なのでそれで割った結果の座標は元の半分になる.
fana

2021/08/23 08:12

> 全部が表示されているわけではない 結果を見れば想像が付くでしょうが,「全部」を表示するには相当に巨大な結果画像になり得ます. (そして像がめちゃくちゃに伸びます.) 実用上は「ある程度の範囲」で良いのではないかと. (例えば,入力画像の四隅の座標値とかを↑の式に突っ込んで計算してみれば結果画像のどこにくるのかがわかるでしょう.)
koomint

2021/08/23 08:16

そんな感じの式、見た気がします!そういうことだったんですね。 わざわざありがとうございます。 そうですね、色々試してみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問