Skip to content

Latest commit

 

History

History
348 lines (276 loc) · 20.6 KB

README.md

File metadata and controls

348 lines (276 loc) · 20.6 KB

《OpenCV4快速入门》(Rust 语言版)

基于冯振版的《OpenCV4快速入门》编写的代码,项目中的代码均本地编译调试运行。

C++ 版本示例可参考这里:https://github.com/lining1111/OpenCV4QuickStartGuide

也可以借鉴:https://github.com/kyunghyunHan/open_cv

chapter1

工欲善其事,必先利其器。
Image Watch 工具 是VSCode上的插件,用于查看图像。
Clion上有 Opencv Image Viewer 工具,用于查看图像。

OpenCV各模块说明

calib3d:        相机标定相关
core:           核心功能模块,主要包含opencv库的基础结构和基本操作。
dnn:            深度学习模块
features2d:     二维特征检测点检测、描述、匹配等
flann:          最近邻匹配、聚类等。
gapi:           该模块对图像处理算法做了加速处理
highgui:        图像显示、按钮、鼠标等操作。
imgcodecs:      负责图像文件读写,如图像读取与保存
imgproc:        图像处理函数
ml:             机器学习相关算法
objdetect:      目标检测,包括Cascade face detector; latent SVM; HOG等
photo:          用于计算摄影处理和恢复照片的算法
stitching:      图像拼接,利用图像特征点进行图像拼接
video:          视频处理,包括背景分割、视频跟踪等
videoio:        负责视频文件的读取和写入

chapter2

VideoWrite中有打开摄像头的动作,由于代码是在虚拟机中,其实虚拟机是要做如下操作的
1、在VM的右下角,将摄像头设备接入到虚拟机内,这时在终端输入 ls /dev/video tab后会有相应的设备符出现
2、在VM中的虚拟机->设置中,将usb设备的兼容性这里改为usb3.1,如果不做,即使1做了也会出现打不开设备的情况

ubuntu中安装cheese,打开摄像头。
其实也可以在系统中安装ros2,用ros2打开摄像头。安装ros2除了可以进行机器人开发外,其实它的数据统一及分发思想对编程是有帮助的

chapter3

仿射变换---图形的旋转    确定旋转角度和旋转中心,之后确定旋转矩阵,最终通过仿射变换(warpAffine())完成图形旋转。
    仿射变换就是图像的旋转、平移和缩放操作的统称,可以表示为线性变换和平移变换的叠加。
    仿射变换的数学表示就是先乘以一个线性变换矩阵,再加上一个平移向量,其中线性变换矩阵为2x2的矩阵,平移向量为2x1的向量。
    仿射变换的矩阵形式如下:
    M = [a, b; c, d]
    T = [tx, ty]
    M*[x, y] + T = [ax + bx + tx, cx + dy + ty]
    知道中心点、角度、比例,用 getRotationMatrix2D()
    仿射变换又称为三点变换,如果已知变换前后两张图像中三个像素点的对应关系,就可以求得仿射变换中的变换矩阵
    getAffineTransform()函数用于求取仿射变换矩阵。

透视变换---图像的投影    按照物体成像投影规律进行变换,即将物体重新投影到新的成像平面。常用于机器人视觉导航研究中
    通过透视变换实现对物体图像的校正,在透视变换中,透视前的图像和透视后的图像之间变换关系可以用一个3x3的矩阵表示,
    
    该矩阵可以通过两幅图像中的4个对应点的坐标求取,因此透视变换又称作四点变换。getPerspectiveTransform()函数用于求取透视变换矩阵。
    调用warpPerspective()函数完成透视变换。

OpenCV中图像的深拷贝和浅拷贝:深拷贝copyTo()不对原图进行修改,而浅拷贝copy()会对原图进行修改。

高斯金字塔,大尺寸-->小尺寸,pyrDown()函数实现
拉普拉斯金字塔,小尺寸-->大尺寸,pyrUp()函数实现

chapter4

