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
沒有留言:
張貼留言