对 《OpenCV3编程入门》第一章的学习笔记:理解什么是计算机视觉,什么是OpenCV,以及其中的联系等等。
PS:此书为2014年出版,opencv的版本和接口也与现在有些不一致了,作此笔记主要是学习opencv基本理念与操作思路原理,感谢浅墨大神,这本书会和浅墨的思想一起历久弥新。
PS:作为学习笔记,我的思路是首先将此书读薄,然后去记忆关键知识点,形成自己学习opencv的架构
思维导图如下:
目录
1.1 OpenCV周边概念认知
1.1.1 图像处理、计算机视觉与OpenCV
图像处理是指计算机对图像进行分析,从而达到所需的效果,一般包含图像压缩、增强和复原,匹配、描述和识别3个部分。
图像处理一般指数字图像处理,数字图像是指用工业相机、摄像机、扫描仪等设备经过拍摄得到的一个大的二维数组。该数组的元素称为像素,其值称为灰度值。而数字图像处理是通过计算机对图像进行去噪、增强、复原、分割、提取特征等处理的方法和技术。
计算机视觉是指用摄像机和电脑代替人眼对目标进行识别、跟踪和测量等机器视觉,并进一步做图形处理。
图像处理和计算机视觉的区别在于:图像处理侧重于“处理”图像——如增强、还原、去噪、分割等等;而计算机视觉重点在于使用计算机来模拟人的视觉,因此模拟才是计算机视觉领域的最终目标。
1.1.2 OpenCV概述
OpenCV(Open Source Computer Vision Library),是基于开源发行的跨平台计算机视觉库,它实现了图像处理和计算机视觉方面的通用算法。
OpenCV于1999年由Intel建立,如今由Willow Garage支持。它是一个基于开源发行的跨平台计算机视觉库,可以运行在Linux、Windows、Mac OS、Android、iOS等操作系统上。OpenCV由一系列C函数和C++类构成,轻量且高效,同时提供C++、Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。
OpenCV的设计目标是执行速度尽量快,主要关注实时应用。它采用优化的C/C++代码编写,能够充分利用多核处理器的优势;其主要目标是建立一个简单易用的计算机视觉框架,来帮助开发用户更快更便捷地设计更复杂的计算机视觉应用。
OpenCV覆盖了计算机视觉的多个应用领域,如工厂产品检测、医学成像、信息安全、用户界面、摄像机标定、立体视觉和机器人等。
OpenCV官网主页:Home - OpenCV
OpenCV Github主页:https://github.com/Itseez/opencv
OpenCV 开发板Wiki主页:http://code.opencv.org
1.1.3 起源及发展
OpenCV项目最早由Intel公司于1999年启动,旨在促进CPU密集型应用。
在Intel的性能库团队的帮助下,OpenCV实现了一些核心代码和算法,并发给了Intel俄罗斯的库团队,由此,OpenCV的诞生,发源于Intel,实现和优化于俄罗斯。
在开始之初,OpenCV有以下三大目标:
- 为基本的视觉应用提供开放且优化的开源代码,避免“闭门造车”
- 提供通用框架传播视觉知识,代码易读且可改写
- OpenCV库采用的协议不要求商业产品继续开发代码,促进基于视觉的商业应用发展
1.1.4 应用概述
- 人机交互
- 物体识别
- 图像分区
- 人脸识别
- 动作识别
- 运动跟踪
- 机器人
1.2 OpenCV基本架构分析
通过OpenCV安装路径下的include目录里面的头文件的分类存放,来一窥OpenCV的组件架构。
进入到..\opencv\build\include目录,可以看到opencv和opencv2这两个文件夹,opencv为旧版头文件,opencv2为新版头文件
在..\opencv\build\include\opencv2目录下,有个名为opecv_modules.hpp的文件,存放的是OpenCV2中与新模块构造相关的说明代码,其定义了OpenCV2中所有组件的宏
此方法应与opencv3一致,只是更新换代的区别。
模块名称 | 模块内容 |
---|---|
【calib3d】 | Calibration(校准)和3D这两次的组合缩写。主要是相机校准和三维重建相关的内容,包括基本的多视角几何算法、单个摄像头标定、物体姿态估计、立体相似性算法、3D信息的重建等。 |
【contrib】 | Contributed/Experimental Stuf的缩写。新增人脸识别、立体匹配、人工视网膜模型等技术。 |
【core】 | 核心功能模块
|
【imgproc】 | Image和Process这两个单词的组合缩写。图像处理模块。
|
【features2d】 | 2D功能框架
|
【flann】 | 高维的近似近邻快速搜索算法库
|
【gpu】 | 运用GPU加速的计算机视觉模块 |
【highgui】 | 高层GUI图形用户界面,包含媒体的输入输出、视频捕捉、图像和视频的编码解码、图形交互界面的接口等内容。 |
【legacy】 | 一些已废弃的代码库 |
【ml】 | 机器学习模块,基本上是统计模型和分类算法
|
【nonfree】 | 一些具有专利的算法模块,包含特征检测和GPU相关的内容 |
【objdetect】 | 目标检测模块,包含Cascade Classification(级联分类)和Latent SVM这两部分。 |
【ocl】 | 运用OpenCL加速的计算机视觉组件模块 |
【photo】 | 包含图像修复和图像去噪两部分 |
【stitching】 | 图像拼接模块
|
【superres】 | 超分辨率技术的相关功能模块 |
【ts】 | OpenCV测试相关代码 |
【video】 | 视频分析组件,包括运动估计、背景分离、对象跟踪等视频处理相关内容 |
【Videostab】 | 视频稳定相关组件 |
可以这么说,OpenCV就是多模块组合的SDK。
1.3 OpenCV3带来了什么
这部分可能有点过时了,就跳过了,有兴趣的读者可以读读文中相关部分。
1.4 OpenCV的下载、安装与配置
同理,根据自身设备进行下载安装。
我使用的是ROS系统,20.04 noetic,那么使用以下命令就可以进行安装了
$ sudo apt install ros-noetic-vision-opencv libopencv-dev python3-opencv
默认安装的版本是opencv4,安装好后的文件放置在/usr/include/opencv4/opencv2中,上文提及到的opecv_modules.hpp同样放置在此文件夹中,其中涉及的模块会有所不同
#define HAVE_OPENCV_ARUCO
#define HAVE_OPENCV_BGSEGM
#define HAVE_OPENCV_BIOINSPIRED
#define HAVE_OPENCV_CALIB3D
#define HAVE_OPENCV_CCALIB
#define HAVE_OPENCV_CORE
#define HAVE_OPENCV_DATASETS
#define HAVE_OPENCV_DNN
#define HAVE_OPENCV_DNN_OBJDETECT
#define HAVE_OPENCV_DNN_SUPERRES
#define HAVE_OPENCV_DPM
#define HAVE_OPENCV_FACE
#define HAVE_OPENCV_FEATURES2D
#define HAVE_OPENCV_FLANN
#define HAVE_OPENCV_FREETYPE
#define HAVE_OPENCV_FUZZY
#define HAVE_OPENCV_HDF
#define HAVE_OPENCV_HFS
#define HAVE_OPENCV_HIGHGUI
#define HAVE_OPENCV_IMG_HASH
#define HAVE_OPENCV_IMGCODECS
#define HAVE_OPENCV_IMGPROC
#define HAVE_OPENCV_LINE_DESCRIPTOR
#define HAVE_OPENCV_ML
#define HAVE_OPENCV_OBJDETECT
#define HAVE_OPENCV_OPTFLOW
#define HAVE_OPENCV_PHASE_UNWRAPPING
#define HAVE_OPENCV_PHOTO
#define HAVE_OPENCV_PLOT
#define HAVE_OPENCV_QUALITY
#define HAVE_OPENCV_REG
#define HAVE_OPENCV_RGBD
#define HAVE_OPENCV_SALIENCY
#define HAVE_OPENCV_SHAPE
#define HAVE_OPENCV_STEREO
#define HAVE_OPENCV_STITCHING
#define HAVE_OPENCV_STRUCTURED_LIGHT
#define HAVE_OPENCV_SUPERRES
#define HAVE_OPENCV_SURFACE_MATCHING
#define HAVE_OPENCV_TEXT
#define HAVE_OPENCV_TRACKING
#define HAVE_OPENCV_VIDEO
#define HAVE_OPENCV_VIDEOIO
#define HAVE_OPENCV_VIDEOSTAB
#define HAVE_OPENCV_VIZ
#define HAVE_OPENCV_XIMGPROC
#define HAVE_OPENCV_XOBJDETECT
#define HAVE_OPENCV_XPHOTO
1.5 快速上手OpenCV图像处理
1.5.1 读取图像与图像显示
读取图像只需要使用imread函数即可,图像显示只需要使用imshow函数即可。
#!/usr/bin/env python3
import rospy
import cv2
rospy.init_node("showimg")
img = cv2.imread("/home/spark/Pictures/bluebox.png")
cv2.imshow('box',img)
cv2.waitKey(0)
然后在终端输入以下命令:
rosrun opencv_learn showimg.py
那么,对代码部分进行解析:
- #!/usr/bin/env python3 #告诉操作系统执行这个脚本的时候,调用/usr/bin下的python3解释器
- import rospy #导入ros python库
import cv2 #导入opencv的库- rospy.init_node("showimg") #初始化ros节点,创建名为showmsg的节点
- img = cv2.imread("/home/spark/Pictures/bluebox.png") #使用imread读取绝对路径下的图片文件
- cv2.imshow('box',img) #使用imshow函数,后面接的是显示的窗口名和显示对象
- cv2.waitKey(0) #调用waitKey函数等待按键按下,以便让窗口一直显示,直到有按键按下
由于在显示过程中,图像窗口过于大,影响到观察与操作了,所以添加窗口调节大小的部分:
#!/usr/bin/env python3
import rospy
import cv2
rospy.init_node("showimg")
img = cv2.imread("/home/spark/Pictures/bluebox.png")
cv2.namedWindow("box",0)
cv2.resizeWindow("box",500,500)
cv2.imshow('box',img)
cv2.waitKey(0)
- cv2.namedWindow("box",0) # 创建一个空窗口,名为box
- cv2.resizeWindow("box",500,500) # 调整窗口的大小,指定窗口的名称,设置宽度与高度
如此,就能方便用户观察了。
1.5.2 图像腐蚀
图像腐蚀是指用图像中的暗色部分“腐蚀”掉图像中的高亮部分。
使用getStructuringElement函数获取指定形状和尺寸结构元素,代表进行腐蚀操作时使用的结构类型。
#!/usr/bin/env python3
import rospy
import cv2
rospy.init_node("showimg")
img = cv2.imread("/home/spark/Pictures/bluebox.png")
cv2.namedWindow("erode_box",0)
cv2.resizeWindow("erode_box",500,500)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(15,15))
img = cv2.erode(img,kernel)
cv2.imshow('erode_box',img)
cv2.waitKey(0)
然后在终端输入以下命令:
rosrun opencv_learn erodeimg.py
基本的读取操作没有变化,只是在此基础上添加了腐蚀的操作
1. kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(15,15))
定义一个kernel核心,由getStructuringElement函数设定一个结构类型
cv2.getStructuringElement(shape, ksize)
- shape:代表形状类型
- cv2. MORPH_RECT:矩形结构元素,所有元素值都是1
- cv2. MORPH_CROSS:十字形结构元素,对角线元素值都是1
- cv2. MORPH_ELLIPSE:椭圆形结构元素
- ksize:代表形状元素的大小
2. img = cv2.erode(msg,kernel)
dst = cv.erode( src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )
其中:
- src 是需要腐蚀的原始图像
- kernel 代表腐蚀操作时所采用的结构类型。
- anchor 代表element结构中锚点的位置,默认为(-1,-1),在核的中心位置。
- iterations 是腐蚀操作的迭代的次数,默认为1
- borderType 代表边界样式
1.5.3 图像模糊
使用均值滤波操作的blur函数
#!/usr/bin/env python3
import rospy
import cv2
rospy.init_node("blurimg")
img = cv2.imread("/home/spark/Pictures/bluebox.png")
cv2.namedWindow("blurbox",0)
cv2.resizeWindow("blurbox",500,500)
img = cv2.blur(img,(15,15))
cv2.imshow('blurbox',img)
cv2.waitKey(0)
dst = blur(src, ksize, dst=None, anchor=None, borderType=None)
均值滤波器,也称低通滤波器
顾名思义,均值滤波器即对滤波核内的数据求均值,然后将这个值赋值给矩阵核心位置。
均值滤波器可以使用cv2.blur() 方法实现
- src:图像
- ksize:滤波核大小,使用元组表示,如(a,b)a表示height(高度),b表示width(宽度)。
- anchor:波核锚点
- borderType:边界类型
详细使用方法可查看
1.5.4 边缘检测
使用canny进行边缘检测。首先载入图像,将其转成灰度图,再使用blur函数进行图像模糊以降噪,再使用canny函数进行边缘检测。
#!/usr/bin/env python3
import rospy
import cv2
rospy.init_node("cannyimg")
img = cv2.imread("/home/spark/Pictures/bluebox.png")
cv2.namedWindow("cannyimg",0)
cv2.resizeWindow("cannyimg",500,500)
# gray
gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# blur
blur_img = cv2.blur(gray_img,(10,10))
#cv2.imshow('blur_img',blur_msg)
# canny
Canny_img = cv2.Canny(blur_img,60,110)
cv2.imshow('cannyimg',Canny_img)
cv2.waitKey(0)
Canny 边缘检测分为如下几个步骤:
步骤 1:去噪。噪声会影响边缘检测的准确性,因此首先要将噪声过滤掉。
步骤 2:计算梯度的幅度与方向。
步骤 3:非极大值抑制,即适当地让边缘“变瘦”。
步骤 4:确定边缘。使用双阈值算法确定最终的边缘信息。
edges = cv.Canny( image, threshold1, threshold2[, apertureSize[, L2gradient]])
其中:
- edges 为计算得到的边缘图像。
- image 为 8 位输入图像。
- threshold1 表示处理过程中的第一个阈值。
- threshold2 表示处理过程中的第二个阈值。
- apertureSize 表示 Sobel 算子的孔径大小。
- L2gradient 为计算图像梯度幅度(gradient magnitude)的标识。其默认值为 False。如果为 True,则使用更精确的 L2 范数进行计算(即两个方向的导数的平方和再开方),否则使用 L1 范数(直接将两个方向导数的绝对值相加)。
1.6 OpenCV视频操作基础
利用VideoCapture对视频进行读取显示,以及调用摄像头。
1.6.1 读取并播放视频
我暂时还没用到使用opencv处理视频的情况,所以暂且留住,待后续涉及到视频的操作的时候会补上。文中也是用简单的几个语句即可实现视频的载入和读取视频帧了。
1.6.2 调用摄像机采集图像
由于是在ROS上执行,采用订阅话题的方式获取摄像机的画面,然后使用opencv进行处理,如灰度化后的canny边缘检测。
在运行程序前要记得启动摄像机。
#!/usr/bin/env python3
import rospy
import cv2
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
def image_cb(img):
prime_img = CvBridge().imgmsg_to_cv2(img, "bgr8")
cv2.imshow("prime_img",prime_img)
cv2.namedWindow("cannyimg",0)
cv2.resizeWindow("cannyimg",500,500)
# gray
gray_img = cv2.cvtColor(prime_img,cv2.COLOR_BGR2GRAY)
# blur
blur_img = cv2.blur(gray_img,(10,10))
#cv2.imshow('blur_img',blur_msg)
# canny
Canny_img = cv2.Canny(blur_img,60,110)
cv2.imshow('cannyimg',Canny_img)
cv2.waitKey(1)
rospy.init_node('camera_canny', anonymous=False)
rospy.Subscriber("/camera/color/image_raw", Image, image_cb, queue_size=1)
rospy.spin()
对上面的代码进行一下解释吧。
首先导入头文件部分:
from sensor_msgs.msg import Image # 传入的图片消息格式
from cv_bridge import CvBridge # CVBridge 主要用作将ros的图像格式转换为opencv能够处理的格式,下文会有所提及
接下来是创建订阅者部分,负责接受摄像机的图像画面:
rospy.Subscriber("/camera/color/image_raw", Image, image_cb, queue_size=1) rospy.spin()
订阅的是彩色图像画面,数据类型为Image,接受到信息的时候就会进入到image_cb回调函数进行处理了。使用订阅时记得在后面加上rospy.spin()语句,作为等待服务的循环语句,不然程序接受不到信息时就会直接跳出了。
最后是回调函数image_cb中的内容解释:
def image_cb(img):
prime_img = CvBridge().imgmsg_to_cv2(img, "bgr8")
cv2.imshow("prime_img",prime_img)
cv2.namedWindow("cannyimg",0)
cv2.resizeWindow("cannyimg",500,500)
# gray
gray_img = cv2.cvtColor(prime_img,cv2.COLOR_BGR2GRAY)
# blur
blur_img = cv2.blur(gray_img,(10,10))
#cv2.imshow('blur_img',blur_msg)
# canny
Canny_img = cv2.Canny(blur_img,60,110)
cv2.imshow('cannyimg',Canny_img)
cv2.waitKey(1)
操作和之前的canny边缘检测类似,只是在处理传入的图像数据img之前,需要使用cvbridge将图像格式进行一次变换,转换成opencv能够处理的图像格式,如8进制的bgr图像。
cv2.waitKey()也需要从原来的0改成1,否则只会读取视频中的一帧后等待。
至此,学习完了书中的第一章,并将其应用在ROS noetic 的opencv4中。