EmguCV Image Process: Estimating Projective Relations in Images
參考OpenCV 2 Computer Vision Application Programming Cookbook第九章
介紹內容如下:
Calibrating a camera
Computing the fundamental matrix of an image pair
Matching images using random sample consensus
Computing a homography between two images
現行的許多照片、影像
都是由數位相機透過光經由鏡頭並投射在感光元件上來捕捉畫面的
因此這中間就產生了一些問題
關於從3D的現實空間,如何投射到2D的影像平面上
這之間的關係,成了很大的一個議題
接下來會介紹一系列與之相關的內容
像是如何透過多種不同的角度的影像
利用基本的投影關係來做電腦視覺的處理
利用不同的方法來增進之前介紹過的特徵配對
要先來介紹的就是:鏡頭矯正 (Calibrate a camera)
不同的攝影鏡頭會有不同的參數來矯正
這些參數通常只有在生產此鏡頭的廠商才會擁有
那如何利用一些電腦視覺的技術來回推這些參數
就是本節要介紹的
而OpenCV提供的方法,讓這非常非常複雜的數學運算
變成簡單的一項工作




可以發現在未經過鏡頭矯正的影像
棋盤都是扭曲變形的
這裡要透過鏡頭矯正把扭曲的棋盤恢復成正常的直線棋盤
首要要準備的就是一個棋盤影像(chessboard)
利用這個棋盤來對鏡頭做矯正
簡單來說OpenCV利用棋盤方正格子的角點產生一些3D畫面的點位
而這些點位只給定X跟Y座標,Z深度座標則設為0
以這些產生的點位與實際影像上的位置做投影的計算處理
擷取棋盤的角點位置非常的簡單
如下所示:
//input a chessboard image
Image<Gray, Byte> image = new Image<Gray, byte>("image.jpg");
//number of corners on the chessboard
Size boardSize = new Size(9, 6);
//Get the chessboard corners
PointF[] points = CameraCalibration.FindChessboardCorners(image, boardSize, Emgu.CV.CvEnum.CALIB_CB_TYPE.DEFAULT);
//Draw the corners
CameraCalibration.DrawChessboardCorners(image, boardSize, points);
EmguCV有直接實作CameraCalibration這個靜態類別
裡面提供了有關鏡頭矯正的靜態方法
這裡要記得提供棋盤的格子的數量
計算的方式是以格子與格子相連的角點位置
如下圖所示:
雖然標記的不是很明顯
但可以看出OpenCV所抓取的角點位置
因此要先計算這些角點位置的數量,才有辦法做角點偵測
這就是簡單的角點偵測的方式
但要做到鏡頭矯正可沒這麼容易
他需要一系列類似的棋盤影像
以不同的角度、方向來做擷取角點的動作
利用這些角點位置計算鏡頭的
內部參數(intrinsic parameters)與外部參數(extrinsic parameters)
其中內部參數包含了
鏡頭矩陣參數(camera matrix)與失真矩陣參數(distortion matrix)
而這兩個參數可用來取得反失真(undistort)的mapping影像
再利用mapping影像去把給定的影像作remapping的動作
才會得到矯正後的結果
因此這裡利用一個CameraCalibrator的類別來提供該功能
class CameraCalibrator
{
//input points: the points in world coordinates
List<MCvPoint3D32f[]> ObjectPoints;
//the points positions in pixels
List<PointF[]> ImagePoints;
//output matrices
IntrinsicCameraParameters IntrinsicParams;
//flag to specify how calibration is done
CALIB_TYPE Flag;
//used in image undistortion
Image<Gray, float> Map1, Map2;
bool MustInitUndistort;
public CameraCalibrator()
{
this.Flag = CALIB_TYPE.DEFAULT;
this.MustInitUndistort = true;
this.ImagePoints = new List<PointF[]>();
this.ObjectPoints = new List<MCvPoint3D32f[]>();
}
...
}
這個類別提供一些全域變數包含紀錄3D點位的ObjectPoints
角點座標的ImagePoints
內部參數IntrinsicParams
矯正方法的列舉Flag
mapping影像Map1, Map2
跟一個控制初始反失真動作的布林變數MustInitUndistort
並在建構式中做初始的動作
/// <summary>
/// Open chessboard images and extract corner points
/// </summary>
/// <param name="fileList">image list path</param>
/// <param name="boardSize">number of corners on the chessboard</param>
/// <returns></returns>
public int AddChessboardPoints(string[] fileList, Size boardSize)
{
//the points on the chessboard
PointF[] imageCorners;
MCvPoint3D32f[] objectCorners = new MCvPoint3D32f[boardSize.Height * boardSize.Width];
//3D Scene Points:
//Initialize the chessboard corners
//in the chessboard reference frame
//The corners are at 3D location (x,y,z) = (i,j,0)
for (int i = 0; i < boardSize.Height; i++)
{
for (int j = 0; j < boardSize.Width; j++)
{
objectCorners[i * boardSize.Width + j] = new MCvPoint3D32f(i, j, 0.0f);
}
}
//2D image points:
Image<Gray, Byte> image;//to contain chessboard image
int successes = 0;
//for all viewpoint
for (int i = 0; i < fileList.Length; i++)
{
//Open the image
image = new Image<Gray, Byte>(fileList[i]);
//Get the chessboard corners
imageCorners = CameraCalibration.FindChessboardCorners(image, boardSize, CALIB_CB_TYPE.DEFAULT);
if (imageCorners == null) continue;
//Get subpixel accuracy on the corners
CvInvoke.cvFindCornerSubPix(image, imageCorners,
boardSize.Height * boardSize.Width,
new Size(5, 5), new Size(-1, -1),
new MCvTermCriteria(30, 0.1));
//If we have a good board, add it to our data
if (imageCorners.Length == boardSize.Height * boardSize.Width)
{
//Add image and scene points form one view
AddPoints(imageCorners, objectCorners);
successes++;
}
}
return successes;
}
接著這個方法先利用給定的棋盤角點的數量
來產生配對的3D座標(X, Y, Z)
再把一系列的棋盤影像讀入
這邊是推薦10~20張不同角度、深度的影像
並利用CameraCalibration.FindChessboardCorners做角點偵測的動作
取得正確數量的角點位置後
把角點座標的集合與配對的3D座標的集合
丟入AddPoints的方法內
/// <summary>
/// Add scene points and corresponding image points
/// </summary>
/// <param name="imageCorners">2D image points from one view</param>
/// <param name="objectCorners">corresponding 3D scene points</param>
void AddPoints(PointF[] imageCorners, MCvPoint3D32f[] objectCorners)
{
//2D image points from one view
this.ImagePoints.Add(imageCorners);
//corresponding 3D scene points
this.ObjectPoints.Add(objectCorners);
}
把角點座標的集合與配對的3D座標的集合加入到全域變數ImagePoints與ObjectPoints
接著就開始做鏡頭的矯正動作
/// <summary>
/// Calibrate the camera
/// </summary>
/// <param name="imageSize">image size</param>
/// <returns>return the re-projection error</returns>
public double Calibrate(Size imageSize)
{
//undistorter must be reinitialized
this.MustInitUndistort = true;
MCvPoint3D32f[][] objectPoints = this.ObjectPoints.ToArray();
PointF[][] imagePoints = this.ImagePoints.ToArray();
this.IntrinsicParams = new IntrinsicCameraParameters();
ExtrinsicCameraParameters[] extrinsicParams;
return CameraCalibration.CalibrateCamera(
objectPoints, //the 3D points
imagePoints, //the image points
imageSize, //image size
this.IntrinsicParams, //output distortion matrix
this.Flag, //set options
out extrinsicParams); //output rotations and translations
}
這邊使用CameraCalibration.CalibrateCamera這個方法要帶入配對的3D座標集合、角點座標集合、影像大小、
內部參數、矯正方法列舉、以及外部參數
這裡要注意的是全域變數ObjectPoints與ImagePoints
是採用List這個型別來處理集合
但在CameraCalibration.CalibrateCamera方法中
是要接收二維矩陣的型別
因此要透過一個ToArray()的轉換
而CameraCalibration.CalibrateCamera會回傳一個double的數值
這個數值的值越小,表示矯正的過程越好
數值越大,可能之後矯正出來的效果就會有問題
處理完後,IntrinsicParams裡面就會存放著
鏡頭矩陣參數(camera matrix)跟失真矩陣參數(distortion matrix)
分別是IntrinsicParams.IntrinsicMatrix與IntrinsicParams.DistortionCoeffs
/// <summary>
/// remove distortion in an image (after calibration)
/// </summary>
/// <param name="image"></param>
/// <returns></returns>
public Image<Gray, Byte> Remap(Image<Gray, Byte> image)
{
Image<Gray, Byte> undistorted = new Image<Gray, byte>(image.Size);
if (this.MustInitUndistort)//called once per calibration
{
this.Map1 = new Image<Gray, float>(image.Size);
this.Map2 = new Image<Gray, float>(image.Size);
CvInvoke.cvInitUndistortRectifyMap(
this.IntrinsicParams.IntrinsicMatrix, //computed camera matrix
this.IntrinsicParams.DistortionCoeffs,//computed distortion matrix
IntPtr.Zero, //optional rectification (none)
this.IntrinsicParams.IntrinsicMatrix, //camera matrix to generate undistorted
this.Map1, //the mapping funtions
this.Map2); //the mapping funtions
this.MustInitUndistort = false;
}
CvInvoke.cvRemap(image, undistorted, Map1, Map2, (int)INTER.CV_INTER_LINEAR, new MCvScalar(0));
return undistorted;
}
接著提供反失真的處理一開始先初始一張與來源影像一樣大的結果影像
與初始兩張與來源影像一樣大但是深度為32bit的mapping影像
接著透過CvInvoke.cvInitUndistortRectifyMap這個方法
帶入鏡頭矩陣參數(camera matrix)、失真矩陣參數(distortion matrix)
第三個參數實際上是一個3X3的矩陣
由於這裡的條件下用不到於是帶入IntPtr.Zero
(程式會轉成帶入單位矩陣(identity matrix))
第四個參數在EmguCV的文件上是:The new camera matrix
也就是輸出影像的鏡頭矩陣參數(camera matrix)
在參考書籍OpenCV 2 Computer Vision Application Programming Cookbook的範例
是帶入一個空的矩陣集合 cv::Mat()
這裡我嘗試了很多次
無論是帶入空的或是單位矩陣,都會有問題
直至查到OpenCV的文件寫說:
In case of a monocular camera, newCameraMatrix is usually equal to cameraMatrix
這一切才豁然開朗
直接帶入原本的鏡頭矩陣參數即可
而後兩個參數就是mapping的影像
之後在呼叫CvInvoke.cvRemap這個方法
帶入原始影像image、以及結果影像undistorted、
兩個mapping影像、及補差值的方法,補差值的顏色
//input a chessboard image
Image<Gray, Byte> image = new Image<Gray, byte>("chessboardImage\image1.jpg");
//chessboard images folder
string[] fileList = Directory.GetFiles("chessboardImage\");
CameraCalibrator cameraCalibrator = new CameraCalibrator();
cameraCalibrator.AddChessboardPoints(fileList, boardSize);
Console.WriteLine(cameraCalibrator.Calibrate(image.Size));
Image<Gray, Byte> result = cameraCalibrator.Remap(image);
CameraCalibrator類別的使用方式如上把結果影像印出來
有看出其中差異了嗎?
把兩張影像放在一起比較看看
經過鏡頭矯正過後
鏡頭的桶狀變形就消失了~
原本的棋盤是扭曲變形的
矯正過後就會恢復正常,呈現直線狀態
這就是camera calibration
而對於攝影成像利用矩陣描述的部分
可以參考維基百科
下一篇介紹如何透過兩張不同角度或不同攝影機拍攝的影像求出fundamental matrix





沒有留言:
張貼留言