在机器视觉领域,摄像头的标定指通过技术手段拿到相机的内参、外参及畸变参数。

相机内参长这样,利用针孔模型,将 3d 物体透视投影到 2d 的相机屏幕上。

P = [ f x 0 c x 0 f y c y 0 0 1 ] P = \begin{bmatrix} f_{x} & 0 & c_{x} \\ 0 & f_{y} & c_{y}\\ 0 & 0 & 1 \end{bmatrix} P=⎣⎡​fx​00​0fy​0​cx​cy​1​⎦⎤​

畸变参数包括 2 类,径向畸变和切向畸变

径向畸变最明显的例子就是鱼眼相机的效果。

大家仔细观察上面的图片,它就能很好地介绍径向畸变。越往镜头边缘,线条弯曲的越明显,本来是直线,现在都变成了曲线,消除畸变就是为了把这些曲线尽量还原成本来的样子。

径向畸变可以被纠正,公式如下。

除了径向畸变外,还有一个畸变就是切向畸变

切向畸变一般来说,是因为相机镜头制造工艺精度不够,透镜和感光器原件没有平行。从而造成了图像的变形。

矫正公式如下:

两个畸变的参数通常用一个向量表示。


但一般只用 4 个参数。
[ k 1 , k 2 , p 1 , p 2 ] [k_{1} ,k_{2},p_{1},p_{2}] [k1​,k2​,p1​,p2​]

如果用 5 个参数,畸变后的相片就成球状了。

我们的目标就是为了标定出相机内参和外参。

OpenCV 官网上有标定代码示例,但是是基于图片的,并且只有一张图片,我们知道一般要得到一个比较好的标定效果的话,大概需要标定 20 张图片左右。

所以,我想改良一下,我就想到了用相机拍摄视频,然后在视频中完成操作。

标定物我选择了传统的棋盘格,源文件在此。


我用 A4 纸打印了出来,然后粘贴在一张硬纸板上。

接下来就可以编写代码了。

代码的流程其实非常简单。

  1. 打开摄像头,获取画面,并监听键盘事件。
  2. 如果检测到空格键,执行棋盘格检测代码。
  3. 如果检测成功,将棋盘格角点信息绘制在画面上,并将结果保存到列表当中。同时更新棋盘格检测成功次数。
  4. 如果棋盘格检测成功次数达到指定值,比如 20,又或者是用户按下 Q 键,退出棋盘格的检测。
  5. 将棋盘格角点信息送入标定函数,获取标定结果并保存。
  6. 标定结果可以用来去畸变。
def calibrate():criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)Nx_cor = 9Ny_cor = 6objp = np.zeros((Nx_cor * Ny_cor, 3), np.float32)objp[:, :2] = np.mgrid[0:Nx_cor, 0:Ny_cor].T.reshape(-1, 2)objpoints = []  # 3d points in real world spaceimgpoints = []  # 2d points in image plane.count = 0  # count 用来标志成功检测到的棋盘格画面数量while (True):ret, frame = cap.read()if cv2.waitKey(1) & 0xFF == ord(' '):# Our operations on the frame come heregray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)ret, corners = cv2.findChessboardCorners(gray, (Nx_cor, Ny_cor), None)  # Find the corners# If found, add object points, image pointsif ret == True:corners = cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria)objpoints.append(objp)imgpoints.append(corners)cv2.drawChessboardCorners(frame, (Nx_cor, Ny_cor), corners, ret)count += 1if count > 20:break# Display the resulting framecv2.imshow('frame', frame)if cv2.waitKey(1) & 0xFF == ord('q'):breakglobal mtx, distret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)print(mtx, dist)mean_error = 0for i in xrange(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 += errorprint "total error: ", mean_error / len(objpoints)

上面是标定的函数。核心是利用了 OpenCV 的几个关键的 API.

# 查找棋盘格角点信息
ret, corners = cv2.findChessboardCorners(gray, (Nx_cor, Ny_cor), None)
# 精细化角点信息
corners = cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria)
# 绘制查找到的角点
cv2.drawChessboardCorners(frame, (Nx_cor, Ny_cor), corners, ret)
# 标定,mtx 是相机内参,dist 是畸变,rvecs,tvecs 分别是旋转矩阵和平移矩阵代表外参
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

需要注意的是,在这里标定板的的棋盘格是检测内点,所以是横向 9 个,竖向 6 个。

Nx_cor = 9
Ny_cor = 6

标定的时候,还需要角点的物理坐标和图像坐标,这是因为需要通过透视成像的原理,来反向拟合相机的参数,原理比较复杂,这个不做解释,有兴趣的同学可以查看相关书籍和资料。

标定后的结果需要衡量误差,下面是代码。

