使用OpenCV完成单目相机矫正

opencv图像畸变矫正:源码学习 opencv提供了直接进行畸变矫正的代码,因在项目中需要使用畸变矫正,因此研究一下opencv中畸变矫正的相关接口与代码,便于学习提升与二次开发。 阅读详情

相机矫正与显示

1、注意事项;
2、Code;
3、效果;
4、参考;

说明

1、相机标定的棋盘格宽高要对应,棋盘格角点识别只寻找内角点(自带例程里是w*h = 6 * 9);
2、注意棋盘格默认坐标系在左上角,drawChessboardCorners()会默认将x方向绘制为红色;
在这里插入图片描述
3、findChessboardCorners()之后必须进行粗角点提取(调用find4QuadCornerSubpix()函数),不然数据有问题;
4、棋盘格空间坐标系表示方法:(0,0,0) 、(50,0,0)、(100,0,0) 、…(0,50,0)、(50,50,0)…;这里使用OpenCV自带的棋盘格图像,棋盘格大小50mm;
5、通过重新计算空间点投影坐标(projectPoints())与计算矩阵范数来衡量相机标定的结果;
6、旋转向量需要调用Rodrigues(tvecsMat[i], rotation_matrix);转换为旋转矩阵;
在这里插入图片描述
7、畸变矩阵:(k1,k2,p1,p2,k3)(径向畸变,切向畸变);相机内参矩阵:相机内参矩阵总是以像素为单位表示的

在这里插入图片描述
在这里插入图片描述

Code

#include <opencv2/opencv.hpp>
#include <iostream>
#include <fstream>

using namespace cv;
using namespace std;