图像直方图是图像处理中非常重要的像素统计结果,图像直方图不再表征任何的图像纹理信息,而是对图像像素的统计。
由于同一物体无论是旋转还是平移在图像中都具有相同的灰度值,因此直方图具有平移不变性、放缩不变性等优点,
因此可以用来查看图像整体的变化形式,例如图像是否过暗、图像像素灰度值主要集中在哪些范围等,
在特定的条件下也可以利用图像直方图进行图像的识别,例如对数字的识别。

chapter5

图像卷积,卷积核是图像处理中常用的工具,卷积核可以实现图像的平滑、锐化、边缘检测、图像滤波等操作。
OpenCV中卷积核的定义:
Mat kernel(rows, cols, CV_32F, Scalar(0));
其中rows和cols分别表示卷积核的行数和列数,CV_32F表示卷积核的数据类型,Scalar(0)表示卷积核的初始值。

图像噪声:
椒盐噪声:随机将图像像素点上的像素值变为0或255
高斯噪声:高斯分布的随机噪声
乘性噪声:图像像素点上的像素值乘以一个随机数
泊松噪声:图像像素点上的像素值服从泊松分布

边缘检测:
Sobel算子:
Scharr算子:
Canny算子:

chapter6

图像像素距离:
欧拉距离:两个像素点之间的直线距离
街区距离:两个像素点之间的X、Y坐标的距离之和
棋盘距离:两个像素点X、Y距离的最大值
由于distanceTransform()函数是计算图像中非0像素距离0像素的最小距离,
而图像中0像素表示黑色,因此为了保证能够清楚的观察到距离变换的结果,
不建议使用尺寸过小或者黑色区域较多的图像,否则distanceTransform()函数处理后的图像中几乎全为黑色,不利于观察

图像连通域:图像中具体相同像素值并且位置相邻的像素点集合称为连通域。
连通域分析:在图像中,对连通域进行分析和处理,可以实现对图像的分割、填充、连接等操作。应用场景:车牌识别、文字识别、目标检测等

与卷积运算类似,都要模板矩阵来控制运算结果,图像的腐蚀和膨胀中,这个模板称为元素结构。
结构元素可以任意指定图像的中心点,并且结构元素的尺寸和具体内容都可以根据需求自己定义。
定义结构元素之后,将结构元素的中心点依次放到图像中每一个非0元素处,
腐蚀:如果此时结构元素内所有的元素所覆盖的图像像素值均不为0,则保留结构元素中心点对应的图像像素,否则将删除结构元素中心点对应的像素
膨胀:如果原图像中某个元素被结构元素覆盖,但是该像素的像素值不与结构元素中心点对应的像素点的像素值相同,那么将原图像中的该像素的像素值修改为结构元素中心点对应点的像素值

图像形态学应用:
开运算:先进行腐蚀操作,再进行膨胀操作。消除噪声点,去除小的连通域,保留大的连通域。
闭运算:先进行膨胀操作,再进行腐蚀操作。填充小的连通域,消除大的连通域。
形态学梯度:基本梯度、内部梯度、外部梯度。
基本梯度:原图像膨胀后图像和腐蚀后图像间的差值图像,
内部梯度:图像是原图像和腐蚀后图像间的差值图像,
外部梯度:膨胀后图像和原图像间的差值图像

顶帽运算:原图像与开运算结果的差。往往用来分离比邻近点亮一些的斑块
底帽运算:(黑帽运算)原图像与闭运算结果的差。往往用来分离比邻近点暗一些的斑块
图像细化:将图像的线条从多像素宽度减少为单位像素宽度。常在文字识别中使用。

chapter7

目标检测

HOG特征:HoughLines() HoughLinesP()
Canny边缘检测:Canny()
轮廓检测:

距的计算:几何距、中心距、Hu距

二维码检测,因为是apt安装的opencv,所以没有qrcode模块,需要自己安装,通过libzbar代替,修改了源码

chapter8

图像分割:漫水填充法、分水岭法、Grabcut法、Mean-Shift法
图像修复:inpaint()

chapter9

特征点:

chapter10

