OpenCV 5.0.0
Open Source Computer Vision
読み込み中...
検索中...
見つかりません
🤖 AIによる機械翻訳(非公式) — これは OpenCV 5.0.0 公式リファレンス(英語)を AI (Claude) で自動翻訳したものです。訳に誤りを含む場合があります。正確な情報は 公式英語版(原文) を参照してください。
姿勢推定

目的

本節では、

  • 3d モジュールを利用して、画像に3D効果を作り出す方法を学ぶ。

基礎

これは短い節になる。前回のカメラキャリブレーションのセッションで、カメラ行列や歪み係数などを求めた。パターン画像が与えられれば、上記の情報を利用してその姿勢、すなわち物体が空間内でどのように配置されているか、どのように回転しているか、どのように変位しているかなどを計算できる。平面物体の場合は Z=0 と仮定でき、問題はカメラが空間内のどこに置かれるとパターン画像が見えるか、ということになる。したがって、物体が空間内でどのように位置しているかが分かれば、その中に2D図形を描いて3D効果をシミュレートできる。その方法を見てみよう。

我々の課題は、チェスボードの最初のコーナーに3D座標軸(X, Y, Z軸)を描くことである。X軸は青、Y軸は緑、Z軸は赤で表す。つまり実質的に、Z軸はチェスボード平面に対して垂直に感じられるはずである。

まず、前回のキャリブレーション結果からカメラ行列と歪み係数を読み込もう。

import numpy as np
import cv2 as cv
import glob
# Load previously saved data
with np.load('B.npz') as X:
mtx, dist, _, _ = [X[i] for i in ('mtx','dist','rvecs','tvecs')]

それでは、チェスボードのコーナー (cv.findChessboardCorners() で取得) と 軸の点 を受け取り、3D軸を描画する draw 関数を作成しよう。

def draw(img, corners, imgpts):
corner = tuple(corners[0].ravel().astype("int32"))
imgpts = imgpts.astype("int32")
img = cv.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5)
img = cv.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5)
img = cv.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5)
return img
void line(InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
Draws a line segment connecting two points.

次に、前回と同様に、終了条件、オブジェクト点(チェスボードのコーナーの3D点)、軸の点を作成する。軸の点は軸を描くための3D空間内の点である。長さ3の軸を描く(このサイズでキャリブレーションしたので、単位はチェスのマス目のサイズになる)。よってX軸は (0,0,0) から (3,0,0) まで描かれ、Y軸も同様である。Z軸は (0,0,0) から (0,0,-3) まで描かれる。負の値はカメラの方向に向かって描かれることを表す。

criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objp = np.zeros((6*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)
axis = np.float32([[3,0,0], [0,3,0], [0,0,-3]]).reshape(-1,3)

では、いつものように各画像を読み込む。7x6 のグリッドを探索する。見つかれば、サブコーナーピクセルで精緻化する。次に回転と並進を計算するために、関数 cv.solvePnPRansac() を使用する。これらの変換行列が得られたら、それらを用いて 軸の点 を画像平面に投影する。簡単に言えば、3D空間の (3,0,0)、(0,3,0)、(0,0,3) のそれぞれに対応する画像平面上の点を求める。それらが得られたら、generateImage() 関数を使って最初のコーナーから各点へ線を描く。完了!!!

for fname in glob.glob('left*.jpg'):
img = cv.imread(fname)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, corners = cv.findChessboardCorners(gray, (7,6),None)
if ret == True:
corners2 = cv.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
# Find the rotation and translation vectors.
ret,rvecs, tvecs = cv.solvePnP(objp, corners2, mtx, dist)
# project 3D points to image plane
imgpts, jac = cv.projectPoints(axis, rvecs, tvecs, mtx, dist)
img = draw(img,corners2,imgpts)
cv.imshow('img',img)
k = cv.waitKey(0) & 0xFF
if k == ord('s'):
cv.imwrite(fname[:6]+'.png', img)
void projectPoints(InputArray objectPoints, InputArray rvec, InputArray tvec, InputArray cameraMatrix, InputArray distCoeffs, OutputArray imagePoints, OutputArray jacobian=noArray(), double aspectRatio=0)
Projects 3D points to an image plane.
bool solvePnP(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess=false, int flags=SOLVEPNP_ITERATIVE)
Finds an object pose from 3D-2D point correspondences:
void imshow(const String &winname, InputArray mat)
Displays an image in the specified window.
int waitKey(int delay=0)
Waits for a pressed key.
void destroyAllWindows()
Destroys all of the HighGUI windows.
bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
Saves an image to a specified file.
Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
Loads an image from a file.
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0, AlgorithmHint hint=cv::ALGO_HINT_DEFAULT)
Converts an image from one color space to another.
void cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria)
Refines the corner locations.
bool findChessboardCorners(InputArray image, Size patternSize, OutputArray corners, int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE)
Finds the positions of internal corners of the chessboard.

以下にいくつかの結果を示す。各軸が3マス分の長さであることに注目してほしい。

image

立方体を描画する

立方体を描きたい場合は、generateImage() 関数と軸の点を次のように変更する。

変更した generateImage() 関数:

def draw(img, corners, imgpts):
imgpts = np.int32(imgpts).reshape(-1,2)
# draw ground floor in green
img = cv.drawContours(img, [imgpts[:4]],-1,(0,255,0),-3)
# draw pillars in blue color
for i,j in zip(range(4),range(4,8)):
img = cv.line(img, tuple(imgpts[i]), tuple(imgpts[j]),(255),3)
# draw top layer in red color
img = cv.drawContours(img, [imgpts[4:]],-1,(0,0,255),3)
return img
void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar &color, int thickness=1, int lineType=LINE_8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point())
Draws contours outlines or filled contours.

変更した軸の点。これらは3D空間における立方体の8つのコーナーである。

axis = np.float32([[0,0,0], [0,3,0], [3,3,0], [3,0,0],
[0,0,-3],[0,3,-3],[3,3,-3],[3,0,-3] ])

そして以下の結果を見てほしい。

image

グラフィックスや拡張現実などに興味があれば、OpenGLを使ってより複雑な図形をレンダリングできる。