int main(int argc, char** argv)
{
	ifstream fin("calibdata.txt");   /*标定用图像文件路径*/
	ofstream fout("calibresult.txt"); /*保存标定结果的文件*/

	//读取每一幅图像,从中提取角点,然后对角点进行亚像素精确化

	cout << "开始提取角点...";
	int image_count = 0; /*图像数量*/
	Size image_size; /*图像尺寸*/
	Size board_size = Size(6, 9);  //标定板每行列角点数

	vector<Point2f> image_points_buf; /*缓存每幅图像上检测到的角点*/
	vector<vector<Point2f>> image_point_seq;  /*保存所有检测到的角点*/

	string filename;
	while (getline(fin,filename))
	{
		image_count++;
		// 用于观察检验输出
		cout << "image_count = " << image_count << endl;		
		Mat grayImg = imread(filename,IMREAD_GRAYSCALE);  //读入灰度图
		
		if (image_count == 1)
		{
			// 读入第一张图片时获取图像的宽高信息
			image_size.width = grayImg.cols;
			image_size.height = grayImg.rows;

			cout << "image_size.width = " << image_size.width << endl;
			cout << "image_size.height = " << image_size.height << endl;
		}
		if (0 == findChessboardCorners(grayImg, board_size, image_points_buf)) /*输入图像必须是8位灰度或彩色图像*/
		{
			cout << "can not find chessboard corners!\n";  //没有找到全部角点
			exit(1);
		}
		else
		{
			Mat rgbImg;
			cvtColor(grayImg, rgbImg, CV_GRAY2BGR);
			/*亚像素精确化*/
			find4QuadCornerSubpix(grayImg, image_points_buf, Size(11, 11));  //粗提取角点 精确化
			/*保存亚像素点*/
			image_point_seq.push_back(image_points_buf); 
			/*绘制出检测到的角点(彩色)*/
			drawChessboardCorners(rgbImg, board_size, image_points_buf, true);

			imshow("Camera Calibration", rgbImg);
			waitKey(500);  //暂停0.5s
		}
	}
	int total = image_point_seq.size();  //读取的总的图像数
	cout << "total = " << total << endl;
	int CornerNum = board_size.width * board_size.height;  /*总的角点数*/

	for (int i = 0; i < total; i++)
	{
		if (0 == i % CornerNum)
		{
			int j = -1;
			j = i / CornerNum;
			int k = j + 1;
			cout << "--> 第 " << k << "图片的数据 -->" << endl;
		}
		if (0 == i % 3)
		{
			cout << endl;
		}
		else
		{
			cout.width(10);
		}
		//输出所有图像中第一个角点坐标
		cout << "-->" << image_point_seq[i][0].x;
		cout << "-->" << image_point_seq[i][0].y;
	}
	cout << "角点提取完成!\n";

	//摄像机标定
	cout << "开始标定..." << endl;
	Size square_size = Size(50, 50); /*棋盘格大小50mm*/

	vector<vector<Point3f>>  object_points; /*保存棋盘上的坐标点*/
	vector<int>  point_counts; //每幅图像中的角点的数量

	Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));  /*摄像机内参数矩阵*/
	Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0));  /*摄像机的5个畸变系数:k1,k2,p1,p2,k3*/

	vector<Mat> tvecsMat; /*存储每幅图像的旋转向量*/
	vector<Mat> rvecsMat; /*存储每幅图像的平移向量*/


	/*设置棋盘点的三维坐标*/
	for (int t = 0; t < image_count; t++)
	{
		vector<Point3f> tempPointSet;
		for (int i = 0; i < board_size.height; i++)   //一定从行开始(刚开始没注意写错了,找了半天)
		{
			for (int j = 0; j < board_size.width; j++)
			{
				Point3f realPoint;
				//注意x,y对应关系
				realPoint.x = j * square_size.width;  //注意棋盘格默认坐标系的位置
				realPoint.y = i * square_size.height;
				realPoint.z = 0;
				tempPointSet.push_back(realPoint);  /*添加一幅图像的所有角点坐标*/
			}
		}
		object_points.push_back(tempPointSet);
	}
	/*初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板*/
	for (int i = 0; i < image_count; i++)
	{
		point_counts.push_back(board_size.width * board_size.height);
	}
	/*calibrate camera*/
	calibrateCamera(object_points, image_point_seq, image_size, cameraMatrix, distCoeffs, rvecsMat, tvecsMat, 0);

	cout << "标定完成!\n";

	//对标定结果进行评价  可以写成一个单独的函数
	cout << "开始评价标定结果.........\n";
	double total_err = 0.0;  //所有图像的平均误差的总和
	double err = 0.0;    /*每幅图像的平均误差*/
	vector<Point2f> image_points2; /*保存重新计算得到的投影点*/

	cout << "\t每幅图像的标定误差:\n";
	fout << "每幅图像的标定误差:\n";

	for (int i = 0; i < image_count; i++)
	{
		vector<Point3f> tempPointSet = object_points[i];
		/*通过得到摄像机内外参数、对空间的三维点进行重新投影计算,得到新的投影点*/
		projectPoints(tempPointSet, rvecsMat[i], tvecsMat[i], cameraMatrix, distCoeffs, image_points2);
		/*计算新的投影点和旧的投影点之间的误差*/
		vector<Point2f> tempImagePoint = image_point_seq[i];
		Mat tempImagePointMat = Mat(1, tempImagePoint.size(),CV_32FC2); //存储未矫正角点的坐标值
		Mat image_points2Mat = Mat(1, image_points2.size(), CV_32FC2);  //存储重新投影计算得到的投影角点坐标值

		for (int j = 0; j < tempImagePoint.size(); j++)
		{
			image_points2Mat.at<Vec2f>(0, j) = Vec2f(image_points2[j].x, image_points2[j].y);
			tempImagePointMat.at<Vec2f>(0, j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
		}
		err = norm(image_points2Mat, tempImagePointMat, NORM_L2); /*矩阵范数运算,用来表征矩阵变化的大小*/
		total_err += (err /= point_counts[i]);
		cout << "第" << i + 1 << "幅图像平均误差: " << err << " 像素" << endl;
		fout << "第" << i + 1 << "幅图像平均误差: " << err << " 像素" << endl;  /*输出到对应文件*/

	}
	cout << "总体平均误差: " << total_err / image_count << "像素" << endl;
	fout << "总体平均误差: " << total_err / image_count << "像素" << endl;
	cout << "评价完成" << endl;

	/*保存标定结果*/
	cout << "开始保存标定结果...." << endl;
	Mat rotation_matrix = Mat(3, 3, CV_32FC1, Scalar::all(0)); /*保存每幅图像的旋转矩阵*/

	fout << "相机内参数矩阵: " << endl;
	fout << cameraMatrix << endl << endl;

	fout << "畸变系数:\n";
	fout << distCoeffs << endl << endl << endl;

	for (int i = 0; i < image_count; i++)
	{
		fout << "第" << i + 1 << "幅图像的旋转向量:" << endl;
		fout << rvecsMat[i] << endl;
		/*将旋转向量对应的转换为旋转矩阵*/
		Rodrigues(rvecsMat[i], rotation_matrix);
		fout << "第" << i + 1 << "幅图像的旋转矩阵: " << endl;
		fout << rotation_matrix << endl;
		fout << "第" << i + 1 << "幅图像的平移矩阵: " << endl;
		fout << tvecsMat[i] << endl << endl;
	}
	cout << "保存完成" << endl;
	fout << endl;

	//显示矫正图像
	Mat src,dst, map1, map2;
	initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),Mat(), image_size,CV_16SC2,map1,map2); /*计算一次数据,多次使用*/
	fin.clear();/*清除读到文件尾标志位*/
	fin.seekg(0, ios::beg);/*定位到文件开头*/
	
	while (getline(fin, filename))
	{
		src = imread(filename, IMREAD_GRAYSCALE);
		remap(src, dst, map1, map2,INTER_LINEAR);
		imshow("矫正图像", dst);
		waitKey(0);
	}	

	return 0;
}