齐次坐标:
直角坐标系,有一个点 P(x,y,z),若有四个不同时为零的数(x1,y1,z1,k),与三维坐标存在关系:
x = x1/k, y = y1/k, z = z1/k
则称(x1,y1,z1,k)为点P的齐次坐标,k一般默认为1
齐次坐标转换的意义在于:能够和表示位姿关系的矩阵(旋转矩阵+位移矩阵)作乘法运算。所以位姿矩阵又叫齐次变换矩阵

相机的内参标定====
相机矩阵和畸变系统统称为相机内参:
相机矩阵:包括焦距(fx,fy),光学中心(Cx,Cy),
        完全取决于相机本身,是相机的固有属性,只需要计算一次,可用矩阵表示如下:
        [fx,    0,  Cx; 
        0,      fy, cy; 
        0,      0,  1];
畸变系统:包括5个参量 k1,k2,p1,p2,k3
findChessboardCorners
find4QuadCornerSubpix
calibrateCamera
前面2个函数都是为第三个函数的输入量作准备的。
从后向前反推...
calibrateCamera 需要3个输入量:
1、点在世界坐标系中的N*3的数组列表(N为点数)
2、点在图像坐标系中的N*2的数组列表(N为点数)
3、图像的尺寸,即图像的宽度和高度
calibrateCamera 返回值:
1、相机内参矩阵
2、相机畸变系数

需要多个图进行前两个函数的计算的原因====
1、在单目相机的标定时,通常采用的是小孔模型,但是这个模型并不是真实的相机投影模型
(真实的相机镜头组往往比较复杂,并没有一个绝对的光心点),只是一个近似,如果一个相机做工比较靠谱,
那么它对应的小孔模型的参数(焦距,主点)会比较稳定,体现在标定结果上就是每次标定的内参数波动会比较小。
相机投影模型只是一个近似的小孔成像模型
2、通常在不同距离标定的相机内参数是不一样的,这个也是因为真实的相机投影过程和小孔模型之间的不是完全对应的,
有论文就分析过这个问题,用真实的镜头组模型计算所谓的小孔模型光心,然后给出了结论:在不同距离等效的相机光心点是不一样的。
3、相机参数标定的过程是非线性方程优化的过程,很难存在数值解,基本上都是近似解,而且优化目标是重投影误差,
因此在优化过程中,会存在很多组组合满足优化目标,也就是局部最小解。
当输入变化时,也就是不同条件下拍摄的棋盘格图像,会影响的是非线性方程的初始解,
也就是说,在解空间中的起始点不同,其优化结果是距离当前位置最近的满足优化目标条件的局部最小解。
虽然每一次求到的解,都是以不同起始点开始搜索的结果,但是其优化的目标函数都是一致的,也就是较小的重投影误差或者固定的迭代次数,
因此,最终优化的重投影误差自然会小,每一组不同的解都是一个局部最小解的组合,当你改变其中的某一个参数,即使改变较小,也会是重投影误差变大。
4、标定的结果其实和标定板的平整度,标定板的位置,在图片中覆盖的范围和光照条件等等因素都有很大的关系。
如果你是在同一时间同样的设备采集的,结果不同的原因应该跟标定数据中标定板的位置和姿态不同导致的。
建议在标定过程中选择光照条件比较好的位置,标定板一定要平整(非常重要),拍摄的时候最好固定位置,防止运动模糊,
所有拍摄的图片中的角点可以覆盖到整张图片,标定板距离相机的位置有远有近,标定板拍摄角度小于45度,并且标定板角点多的时候应该多采集标定图片数量。
重投影误差的话按道理是训练集上面的误差,客观评测的话应该再另外采集测试集。
不同角度,距离拍摄棋盘格会导致角点在图像中的位置,密度,不完全一致,导致每一次的标定结果不一致。
比如一次标定中角点更集中于图像边缘,另一次标定中角点更集中于图像中心。
鱼和熊掌往往不能兼得,标定板占据画面的大部分位置往往意味着角点比较稀疏,在中心位置不够密集。
单纯的对标定结果其中某一个参数横向比较没有太大对意义,应该考虑参数之间的关联。
如果标定某些参数之间的关联不正常(比如很强的线性关系),往往代表着标定过程中可能出现了错误。

