ROS中的OpenCV摄像头标定(一)
ROS中的OpenCV摄像头标定(一)
摄像头是一种非常精密的光学仪器,它对外界环境的感知是非常敏感的。由于摄像头内部和外部的一些原因,生成的物体图像常常会发生一定的畸变,例如在鱼眼摄像头,畸变是非常大的,如果直接将采集到的图像拿来进行图像处理的话,会产生很大的问题,为了避免数据源造成的误差,需要针对摄像头的参数进行标定。
对于OpenCV摄像头标定的内容提供两个不同的方法,分别是利用OpenCV例程进行标定和利用Camera Calibration
功能包进行标定,这篇内容讲述利用OpenCV例程进行标定,利用Camera Calibration
进行标定的内容参考ROS中的OpenCV摄像头标定(二)
利用OpenCV例程进行标定
参考博客:OpenCV摄像头标定
本文介绍使用OpenCV自带的标定例程对单目摄像头标定的过程
设备说明
操作系统:Ubuntu 18.04
OpenCV版本: 3.2.0
摄像头: 640×480像素,90度广角镜头
(一)标定步骤
1、标定例程
进入你的OpenCV安装目录,找到samples/cpp/tutorial_code/calib3d/camera_calibration
目录,把它拷贝到一个合适的位置。(因为可能需要修改一些代码,因此不建议直接在原目录下使用。)
2、修改标定配置参数
找到camera_calibration/in_VID5.xml
文件,这是标定程序使用的配置文件,需要设置里面的几个参数。
棋盘格的宽度和高度。
<!-- Number of inner corners per a item row and column. (square, circle) --> <BoardSize_Width>9</BoardSize_Width> <BoardSize_Height>6</BoardSize_Height>
需要特别注意的是,这里的宽度和高度是指内部交叉点的个数,而不是方形格的个数。如下图所示的棋盘格,内部交叉点的宽度是9,高度是6。请务必填写正确,否则无法标定。
每格的宽度
每格的宽度应设置为实际的毫米数,该参数的实际用途尚待考证。目前看来,即使设置的不准确也无大碍。
<!-- The size of a square in some user defined metric system (pixel, millimeter)--> <Square_Size>20</Square_Size>
选择输入方式
例程提供了三种输入方式。不过,如果待标定的摄像头已经接入电脑,建议使用input camera方式。该方式只需要设置视频输入设备号,对于笔记本电脑来说,通常0表示笔记本自带摄像头,1表示外接摄像头。
<!-- The input to use for calibration. To use an input camera -> give the ID of the camera, like "1" To use an input video -> give the path of the input video, like "/tmp/x.avi" To use an image list -> give the path to the XML or YAML file containing the list of the images, like "/tmp/circles_list.xml" --> <Input>"1"</Input>
如果不知道自己的设备号,可以通过下面指令查询
ls /dev
3、编译
新建camera_calibration/CMakeLists.txt
文件,写入如下内容。
project(Camera_Calibration)
set(CMAKE_CXX_STANDARD 11)
find_package(OpenCV 3.0 QUIET)
if(NOT OpenCV_FOUND)
find_package(OpenCV 2.4.3 QUIET)
if(NOT OpenCV_FOUND)
message(FATAL_ERROR "OpenCV > 2.4.3 not found.")
endif()
endif()
include_directories(${OpenCV_INCLUDE_DIR})
add_executable(Camera_Calibration camera_calibration.cpp)
target_link_libraries(Camera_Calibration ${OpenCV_LIBS})
编译
zacdeng@zacdeng-PC:camera_calibration/build$ cmake ..
zacdeng@zacdeng-PC:camera_calibration/build$ make
4、运行
运行时需要传入配置文件:
zacdeng@zacdeng-PC:camera_calibration/build$ ./Camera_Calibration ../in_VID5.xml
程序启动后会出现当前摄像头拍摄到的画面,右下角有操作提示。按下键盘’q’键开始标定。请务必使摄像头从不同方向拍摄棋盘格,以保证程序准确计算图像畸变。共拍摄25张照片后自动结束标定,标定结果写入camera_calibration/out_camera_data.xml
。此时,为了查看标定效果,可以按下键盘’u’键,画面将切换到去畸变后的图像,如果畸变完全消除,则标定成功,否则应该重新标定。
这里有一些注意事项:
- 可以直接在另一台电脑屏幕上显示棋盘格,而不必打印出来。屏幕上显示的棋盘格更平整,不会引入额外的误差。
- 要在上下左右各个角度拍摄棋盘格,以减少各个图片间的相关性,有利于求解相机参数和畸变系数。
现在,就可以使用标定好的相机内参和畸变系数啦!
(二)相机去畸变
有了标定好的参数,如何把输入图片的畸变去除呢?其实很简单,只需要调用OpenCV提供的一个函数就可以了:
cv2.undistort(frame,mK,mDistCoef,None,mK)
其中frame为原图片,mK为相机内参数矩阵,mDistCoef为畸变矩阵。后两个矩阵都可以在camera_calibration/out_camera_data.xml
中找到。
下面给出一个我自己使用的例程:
# -*- coding: utf-8 -*-
# @Author : Zachary Deng
# @Email : zacdeng0720@gmail.com
# @Time : 2021/4/20 12:00
# @Function: OpenCV标定应用及二维码识别
import numpy as np
import cv2
import pyzbar.pyzbar as pyzbar
mDistCoef = np.array([ -4.1802327018241026e-001, 5.0715243805833121e-001, 0., 0.,
-5.7843596847939704e-001 ])
mK = np.array([ [6.5746697810243404e+002, 0., 3.1950000000000000e+002],[ 0.,
6.5746697810243404e+002, 2.3950000000000000e+002], [0., 0., 1. ]])
def decodeDisplay(image):
barcodes = pyzbar.decode(image)
for barcode in barcodes:
# 提取二维码的边界框的位置
# 画出图像中条形码的边界框
(x, y, w, h) = barcode.rect
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
# 提取二维码数据为字节对象,所以如果我们想在输出图像上
# 画出来,就需要先将它转换成字符串
barcodeData = barcode.data.decode("utf-8")
barcodeType = barcode.type
# 绘出图像上条形码的数据和条形码类型
text = "{} ({})".format(barcodeData, barcodeType)
cv2.putText(image, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX,
.5, (0, 0, 125), 2)
# 向终端打印条形码数据和条形码类型
print("[INFO] Found {} barcode: {}".format(barcodeType, barcodeData))
return image
def detect():
camera = cv2.VideoCapture(0)
while True:
# 读取当前帧
ret, frame = camera.read()
#frame = cv2.imread("/home/ucar/Documents/temp/1.png")
# 转为灰度图像
dst=cv2.undistort(frame,mK,mDistCoef,None,mK)
gray = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY)
im = decodeDisplay(gray)
cv2.waitKey(5)
cv2.imshow("camera", im)
camera.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
detect()
(三)效果展示
标定前:
标定后:
(四)理论知识——相机模型
光会用还不够,我们应该至少了解为什么需要标定,标定有什么用。
简单来说,标定是为了能够从空间点的像素坐标映射到世界坐标,这是3D立体视觉必须经过的过程。这一过程需要三步,第一步从畸变的像素坐标映射到去畸变的像素坐标,需要用到畸变矩阵mDistCoef;第二步从去畸变的像素坐标映射到相机坐标,需要用到相机内参数矩阵mK;第三步从相机坐标映射到世界坐标,需要用到相机外参数矩阵,也就是相机位姿变换矩阵。下图展示了第二步和第三步的过程。
更详细的内容请参考:机器视觉的相机标定到底是什么?