mean_error = 0
for i in xrange(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 += errorprint "total error: ", mean_error / len(objpoints)

通过 cv2.projectPoints()方法将角点的物理坐标、标定得到的外参重新投影得到新的角点的图像坐标。

然后将新的图像坐标与之前检测角点时的真实图像坐标对比,以此来衡量标定的精确性。

np.savez('calibrate.npz', mtx=mtx, dist=dist[0:4])

这行代码的用途是为了将标定的结果序列化,保存到本地,以备以后直接使用,畸变参数我只保存了 4 个,原因前面有讲过。

标定得到的相机内参与畸变参数可以用来消除相机原始画面的畸变,代码如下:

def undistortion(img, mtx, dist):h, w = img.shape[:2]newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))dst = cv2.undistort(img, mtx, dist, None, newcameramtx)# crop the imagex, y, w, h = roiif roi != (0, 0, 0, 0):dst = dst[y:y + h, x:x + w]return dst

所以,我们可以编写测试代码,如果本地有标定好的参数,那么就直接加载。如果没有的话,那就标定一次。

拿到内参和畸变参数后,我们可以打开摄像头,然后去畸变,然后你可以直接观察效果。

if __name__ == '__main__':cap = cv2.VideoCapture(0)mtx = []dist = []try:npzfile = np.load('calibrate.npz')mtx = npzfile['mtx']dist = npzfile['dist']except IOError:calibrate()print('dist', dist[0:4])while (True):ret, frame = cap.read()frame = undistortion(frame, mtx, dist[0:4])# Display the resulting framecv2.imshow('frame', frame)if cv2.waitKey(1) & 0xFF == ord('q'):breakcap.release()cv2.destroyAllWindows()

你可以变换姿态,然后按空格键进行图像的抓取。

完整代码如下:

#encoding=utf-8
import numpy as np
import cv2def undistortion(img, mtx, dist):h, w = img.shape[:2]newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))print('roi ', roi)dst = cv2.undistort(img, mtx, dist, None, newcameramtx)# crop the imagex, y, w, h = roiif roi != (0, 0, 0, 0):dst = dst[y:y + h, x:x + w]return dstdef calibrate():criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)Nx_cor = 9Ny_cor = 6objp = np.zeros((Nx_cor * Ny_cor, 3), np.float32)objp[:, :2] = np.mgrid[0:Nx_cor, 0:Ny_cor].T.reshape(-1, 2)objpoints = []  # 3d points in real world spaceimgpoints = []  # 2d points in image plane.count = 0  # count 用来标志成功检测到的棋盘格画面数量while (True):ret, frame = cap.read()if cv2.waitKey(1) & 0xFF == ord(' '):# Our operations on the frame come heregray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)ret, corners = cv2.findChessboardCorners(gray, (Nx_cor, Ny_cor), None)  # Find the corners# If found, add object points, image pointsif ret == True:corners = cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria)objpoints.append(objp)imgpoints.append(corners)cv2.drawChessboardCorners(frame, (Nx_cor, Ny_cor), corners, ret)count += 1if count > 20:break# Display the resulting framecv2.imshow('frame', frame)if cv2.waitKey(1) & 0xFF == ord('q'):breakglobal mtx, distret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)print(mtx, dist)mean_error = 0for i in xrange(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 += errorprint "total error: ", mean_error / len(objpoints)# # When everything done, release the capturenp.savez('calibrate.npz', mtx=mtx, dist=dist[0:4])def undistortion(img, mtx, dist):h, w = img.shape[:2]newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))dst = cv2.undistort(img, mtx, dist, None, newcameramtx)# crop the imagex, y, w, h = roiif roi != (0, 0, 0, 0):dst = dst[y:y + h, x:x + w]return dstif __name__ == '__main__':cap = cv2.VideoCapture(0)mtx = []dist = []try:npzfile = np.load('calibrate.npz')mtx = npzfile['mtx']dist = npzfile['dist']except IOError:calibrate()print('dist', dist[0:4])while (True):ret, frame = cap.read()frame = undistortion(frame, mtx, dist[0:4])# Display the resulting framecv2.imshow('frame', frame)if cv2.waitKey(1) & 0xFF == ord('q'):breakcap.release()cv2.destroyAllWindows()

如果觉得文章有所帮助,可以点击这行字给我博客投票,046 号