运行效果

矫正显示图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
输出部分结果:

第1幅图像平均误差: 0.061354 像素
第2幅图像平均误差: 0.0556064 像素
第3幅图像平均误差: 0.0605988 像素
第4幅图像平均误差: 0.0613993 像素
第5幅图像平均误差: 0.0539623 像素
第6幅图像平均误差: 0.0568119 像素
第7幅图像平均误差: 0.0553855 像素
第8幅图像平均误差: 0.0675104 像素
第9幅图像平均误差: 0.0552258 像素
第10幅图像平均误差: 0.0535394 像素
第11幅图像平均误差: 0.0679826 像素
第12幅图像平均误差: 0.0553539 像素
第13幅图像平均误差: 0.05476 像素
总体平均误差: 0.0584223像素
相机内参数矩阵: 
[531.7211458925117, 0, 340.5939787009914;
 0, 531.6527520201263, 232.1816395637914;
 0, 0, 1]

畸变系数:
[-0.2649624107640241, -0.06179144766431392, 0.0007444186953668426, -0.0006773403737711846, 0.3589273922760235]


第1幅图像的旋转向量:
[-147.1977864401933;
 -211.9918058168564;
 794.5893885356636]
第1幅图像的旋转矩阵: 
[0.9783750415377405, 0.2008112739236345, 0.04956924814026904;
 -0.1988157450784841, 0.9791192015449368, -0.04240151736545007;
 -0.05704890537873422, 0.03162943931169756, 0.9978702325272139]
第1幅图像的平移矩阵: 
[-1.985290932382638;
 -2.010358828096781;
 0.1155827105495813]

参考

1、https://blog.youkuaiyun.com/weixin_30244681/article/details/95617791
2、https://blog.youkuaiyun.com/t247555529/article/details/47836233
3、https://blog.youkuaiyun.com/shenxiaolu1984/article/details/50165635

相机的畸变矫正与opencv代码说明 相机畸变模型 opencv 阅读详情

相关推荐

基于同心圆的单目相机标定

网上绝大部分博客关于相机标定的讲解全都关于理论上的,很少有代码的实现。因此,打算写这篇博客。博客并不涉及相关公式推导,假设大家都已经懂三大世界坐标系、内外参等名词含义。 一:相机标定的作用 (1):求解内外参数。 (2):用于处理畸变矫正。 二:标定的流程 (1):准备若干张标定图片(至少四张) (2):图像预处理,清除图像上无关的轮廓信息 (3):提取角点信息。(同心圆的圆心即为角点) (4):...

qq_42027706的博客 2739

opencv对相机进行畸变校正,及校正前后的坐标对应

目前有个项目,需要用到热成像相机。但是这个热成像相机他的畸变比较厉害,因此需要用标定板进行标定,从而消除镜头畸变。

joyopirate的博客 9252

<视觉>——单目相机的标定(简单原理+Opencv实现)

单目相机的标定原理大致如下: 世界坐标到像素坐标的转换。 期间的参数有S尺度因子,内参矩阵K,旋转矩阵R,平移矩阵T,一共八个未知数。 在Opencv中我们可以方便的根据相机拍摄不同位姿的标定板图片来标定相机,从而求得这些参数,从而可以将相机下的坐标和实际的世界坐标联系起来,求取单应性矩阵H 参考: 单目相机综述:写的特别好 https://www.cnblo...

Kano365的博客 1万+

基于C#的图像几何校正