我们实际做标定的时候需要注意采集数据的一些要点:标定板的平整度,棋盘格大小(尽量大),图像画质,角点分布是否均匀,
角点是否覆盖了整个画面。这些因素都会影响单次标定相机内参的稳定性。

关于相机标定结果好坏的评价方法,不仅仅有重投影误差,很多情况下,重投影误差小,并不能说明标定参数好,要根据应用来评价的,
例如想对畸变比较大的图像去畸变,那就通过对边界直线的矫直效果看标定参数的好坏;
如果想用双目进行深度计算,可以看用标定参数对左图右图分别进行畸变矫正和极线矫正后,左右图上的特征点能否对齐,
也可以看看立体匹配的效果,看看误匹配区域的大小等等。

相机标定的过程,其实是分离线性模型参数和非线性模型参数的过程,至于分离的好不好,可以通过上述评价方法来评判。
对于不同的输入,标定参数会相差很大,应用效果也会差别很大,
参看opencv自带的标定图像和Halcon自带的标定图像,棋盘格摆放位置看似没什么规律或者要点,但是在精简标定图像的过程就会发现,
其实,有些图片对标定参数的影响是主要的,也就是说,常说的15张-25张标定图像中,有很多图像是无效图像,
这一点也可以解释,为什么用立体标定块,或者三立面标定板也可以得到比较好的标定结果。
至于什么样的标定图像,即棋盘格怎么摆放,角点密度怎么样,棋盘格在画幅中所占比例等等,能够得到比较好的标定参数,也就是什么样的图片是有效图片。

需要多个图进行前两个函数的计算===张正友标定法


矩阵的知识储备:
矩阵为mxn的二维数组,其中m为行数,n为列数。
矩阵的点乘,Dot(),要求两个矩阵的行向量或列向量相等,或者同时相等,矩阵对应各元素相乘
矩阵的叉乘,Multiply(),要求两个矩阵的维度相同,即mxn和nxm 得到mxm的矩阵,中间相同留两边

坐标系:
--- 右手坐标系:是在空间中规定直角坐标系的方法之一。 
    此坐标系中x轴,y轴和z轴的正方向是如下规定的:
    把右手放在原点的位置,使大拇指,食指和中指互成直角,
    把大拇指指向x轴的正方向,食指指向y轴的正方向时,中指所指的方向就是z轴的正方向
--- 左手坐标系:是在空间中规定直角坐标系的方法之一。
    此坐标系中x轴,y轴和z轴的正方向是如下规定的:
    把左手放在原点的位置,使大拇指,食指和中指互成直角,
    把大拇指指向x轴的正方向,食指指向y轴的正方向时,中指所指的方向就是z轴的正方向

笛卡尔坐标系为右手坐标系

单目相机标定

*** 相机系统的标定,分析相机模型,可以得到是线性系统,求解线性系统就是解n元一次方程组。 ***

单目相机标定,就是通过拍摄一些棋盘格图片,然后根据图片中的棋盘格角点信息,求出相机内参矩阵和畸变系数

先从直觉上体会下,照片上的像素怎么对应到相机坐标系下的三维坐标。
首先,像素转图像坐标系====
在相机正对图像平面的时候,像素点间距应该和实际的距离有一次函数关系,即===缩放关系===
x_pic = x_pixel * p1;//p1为系数1
y_pic = y_pixel * p2;//p2为系数2
同时因为像素坐标系从左上角开始,而图像坐标系是从中心点开始,所以存在位移关系,
且x轴的位移与宽度width相关,y轴的位移与高度height相关,即
x_pic = x_pixel * p1 + p3;//p3为x轴的位移 p3 = -width * p1 / 2
y_pic = y_pixel * p2 + p4;//p4为y轴的位移 p4 = -height * p2 / 2
其次,图像坐标系转相机坐标系====
相机坐标系和图像坐标系存在旋转关系,所以存在旋转关系,即M_rotate,可以由欧拉角获取的
eigen3库 Eigen::AngleAxisd(角度,轴)
最后,还要考虑像素坐标系的畸变。

