摄像机标定
下面这段代码是 OpenCV-Python-Tutorial-中文版.pdf (P243)中的实现,
当前系列所有demo下载地址:
https://github.com/GaoRenBao/OpenCv4-Demo
https://gitee.com/fuckgrb/OpenCv4-Demo
不同编程语言对应的OpenCv版本以及开发环境信息如下:
语言 | OpenCv版本 | IDE |
C# | OpenCvSharp4.4.8.0.20230708 | Visual Studio 2022 |
C++ | OpenCv-4.5.5-vc14_vc15 | Visual Studio 2022 |
Python | OpenCv-Python (4.6.0.66) | PyCharm Community Edition 2022.1.3 |
棋盘格下载地址:check-108.pdf
参考博客:http://blog.csdn.net/dcrmg/article/details/52939318
为了找到棋盘的图案我们要使用函数 cv2fndChessboardCorners()。我们还需要传入图案的类型,比如说 8x8 的格子或5x5的格子等。在本例中我们使用的是7x8的格子。(通常情况下棋盘都是 8x8 或者7x7)。它会返回角点,如果得到图像的话返回值类型(Retval)就会是True。这些角点会按顺序排列(从左到右,从上到下 )。
patternSize:(w,h),棋盘上每一排和每一列的内角数。w=棋盘板一行上黑白块的数量-1,h=棋盘板一列上黑白块的数量-1,例如:10x6的棋盘板,则(w,h)=(9,5)
其他:
这个函数可能不会找出所有图像中应有的图案。所以一个好的方法是编写代码,启动摄像机并在每一帧中检查是否有应有的图案。在我们获得图案之后我们要找到角点并把它们保存成一个列表。在读取下一帧图像之前要设置一定的间隔,这样我们就有足够的时间调整棋盘的方向。继续这个过程直到我们得到足够多好的图案。就算是我们举得这个例子,在所有的 14 幅图像中也不知道有几幅是好的。所以我们要读取每一张图像从其中找到好的能用的。
其他:
除了使用棋盘之外,我们还可以使用环形格子,但是要使用函数cv2.findCirclesGrid来找图案。据说使用环形格子只需要很少的图像就可以了。
demo1:

demo2:
经过纠正,你会发现结果图像中所有的边界变直了

demo3:
调用摄像头的效果:

C#版本代码如下:
参考博客:https://blog.csdn.net/jimtien/article/details/119009460
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace demo
{
internal class Program
{
static void Main(string[] args)
{
//demo1();
demo3();
}
static List<Point3f> GetChessboardObjectPoints(Size boardSize, float squareSize)
{
List<Point3f> objectPoints = new List<Point3f>() { };
for (int i = 0; i < boardSize.Height; ++i)
{
for (int j = 0; j < boardSize.Width; ++j)
{
objectPoints.Add(new Point3f(squareSize * j, squareSize * i, 0.0f));
}
}
return objectPoints;
}
public static Mat[] calcBoardCornerPositions(Size BoardSize, float SquareSize, int imagesCount)
{
Mat[] corners = new Mat[imagesCount];
for (int k = 0; k < imagesCount; k++)
{
Point3f[] p = new Point3f[BoardSize.Height * BoardSize.Width];
for (int i = 0; i < BoardSize.Height; i++)
{
for (int j = 0; j < BoardSize.Width; j++)
{
p[i * BoardSize.Width + j] = new Point3f(j * SquareSize, i * SquareSize, 0);
}
}
corners[k] = Mat.FromArray<Point3f>(p);
}
return corners;
}
static void demo1()
{
List<string> paths = new List<string>()
{
"../../../images/lefts/",
"../../../images/rights/"
};
List<string> images = new List<string>();
foreach (string path in paths)
{
DirectoryInfo di = new DirectoryInfo(path);
FileInfo[] fis = di.GetFiles("*.jpg");
Mat srcImage = new Mat();
foreach (FileInfo fi in fis)
{
images.Add(fi.FullName);
}
}
TermCriteria criteria = new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.MaxIter, 30, 0.001);
// 棋盘格内角点数量(行数和列数)
Size chessboardSize = new Size(7, 6);
// 棋盘格尺寸(单位:米)
float squareSize = 0.025f;
// 存储棋盘格角点坐标的容器
List<List<Point3f>> objpoints = new List<List<Point3f>>();
// 存储检测到的棋盘格角点坐标的容器
List<List<Point2f>> imgpoints = new List<List<Point2f>>();
Mat gray = new Mat();
foreach (var image in images)
{
Mat img = Cv2.ImRead(image);
if (!img.Empty())
{
Cv2.CvtColor(img, gray, ColorConversionCodes.BGR2GRAY);
// Find the chess board corners
Point2f[] corners = new Point2f[] { };
bool ret = Cv2.FindChessboardCorners(gray, chessboardSize, out corners);
// If found, add object points, image points(after refining them)
if (ret == true)
{
Cv2.CornerSubPix(gray, corners, new Size(11, 11), new Size(-1, -1), criteria);
// Draw and display the corners
Cv2.DrawChessboardCorners(img, chessboardSize, corners, ret);
objpoints.Add(GetChessboardObjectPoints(chessboardSize, squareSize));
imgpoints.Add(corners.ToList());
Cv2.ImShow("img", img);
Cv2.WaitKey(100);
}
}
}
}
static void demo3()
{
List<string> paths = new List<string>()
{
"../../../images/lefts/",
"../../../images/rights/"
};
List<string> imagesList = new List<string>();
foreach (string path in paths)
{
DirectoryInfo di = new DirectoryInfo(path);
FileInfo[] fis = di.GetFiles("*.jpg");
Mat srcImage = new Mat();
foreach (FileInfo fi in fis)
{
imagesList.Add(fi.FullName);
}
}
TermCriteria criteria = new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.MaxIter, 30, 0.001);
Size BoardSize = new Size(9, 6);
int SquareSize = 25; //每格的宽度应设置为实际的毫米数
Mat img = new Mat();
Mat gray = new Mat();
List<Mat> imagesPointsM = new List<Mat>();
List<Point2f[]> imgpoints = new List<Point2f[]>();
for (int i = 0; i < imagesList.Count; i++)
{
img = new Mat(imagesList[i]);
if (!img.Empty())
{
Cv2.CvtColor(img, gray, ColorConversionCodes.BGR2GRAY);
Point2f[] pointBuf;
bool found = Cv2.FindChessboardCorners(img, BoardSize, out pointBuf);
if (found == true)
{
Mat viewGray = new Mat();
Cv2.CvtColor(img, viewGray, ColorConversionCodes.BGR2GRAY);
Cv2.CornerSubPix(viewGray, pointBuf, new Size(11, 11), new Size(-1, -1), criteria);
imgpoints.Add(pointBuf);
imagesPointsM.Add(Mat.FromArray<Point2f>(pointBuf));
Cv2.DrawChessboardCorners(img, BoardSize, pointBuf, found);
Cv2.ImShow("img", img);
Cv2.WaitKey(10);
}
}
}
// 执行相机标定
Mat[] rvecs = new Mat[] { };
Mat[] tvecs = new Mat[] { };
Mat mtx = new Mat();
Mat dist = new Mat();
Mat[] objpoints = calcBoardCornerPositions(BoardSize, SquareSize, imagesList.Count);
Cv2.CalibrateCamera(objpoints, imagesPointsM.ToArray(), new Size(gray.Cols, gray.Rows), mtx, dist, out rvecs, out tvecs, CalibrationFlags.None);
// 畸变校正
img = Cv2.ImRead("../../../images/lefts/left12.jpg");
int h = img.Rows;
int w = img.Cols;
Mat mapx = new Mat();
Mat mapy = new Mat();
Rect roi = new Rect();
Mat newcameramtx = Cv2.GetOptimalNewCameraMatrix(mtx, dist, new Size(w, h), 1, new Size(w, h), out roi);
// undistort
Mat dst = new Mat();
Cv2.Undistort(img, dst, mtx, dist, newcameramtx);
// crop the image
dst = new Mat(dst, roi);
Cv2.ImShow("calibresult1.png", dst);
Cv2.InitUndistortRectifyMap(mtx, dist, new Mat(), newcameramtx, new Size(w, h), 5, mapx, mapy);
Cv2.Remap(img, dst, mapx, mapy, InterpolationFlags.Linear);
// crop the image
dst = new Mat(dst, roi);
Cv2.ImShow("calibresult2.png", dst);
double mean_error = 0;
for (int i = 0; i < objpoints.Length; ++i)
{
Mat imagePoints2 = new Mat();
Cv2.ProjectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist, imagePoints2);
double error = Cv2.Norm(imagesPointsM[i], imagePoints2, NormTypes.L2);
Console.WriteLine($"total error :{mean_error / objpoints.Count()}");
}
Cv2.WaitKey();
}
}
}
C++版本代码如下:
#include <opencv2/opencv.hpp>
#include <opencv2/ml.hpp>
#include <opencv2/cvconfig.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <io.h>
#include <string>
#include <iostream>
#include <ctime>
using namespace cv;
using namespace std;
std::vector<cv::Point3f> GetChessboardObjectPoints(const cv::Size& boardSize, float squareSize) {
std::vector<cv::Point3f> objectPoints;
for (int i = 0; i < boardSize.height; ++i) {
for (int j = 0; j < boardSize.width; ++j) {
objectPoints.push_back(Point3f(squareSize * j, squareSize * i, 0.0f));
}
}
return objectPoints;
}
void demo1()
{
// 获取图片目录
std::string inPath1 = "..\\images\\lefts\\left*.jpg";
std::string inPath2 = "..\\images\\rights\\right*.jpg";
intptr_t handle1, handle2;
struct _finddata_t fileinfo1, fileinfo2;
handle1 = _findfirst(inPath1.c_str(), &fileinfo1);
handle2 = _findfirst(inPath2.c_str(), &fileinfo2);
vector<string> images;
do {
string Name = fileinfo1.name;
images.push_back("..\\images\\lefts\\" + Name);
} while (!_findnext(handle1, &fileinfo1));
do {
string Name = fileinfo2.name;
images.push_back("..\\images\\lefts\\" + Name);
} while (!_findnext(handle2, &fileinfo2));
TermCriteria criteria = TermCriteria(TermCriteria::EPS | TermCriteria::MAX_ITER, 30, 0.001);
// 棋盘格内角点数量(行数和列数)
cv::Size chessboardSize(7, 6);
// 棋盘格尺寸(单位:米)
float squareSize = 0.025f;
// 存储棋盘格角点坐标的容器
std::vector<std::vector<cv::Point3f>> objpoints;
// 存储检测到的棋盘格角点坐标的容器
std::vector<std::vector<cv::Point2f>> imgpoints;
Mat gray;
for (int i = 0; i < images.size(); i++)
{
Mat img = imread(images[i]);
if (!img.empty())
{
cvtColor(img, gray, COLOR_BGR2GRAY);
// Find the chess board corners
std::vector<cv::Point2f> corners;
bool ret = findChessboardCorners(gray, chessboardSize, corners);
// If found, add object points, image points(after refining them)
if (ret == true)
{
cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1), criteria);
// Draw and display the corners
drawChessboardCorners(img, chessboardSize, corners, ret);
objpoints.push_back(GetChessboardObjectPoints(chessboardSize, squareSize));
imgpoints.push_back(corners);
imshow("img", img);
waitKey(100);
}
}
}
}
void demo2()
{
// 获取图片目录
std::string inPath1 = "..\\images\\lefts\\left*.jpg";
std::string inPath2 = "..\\images\\rights\\right*.jpg";
intptr_t handle1, handle2;
struct _finddata_t fileinfo1, fileinfo2;
handle1 = _findfirst(inPath1.c_str(), &fileinfo1);
handle2 = _findfirst(inPath2.c_str(), &fileinfo2);
vector<string> images;
do {
string Name = fileinfo1.name;
images.push_back("..\\images\\lefts\\" + Name);
} while (!_findnext(handle1, &fileinfo1));
do {
string Name = fileinfo2.name;
images.push_back("..\\images\\lefts\\" + Name);
} while (!_findnext(handle2, &fileinfo2));
TermCriteria criteria = TermCriteria(TermCriteria::EPS | TermCriteria::MAX_ITER, 30, 0.001);
// 棋盘格内角点数量(行数和列数)
cv::Size chessboardSize(7, 6);
// 棋盘格尺寸(单位:米)
float squareSize = 0.025f;
// 存储棋盘格角点坐标的容器
std::vector<std::vector<cv::Point3f>> objpoints;
// 存储检测到的棋盘格角点坐标的容器
std::vector<std::vector<cv::Point2f>> imgpoints;
Mat gray;
for (int i = 0; i < images.size(); i++)
{
Mat img = imread(images[i]);
if (!img.empty())
{
cvtColor(img, gray, COLOR_BGR2GRAY);
// Find the chess board corners
std::vector<cv::Point2f> corners;
bool ret = findChessboardCorners(gray, chessboardSize, corners);
// If found, add object points, image points(after refining them)
if (ret == true)
{
cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1), criteria);
// Draw and display the corners
drawChessboardCorners(img, chessboardSize, corners, ret);
objpoints.push_back(GetChessboardObjectPoints(chessboardSize, squareSize));
imgpoints.push_back(corners);
imshow("img", img);
waitKey(10);
}
}
}
// 执行相机标定
cv::Mat mtx, dist;
std::vector<cv::Mat> rvecs, tvecs;
cv::calibrateCamera(objpoints, imgpoints, cv::Size(gray.cols, gray.rows), mtx, dist, rvecs, tvecs);
// 畸变校正
Mat img = imread("../images/lefts/left12.jpg");
int h = img.rows;
int w = img.cols;
cv::Mat mapx, mapy;
Rect roi;
cv::Mat newcameramtx = getOptimalNewCameraMatrix(mtx, dist, Size(w,h), 1, Size(w, h), &roi);
// undistort
Mat dst;
undistort(img, dst, mtx, dist, newcameramtx);
// crop the image
dst = dst(roi);
imshow("calibresult1.png", dst);
initUndistortRectifyMap(mtx, dist, cv::Mat(), newcameramtx, Size(w, h), 5, mapx, mapy);
remap(img, dst, mapx, mapy, INTER_LINEAR);
// crop the image
dst = dst(roi);
imshow("calibresult2.png", dst);
// 你会发现结果图像中所有的边界变直了
// 反向投影误差
// 我们可以利用反向投影误差对我们找到的参数的准确性进行估计。得到的结果越接近 0越好。
// 有了内部参数,畸变参数和旋转变换矩阵,我们就可以使用cv2.projectPoints()将对象点转换到图像点。
// 然后就可以计算变换得到图像与角点检测算法的绝对差了。
// 然后我们计算所有标定图像的误差平均值。
int mean_error = 0;
for (int i = 0; i < objpoints.size(); i++)
{
vector<Point2f> imgpoints2;
projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist, imgpoints2);
int error = norm(imgpoints[i], imgpoints2, NORM_L2) / imgpoints2.size();
mean_error += error;
cout << "total error :" << mean_error / objpoints.size() << endl;
}
waitKey(0);
}
int main()
{
//demo1();
demo2();
}
Python版本代码如下:
demo1:
import numpy as np
import cv2
import glob
# termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6 * 7, 3), np.float32)
objp[:, :2] = np.mgrid[0:7, 0:6].T.reshape(-1, 2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
images = glob.glob('../images/lefts/left*.jpg')
images += glob.glob('../images/rights/right*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Find the chess board corners
ret, corners = cv2.findChessboardCorners(gray, (7, 6), None)
# If found, add object points, image points (after refining them)
if ret == True:
objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
imgpoints.append(corners)
# Draw and display the corners
cv2.drawChessboardCorners(img, (7, 6), corners2, ret)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
demo2:
import numpy as np
import cv2
import glob
# termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6 * 7, 3), np.float32)
objp[:, :2] = np.mgrid[0:7, 0:6].T.reshape(-1, 2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
images = glob.glob('../images/lefts/left*.jpg')
images += glob.glob('../images/rights/right*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Find the chess board corners
ret, corners = cv2.findChessboardCorners(gray, (7, 6), None)
# If found, add object points, image points (after refining them)
if ret == True:
objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
imgpoints.append(corners)
# Draw and display the corners
cv2.drawChessboardCorners(img, (7, 6), corners2, ret)
cv2.imshow('img', img)
cv2.waitKey(10)
cv2.destroyAllWindows()
##################### 标定 #####################
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# 畸变校正
img = cv2.imread('../images/lefts/left12.jpg')
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
# 使用 cv2.undistort() 是最简单的方法。只 使用这个函数和上面得到 的 ROI 对结果进行裁剪。
# undistort
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# crop the image
x, y, w, h = roi
dst = dst[y:y + h, x:x + w]
cv2.imshow('calibresult1.png', dst)
# 使用 remapping 应 属于 曲线救国 了。 先我们 找到从畸变图像到畸变图像的映射方程。再使用 重映射方程
# undistort
mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w, h), 5)
dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
# crop the image
x, y, w, h = roi
dst = dst[y:y + h, x:x + w]
cv2.imshow('calibresult2.png', dst)
# 你会发现结果图像中所有的边界变直了
# 反向投影误差
# 我们可以利用反向投影误差对我们找到的参数的准确性进行估计。得到的结果越接近 0越好。
# 有了内部参数,畸变参数和旋转变换矩阵,我们就可以使用cv2.projectPoints()将对象点转换到图像点。
# 然后就可以计算变换得到图像与角点检测算法的绝对差了。
# 然后我们计算所有标定图像的误差平均值。
mean_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
mean_error += error
print("total error: ", mean_error / len(objpoints))
cv2.waitKey(0)
demo3:
import numpy as np
import cv2
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
cap = cv2.VideoCapture(0)
# 等比缩放
# frame_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
# frame_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
#
# frame_height = int(480 / frame_width * frame_height)
# ret = cap.set(cv2.CAP_PROP_FRAME_HEIGHT, frame_height)
# ret = cap.set(cv2.CAP_PROP_FRAME_WIDTH, 480)
while cap.isOpened():
# img = cv2.imread(fname)
ret, img = cap.read()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(image=gray, patternSize=(8, 6), corners=None)
'''
第一个参数Image,传入拍摄的棋盘图Mat图像,必须是8位的灰度或者彩色图像;
第二个参数patternSize,每个棋盘图上内角点的行列数,一般情况下,行列数不要相同,便于后续标定程序识别标定板的方向;
第三个参数corners,用于存储检测到的内角点图像坐标位置,一般用元素是Point2f的向量来表示:vector<Point2f> image_points_buf;
第四个参数flage:用于定义棋盘图上内角点查找的不同处理方式,有默认值。
'''
print(corners)
print('---------')
if ret == True:
# objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
# imgpoints.append(corners)
# Draw and display the corners
cv2.drawChessboardCorners(img, (8, 6), corners2, ret)
cv2.imshow('img', img)
key = cv2.waitKey(delay=10)
if key == ord("q"):
break
cv2.destroyAllWindows()