OpenCV 标定摄像头(Python 版本代码,视频中标定,亲测可用)相关推荐

  1. ofd格式转pdf,所需代码和jar包--亲测可用

    ofd格式转pdf,所需代码和jar包–亲测可用 工具类package com.comingnet.commons.util.file;import java.nio.file.Path; impor ...

  2. 180°和360°伺服电机速度控制,转向控制Arduino代码与库(亲测可用)

    写在前面的话:如果你只想要我代码,建议你复制粘贴第一个和最后一个,试试.如果你想学习以后如何解决类似问题而不是仅仅的抄代码,建议你读完我啰嗦的话.希望对你有所帮助.你可以抄袭我代码,但请添加出处/引文 ...

  3. Android开发,登录注册界面中如何添加视频背景,亲测可用

    此篇文章属个人查阅资料整理所著,希望能对您有所帮助,欢迎各位留言指正,抱拳了 一. 首先在res文件夹下添加raw文件夹并将要添加的背景视频放进去: 二.在MyViewpager.java(此为要显示 ...

  4. python资源论坛_五个亲测可用的Python论坛类网站开源框架

    1.LBForum LBForum是用django开发的论坛系统,LBForum主要注重部署的方便性和易用性,功能方面目前还比较简单. LBForum的开发尽量遵照Django可复用app原则,因此即 ...

  5. python京东抢购软件_福利来了,python 京东抢购茅台脚本(亲测可用)

    由于项目遵循GPL-3.0 License协议,明确项目内所有资源文件,禁止任何公众号.自媒体进行任何形式的转载.发布,故不写长篇篇幅来描述代码内容. 基于原作者的描述和本人的亲身实战,确实可以抢到茅 ...

  6. Python 京东抢购茅台脚本(亲测可用)

    源代码可参考作者:https://github.com/huanghyw/jd_seckill 由于项目遵循GPL-3.0 License协议,明确项目内所有资源文件,禁止任何公众号.自媒体进行任何形 ...

  7. Win7安装高版本的NodeJS方法,亲测可用

    Win7安装高版本的NodeJS方法 正常情况下,Win7所能支持的Node.js最高版本为:V13.14,在开发过程中,git下来的项目由于node版本比较高的原因,好多package都不能还原或出 ...

  8. FC金手指代码大全·持续更新-亲测可用-FC 经典游戏完整可用的金手指大全---持续更新,偶尔玩玩经典回味无穷,小时候不能通关的现在通通通关一遍

    FC 经典游戏完整可用的金手指大全-持续更新,偶尔玩玩经典回味无穷,小时候不能通关的现在通通通关一遍 2021年5月11日更新: 每次翻金手指一些垃圾小网站标题党吸引进去吓一大堆木马什么也没有,什么x ...

  9. (亲测可用)html5 file调用手机摄像头

    在切图网一个客户的webapp项目中需要用到 html5调用手机摄像头,找了很多资料,大都是 js调用api  然后怎样怎样,做了几个demo测试发现根本不行, 后来恍然大悟,用html5自带的 in ...

最新文章

  1. offsetTop,clientX,clientTop,clientWidth,offsetWidth 坐标,一次弄明白
  2. android在控制台签名apk
  3. CentOS系统安装(上):图形/文本界面安装
  4. mysql 5.0 修改字符集_修改及查看mysql数据库的字符集
  5. SQL中truncate table和delete的区别 --转
  6. 从业回忆,一次大胆的冒险,程序员转岗项目经理
  7. 9-4:C++多态之单继承和多继承中的虚函数表
  8. Finally it is here - Physbam source code has been released!
  9. 多表查询,初识pymysql模块
  10. C++多继承构造和析构顺序
  11. C++ 柔性数组(转载)
  12. python显示文字框_python如何使用文本框
  13. 【其他】Windows Media Services 无法启动
  14. matlab freqz-m,Matlab函数freqs和freqz
  15. CCF推荐会议(人工智能与模式识别)
  16. 用u盘给服务器装win7系统,用U盘装系统装Win7图文教程
  17. PhotoShop中让索引图片解锁使用
  18. 蓝桥杯 算法训练 幸运的店家
  19. 黑苹果升级更新macOS 13 Ventura 问题整理
  20. 电子墨水屏标签:低功耗处理器技术

热门文章

  1. 全麦吐司和普通吐司的区别_全麦土司和全麦面包的区别?
  2. ubuntu退出shell终端命令_Ubuntu下,清屏等终端常用命令
  3. 无法量化的分析指标体系构建思路
  4. CSS3新增text-shadow轻松实现文字阴影效果
  5. 【报错】npoints >= 0 (depth == CV_32F || depth == CV_32S) in function ‘cv::contourArea‘
  6. 马云创办的「中国黄页」上线 | 历史上的今天
  7. Educoder JavaScript
  8. 赫迈泽牵手苹果Homekit,预定中国智能家居用户1.75亿?
  9. 树莓派3B使用问题#1 关于3.5mm AV接口的问题
  10. 主流浏览器对HTML5的支持情况