相机坐标系转大地坐标系===
存在旋转和位移。

立体视觉:
相机的内参系数:反映了环境信息到图像信息之间的映射关系
===相机内参
---下面的是二维正对图像视角(左手坐标系)
像素坐标系:一幅图像在计算机存储中不同像素点的坐标,其原点位于图像左上角,x轴(u)向右,y轴(v)向下。
图像坐标系:图像坐标系是图像像素点在图像中的位置,原点位于图像的中心,x轴向右,y轴向下。
像素坐标系到图像坐标系的转换:见书P324
假设点P,像素坐标系为P_pixel(u,v),图像坐标系为P_pic(x,y),则有:

u =  x/dx + u0
v = y/dy + v0

其中
dx 表示一个像素的物理宽度,
dy 表示一个像素的物理高度,
u0=width/2,(width为图像的宽度),
v0=height/2,(height为图像的高度),
后续为了方便计算,用齐次坐标表示
    C = [1/dx,  0,      u0;
        0,      1/dy,   v0;
        0,      0,      1]

像素坐标    vecPixel = [u,v,1]
图片坐标    vecPic = [x,y,1]
        vecPixel = C * vecPic(叉乘)---------------------------(1)

相机坐标系:用来描述相机观测环境和相机之间相对位姿关系的参考系。(右手坐标系)
单目相机模型,多采用针孔模型进行描述,针孔模型中,(右手坐标系)相机坐标系的原点位于相机的光心,x轴向右,y轴向下,z轴垂直于成像平面。
真实点位于z轴正向,图像点位于z轴负向。
通过小孔成像模型,可以得到图像坐标系和相机坐标系之间的转换关系。
P_camera(xc,yc,zc) P_pic(x,y,1),求点的映射关系,可以通过两个平面zx,zy分别求得x轴,y轴的映射关系
相机的焦距为f(平行光从透镜的光心入射时,透镜光心到光聚集之焦点的距离)
x = f*xc/zc
y = f*yc/zc
即有下面的齐次坐标表示
vecCamera = [xc,yc,zc]
vecPic = [x,y,1]
K1 = [f,0,0;
    0,f,0;
    0,0,1]

zc*vecPic = K1 * vecCamera(叉乘)-------------------------------(2)

综合上面的(1)(2)得到

zc*vecPixel = zc* C * vecPic = C*K1*vecCamera(叉乘)
整理得到
C*K1=K = [fx,0,u0;
        0,fy,v0;
        0,0,1]

zc*vecPic = K*vecCamera(叉乘)------------------------------------(3)
其中 
fx=f/dx,
fy=f/dy,
u0=width/2,
v0=height/2,
f 为焦距,
dx 表示一个像素的物理宽度,
dy 表示一个像素的物理高度,

可以看出K只与相机的内部参数相关,因此K为相机内参矩阵

===相机外参
假设点P 世界坐标系P_world(xw,yw,zw),相机坐标系P_camera(xc,yc,zc),

相机坐标系到世界坐标系:
相机外参矩阵:P331 矩阵R、t R为3x3的旋转矩阵,t为3x1的平移向量
---相机外参矩阵:
    R = [R11,R12,R13;
        R21,R22,R23;
        R31,R32,R33]
    t = [t1,t2,t3]
vecWorld = [xw,yw,zw,1]
vecCamera = [xc,yc,zc]
vecCamera = R*vecWorld + t
整理下得到
vecCamera = [R t]*(vecWorld,1)--------------------------------------(4)
综合(3)(4)得到
zc*vecPic = K*[R t]*(vecWorld,1)------------------------------------(5) 注意这里修订了书中P332中(10-8)的公式


===相机畸变
单目相机的标定:calibrateCamera(),参考程序myCalibrateCamera

双目视觉

双目标定:steroCalibrate(),参考程序myStereoCalibrate

chapter11

目标跟踪

chapter12

机器学习