书名:深度学习与计算机视觉 项目式教材
ISBN:978-7-115-64779-5
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
编 著 彭 飞 张 强
责任编辑 谢晓芳
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
本书基于国产自主可控龙芯处理器,系统地介绍计算机视觉领域的基本理论与实践,并结合当前主流的深度学习框架和龙芯平台以项目式教学的形式讲述任务的实施。本书主要包括OpenCV基础功能实战、深度学习框架的部署、计算机视觉技术基础知识、图像分类网络的部署、目标检测网络的部署、图像分割网络的部署、龙芯智能计算平台模型的训练和龙芯智能计算平台的推理部署等内容。通过阅读本书,读者能够了解和掌握深度学习在计算机视觉中的应用,并基于国产自主可控龙芯处理器进行工程实践。
本书适合深度学习与计算机视觉领域的从业者、深度学习与计算机视觉的爱好者阅读,也可作为高等院校计算机相关专业的教材。
人工智能正引领产业发展的新一轮技术革命,成为国家产业发展的战略控制点,对经济发展、社会进步和人类生活产生了深远影响。许多国家在战略层面对其予以高度重视,相关科研机构大量涌现,各大科技“巨头”大力布局,新兴企业迅速崛起,人工智能技术开始广泛应用于各行各业,展现出可观的商业价值和巨大的发展潜力。
近几年,国家对人工智能产业的重视达到空前的高度,不断加强对人工智能技术的研发和应用,陆续出台了一系列关于人工智能发展的政策和措施。我国对人工智能的重视主要体现为以下几点。
● 将人工智能技术作为国家战略发展的重要技术,发布了《新一代人工智能发展规划》,明确了人工智能的发展目标和路线。
● 加大对人工智能研发的投入,鼓励各行业、企业研发和应用人工智能技术,推动人工智能与实体经济深度融合等。
● 将人工智能作为重点发展的产业之一,鼓励各行业、企业在人工智能领域进行投资和创新。政府支持人工智能产业的发展,推动人工智能技术在教育、金融、医疗、交通等领域的应用。
● 重视人才培养,鼓励高校和科研机构加强人工智能领域的教育与研究。鼓励企业加强人才培养和引进,提高其在人工智能领域的专业技能和创新能力。
《促进新一代人工智能产业发展三年行动计划(2018—2020 年)》强调,要形成安全可信、自主可控的基础软硬件技术生态,布局若干人工智能算力中心,形成广域协同的智能计算平台,从而提供普惠算力和服务支撑。国家发展改革委等部门印发的《关于促进电子产品消费的若干措施》提到,鼓励科研院所和市场主体积极应用国产人工智能(AI)技术提升电子产品智能化水平,增强人机交互便利性。各级地方政府也在大力推动人工智能国产化自主可控技术的实现,如《北京市加快建设具有全球影响力的人工智能创新策源地实施方案(2023—2025年)》提到,要推动一批国产替代,技术攻坚取得新突破。
人工智能算力布局初步形成,国产CPU处理器芯片、人工智能芯片和深度学习框架等基础软硬件产品的市场占比显著提升,算力、芯片等基本实现自主可控,全面兼容主流深度学习框架。人工智能算力资源并网互联,推动基础软硬件实现高质量自主可控。我国关于人工智能国产化的政策主要体现在以下几方面。
● 支持研发创新:鼓励企业、高校、科研机构等开展人工智能技术的研发和创新,提升国产化人工智能技术的水平和能力。
● 培育优质企业:支持培育具有自主知识产权的人工智能企业,鼓励企业完善产业生态,提升市场竞争力。
● 推进应用落地:鼓励企业和机构在各个领域推进人工智能应用落地,提升国产化人工智能产品的质量,增加其市场份额。
● 加强人才培养:加强人工智能领域人才的培养和引进,为国产化人工智能技术的发展提供人才保障。
● 加强安全保障:加强人工智能技术的安全保障,确保国产化人工智能产品和服务安全可靠。
龙芯中科技术股份有限公司(简称龙芯)一直以开放的态度面对人工智能技术的发展,积极投入人工智能深度学习平台的建设,围绕LoongArch自主指令集架构,从基础软硬件到算法及框架,再到最终的产品应用,形成了完整的人工智能技术栈,构建了多层的人工智能软件生态体系和全域异构硬件支撑体系,打造出自主可控的国产化龙芯智能计算平台,以满足人工智能在计算机视觉、自然语言处理、语音识别、智能交互、推荐算法、多模态交互等领域的应用场景需求。
在人工智能产业发展十分重要以及我国对自主可控技术的需求十分迫切的环境下,发展基于国产自主可控平台的人工智能技术势在必行。本书介绍计算机视觉技术的基本原理,并结合国产自主可控龙芯处理器阐述具体实践,让读者能够基于国产自主可控龙芯平台进行具体工程实践。
彭飞
2024年4月
本项目介绍OpenCV的主要模块,以及如何基于龙芯平台的OpenCV的基础功能完成相关任务。该项目的主要目标是帮助开发者熟悉并掌握OpenCV库的基本功能和使用方法,通过基于龙芯平台的实践应用,加深对计算机视觉领域的理解。
OpenCV是一种开源的计算机视觉库(Computer Vision Library,CVL),一直被广泛使用。OpenCV底层由C/C++语言实现,其上层提供了Python、Java、JavaScript、Ruby、MATLAB等语言的接口,可以实现多平台无缝兼容。OpenCV拥有超过2500种优化算法,包括一套全面的、经典的和先进的计算机视觉(Computer Vision,CV)和机器学习(Machine Learning,ML)算法,这些算法涉及图像识别和目标检测(object detection)等方面。目前,主要由来自微软、IBM、西门子、谷歌、英特尔、斯坦福大学、麻省理工学院、卡耐基-梅隆大学、剑桥大学等公司或大学的研究人员共同维护和支持OpenCV的开源库开发。
OpenCV包含稳定的主仓库,以及具有实验性质的或需为专利付费的opencv_contrib仓库。OpenCV模块中的硬件加速层提供了针对不同CPU和GPU的优化加速,以及不同的软件/硬件加速库。OpenCV不仅为不同的操作系统提供了视频I/O、文件I/O和用户界面(User Interface,UI),还提供了详细的用户指导手册以及大量的单元测试和例程。
OpenCV提供了许多强大的图像和视频处理功能。其主要模块如下。
● core模块。该模块为核心模块,包含基础数据结构、动态数据结构、算法(线性代数、快速傅里叶变换等相关算法)、绘图函数、XML/YAML文件I/O、系统函数和宏等。
● imgcodecs模块。该模块为图像编解码模块,用于读取、写入图像并对各种格式的图像进行编解码处理,包括JPEG、PNG、BMP等格式的图像。
● imgproc模块。该模块为图像处理模块,主要用于进行滤波操作、形态学处理、几何变换、色彩空间转换、直方图计算、结构形状分析、运动分析、特征检测、目标检测等。
● highgui模块。该模块为高级用户交互模块,包含图形用户界面(Graphics User Interface,GUI)、图像和视频I/O,提供了窗口操作功能,如创建显示图像或者视频的窗口、通过命令窗口响应键盘和鼠标事件、操作窗口中图像的某个区域等。
● features2d模块。该模块为二维特征检测与描述模块,主要用于图像特征检测、描述、匹配等。利用该模块可以从二维图像中检测和提取对象的特征。
● calib3d模块。该模块为三维重建模块,提供了三维重建功能,可根据二维图像创建三维场景。它主要用于完成相机标定、立体视觉模拟、姿态估计和三维物体重建等任务。
● video模块。该模块为视频模块,提供了光流法、运动模板、背景分离、目标跟踪等视频处理技术,以及视频分析(video analysis)功能,如分析视频中连续帧的运动、跟踪视频中的目标。该模块还提供了视频稳定处理功能,可解决拍摄视频时的抖动问题等。
● gapi模块。该模块为图形加速模块,可对图像算法做加速处理,主要用于图像和视频数据的高性能处理和计算,提供简化编程模型和优化图像处理的算法。
● objdetect模块。该模块为目标检测模块,不仅提供了目标检测功能,还提供了常见的目标检测算法和预训练的目标检测模型。
● ml模块。该模块为机器学习模块,提供了机器学习功能,包含常见的统计模型和分类算法等机器学习算法,如k近邻(k-Nearest Neighbors,kNN)、k均值聚类(k-Means Clustering)、支持向量机(Support Vector Machine,SVM)、神经网络(neural network)等。
● dnn模块。该模块为深度神经网络(Deep Neural Network,DNN)模块,提供了深度学习功能,支持Caffe、TensorFlow、PyTorch、DarkNet等主流的深度学习框架,用于加载和运行深度学习模型,从而可以进行图像分类(image classification)、目标检测、语义分割(semantic segmentation)等操作。
● ts模块。该模块为测试模块,主要包含OpenCV的单元测试和功能测试框架,用于验证OpenCV相关库的正确性和稳定性。
OpenCV还提供了许多其他模块,可以用于图像拼接、图像分割(image segmentation)、目标识别等。用户可以根据自己的需求组合使用这些模块,以完成特定的计算机视觉任务。
1999年,加里·布拉德斯基(Gary Bradski)在英特尔公司创建了计算机视觉库项目。该项目旨在提供通用的计算机视觉接口,并以开源的方式发布。
本节介绍OpenCV的版本演进。
OpenCV 1.x是最早的版本,这个版本提供了许多基本的图像处理功能和算法。OpenCV 1.x主要关注实时计算机视觉,并且强调图像处理的速度和准确性。OpenCV 1.x提供了多种图像处理和分析方法,包括滤波、边缘检测、形态学处理、测量分析等;支持多种格式的图像的读取、保存和显示,包括常见的BMP、JPEG、PNG等格式的图像;提供了多种图像变换和特征提取方法,如直方图均衡化、色彩空间转换、边缘检测等;支持多种窗口操作,如滑动窗口、固定窗口等,方便进行图像分析和检测等。
OpenCV 2.x是基于C++的版本,比OpenCV 1.x易用,具有更好的封装和自动内存管理功能。在OpenCV 2.x中,所有类和函数都包含在命名空间“cv”中。与OpenCV 1.x相比,OpenCV 2.x更加灵活和强大,具有更多的特性和算法。在OpenCV 2.x中,可以基于面向对象的思想使用各种函数库,使代码更加清晰和易于维护。
另外,OpenCV 2.x中增加了许多新的功能和算法,如视频分析、3D重建、机器学习等领域的功能和算法。OpenCV 2.x还支持多种语言接口,包括Python、Java和MATLAB等语言的接口。
OpenCV 3.x在OpenCV 2.x的基础上进行改进和扩展,具有更多的新特性和算法。OpenCV 3.x进一步加强了面向对象的编程思想,使代码更加简洁、易读和易于维护。OpenCV 3.x中增加了一些新的功能,例如深度学习、全景拼接、立体视觉等。此外,OpenCV 3.x还加强了与其他计算机视觉库(如Halcon、Media SDK等)的集成和互操作性。
OpenCV 4.x在OpenCV 3.x的基础上进行了改进和扩展,如OpenCV 4.x中添加了新的模块G-API,它可以作为基于图形的图像处理管线(pipeline)的引擎;OpenCV 4.x的dnn模块支持ONNX(Open Neural Network Exchange,开放神经网络交换)格式的网络;OpenCV 4.x将二维码检测器和解码器添加到objdetect模块中,并将稠密逆搜索(Dense Inverse Search,DIS)光流算法从opencv_contrib转移到video模块。
相对于OpenCV 3.x,OpenCV 4.x增加了很多新的功能和算法,如神经风格迁移、目标检测和跟踪等。OpenCV 4.x还加强了代码的优化和重构,提高了代码的执行效率。
OpenCV-Python是由原始OpenCV C++实现的Python包装器,是OpenCV库的Python接口。
Python是一种面向对象的、解释型的计算机高级程序设计语言。Python 凭借语法简洁、易于学习、功能强大、可扩展性强、跨平台等特点,成为继Java和C语言之后的又一热门程序设计语言。它的简单性和代码可读性使程序员能够用更少的代码实现功能。
但与C/C++相比,Python的执行速度较慢。Python可以使用C/C++轻松扩展,使用户可以使用C/C++编写计算密集型代码,并使用Python进行封装。使用OpenCV-Python主要有两大好处:第一,代码运行速度与原始C/C++代码的一样快,因为它在后台运行的实际是C++代码;第二,用Python编写代码比用C/C+容易。
OpenCV-Python需要使用NumPy库,OpenCV在程序中使用NumPy数组存储图像数据。
龙芯平台支持OpenCV,当前OpenCV开源社区已经支持LoongArch自主指令集架构(龙架构)。基于龙芯平台,用户可以编写程序、调用OpenCV接口来实现具体功能,如图像与视频I/O、二值图像分析与处理、颜色空间转换、视频对象分析与跟踪、边缘轮廓检测与提取、图像特征提取与匹配、深度学习模型推理等。
龙芯平台操作系统源中已经集成了OpenCV的相关软件包,用户只需要通过终端系统即可完成OpenCV的安装。如在Loongnix 20系统上,直接通过以下命令即可安装OpenCV。
sudo apt-get install libopencv-dev python3-opencv
基于龙芯平台,利用OpenCV实现读取图像、显示图像与保存图像的功能。
图像的读取、显示与保存是工程应用和学术研究中的基础操作,OpenCV提供用于完成这些基础操作的API函数,它们分别为imread()、imshow()、imwrite()和waitKey()。利用OpenCV实现图像的读取与显示的主要流程为引入OpenCV→读取图像→显示图像→等待用户输入。
OpenCV的imread()函数用于将文件中的图像读入内存,支持多种静态图像格式,如BMP、PNG、JPEG和TIFF等。imread()函数的完整格式如下。
img=cv2.imread(filename, flag)
其中,filename为文件名,flag为图像读取格式标志。若imread()函数正确读取图像,返回表示图像的NumPy数组;否则,返回NULL。
OpenCV的imshow(winname,mat)函数用于在指定的窗口中显示图像。若窗口已存在,图像直接显示在该窗口中;否则,新建一个名为winname的窗口,并显示mat参数对应的图像。
OpenCV的imwrite()函数用于将NumPy数组中保存的图像写入文件。
waitKey()函数的功能是等待用户输入。该函数的基本格式如下。
rv=cv2.waitKey([delay])
参数说明如下。
● rv:保存函数返回值。如果没有按某个键,返回−1;否则,返回所按键的对应ASCII值。
● delay:等待按键的时间(单位为ms)。若delay为负数或0,表示无限等待,其默认值为0;若设置了delay参数,等待指定时间后,waitKey()函数返回−1。
以下是使用OpenCV接口实现图像的读取、显示与保存的代码。
import cv2
# 使用imread()函数读取图片。0表示灰度图,1表示彩色图,16表示缩放后的灰度图,17表示缩放后的彩色图
img = cv2.imread("./test.jpg", 1)
# 显示
# cv2.imshow("imshowtest", img)
# 保存
cv2.imwrite("test1.jpg", img)
cv2.waitKey(0)
运行结果如图1-1所示。
▲图1-1 运行结果
基于龙芯平台,利用OpenCV和本地摄像头获取视频、显示视频和保存视频。
在计算机中,视频资源可以源自专用摄像机、网络摄像头,也可以源自本地视频文件或图像序列文件。视频处理的是运动图像,而不是静止图像。OpenCV的VideoCapture类和VideoWriter类是视频处理中重要的类,提供了视频处理功能,可以支持多种格式的视频文件。
VideoCapture类是捕获视频对象的类,支持返回获取的外部视频对象。通过对返回的外部视频对象进行读取,VideoWriter类可用作把视频对象保存至本地的程序接口,完成视频的显示、保存操作。
视频处理的基本操作步骤如下。
(1)以视频文件或者摄像头作为数据源,创建VideoCapture对象。
(2)调用VideoCapture对象的read()方法获取视频中的帧,这里每一帧都是一幅图像。获取视频的流程图如图1-2所示。
▲图1-2 获取视频的流程图
(3)调用VideoWriter对象的write()方法将帧写入指定的视频文件。保存视频的流程图如图1-3所示。
▲图1-3 保存视频的流程图
通过本地摄像头获取视频、显示视频并保存视频的代码如下。
import cv2
# 通过本地摄像头获取视频并保存至当前文件夹
# 创建VideoCapture对象,视频源为默认摄像头0
cap = cv2.VideoCapture(0)
# 检查摄像头是否成功打开
if (cap.isOpened() == False):
print("Error ")
# 默认分辨率取决于系统
# 将分辨率从float类型转换为int类型
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
# 定义编解码器并创建VideoWriter对象,把输出结果存储在r8c_SaveVideo.avi文件中
out = cv2.VideoWriter('Resources/r8c_SaveVideo.avi', cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'), 35,(frame_width, frame_height))
# 循环读取视频帧,直到视频结束
while True:
ret, frame = cap.read()
if ret == True:
# 将视频帧写入文件r8c_SaveVideo.avi
out.write(frame)
# 显示视频帧
cv2.imshow('frame', frame)
# 按Q键停止记录
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 跳出循环
else:
break
# 释放视频捕获器以及视频写对象
cap.release()
out.release()
# 关闭所有的窗口
cv2.destroyAllWindows()
视频获取与显示结果如图1-4所示。
▲图1-4 视频获取与显示结果
基于龙芯平台,利用OpenCV的相关函数实现常用的几何图形绘制。
在图像处理中,我们经常需要将某个感兴趣的区域用图形标注出来,以便在开发时观察和调试,尤其是在进行物体检测与物体跟踪时,绘图是必不可少的操作。OpenCV提供了一系列的几何图形绘制函数,可以实现在图像中绘制线段、矩形、圆形、椭圆形、多边形、文本等功能。
line()函数用于绘制线段,其基本格式如下。
cv2.line(img, start_point, end_point, color, thickness=0)
参数如下。
● img:指定需要绘制的图像。
● start_point:指定线段的起始坐标,必须是元组类型。
● end_point:指定线段的结束坐标,必须是元组类型。
● color:指定线条的颜色,必须是元组类型,通常使用BGR颜色值表示颜色,如(255, 0, 0)表示红色。
● thickness:指定线条的宽度,默认值为1,若设置为−1,表示绘制填充图形。
rectangle()函数用于绘制矩形,其基本格式如下。
cv2.rectangle(img, point1, point2, color, thickness=0)
参数如下。
● img:指定需要绘制的图像。
● point1:指定矩形左上角顶点的坐标,必须是元组类型。
● point2:指定矩形右下角顶点的坐标,必须是元组类型。
● color:指定线条的颜色,必须是元组类型。
● thickness:指定线条的宽度。
注意,该函数每调用一次,就会产生一个矩形,多次调用就会产生多个矩形。
circle()函数用于绘制圆形,其基本格式如下。
cv2.circle(img, center, R, color, thickness=0)
参数如下。
● img:指定需要绘制的图像。
● center:指定圆心坐标,必须是元组类型。
● R:指定圆形的半径。
● color:指定线条的颜色,必须是元组类型。
● thickness:指定线条的宽度。
ellipse()函数用于绘制椭圆形,其基本格式如下。
cv2.ellipse(img, center, (a, b), direction, angle_start, angle_end, color, thickness)
参数如下。
● img:指定需要绘制的图像。
● center:指定椭圆形的中心坐标。
● (a,b):指定椭圆形的长轴和短轴。
● direction:指定顺时针方向的旋转角度。
● angle_start:指定绘制椭圆形开始的角度。
● angle_end:指定绘制椭圆形结束的角度。
● color:指定线条的颜色。
● thickness:指定线条的宽度。
polylines()函数用于绘制多边形,其基本格式如下。
cv2.polylines(img, pts, isClosed, color, thickness=0)
参数如下。
● img:指定需要绘制的图像。
● pts:指定点的坐标集合,一般以列表的形式填入。
● isClosed:指定多边形是否闭合。若它为False,表示不闭合;若为True,表示闭合。
● color:指定线条的颜色。
● thickness:指定线条的宽度。
putText()函数用于绘制文本,其基本格式如下。
cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType)
参数如下。
● img:指定需要添加文字的背景图。
● text:指定需要添加的文字。
● org:指定添加文字的位置。
● fontFace:指定字体。
● fontScale:指定字号大小。
● color:指定文字的颜色。
● thickness:指定线条的宽度。
● lineType:指定线条的种类。
利用OpenCV的相关函数进行几何图形绘制的代码如下。
import cv2
import numpy as np
# 创建一张图片
img = np.ones((500, 500, 3), np.uint8)
# 绘制线段
cv2.line(img, (60, 240), (220, 240), BGR(0, 0, 255):红色, 3, cv2.LINE_AA)
cv2.line(img, (140, 160), (140, 320), BGR(0, 0, 255):红色, 3, cv2.LINE_AA)
# 绘制矩形
cv2.rectangle(img, (100, 200), (180, 280), (0, 255, 255), 3, cv2.LINE_AA)
cv2.rectangle(img, (80, 180), (200, 300), (0, 255, 255), 3, cv2.LINE_AA)
cv2.rectangle(img, (60, 160), (220, 320), (0, 255, 255), 3, cv2.LINE_AA)
# 绘制圆形
cv2.circle(img, (140, 240), 80, (255, 255, 0), 2, cv2.LINE_AA)
cv2.circle(img, (140, 240), 60, (255, 255, 0), 2, cv2.LINE_AA)
cv2.circle(img, (140, 240), 40, (255, 255, 0), 2, cv2.LINE_AA)
# 绘制椭圆形
cv2.ellipse(img, (140, 80), (100, 50), 0, 0, 360, (0, 255, 0), 2, cv2.LINE_AA)
cv2.ellipse(img, (140, 80), (90, 40), 0, 0, 90, (255, 0, 0), 2, cv2.LINE_AA)
cv2.ellipse(img, (140, 80), (90, 40), 0, 270, 360, (0, 255, 0), 2, cv2.LINE_AA)
cv2.ellipse(img, (140, 80), (80, 30), 0, 0, 90, (0, 255, 0), 2, cv2.LINE_AA)
cv2.ellipse(img, (140, 80), (80, 30), 0, 270, 360, (255, 255, 0), 2, cv2.LINE_AA)
cv2.ellipse(img, (140, 80), (70, 20), 0, 90, 270, (255, 255, 0), 2, cv2.LINE_AA)
# 绘制多边形
pts = np.array([[400, 100], [300, 140], [450, 250], [350, 250]], np.int32)
cv2.polylines(img, [pts], True, (0, 0, 255), 1, cv2.LINE_AA)
# 绘制文字
cv2.putText(img, 'loongson Platform: OpenCV', (0, 450), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,0),4)
cv2.imshow("Image ", img)
cv2.waitKey(0)
几何图形绘制结果如图1-5所示。
▲图1-5 几何图形绘制结果
本任务要求结合图形绘制的知识,基于龙芯平台,利用OpenCV实现鼠标事件的交互与响应,完成鼠标操作的综合实验。
在使用OpenCV实现计算机视觉算法的过程中,通常需要大量使用highgui模块进行一些交互式的操作,如鼠标事件监听或者滑块交互操作。这些操作需要在OpenCV中通过响应函数并结合回调函数来实现。
OpenCV提供了鼠标事件响应函数,程序调用鼠标事件响应函数之后,会一直监听鼠标动作,一旦监听到新的鼠标事件发生,就会通过调用回调函数指针onMouse所指向的函数,实现鼠标动作对应的功能。因此,鼠标事件响应函数在程序中是一直运行的,但鼠标事件响应函数本身并不直接实现某个功能,而通过onMouse回调函数指针调用回调函数间接实现具体的功能。鼠标回调函数的基本格式如下(mouseCallback是指定义的函数的名称)。
def mouseCallback(event, x, y, flags, param)
参数如下。
● event:指定调用时传递给函数的鼠标事件对象。
● x、y:指定鼠标事件触发时,鼠标指针在窗口中的坐标。
● flags:指定鼠标事件触发时,鼠标或键盘按键的具体操作,一般可以设置为以下常量。
√ cv2.EVENT_LBUTTONDBLCLK:双击鼠标左键。
√ cv2.EVENT_LBUTTONDOWN:按下鼠标左键。
√ cv2.EVENT_LBUTTONUP:释放鼠标左键。
√ cv2.EVENT_MBUTTONDBLCLK:双击鼠标中键。
√ cv2.EVENT_MBUTTONDOWN:按下鼠标中键。
√ cv2.EVENT_MBUTTONUP:释放鼠标中键。
√ cv2.EVENT_MOUSEHWHEEL:滚动鼠标中键(正值、负值分别表示向左、向右滚动)。
√ cv2.EVENT_MOUSEMOVE:鼠标移动。
√ cv2.EVENT_MOUSEWHEEL:滚动鼠标中键(正值、负值分别表示向前、向后滚动)。
√ cv2.EVENT_RBUTTONDBLCLK:双击鼠标右键。
√ cv2.EVENT_RBUTTONDOWN:按鼠标右键。
√ cv2.EVENT_RBUTTONUP:释放鼠标右键。
√ cv2.EVENT_FLAG_ALTKEY:按Alt键。
√ cv2.EVENT_FLAG_CTRLKEY:按Ctrl键。
√ cv2.EVENT_FLAG_LBUTTON:按住鼠标左键拖动。
√ cv2.EVENT_FLAG_MBUTTON:按住鼠标中键拖动。
√ cv2.EVENT_FLAG_RBUTTON:按住鼠标右键拖动。
√ cv2.EVENT_FLAG_SHIFTKEY:按Shift键。
● param:指定传递给回调函数的其他数据。
setMouseCallback()函数用于为图像窗口绑定鼠标事件回调函数,其基本格式如下。
cv2.setMouseCallback(wname, mouseCallback)
参数如下。
● wname:指定图像窗口的名称。
● mouseCallback:指定鼠标事件回调函数的名称。
以下是使用OpenCV接口实现鼠标事件的交互与响应的代码。
# 定义绘制矩形的函数
import cv2 as cv
import numpy as np
ox = 0
oy = 0
sx = 0
sy = 0
def draw_rectangle(event, x, y, flags, param):
global img
global ox, oy, sx, sy
if event == cv.EVENT_LBUTTONDBLCLK:
img = np.ones((500, 500, 3), np.uint8) * 100
elif event != cv.EVENT_MOUSEMOVE and flags == cv.EVENT_FLAG_LBUTTON:
sx, sy = x, y
ox, oy = x, y
elif event == cv.EVENT_MOUSEMOVE and flags == cv.EVENT_FLAG_LBUTTON:
cv.line(img, (ox, oy), (x, y), (255, 255, 255), 3, cv.LINE_AA)
ox, oy = x, y
elif flags != cv.EVENT_FLAG_LBUTTON and event != cv.EVENT_MOUSEMOVE:
cv.rectangle(img, (sx, sy), (x, y), (255, 0, 255), 3, cv.LINE_AA)
# 创建窗体,绑定监听回调函数
cv.namedWindow("image")
cv.setMouseCallback('image', draw_rectangle)
# 创建矩阵图像
img = np.ones((500, 500, 3), np.uint8)
while True:
cv.imshow("image", img)
k = cv.waitKey(25) & 0xFF
if chr(k) == 'q':
break
鼠标事件的交互与响应结果如图1-6所示。
▲图1-6 鼠标事件的交互与响应结果
本任务要求基于龙芯平台完成6种图像几何变换——平移、缩放、旋转、翻转、仿射变换和透视变换。
图像几何变换(也称为图像空间变换)是一种改变图像中像素空间位置但不改变像素值的过程。常见的图像几何变换包括平移、缩放、旋转等。这些变换可以通过变换矩阵实现,如平移和缩放通常以图像坐标系的原点(左上角)为变换中心,而旋转这类变换通常以图像的中心(即笛卡儿坐标系的原点)为变换中心。
在进行图像几何变换时,需要考虑两种坐标系间的变换,以确保图像的准确性和美观度。要完成一张图像的几何变换需要两种算法:一种算法用于实现空间坐标变换,描述每个像素如何从初始位置移动到终止位置;另一种算法是插值算法,用于输出图像的每个像素的灰度值。
OpenCV提供了两种变换函数,分别为warpAffine()和warpPerspective(),它们可以进行所有类型的变换。warpAffine()使用2×3的变换矩阵作为输入,warpPerspective()使用3×3的变换矩阵作为输入。
图像平移就是将图像中所有的点按照平移量水平或者垂直移动。图像平移是通过函数warpAffine()来实现的,其基本格式如下。
warpAffine(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None)
参数如下。
● src:指定需要变换的原始图像。
● M:指定变换矩阵M。
● dsize:指定变换图像的大小。如果变换图像的大小和原始图像的大小不相同,那么函数会自动通过插值调整像素间的关系。
● border Mode:像素外推模式。
● borderValue:外边框值。
图像平移是指沿着x轴方向移动tx的距离,沿着y轴方向移动ty的距离,可以创建一个类似于下面的变换矩阵M。
我们可以通过NumPy产生变换矩阵(必须是float类型的),并将其赋给warpAffine()函数。
缩放指调整图像的大小。OpenCV为缩放图像提供了函数resize(),其基本格式如下。
resize(src, dsize, dst=None, fx=None, fy=None, interpolation=None)
参数如下。
● src:指定输入的图像。
● dsize:指定输出图像的大小。
● fx、fy:分别指定沿x轴和y轴的缩放系数。dsize和fx、fy不能同时为0。
● interpolation:指定插值方式,有以下几种插值方式。
√ INTER_NEAREST:最近邻插值。
√ INTER_LINEAR:线性插值(默认)。
√ INTER_AREA:区域插值。
√ INTER_CUBIC:三次样条插值。
√ INTER_LANCZOS4:Lanczos插值。
对于图像平移和缩放而言,只要以图像的坐标系原点为变换中心进行变换即可,但是进行图像旋转操作时需要以图像的中心为中心,将图像的坐标转换成以指定中心为原点的笛卡儿坐标。在OpenCV中,getRotationMatrix2D()函数可用于计算执行旋转操作的变换矩阵,其基本格式如下。
m = cv2.getRotationMatrix2D(center, angle, scale)
参数如下。
● center:指定原始图像中作为旋转中心的坐标。
● angle:指定旋转角度。若它为正数,表示按逆时针方向旋转;若它为负数,表示按顺时针方向旋转;若它为零,表示不旋转。
● scale:指定目标图像与原始图像的大小比例。
例如,若原始图像的宽度为width,高度为height,以图像的中心作为中心顺时针旋转60°,并将图像缩小50%,则用于计算变换矩阵的语句如下。
m = cv2.getRotationMatrix2D((width/2, height/2), -60, 0.5)
翻转也称为镜像变换,翻转后的图像与原始图像是对称的。翻转又可分为以下3种情况。
● 绕x轴翻转:将图像以x轴为对称轴进行对称变换。
● 绕y轴翻转:将图像以y轴为对称轴进行对称变换。
● 绕x轴和y轴同时翻转:将图像以原点为对称中心进行对称变换。
图像翻转在OpenCV中主要通过调用flip()函数实现,其基本格式如下。
flip(src, flipCode, dst=None)
常用的参数如下。
● src:指定原始图像。
● flipCode:指定翻转方向。如果flipCode = 0,则以x轴为对称轴翻转;如果 flipCode > 0,则以y轴为对称轴翻转;如果flipCode < 0,则沿x轴、y轴方向同时翻转。
仿射变换(又称仿射映射)是指在几何中,对一个向量空间进行一次线性变换并接上一个平移,使它变换为另一个向量空间。这种变换可以描述为从一个向量空间通过一次线性变换和一次平移得到另一个向量空间的过程。
在仿射变换中,原始图像中平行的线在输出图像中仍然保持平行。为了求出变换矩阵,需要找到原始图像中的3个点,以及这3个点在输出图像中的位置。getAffineTransform()会创建一个2×3的矩阵,将这个矩阵传入warpAffine()中。在OpenCV中,getAffineTransform()函数用于计算变换矩阵,其基本格式如下。
m = cv2.getAffineTransform(src, dst)
参数如下。
● src:指定原始图像的3个点的坐标。
● dst:指定输出图像的3个点的坐标。
在getAffineTransform()函数中,参数src和dst都是包含3个二维数组(x, y)的数组,用于定义两个平行四边形。src和dst中的3个点分别对应平行四边形的左上角、右上角、左下角。由getAffineTransform()函数得到的变换矩阵M将作为warpAffine()函数的参数,将src中的点仿射到dst中。选择3个点主要是因为三角形可以表现出变换的尺度和角度。
透视变换会将图像变换为任意的四边形,原始图像中的所有直线在变换后的图像中仍然是直线。在OpenCV中,使用warpPerspective()函数实现透视变换操作,其基本格式如下。
dst=cv2.warpPerspective(src,M,dsize[,flags[,borderMode[,borderValue]]])
参数如下。
● flags:插值方法的组合,一般为默认值。
● borderMode:像素外推模式。
● borderValue:外边框值。
其中,M表示大小为3×3的变换矩阵,其他参数的含义与warpAffine()函数中的一致。
另外,getPerspectiveTransform()函数用于计算透视变换使用的变换矩阵,其基本格式如下。
M=cv2.getPerspectiveTransform(src,dst)
参数如下。
● src:指定原始图像中4个点的坐标。
● dst:指定原始图像中4个点在变换后的目标图像中的对应坐标。
以下是使用OpenCV接口实现图像平移的代码。
import cv2
import numpy as np
# 读取图像
src = cv2.imread("./SunsetSea.png", 17)
rows, cols = src.shape[:2]
# 指定平移的距离
tx = 100
ty = 100
# 生成变换矩阵
affine = np.float32([[1, 0, tx], [0, 1, ty]])
dst = cv2.warpAffine(src, affine, (cols, rows))
# 显示图像
cv2.imshow('src', src)
cv2.imshow("dst", dst)
# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()
图像平移结果如图1-7所示。
▲图1-7 图像平移结果
注意,warpAffine()函数的第三个参数指定输出图像的大小,这里设置的大小是原始图像的大小,所以输出图像会有部分被遮挡。
以下是使用OpenCV接口实现图像缩放的代码。
import cv2
import numpy as np
# 读取图像
img = cv2.imread("./SunsetSea.png", 17)
# 获取图像的高度、宽度
imageInfo = img.shape
h = imageInfo[0]
w = imageInfo[1]
# 生成变换矩阵
mat = np.array([[.5, 0, 0], [0, .5, 0]], np.float32)
dst = cv2.warpAffine(img, mat, (w, h))
# 缩放图像
mat_r = cv2.resize(img, (w * 2, h * 2))
# 显示图像
cv2.imshow("src Image", img)
cv2.imshow("dst Image", dst)
# 等待显示
cv2.waitKey(0)
图像缩放结果如图1-8所示。
▲图1-8 图像缩放结果
以下是使用OpenCV接口实现图像旋转的代码。
import cv2
# 读取图像
img = cv2.imread("./SunsetSea.png", 17)
# 获取图像的高度、宽度
imageInfo = img.shape
h = imageInfo[0]
w = imageInfo[1]
# 旋转图像
rotate = cv2.getRotationMatrix2D((w / 2, h / 2), 30, .5)
dst = cv2.warpAffine(img, rotate, (w, h))
# 显示图像
cv2.imshow("src Image", img)
cv2.imshow("dst Image", dst)
# 等待显示
cv2.waitKey(0)
图像旋转结果如图1-9所示。
▲图1-9 图像旋转结果
以下是使用OpenCV接口实现图像翻转的代码。
import cv2
import matplotlib.pyplot as plt
# 读取图片,并由BGR格式转换为RGB格式
img = cv2.imread("Resources/SunsetSea.png", 17)
src = cv2.cvtColor(img, cv.COLOR_BGR2RGB)
# 翻转图像
# 如果flipCode为0 ,则以x轴为对称轴翻转;如果flipCode > 0,则以y轴为对称轴翻转;
# 如果flipCode < 0,则沿x轴、y轴方向同时翻转
img1 = cv2.flip(src, 0)
img2 = cv2.flip(src, 1)
img3 = cv2.flip(src, -1)
# 显示图形
titles = ['Source', 'Ima1', 'Ima2', 'Ima3']
images = [src, img1, img2, img3]
for i in range(4):
plt.subplot(2, 2, i + 1)
plt.imshow(images[i])
plt.title(titles[i])
plt.xticks([])
plt.yticks([])
plt.show()
图像翻转结果如图1-10所示。
▲图1-10 图像翻转结果
以下是使用OpenCV接口实现图像仿射变换的代码。
import cv2
import numpy as np
# 读取图像
img=cv2.imread("Resources/SunsetSea.png", 17)
# 显示图像
cv2.imshow('img',img)
# 获得图像的高度、宽度
height=img.shape[0]
width=img.shape[1]
dsize=(width,height)
# 取原始图像中的3个点
src=np.float32([[0,0],[width-10,0],[0,height-1]])
# 设置3个点在目标图像中的坐标
dst=np.float32([[50,50],[width-100,80],[100,height-100]])
# 创建变换矩阵
m = cv2.getAffineTransform(src, dst)
# 执行变换
img2=cv2.warpAffine(img,m,dsize)
# 显示图像
cv2.imshow('imgThreePoint',img2)
cv2.waitKey(0)
图像仿射变换结果如图1-11所示。
▲图1-11 图像仿射变换结果
以下是使用OpenCV接口实现图像透视变换的代码。
import cv2
import numpy as np
# 读取图像
img=cv2.imread("Resources/SunsetSea.png", 17)
# 显示图像
cv2.imshow('img',img)
# 获得图像的高度、宽度
height=img.shape[0]
width=img.shape[1]
dsize=(width,height)
# 取原始图像中的4个点
src=np.float32([[0,0],[width-10,0],[0,height-10],[width-1,height-1]])
# 设置4个点在目标图像中的坐标
dst=np.float32([[50,50],[width-50,80],[50,height-100],[width-100,height-10]])
# 创建变换矩阵
m = cv2.getPerspectiveTransform(src, dst)
# 执行变换
img2=cv2.warpPerspective(img,m,dsize)
# 显示图像
cv2.imshow('imgFourPoint',img2)
cv2.waitKey(0)
图像透视变换结果如图1-12所示。
▲图1-12 图像透视变换结果
基于龙芯平台,利用OpenCV实现图像滤波技术,包括均值滤波、高斯滤波、方框滤波、中值滤波和双边滤波。
图像滤波即图像平滑,是在尽量保留图像细节特征的条件下对目标图像的噪声进行抑制,使图像变得平滑、锐化、边界增强的过程。图像滤波是图像预处理中不可缺少的操作,其处理效果将直接影响后续图像处理和分析的有效性与可靠性。进行滤波处理的要求是不能损坏图像的轮廓及边缘等重要信息,且会使图像清晰、视觉效果好。常见的图像滤波技术包括均值滤波、高斯滤波、方框滤波、中值滤波和双边滤波等。下面对这几种滤波技术进行介绍。
均值滤波是典型的线性滤波算法,也称为线性滤波,采用的主要方法为邻域平均法。
均值滤波的基本原理是用像素均值代替原始图像中的各个像素值,即为待处理的当前像素(x,y)选择一个模板,该模板由其近邻的若干像素组成,求模板中所有像素的均值,再把该均值赋予当前像素(x,y),作为处理后图像在该点上的灰度值。但是,均值滤波本身存在固有的缺陷,不能很好地保护图像的细节特征,在为图像去噪的同时会破坏图像的细节特征,从而使图像变得模糊,不能很好地去除噪声。
在OpenCV中,blur()函数可以用于实现均值滤波,其基本格式如下。
dst=cv2.blur(src, ksize [,anchor [,borderType]])
参数如下。
● dst:指定均值滤波的结果图像。
● src:指定原始图像。
● ksize:指定卷积核大小,表示为(width,height),width和height通常设置为相同值,且为正奇数。
● anchor:指定锚点,默认值为(−1,−1),表示锚点位于卷积核的中心。
● borderType:指定边界值的处理方式。
高斯滤波是一种线性平滑滤波器,适用于消除高斯噪声,广泛应用于图像处理的降噪过程。通俗地讲,高斯滤波就是对整幅图像进行加权平均的过程,每一像素的值都由其本身和邻域内的其他像素值经过加权平均后得到。
高斯滤波的具体操作如下:用一个模板扫描图像中的每一像素,用模板确定的邻域内像素的加权平均灰度值替代模板中心像素的值。
在OpenCV中,GaussianBlur()函数可以用于实现高斯滤波,其基本格式如下。
dst=cv2.GaussianBlur(src, ksize, sigmaX [,sigmaY [,borderType]])
参数如下。
● sigmaX:指定水平方向上的权重值。
● sigmaY:指定垂直方向上的权重值。
其他参数的含义和blur()函数中的一致。若sigmaY为0,则令其等于sigmaX;若sigmaX和sigmaY均为0,则按下面的公式计算sigmaX和sigmaY的值,其中ksize为(width, height)。
sigmaX=0.3((width−1)×0.5−1)+0.8
sigmaY=0.3((height−1)×0.5−1)+0.8
方框滤波是均值滤波的一种实现形式,也可以称为线性滤波。在均值滤波中,滤波结果的像素值是任意一个点的邻域像素平均值,即各邻域像素值之和的均值。而在方框滤波中,可以自由选择是否对滤波的结果进行归一化,即可以自由选择滤波结果是邻域像素值之和的平均值,还是邻域像素值之和。
在OpenCV中,boxFilter()函数可以用于实现方框滤波,其基本格式如下。
dst=cv2.boxFilter(src, ddepth, ksize[,anchor[,normalize[,borderType]]])
参数如下。
● ddepth:指定目标图像的深度,一般为−1,表示与原始图像的深度一致。
● normalize:若为True(默认值),表示执行归一化操作;若为False,表示不执行归一化操作。
其他参数的含义和blur()函数中的一致。
中值滤波是一种非线性的信号处理方法,也是一种统计排序滤波器。中值滤波将每一个像素的灰度值设置为该像素某邻域内的所有像素灰度值的中值,从而消除孤立的噪声点。中值滤波的基本原理是用某种结构的二维滑动模板,将模板内的像素按照像素值的大小进行排序,生成单调上升(或下降)的二维数据序列,从而选择中间值作为输出像素值。
中值滤波在图像处理中主要用于去除噪声,尤其是对于椒盐噪声(脉冲噪声)的去除效果显著,同时能保留图像的边缘特征,不会使图像产生显著的模糊效果。
在OpenCV中,medianBlur()函数可以用于实现中值滤波,其基本格式如下。
dst=cv2.medianBlur(src, ksize)
其中,ksize为卷积核大小,必须是大于1的奇数。
双边滤波是一种非线性的滤波方法,结合图像的空间邻近度和像素值相似度进行滤波处理。双边滤波在平滑滤波的同时能够大量保留图像的边缘和细节特征。在灰度值变化平缓的区域,值域滤波系数接近1,此时空域滤波起主要作用,双边滤波器将退化为传统的高斯低通滤波器,对图像进行平滑处理。但当图像发生剧烈变化的时候,像素间差异较大,值域滤波将起主要作用,因此能保留图像的边缘特征。
在OpenCV中,bilateralFilter()函数可以用于实现双边滤波,其基本格式如下。
dst=cv2.bilateralFilter(src,d,sigmaColor,sigmaSpace[,borderType])
参数如下。
● d:指定以当前点为中心的邻域的直径,一般为5。
● sigmaColor:指定颜色空间标准差,用于确定颜色相似性的权重。
● sigmaSpace:指定空间坐标标准差,这个值越大表示有更多的像素点会参与滤波计算。
在bilateralFilter()函数中,当d>0时,将会忽略sigmaSpace的值,由d决定邻域大小;否则,d将由sigmaSpace计算得出,与sigmaSpace成正比。
以下是使用OpenCV接口实现均值滤波的代码。
import cv2
img=cv2.imread('test.jpg')
cv2.imshow('img',img)
img2=cv2.blur(img,(20,20))
cv2.imshow('imgBlur',img2)
cv2.waitKey(0)
均值滤波结果如图1-13所示。
▲图1-13 均值滤波结果
以下是使用OpenCV接口实现高斯滤波的代码。
import cv2
img=cv2.imread('test.jpg')
cv2.imshow('img',img)
img2=cv2.GaussianBlur(img,(5,5),0,0)
cv2.imshow('imgBlur',img2)
cv2.waitKey(0)
高斯滤波结果如图1-14所示。
▲图1-14 高斯滤波结果
以下是使用OpenCV接口实现方框滤波的代码。
import cv2
img=cv2.imread('test.jpg')
cv2.imshow('img',img)
img2=cv2.boxFilter(img,-1,(3,3),normalize=False)
cv2.imshow('imgBlur',img2)
cv2.waitKey(0)
方框滤波结果如图1-15所示。
▲图1-15 方框滤波结果
以下是使用OpenCV接口实现中值滤波的代码。
import cv2
img=cv2.imread('test.jpg')
cv2.imshow('img',img)
img2=cv2.medianBlur(img,21)
cv2.imshow('imgBlur',img2)
cv2.waitKey(0)
中值滤波结果如图1-16所示。
▲图1-16 中值滤波结果
以下是使用OpenCV接口实现双边滤波的代码。
import numpy as np
import cv2
img=cv2.imread('test.jpg')
cv2.imshow('img',img)
img2=cv2.bilateralFilter(img,20,100,100)
cv2.imshow('imgBlur',img2)
cv2.waitKey(0)
双边滤波结果如图1-17所示。
▲图1-17 双边滤波结果
基于龙芯平台,利用OpenCV对图像进行边缘检测,主要实现拉普拉斯(Laplacian)边缘检测、Sobel边缘检测和Canny边缘检测。
图像边缘是图像中重要的结构性特征,往往存在于目标和背景以及不同的区域之间,因此可以作为图像分割的重要依据。边缘检测是一种图像处理技术,也是图像处理的一个重要操作,主要用于从图像中检测出物体的轮廓或边缘,以显示出图像的基本构成。
边缘检测技术主要用于检测图像中的一些像素。当被检测的图像周围的像素的灰度值发生了明显的变化时,则认为图像中出现了不同的物体,可以将这些灰度值发生明显变化的像素作为一个集合,用来标注图像中不同物体的边缘。边缘区域的灰度值剖面可被看作阶跃,即图像的灰度从一个很小的缓冲区域内急剧变化到另一个相差较明显的缓冲区域。
边缘检测过程中提取的是图像中不连续部分的特征,提取出来的闭合边缘可以作为一个区域。与区域划分不同的是,边缘检测不需要逐个对像素进行比较,非常适用于对大图像的处理。常用的边缘计算算子有拉普拉斯算子、Sobel算子和Canny算子。以下对基于这几种算子的边缘检测技术进行阐述。
拉普拉斯边缘检测是一种处理图像的边缘检测技术。它利用拉普拉斯算子识别图像中的边缘和纹理特征。拉普拉斯算子是一个二阶微分算子,用于检测图像中像素灰度值变化相对明显的位置,即边缘位置。通过卷积运算,可以将拉普拉斯算子应用于图像,得到图像的二阶微分。然后,通过阈值处理,将二阶微分值大于阈值的像素点标记为边缘点。
拉普拉斯边缘检测使用图像矩阵与拉普拉斯核进行卷积运算,其本质是计算图像中任意一点与其在水平方向和垂直方向上的4个相邻点的平均值的差值。常用的拉普拉斯核如图1-18所示。
▲图1-18 常用的拉普拉斯核
在OpenCV中,Laplacian()函数可以用于实现拉普拉斯边缘检测,其基本格式如下。
dst=cv2.Laplacian(src,ddepth[,ksize[,scale[,delta[,borderType]]]])
参数如下。
● dst:指定边缘检测的结果图像。
● src:指定原始图像。
● ddepth:指定目标图像的深度。
● ksize:指定用于计算二阶导数的滤波器的系数,必须为正奇数。
● scale:指定可选的比例因子。
● delta:指定添加到边缘检测结果中的可选增量值。
● borderType:指定边界值类型。
拉普拉斯边缘检测具有以下特点。
● 对噪声敏感。由于拉普拉斯算子是一种二阶微分算子,对噪声比较敏感,因此在使用拉普拉斯算子进行边缘检测之前,需要进行噪声抑制处理。
● 可能会产生伪边缘。由于拉普拉斯算子具有锐化效果,可能会将图像中的一些细节特征错误地识别为边缘,因此在使用拉普拉斯算子进行边缘检测时,需要进行后处理擦操作,如使用形态学操作去除伪边缘。
● 对图像中的细节敏感。拉普拉斯边缘检测算法对图像中的细节比较敏感,因此在使用该算法时需要注意选择合适的阈值和参数值,以避免产生过多的伪边缘和细节特征。
Sobel边缘检测算法主要通过计算图像中某像素周围的灰度值差异,得到该像素的梯度值,从而判断该点是不是图像的边缘点。Sobel算子是一种离散微分算子,可以对图像中的每一像素与其周围的像素进行卷积运算,进而得到该像素的梯度值。Sobel算子可以在水平与垂直两个方向分别检测图像中的水平边缘和垂直边缘。
在OpenCV中,Sobel()函数可以用于实现Sobel边缘检测,其基本格式如下。
dst=cv2.Sobel(src, depth, dx, dy[,ksize[,scale[,delta[,borderType]]]])
参数如下。
● dst:指定边缘检测的结果图像。
● src:指定原始图像。
● depth:指定目标图像的深度。
● dx:指定导数x的阶数。
● dy:指定导数y的阶数。
● ksize:指定扩展的Sobel核的大小,必须是1、3、5或7。
● scale:指定用于计算导数的可选比例因子。
● delta:指定添加到边缘检测结果中的可选增量值。
● borderType:指定边界值类型。
Sobel边缘检测具有以下特点。
● 准确性高。Sobel算子能够准确地检测出图像中的边缘和细节特征,并且可以有效地抑制噪声的干扰。
● 速度快。Sobel算子的计算过程相对简单,因此其执行速度较快,适合用于实时图像处理场景。
● 可视化效果好。通过生成二值图像,Sobel边缘检测可以清晰地显示出图像中的边缘和细节特征,使结果易于理解和分析。
拉普拉斯边缘检测和Sobel边缘检测都通过卷积运算计算边缘,它们的算法比较简单,因此其结果可能会损失过多的边缘信息或有很多的噪声。Canny边缘检测是一个多阶段的算法,包括图像降噪、图像梯度计算、非极大值抑制(Non-Maximum Suppression,NMS)、双阈值筛选等步骤。
Canny边缘检测的基本原理是通过寻找图像中灰度值变化相对明显的区域确定图像的边缘。Canny边缘检测的具体步骤如下。
(1)使用高斯滤波器对图像进行平滑处理,其目的是去除噪声的影响。
(2)计算图像的梯度,得到可能是边缘的像素集合。
(3)通过NMS方法,保留局部范围内梯度方向上灰度值变化最大的点,即真正的边缘点。
(4)通过双阈值筛选,将灰度值变化大于高阈值的像素设置为强边缘像素,将灰度变化低于低阈值的像素剔除,而对于灰度值变化在低阈值和高阈值之间的像素,则需要进一步判断其邻域内是否存在强边缘像素。如果存在,则保留;如果不存在,则剔除。
在OpenCV中,Canny()函数可以用于实现Canny边缘检测,其基本格式如下。
dst=cv2.Canny(src, threshold1, threshold2[,apertureSize[,L2gradient]])
参数如下。
● dst:指定边缘检测的结果图像。
● src:指定原始图像。
● threshold1:指定第1个阈值。
● threshold2:指定第2个阈值。
● apertureSize:指定计算梯度时使用的Sobel核的大小。
● L2gradient:指定是否使用L2范数计算梯度。默认为False,使用L1范数。
Canny边缘检测具有以下特点。
● 准确性高。Canny边缘检测算法能够准确地检测出图像中的边缘和细节特征,并且能够高效地抑制噪声干扰。
● 边缘定位准确。Canny边缘检测算法使用多阶段处理方法,不仅能够准确地定位边缘,而且能够提供单像素宽度的边缘。
● 对噪声具有平滑作用。Canny边缘检测算法中使用的高斯滤波器能够平滑图像,从而减少噪声的影响。
● 可调节参数少。Canny边缘检测算法使用的参数较少,能够方便开发者调整和使用。
以下是使用OpenCV接口实现拉普拉斯边缘检测的代码。
import cv2
img=cv2.imread('test.jpg') # 读取图像
cv2.imshow('original',img) # 显示原始图像
img2=cv2.Laplacian(img,cv2.CV_8U) # 边缘检测
cv2.imshow('Laplacian',img2) # 显示结果
cv2.waitKey(0)
拉普拉斯边缘检测的结果如图1-19所示。
▲图1-19 拉普拉斯边缘检测的结果
以下是使用OpenCV接口实现Sobel边缘检测的代码。
import cv2
img=cv2.imread('test.jpg') # 读取图像
cv2.imshow('original',img) # 显示原始图像
img2=cv2.Sobel(img,cv2.CV_8U,0,1) # 边缘检测
cv2.imshow('Sobel',img2) # 显示结果
cv2.waitKey(0)
Sobel边缘检测的结果如图1-20所示。
▲图1-20 Sobel边缘检测的结果
以下是使用OpenCV接口实现Canny边缘检测的代码。
import cv2
img=cv2.imread('test.jpg') # 读取图像
cv2.imshow('original',img) # 显示原始图像
img2=cv2.Canny(img,200,300) # 边缘检测
cv2.imshow('Canny',img2) # 显示结果
cv2.waitKey(0)
Canny边缘检测的结果如图1-21所示。
▲图1-21 Canny边缘检测的结果
人脸检测(face detection)是指在图像或者视频中识别和定位人脸。人脸检测技术采用一系列算法,在输入的图像或视频中检测出人脸的位置、大小和姿态等信息。人脸检测技术的使用通常包括人脸候选区域选取、人脸特征提取和分类器分类等。本任务要求实现两种人脸检测方法——基于Haar的人脸检测和基于深度学习的人脸检测。
基于Haar的人脸检测利用Haar特征和AdaBoost算法来识别与定位人脸。其中,Haar特征是一种描述图像中特定区域亮度变化特征的数学方法,可以用于检测图像中的边缘、线条和斑点等特征。而AdaBoost算法是一种机器学习方法,它通过迭代增强简单分类器的性能,提取最优特征,构建强分类器。
基于Haar的人脸检测通常包括以下几个步骤。
(1)收集样本。收集一些正面人脸和非人脸的样本,这些样本是用于进行Haar训练的数据集。
(2)提取Haar特征。对收集的正面人脸和非人脸的样本进行Haar特征提取,选择最能够区分正面人脸和非人脸的特征。
(3)训练Haar分类器。使用AdaBoost算法训练Haar分类器。
(4)形成级联分类器。在进行Haar特征检测时,需要运用多个分类器的级联结构,形成级联分类器,以达到降低误检率的效果。如果某一级分类器负责的特征检测结果不存在,则将该样本剔除,只将检测通过的样本传递到下一个选择器。
(5)滑动窗口。在图像中进行人脸检测时,需要对图像进行滑动窗口的操作,将整个图像划分为很多小区域,对每个小区域应用级联分类器。一旦级联分类器检测到人脸,则对该区域进行标记。
(6)采用NMS算法。为了消除检测到的重叠区域,一般采用NMS算法。假如检测到的两个人脸区域的重叠超过一定比例,就将它们归为一类,从而避免同一个人的脸被检测多次。
使用OpenCV 提供的Haar级联分类器可以进行人脸检测实验。OpenCV 源码中的data/haarcascades文件夹包含许多训练好的Haar级联分类器文件,部分如下。
● haarcascade_eye.xml:用于人眼检测。
● haarcascade_eye_tree_eyeglasses.xml:用于眼镜检测。
● haarcascade_frontalcatface.xml:用于猫脸检测。
● haarcascade_frontalface_alt.xml:用于人脸检测。
● haarcascade_profileface.xml:用于侧脸检测。
在OpenCV中,CascadeClassifier()函数可以用于加载分类器,其基本格式如下。
faceClassifier=cv2.CascadeClassifier(filename)
参数如下。
● faceClassifier:指定返回的级联分类器对象。
● filename:指定级联分类器的文件名。
另外,使用级联分类器对象的detectMultiScale()方法可以执行人脸检测,其基本格式如下。
objects=faceClassifier.detectMultiScale(image[,scaleFactor[,minNeighbors[,flags
[,minSize[,maxSize]]]]])
参数如下。
● objects:指定返回的目标矩形,矩形中为人脸。
● image:指定输入图像,通常为灰度图。
● scaleFactor:指定图像缩放比例。
● minNeighbors:指定构成目标矩形的最少相邻矩形个数。
● flags:在低版本的OpenCV中使用,在高版本的OpenCV中通常省略。
● minSize:指定目标矩形的最小尺寸。
● maxSize:指定目标矩形的最大尺寸。
基于深度学习的人脸检测利用深度学习算法检测图像或视频中的人脸,通常采用卷积神经网络(Convolutional Neural Network,CNN)来进行处理。与基于Haar的人脸检测相比,基于深度学习的人脸检测具有更高的准确性和鲁棒性,能够更好地处理复杂的人脸姿态、表情和光照变化。
基于深度学习的人脸检测通常包括以下几个步骤。
(1)特征提取。使用深度学习相关算法提取图像中的特征。由于特征提取需要通过大量的标注数据进行训练,因此基于深度学习的人脸检测算法通常需要大量标注数据集。
(2)生成人脸候选区域。在图像中生成人脸候选区域。这一步可以通过滑动窗口或者区域提议算法实现。
(3)分类和回归。对人脸候选区域进行分类和回归,确定其是不是人脸,并进行边界框的调整。这一步通常需要使用CNN等深度学习算法。
(4)进行后处理。对检测结果进行后处理,如NMS等,从而优化检测的结果。
在OpenCV中,dnn模块提供了基于深度学习的人脸检测器。dnn模块使用主流的深度学习框架,包括Caffe、TensorFlow和PyTorch等。基于深度学习的人脸检测算法有很多种,如多任务卷积神经网络(Multi-Task Convolutional Neural Network,MTCNN)、YOLO(You Only Look Once)、单阶段多框检测器(Single Shot MultiBox Detector,SSD)等。这些算法在人脸检测的准确性和速度方面都取得了不错的效果,因此在人脸识别、智能安防、人机交互等领域得到了广泛应用。
基于Haar的人脸检测主要使用haarcascade_frontalface_default.xml与haarcascade_eye.xml分别检测图像中的人脸和眼睛。具体代码如下。
import cv2
# 打开输入图像
img=cv2.imread('face.png')
# 转换为灰度图
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 加载人脸分类器
face = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
# 加载眼睛分类器
eye = cv2.CascadeClassifier('haarcascade_eye.xml')
# 执行人脸检测
faces = face.detectMultiScale(gray)
for x,y,w,h in faces:
# 绘制矩形,标注人脸
cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
# 根据人脸获得眼睛的检测范围
roi_eye = gray[y:y+h, x:x+w]
# 在人脸范围内检测眼睛
eyes = eye.detectMultiScale(roi_eye)
# 标注眼睛
for (ex,ey,ew,eh) in eyes:
cv2.circle(img[y:y+h, x:x+w],(int(ex+ew/2),
int(ey+eh/2)),int(max(ew,eh)/2),(0,255,0),2)
# 显示检测结果
cv2.imshow('face',img)
cv2.waitKey(0)
基于Haar的人脸检测结果如图1-22所示。
▲图1-22 基于Haar的人脸检测结果
OpenCV源码的sources/samples/dnn/face_detector文件夹提供了模型配置文件,但一般没有提供预训练模型文件。可运行该文件夹中的download_weights.py来下载预训练模型文件。
使用预训练模型进行人脸检测主要包括以下步骤。
(1)调用dnn.readNetFromCaffe()函数或dnn.readNetFromTensorFlow()函数加载预训练模型,并创建检测器。
(2)调用dnn.blobFromImage()函数将待检测的图像转换为图像块数据。
(3)调用检测器的setInput()方法将图像块数据设置成预训练模型的输入数据。
(4)调用检测器的forward()方法执行计算,获得预测结果。
(5)将可信度高于指定值的预测结果指定为检测结果,并在原始图像中标注人脸,同时输出可信度。
以下是使用OpenCV的深度学习库接口实现人脸检测的代码。
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 加载预训练模型
dnnnet = cv2.dnn.readNetFromCaffe("deploy.prototxt","res10_300x300_ssd_iter_140000_
fp16.caffemodel")
# 读取图像
img = cv2.imread("heard.jpg")
# 获得图像尺寸
h, w = img.shape[:2]
# 创建图像块数据
blobs = cv2.dnn.blobFromImage(img,1.0,(300,300), [104., 117., 123.], False, False)
# 将图像块数据设置为输入数据
dnnnet.setInput(blobs)
# 执行计算,获得预测结果
detections = dnnnet.forward()
faces = 0
# 迭代,输出可信度高的人脸检测结果
for i in range(0, detections.shape[2]):
# 获得可信度
confidence = detections[0, 0, i, 2]
# 输出可信度高于80%的结果
if confidence > 0.8:
faces += 1
# 获得人脸在图像中的坐标
box = detections[0,0,i,3:7]*np.array([w,h,w,h])
x1,y1,x2,y2 = box.astype("int")
# 标注人脸范围
cv2.rectangle(img,(x1,y1),(x2,y2),(255,0,0),2)
cv2.imshow('faces',img)
cv2.waitKey(0)
基于深度学习的人脸检测结果如图1-23所示。
▲图1-23 基于深度学习的人脸检测结果
本项目基于龙芯平台,不仅介绍了利用OpenCV进行相关图像处理操作的基础知识,还讲述了多种图像处理操作的实现方法,并给出了代码。掌握OpenCV的相关知识可为后文的项目学习奠定基础。