本文主要是在前两篇博客的基础上做的更新,对有畸变的网络摄像机进行标定。理论基础知识请参考:
《相机参数标定(camera calibration)及标定结果如何使用》
https://blog.csdn.net/Aoulun/article/details/78768570
《第二更,相机参数标定基础:从小孔成像开始到单双目标定》
https://blog.csdn.net/Aoulun/article/details/104780993
关于双目相机标定的具体实践,可参考我的另外一篇博客
《第四更,双目相机标定实践(完整过程)》
https://blog.csdn.net/Aoulun/article/details/104789685
本文所有代码和图片均已上传,可正常使用,关注公众号下载。如果您觉得本文对您帮助很大可关注公众号分享,或者打赏作者,您的支持是我持续创作的动力。
目录
01 前言
02 实验条件
03 实验内容
04 实验步骤
05 实验代码
06 实验图片
07 实验结果
昨天我们村的村支书赵建国特意驱赶牛车30里找到。说村口的摄像头看人是扁的。尤其是村口孙建国家旁边安的那个摄像头里的钱建国简直就不像个人。我一寻思,这事有点不对。钱建国今年1月22日刚从外省打工回来,按照上级的要求,以及村支书的指示,必须在家隔离14天。按道理说,钱建国不应该出现在村口的孙建国家旁边啊。我想都不用想,就知道是啥事。孙建国1月25号从外省回来,也在隔离中。孙建国欠了钱建国钱,钱建国去讨债了。
为了村里的抗疫大计,我亲自戴了两层口罩,跟村里的水电工李建国一起把摄像头卸了,搬回家,好好研究一番。看看到底是那除了毛病。根据现场的实际查勘,初步断定是摄像头的畸变太严重,晃了村支书的眼。这事必须解决,绝不能因为摄像头的事,跟村支书添乱。
主要的实验器材都在下面这两幅图中了
主要的实验器材在如下清单中显示
主要的实验内容包括:
1.搭建整个实验平台,观察摄像机拍照情况
2.标定网络摄像机,求取相机参数
3.用标定的相机矫正拍摄的图片
4.向村支书汇报
(4.1)搭建实验平台
首先,我按照某康网络相机拍照的基本流程,搭建了平台。
摄像机采用电源适配器通电。
数据传输采用1米长网线跟笔记本电脑链接。
根据某康开放的API,自己搭建了一个相机连接采集MFC窗口,窗口具有预览功能。
点击预览按钮,左侧窗口可以实时预览我老家农家小院储物间的内景。
单击抓图按钮,保存图像。由于我在代码里把路径写死了,所以,没有配置路径的窗口。
从预览的效果来看,笔直的墙壁、取暖器排气管路、梯子边缘都有明显的弯曲。
所以,本次相机标定的一个重要任务是,不光是求得相机的参数,还要对图像畸变进行矫正。
(4.2)标定相机
标定板的实际尺寸参考如下图所示。实际长度是,每个单元格的长度是2厘米。
(4.2.1)拍摄标定图片
我从拍摄的所有图片中,挑选了21幅作为标定用图。
你肯定会质疑,为什么标定板占图像的比例这么小,不应该是占很大一部分才对嘛?且听过说。我用的这个网络摄像机,在这个位置以外,拍摄的照片是清晰的。如果在这个位置以内,照片将会模糊。可以看个图。
可以看到,标定板上的黑白格明显模糊,边缘不够清晰。
换句话说,笔者认为除非在要求非常严格的场合,否则满足一般条件的应用,之前调校的距离是可以的。并不会影响你学习相机标定。博主一并把这些图片都放到公众号内,供下载。
我们将图片中的标定板放大,可以看到,在黑白格的交点处,没有明显的连接。这个也没有办法,相机的成像质量真心不好。那怎么解决这个问题呢?
(4.2.2)图片预处理
我首先想到的是,把图片尺寸缩小到原来的一半。看看实际效果。勉强可以用。
相应的,把待矫正图片也做尺寸缩小处理。
(4.2.3)标定参数求取
具体理论内容可以参考另外两篇博客
《相机参数标定(camera calibration)及标定结果如何使用》
《第二更,相机参数标定基础:从小孔成像开始到单双目标定》
这里只说一说具体的步骤,详细的代码会在第四章贴出来。
(a)读取所有标定图
采用cv2.imread(img_path)方法,非常之简单。
(b)寻找角点
寻找角点,采用cv2.drawChessboardCorners(),也是现成的。大家可以看下我的找角点结果,非常完美。
来个高清大图,不可否认,有些角点找的有偏差,但是,作为实验来说,我们并不是太关心。主要还是讲流程。
(c)计算参数矩阵
我们带着问题来看计算过程。
问:为什么需要拍摄那么多图片,一张不行吗?
博主在另外两篇博客当中也提到,根据张征友标定法,只需要求得B矩阵和H矩阵就可以换算出参数矩阵。而B矩阵和H矩阵的求解是通过最大似然估计来优化得到的。详细可参考:
https://www-users.cs.umn.edu/~hspark/CSci5980/zhang.pdf
那么优化过程必然是需要多幅图像共同作用的。在上面的文献中,作者给出了多少图像是合适的,这里不再赘述。
将上面步骤产生的所有角点都组合起来,作为函数输入,可以实现求解:
cv2.calibrateCamera()
返回结果中,就包含内参和畸变参数。
另外,标定的过程,实际上就是一个矩阵关系的转换过程。什么意思呢?
举个例子,根据实际的标定板模样,我在电脑里假定了一个多长多宽的假想标定板图像,而标定的过程就是,所有拍摄到的图像中的标定板,都应该往我假定的标定板靠。如果对不上,那么就有系数上的关系,这个系数上的关系就是我们要标定的参数。
这个理解在程序中也有体现。
(4.3)矫正拍摄的图像
我们用(4.2)中得到的畸变参数来矫正有问题的图像,得到如下的结果。
效果还是可以的。
(4.4)向村支书汇报
看到上面的效果,我可以向村支书汇报了。明天他就要找我要结果,我得快点过去。不能耽误了抗疫大事。
import argparse
from argparse import RawTextHelpFormatter
import numpy as np
import cv2
def cam_calib_find_corners(img, rlt_dir, img_idx, col, row):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray,(col,row), None)
#为了得到稍微精确一点的角点坐标,进一步对角点进行亚像素寻找
corners2 = cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 10, 0.001))
if ret == True:
#保存角点图像
sav_path = rlt_dir + "\\" + str(img_idx) + "_corner.jpg"
cv2.drawChessboardCorners(img, (col,row), corners2, ret)
cv2.imwrite(sav_path, img)
return (ret, corners2)
def cam_calib_calibrate(img_dir, rlt_dir, col, row, img_num):
w = 0
h = 0
all_corners = []
patterns = []
#标定相机,先搞这么一个假想的板子,标定就是把图像中的板子往假想的板子上靠,靠的过程就是计算参数的过程
x,y = np.meshgrid(range(col),range(row))
prod = row * col
pattern_points=np.hstack((x.reshape(prod,1),y.reshape(prod,1),np.zeros((prod,1)))).astype(np.float32)
for i in range(1,img_num+1):
img_path = img_dir + "\\" + str(i) + ".jpg"
print (img_path)
#读取图像
img = cv2.imread(img_path)
(h, w) = img.shape[:2]
#提取角点
ret, corners = cam_calib_find_corners(img, rlt_dir, i, col, row)
#合并所有角点
all_corners.append(corners)
patterns.append(pattern_points)
rms, cameraMatrix, distCoeffs, rvecs, tvecs = cv2.calibrateCamera(patterns, all_corners, (w, h), None, None)
return (cameraMatrix, distCoeffs)
def cam_calib_correct_img(crct_img_dir, cameraMatrix, distCoeffs):
for i in range(1,3):
crct_img_path = crct_img_dir + "\\" + str(i) + ".jpg"
img = cv2.imread(crct_img_path)
(h1, w1) = img.shape[:2]
#对参数做处理,使得最后的输出的矫正图像去表不必要的边缘。
newcameramtx,roi = cv2.getOptimalNewCameraMatrix(cameraMatrix,distCoeffs,(w1,h1),1,(w1,h1))
#矫正
dst = cv2.undistort(img, cameraMatrix, distCoeffs, None, newcameramtx)
# 保存矫正图像
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
rlt_path = crct_img_dir + "\\rlt\\" + str(i) + "_crct.jpg"
cv2.imwrite(rlt_path, dst)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="读取标定的图片并保存结果",formatter_class=RawTextHelpFormatter)
parser.add_argument("--img_dir",help="标定图片路径",type=str,metavar='', default="E:\\calib_img")
parser.add_argument("--rlt_dir",help="保存路径",type=str,metavar='',default="E:\\calib_img\\rlt")
parser.add_argument("--crct_img_dir",help="待矫正图像路径",type=str,metavar='',default="E:\\crct_img")
parser.add_argument("--row_num",help="每一行有多少个角点,边缘处的不算",type=int,metavar='',default="7")
parser.add_argument("--col_num",help="每一列有多少个角点,边缘处的不算",type=int,metavar='',default="6")
parser.add_argument("--img_num",help="多少幅图像",type=int,metavar='',default="21")
args=parser.parse_args()
# 标定相机
cameraMatrix, distCoeffs = cam_calib_calibrate(args.img_dir, args.rlt_dir, args.row_num, args.col_num, args.img_num)
#矫正图片
cam_calib_correct_img(args.crct_img_dir, cameraMatrix, distCoeffs)
代码已经上传到了公众号,可以关注下载。
环境的搭建,可以参考另外的博客。我是在tensorflow的环境下跑的。
《深度学习完全攻略!(连载六UDA10.1+tensorflow+VS+anaconda3安装)》
在拍摄的时候,我尽量让标定板能够准确对焦。但缺点是,标定板只占图像的一小部分。
博主已经将,所有的图片都上传到了公众号。大家可以下载自己实验研究。
从网上的各种资料来看,有对标定的放置特殊说明的,有对拍摄照片数量说明的。
博主本实验,对最终结果要求不高,但是效果可以接受。
所以,在标定的过程中,还是要根据实际情况来。
村支书对我的工作给予了高度评价,并表示,如果以后还有这样的事情,也把这个免费的锻炼机会交给我。
后来这个事被隔壁村主任知道了,他说找我有点事。我猜测可能跟他最近做的双目测距有关。
本文结束,后续请关注第四更,双目标定。