界面如下: 左边读入畸变图像,并在图像上鼠标点击四个匹配点坐标(也可手动输入),并在右边图片框依次点击四个点对应的正常图像位置,点击图像校正,结果如下: 图像校正关键部分代码(图像校正鼠标按下事件): private void button1_Click_1(object sender, EventArgs e) { try { ...

weixin_41048471的博客 4604

【OpenCV3.3】通过透视变换矫正变形图像

在平面图像处理中,因为镜头角度等原因,容易导致图像出现倾斜、变形等情况,为了方便后续处理我们常常需要进行图像矫正,其中主要技术原理是两种变换类型--仿射变换(Affine Transformation)和透视变换(Perspective Transformation)。         仿射变换是二维坐标间的线性变换, 故而变换后的图像仍然具有原图的一些性质,包括“平直性”以及“平行性”,常用于

Rprop 5万+

单目相机标定python+opencv

1.标定过程 准备20-30张拍摄的标定板图像,以20张图片为例。 提取20张图片的的角点信息。 在上一步的基础上,提取精度更高的角点坐标。 把找到的角点在图上画出来,显得更加直观。 相机标定计算,根据上面提取到的角点坐标信息和世界坐标系中的三维点进行计算。 对标定结果进行评价 查看标定效果——利用标定结果对棋盘图进行矫正 (两种方法) 2.python+opencv 这里选择python+o...

风源- 4766

【ros学习-课设-人脸识别追踪】

更新1:使用ros工具包标定USB单目相机没哈前言,第一篇博客,多多关照 环境:Ubuntu20.04 已安装ros 暂时还没有使用anaconda进行python环境管理要安装opencv-python,先换个源: pip国内的一些镜像   阿里云 http://mirrors.aliyun.com/pypi/simple/   中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/   豆瓣(douban) http://pypi.douban.com/simpl

HWP221119的博客 1975

单目相机测距

单目相机测距 单目测距的小项目,大概需要就是用单目相机,对一个特定的目标进行识别并测算相机与该目标的距离。所以便去网上找了一堆教程,这里给大家总结一下,希望给小白们一个参考。 首先是基本需求了 · opencv自然要会的,这咱就不多说了,会一点就行 · 需要一个摄像头,我用的是一个畸变很大的鱼眼免驱动摄像头,大家用电脑上的那个自带摄像头也可以的,就是不方便。 · 需要MATLAB进行相...

吴建明wujianming_110117 2627

单目相机标定(使用Matlab)

内容一.单目视觉成像原理1.理想情况下相机成像模型1.1 世界坐标系 -> 相机坐标系1.2 相机坐标系 -> 图像坐标系1.3 图像坐标系 -> 像素坐标系1.4 总结:世界坐标系 -> 像素坐标系二.考虑畸变情况下相机成像模型 一.单目视觉成像原理 1.理想情况下相机成像模型 在理想情况下,相机成像模型可以看作是⼩孔成像模型,如下图所示, 为了便于计算,我们将成像平面进行翻转(它们在数学上是等价的,并且相机硬件会自动帮我们处理),我们假设成像平面翻转到了相机光心的正前方,翻转后

boss-dog 8296

基于OpenCV的实时文档扫描与矫正技术

实时摄像头捕获图像边缘检测和轮廓查找文档轮廓识别透视变换矫正文档二值化处理增强可读性本文介绍了一个基于OpenCV的实时文档扫描与矫正系统,通过边缘检测、轮廓分析和透视变换等技术,实现了文档的自动检测和矫正。该系统可以方便地应用于日常文档数字化工作,提高工作效率。完整代码已在上文中给出,读者可以根据自己的需求进行修改和扩展。OpenCV提供了强大的图像处理能力,结合Python的简洁语法,使得开发这样的实用系统变得简单高效。

zdx2585503940的博客 3077

C#图像处理:EmguCV相机标定与畸变矫正实现代码

而通过图像畸变矫正,我们可以对新的图像进行畸变矫正操作。通过对相机进行标定和畸变矫正,可以提高图像的质量和准确性,从而为后续的图像处理任务提供更好的基础。标定图像要求包含多个棋盘格子的平面,这样可以根据特征点的位置计算相机的内参和外参。函数,该函数接受一张图像、相机内参矩阵和畸变系数作为输入,并返回经过畸变矫正后的图像。现在,我们可以使用上述的相机标定和图像畸变矫正函数来测试和应用我们的代码了。函数,我们可以通过相机内参矩阵和畸变系数对输入图像进行畸变矫正。完成相机标定后,我们可以对新的图像进行畸变矫正。

BugHunterX的博客 933

使用opencv实现图像的扭曲矫正

在图像处理中,经常需要对图像进行各种操作如平移、缩放、旋转、翻转等,这些都是图像的仿射变换。图像仿射变换又称为图像仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。通常图像的旋转加上拉升就是图像仿射变换,仿射变换需要一个M矩阵实现,但是由于仿射变换比较复杂,很难找到这个M矩阵.

十年以上架构设计经验,专注于软件架构和人工智能领域,对机器视觉、NLP、音视频等领域都有涉猎 1万+

相机畸变校正实现(C#、C、C++)通用

笔者仿照matlab相机畸变校正算法,在获取相机内外参数的前提下,使用代码实现了相机畸变的校正,该代码经试验后可以完美的在C#、C以及C++编程环境中使用。

opencv鱼眼镜头矫正

鱼眼镜头是一种视场角很大的镜头,但是得到的图片有很大的畸变,所以需要对鱼眼镜头进行标定,标定所得的参数可以对鱼眼镜头的图像进行矫正。

yuanlulu的博客 1万+

C# winform使用opencvsharp部署RecRecNet广角图像畸变矫正校正演示源码.7z

【测试环境】 vs2019 netframeowrk==4.7.2 opencvsharp==4.8.0 【视频演示】 https://www.bilibili.com/video/BV1zJ4m1u7VZ/ 【博文地址】 https://blog.youkuaiyun.com/FL1623863129/article/details/139760635

l0phtcrack 7(爆破管理员密码)使用教程

l0phtcrack7简称lc7,是爆破计算机管理员密码的软件,早期是网络安全管理员用来检测系统用户是否使用了不安全的密码,后被黑客用于用户口令破解 功能:该工具支持远程破解,理论上能破解所有密码,但对复杂度比较高的密码,破解时间很长 使用前提:1.登录了需要破解的计算机 2.知道需要破解的计算机的一个管理员的账户,密码 ** 也许你会疑惑,我都知道了一个管理员账户密码了,还要另外的管理员密码...

Xiongcanne的博客 2万+

OpenCV实战1——图像矫正技术

当我们遇到一些歪歪扭扭的照片,比如下面这些照片: 这些图片让人看得真不舒服!我们可以用PS来处理?但如果有1000张图,我们只能交给计算机去做! 对于图像矫正的问题,在图像处理领域很多,比如人民币的矫正、文本的矫正、车牌的矫正、身份证矫正等等。这些都是因为拍摄者总不可能100%正确地拍摄好图片,这就要求我们通过后期的图像处理技术将图片还原好,才能进一步做后面的处理,比如数字分割...

知识搬运工的博客 4957

光猫改桥接后,一根网线搞定IPTV和上网的保姆级教程(附VLAN配置避坑点)

本文详细介绍了光猫改桥接后如何通过单线复用技术实现IPTV和上网功能共存。从VLAN配置原理到具体操作步骤,包括光猫超级管理员权限获取、VLAN绑定设置及路由器IPTV功能配置,提供了全面的保姆级教程。特别针对常见问题如IPTV信号丢失、网速下降等给出了实用解决方案,帮助用户轻松避坑。

weixin_29927965的博客 1738

Python-Opencv基于透视变换的图像矫正

一、自动获取图像顶点变换(获取图像轮廓顶点矫正) 图像旋转校正思路如下 1、以灰度图读入 2、腐蚀膨胀,闭合等操作 3、二值化图像 4、获取图像顶点 5、透视矫正 #(基于透视的图像矫正) import cv2 import math import numpy as np def Img_Outline(input_dir): original_img = cv2.imread(inpu...

weixin_43431189的博客 1万+

几率大的Redis面试题(含答案)

本文的面试题如下: Redis 持久化机制 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题 热点数据和冷数据是什么 Memcache与Redis的区别都有哪些? 单线程的redis为什么这么快 redis的数据类型,以及每种数据类型的使用场景,Redis 内部结构 redis的过期策略以及内存淘汰机制【~】 Redis 为什么是单线程的,优点 如何解决redis的并发竞争key问题 Red...

睶 38万+

KF-gins输出结果绘图

KF-gins输出结果绘图

m0_51977577的博客 1107

我是这样搞定第一次单目相机测距的

点击上方“小白学视觉”,选择加"星标"或“置顶”重磅干货,第一时间送达之前在做一个单目测距的小项目,大概需要就是用单目相机,对一个特定的目标进行识别并测算相机与该目标的距离。所以便去网上找...

小白学视觉 3720

(四) OpenClaw-Skill技能全面了解和自定义技能

OpenClaw Skills是提升AI代理功能性的关键模块。本文系统介绍了Skills的核心概念、获取渠道和创建方法。Skills作为标准化指令集合,能让OpenClaw从单纯理解升级为实际执行任务,相比传统System Prompt具有模块化、可复用等优势。用户可通过Clawhub官方商店、GitHub社区等渠道获取5700+现成技能,或按需开发自定义Skill。文章详细演示了创建"Hello World"示例技能的完整流程,包括编写Python脚本、配置SKILL.md文件、部署。

直到世界的尽头 8293
上一篇: PCL点云库(Point Cloud Library)简介
下一篇: OpenCV中XML与YAML文件读取
hellohake
博客等级 码龄10年 4543粉丝 108原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值