vision_note01

本课程是从事CV⽅向⼯作基础课程,以 OpenCV为基础,介绍传统的图像处理算 法,为图像处理算法⼯作打下坚实的基础。
主要内容
OpenCV是应⽤⼴泛的开源图像处理库,我们以其为基础,介绍相关的图像处理⽅法:包括基本的图像处理⽅法:⼏何变换,形态学变换,图像平滑,直⽅图操作,模板匹配,霍夫变换等;特征提取和描述⽅法:理解⻆点特征, Harris Shi Tomas 算法, SIFT/SURF 算法, Fast 算法, ORB 算法等;还有 OpenCV在视频操作中的应⽤,最后的案例是使⽤ OpenCV 进⾏⼈脸检测。
OpenCV 简介
主要内容
该章节主要介绍:
图像的起源和数字图像
OpenCV 的简介及其部署⽅法
OpenCV 中包含的主要模块。
图像处理
学习⽬标
  • 了解图像的起源
  • 知道数字图像的表示
1 图像的起源
1.1 图像是什么
图像是⼈类视觉的基础,是⾃然景物的客观反映,是⼈类认识世界和⼈类本身的重要源泉。 是物体反射或透射光的分布, 像“是⼈的视觉系统所接受的图在⼈脑中所形成的印象或认识,照⽚、绘画、剪贴画、地图、书法作品、⼿写汉学、传真、卫星云图、影视画⾯、 X光⽚、脑电图、⼼电图等都是图像。 姚敏 . 数字图像处理:机械⼯业出版社, 2014 年。

1.2 模拟图像和数字图像
图像起源于 1826年前后法国科学家Joseph Nicéphore Niépce发明的第⼀张可永久保存的照⽚,属于模拟图像。模拟图像⼜称连续图像,它通过某种物理量(如光、电等)的强弱变化来记录图像亮度信息,所以是连续变换的。模拟信号的特点是容易受⼲扰,如今已经基本全⾯被数字图像替代。
在第⼀次世界⼤战后, 1921年美国科学家发明了Bartlane System,并从伦敦传到纽约传输了第⼀幅数字图像,其亮度⽤离散数值表示,将图⽚编码成5个灰度级, 如下图所示,通过海底电缆进⾏传输。在发送端图⽚被编码并使⽤打孔带记录,通过系统传输后再接收⽅使⽤特殊的打印机恢复成图像。

1950 年左右,计算机被发明,数字图像处理学科正式诞⽣。
模拟图像和数字图像的对⽐,我们可以看⼀下:

2 数字图像的表示
2.1 位数
计算机采⽤ 0/1 编码的系统,数字图像也是利⽤ 0/1来记录信息,我们平常接触的图像都是 8 位数图像,包含 0 255 灰度,其中 0 ,代表最⿊,1,表示最⽩。

⼈眼对灰度更敏感⼀些,在16位到32位之间。

2.2 图像的分类
⼆值图像:
⼀幅⼆值图像的⼆维矩阵仅由 0 1 两个值构成, “0” 代表⿊⾊, “1”代⽩⾊。由于每⼀像素(矩阵中每⼀元素)取值仅有 0 1两种可能,所以计算机中⼆值图像的数据类型通常为 1 个⼆进制位。⼆值图像通常⽤于⽂字、线条图的扫描识别( OCR)和掩膜图像的存储。
灰度图:
每个像素只有⼀个采样颜⾊的图像,这类图像通常显示为从最暗⿊⾊到最亮的⽩⾊的灰度,尽管理论上这个采样可以任何颜⾊的不同深浅,甚⾄可以是不同亮度上的不同颜⾊。灰度图像与⿊⽩图像不同,在计算机图像领域中⿊⽩图像只有⿊⾊与⽩⾊两种颜⾊;但是,灰度图像在⿊⾊与⽩⾊之间还有许多级的颜⾊深度。灰度图像经常是在单个电磁波频谱如可⻅光内测量每个像素的亮度得到的,⽤于显示的灰度图像通常⽤每个采样像素 8 位的⾮线性尺度来保存,这样可以有 256级灰度(如果⽤16 位,则有 65536 级)。
彩⾊图:
每个像素通常是由红( R )、绿(G)、蓝(B)三个分量来表示的,分量介于 (0 255 )。 RGB图像与索引图像⼀样都可以⽤来表示彩⾊图像。与索引图像⼀样,它分别⽤红( R )、绿( G)、蓝(B )三原⾊的组合来表示每个像素的颜⾊。
但与索引图像不同的是, RGB 图像每⼀个像素的颜⾊值(由 RGB三原⾊表示)直接存放在图像矩阵中,由于每⼀像素的颜⾊需由 R G B 三个分量来表示, M 、N 分别表示图像的⾏列数,三个 M x N 的⼆维矩阵分别表示各个像素的 R G B三个颜⾊分量。 RGB 图像的数据类型⼀般为 8位⽆符号整形,通常⽤于表示和存放真彩⾊图像。
总结
1. 图像是什么
图:物体反射或透射光的分布
像:⼈的视觉系统所接受的图在⼈脑中所形成的印象或认识
2. 模拟图像和数字图像
模拟图像:连续存储的数据,
数字图像:分级存储的数据
3. 数字图像
位数:图像的表示,常⻅的就是 8
分类:⼆值图像,灰度图像和彩⾊图像
OpenCV 简介
学习⽬标
了解 OpenCV 是什么
能够独⽴安装 OpenCV
1 什么是 OpenCV
1.1 OpenCV 简介

OpenCV 是⼀款由 Intel公司俄罗斯团队发起并参与和维护的⼀个计算机视觉处理开源软件库,⽀持与计算机视觉和机器学习相关的众多算法,并且正在⽇益扩展。
OpenCV 的优势:
1. 编程语⾔
OpenCV 基于 C++ 实现,同时提供 python, Ruby, Matlab 等语⾔的接⼝。
OpenCV-Python OpenCV Python API ,结合了 OpenCV C++ API 和Python语⾔的最佳特性。
2. 跨平台
可以在不同的系统平台上使⽤,包括 Windows Linux OS X Android和iOS 。基于 CUDA OpenCL 的⾼速 GPU 操作接⼝也在积极开发中
3. 活跃的开发团队
4. 丰富的 API
完善的传统计算机视觉算法,涵盖主流的机器学习算法,同时添加了对深度学习的⽀持。
1.2 OpenCV-Python
OpenCV-Python 是⼀个 Python 绑定库,旨在解决计算机视觉问题。
Python 是⼀种由 Guido van Rossum开发的通⽤编程语⾔,它很快就变得⾮常流 ⾏,主要是因为它的简单性和代码可读性。它使程序员能够⽤更少的代码⾏表达思
想,⽽不会降低可读性。
C / C++ 等语⾔相⽐, Python 速度较慢。也就是说, Python 可以使⽤ C / C++轻松扩展,这使我们可以在 C / C++ 中编写计算密集型代码,并创建可⽤作 Python 模块
Python 包装器。这给我们带来了两个好处:⾸先,代码与原始 C / C++代码⼀样快(因为它是在后台⼯作的实际 C++ 代码),其次,在 Python 中编写代码⽐使⽤ C /
C++ 更容易。 OpenCV-Python 是原始 OpenCV C++ 实现的 Python 包装器。
OpenCV-Python 使⽤ Numpy ,这是⼀个⾼度优化的数据库操作库,具有MATLAB⻛格的语法。所有 OpenCV 数组结构都转换为 Numpy 数组。这也使得与使⽤ Numpy
的其他库(如 SciPy Matplotlib )集成更容易。
2 OpenCV 部署⽅法
安装 OpenCV 之前需要先安装 numpy, matplotlib
创建 Python 虚拟环境 cv, cv 中安装即可。
先安装 OpenCV-Python, 由于⼀些经典的算法被申请了版权,新版本有很⼤的限制,所以选⽤ 3.4.3 以下的版本
pip install opencv-python==3.4.2.17
现在可以测试下是否安装成功,运⾏以下代码⽆报错则说明安装成功。
import cv2
# 读⼀个图⽚并进⾏显示(图⽚路径需⾃⼰指定)
lena=cv2.imread("1.jpg")
cv2.imshow("image",lena)
cv2.waitKey(0)

 如果我们要利⽤SIFTSURF等进⾏特征提取时,还需要安装:

pip install opencv-contrib-python==3.4.2.17
总结
1. OpenCV 是计算机视觉的开源库
优势:
  • ⽀持多种编程语⾔
  • 跨平台
  • 活跃的开发团队
  • 丰富的API
2. 能够独⽴的安装 OpenCV-python
OpenCV 的模块
学习⽬标
了解 OpenCV 的主要模块

 

1 OpenCV 的模块
下图列出了 OpenCV 中包含的各个模块:

其中 core highgui imgproc是最基础的模块,该课程主要是围绕这⼏个模块展开的,分别介绍如下:
core模块实现了最核⼼的数据结构及其基本运算,如绘图函数、数组操作相关函数等。
highgui 模块实现了视频与图像的读取、显示、存储等接⼝。
imgproc模块实现了图像处理的基础⽅法,包括图像滤波、图像的⼏何变换、平滑、阈值分割、形态学处理、边缘检测、⽬标检测、运动分析和对象跟踪等。
对于图像处理其他更⾼层次的⽅向及应⽤, OpenCV 也有相关的模块实现
features2d 模块⽤于提取图像特征以及特征匹配, nonfree模块实现了⼀些专利算法,如 sift 特征。
objdetect 模块实现了⼀些⽬标检测的功能,经典的基于 Haar LBP特征的⼈脸检测,基于 HOG 的⾏⼈、汽⻋等⽬标检测,分类器使⽤Cascade Classification (级联分类)和 Latent SVM 等。
stitching 模块实现了图像拼接功能。
FLANN 模块( Fast Library for Approximate Nearest Neighbors),包含快速近似最近邻搜索 FLANN 和聚类 Clustering 算法。
ml 模块机器学习模块( SVM ,决策树, Boosting 等等)。
photo 模块包含图像修复和图像去噪两部分。
video 模块针对视频处理,如背景分离,前景检测、对象跟踪等。
calib3d 模块即 Calibration (校准)3D,这个模块主要是相机校准和三维重建相关的内容。包含了基本的多视⻆⼏何算法,单个⽴体摄像头标定,物体姿态估计,⽴体相似性算法, 3D 信息的重建等等。
G-API 模块包含超⾼效的图像处理 pipeline 引擎
总结
1. OpenCV 的模块
core :最核⼼的数据结构
highgui :视频与图像的读取、显示、存储
imgproc :图像处理的基础⽅法
features2d :图像特征以及特征匹配
OpenCV 基本操作
主要内容
本章主要介绍图像的基础操作,包括:
图像的 IO 操作,读取和保存⽅法
在图像上绘制⼏何图形
怎么获取图像的属性
怎么访问图像的像素,进⾏通道分离,合并等
怎么实现颜⾊空间的变换
图像的算术运算
图像的基础操作
学习⽬标
  • 掌握图像的读取和保存⽅法
  • 能够使⽤OpenCV在图像上绘制⼏何图形
  • 能够访问图像的像素
  • 能够获取图像的属性,并进⾏通道的分离和合并
  • 能够实现颜⾊空间的变换
1 图像的 IO 操作
这⾥我们会给⼤家介绍如何读取图像,如何显示图像和如何保存图像。
1.1 读取图像
1. API  
cv.imread()
参数:
  • 要读取的图像
  • 读取⽅式的标志
cv.IMREAD_COLOR:以彩⾊模式加载图像,任何图像的透明度都将被忽略。这是默认参数。
cv.IMREAD_GRAYSCALE :以灰度模式加载图像
cv.IMREAD_UNCHANGED :包括 alpha 通道的加载图像模式。
可以使⽤ 1 0 或者 -1 来替代上⾯三个标志
参考代码
import numpy as np
import cv2 as cv
# 以灰度图的形式读取图像
img = cv.imread('messi5.jpg',0)
注意:如果加载的路径有错误,不会报错,会返回⼀个 None
1.2 显示图像

1 . API 

cv.imshow()
参数:
  • 显示图像的窗⼝名称,以字符串类型表示
  • 要加载的图像
注意:在调⽤显示图像的 API 后,要调⽤ cv.waitKey()给图像绘制留下时间,否则窗⼝会出现⽆响应情况,并且图像⽆法显示出来。
另外我们也可使⽤ matplotlib 对图像进⾏展示。
1. 参考代码
# opencv中显示
cv.imshow('image',img)
cv.waitKey(0)
# matplotlib中展示
plt.imshow(img[:,:,::-1])
1.3 保存图像
1. API
cv.imwrite()
参数:
⽂件名,要保存在哪⾥
要保存的图像
2. 参考代码
cv.imwrite('messigray.png',img)
1.4 总结
我们通过加载灰度图像,显示图像,如果按 's' 并退出则保存图像,或者按ESC 键直接退出⽽不保存。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 读取图像
img = cv.imread('messi5.jpg',0)
# 2 显示图像
# 2.1 利⽤opencv展示图像
cv.imshow('image',img)
# 2.2 在matplotplotlib中展示图像
plt.imshow(img[:,:,::-1])
plt.title('匹配结果'), plt.xticks([]), plt.yticks([])
plt.show()
k = cv.waitKey(0)
# 3 保存图像
cv.imwrite('messigray.png',img)
2 绘制⼏何图形
2.1 绘制直线
cv.line(img,start,end,color,thickness)

 参数:

  • img:要绘制直线的图像
  • Start,end: 直线的起点和终点
  • color: 线条的颜⾊
  • Thickness: 线条宽度
2.2 绘制圆形
cv.circle(img,centerpoint, r, color, thickness)

 参数:

  • img:要绘制圆形的图像
  • Centerpoint, r: 圆⼼和半径
  • color: 线条的颜⾊
  • Thickness: 线条宽度,为-1时⽣成闭合图案并填充颜⾊

2.3 绘制矩形  

cv.rectangle(img,leftupper,rightdown,color,thickness)
参数:
  • img:要绘制矩形的图像
  • Leftupper, rightdown: 矩形的左上⻆和右下⻆坐标
  • color: 线条的颜⾊
  • Thickness: 线条宽度

2.4 向图像中添加⽂字

cv.putText(img,text,station, font, fontsize,color,thickness,cv.LINE_AA)

 参数:

  • img: 图像
  • text:要写⼊的⽂本数据
  • station:⽂本的放置位置
  • font:字体
  • Fontsize :字体⼤⼩
2.5 效果展示
我们⽣成⼀个全⿊的图像,然后在⾥⾯绘制图像并添加⽂字
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 创建⼀个空⽩的图像
img = np.zeros((512,512,3), np.uint8)
# 2 绘制图形
cv.line(img,(0,0),(511,511),(255,0,0),5)
cv.rectangle(img,(384,0),(510,128),(0,255,0),3)
cv.circle(img,(447,63), 63, (0,0,255), -1)
font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img,'OpenCV',(10,500), font, 4,(255,255,255),2,cv.LINE_AA)
# 3 图像展示
plt.imshow(img[:,:,::-1])
plt.title('匹配结果'), plt.xticks([]), plt.yticks([])
plt.show()
结果:

3 获取并修改图像中的像素点
我们可以通过⾏和列的坐标值获取该像素点的像素值。对于BGR图像,它返回⼀个蓝,绿,红值的数组。对于灰度图像,仅返回相应的强度值。使⽤相同的⽅法对像素值进⾏修改。
import numpy as np
import cv2 as cv
img = cv.imread('messi5.jpg')
# 获取某个像素点的值
px = img[100,100]
# 仅获取蓝⾊通道的强度值
blue = img[100,100,0]
# 修改某个位置的像素值
img[100,100] = [255,255,255]
4 获取图像的属性
图像属性包括⾏数,列数和通道数,图像数据类型,像素数等。

5 图像通道的拆分与合并
有时需要在 B G R 通道图像上单独⼯作。在这种情况下,需要将 BGR图像分割为单个通道。或者在其他情况下,可能需要将这些单独的通道合并到 BGR图像。你可以通过以下⽅式完成。
# 通道拆分
b,g,r = cv.split(img)
# 通道合并
img = cv.merge((b,g,r))
6 ⾊彩空间的改变
OpenCV 中有 150 多种颜⾊空间转换⽅法。最⼴泛使⽤的转换⽅法有两种,
BGR↔Gray BGR↔HSV
API
cv.cvtColor(input_image,flag)
参数:
input_image: 进⾏颜⾊空间转换的图像
flag: 转换类型
  • cv.COLOR_BGR2GRAY : BGR↔Gray
  • cv.COLOR_BGR2HSV: BGR→HSV
总结:
1. 图像 IO 操作的 API
cv.imread(): 读取图像
cv.imshow() :显示图像
cv.imwrite(): 保存图像
2. 在图像上绘制⼏何图像
cv.line(): 绘制直线
cv.circle(): 绘制圆形
cv.rectangle(): 绘制矩形
cv.putText(): 在图像上添加⽂字
3. 直接使⽤⾏列索引获取图像中的像素并进⾏修改
4. 图像的属性

5. 拆分通道: cv.split()
通道合并: cv.merge()
6. ⾊彩空间的改变
cv.cvtColor(input_image flag)
算数操作
学习⽬标
了解图像的加法、混合操作
1. 图像的加法
你可以使⽤ OpenCV cv.add() 函数把两幅图像相加,或者可以简单地通过numpy操作添加两个图像,如 res = img1 + img2。两个图像应该具有相同的⼤⼩和类型, 或者第⼆个图像可以是标量值。
注意: OpenCV 加法和 Numpy 加法之间存在差异。 OpenCV的加法是饱和操作,⽽Numpy 添加是模运算。
参考以下代码:
>>> x = np.uint8([250])
>>> y = np.uint8([10])
>>> print( cv.add(x,y) ) # 250+10 = 260 => 255
[[255]]
>>> print( x+y ) # 250+10 = 260 % 256 = 4
[4]
这种差别在你对两幅图像进⾏加法时会更加明显。 OpenCV 的结果会更好⼀点。所以我们尽量使⽤ OpenCV 中的函数。
我们将下⾯两幅图像:

代码:

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 读取图像
img1 = cv.imread("view.jpg")
img2 = cv.imread("rain.jpg")
# 2 加法操作
img3 = cv.add(img1,img2) # cv中的加法
img4 = img1+img2 # 直接相加
# 3 图像显示
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(img3[:,:,::-1])
axes[0].set_title("cv中的加法")
axes[1].imshow(img4[:,:,::-1])
axes[1].set_title("直接相加")
plt.show()

结果如下所示:

2. 图像的混合
这其实也是加法,但是不同的是两幅图像的权重不同,这就会给⼈⼀种混合或者透明的感觉。图像混合的计算公式如下:
g(x) = (1−α)f0(x) + αf1(x)
通过修改 α 的值( 0 → 1 ),可以实现⾮常炫酷的混合。
现在我们把两幅图混合在⼀起。第⼀幅图的权重是 0.7 ,第⼆幅图的权重是 0.3。函数 cv2.addWeighted() 可以按下⾯的公式对图⽚进⾏混合操作。
dst = α img1 + β img2 + γ
这⾥ γ 取为零。
参考以下代码:
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 读取图像
img1 = cv.imread("view.jpg")
img2 = cv.imread("rain.jpg")
# 2 图像混合
img3 = cv.addWeighted(img1,0.7,img2,0.3,0)
# 3 图像显示
plt.figure(figsize=(8,8))
plt.imshow(img3[:,:,::-1])
plt.show()

窗⼝将如下图显示:

总结
1. 图像加法:将两幅图像加载⼀起
cv.add()
2. 图像的混合:将两幅图像按照不同的⽐例进⾏混合
cv.addWeighted()
注意:这⾥都要求两幅图像是相同⼤⼩的。
OpenCV 图像处理
主要内容
  • 图像的⼏何变换
  • 图像的形态学转换
  • 图像的平滑⽅法
  • 直⽅图的⽅法
  • 边缘检测的⽅法
  • 模板匹配和霍夫变换的应⽤

⼏何变换

学习⽬标
  • 掌握图像的缩放,平移,旋转等
  • 了解数字图像的仿射变换和透射变换
1 图像缩放
缩放是对图像的⼤⼩进⾏调整,即使图像放⼤或缩⼩。
1. API  
cv2.resize(src,dsize,fx=0,fy=0,interpolation=cv2.INTER_LINEAR)
参数:
  • src : 输⼊图像
  • dsize: 绝对尺⼨,直接指定调整后图像的⼤⼩
  • fx,fy: 相对尺⼨,将dsize设置为None,然后将fxfy设置为⽐例因⼦即可
  • interpolation:插值⽅法,

2. 示例

import cv2 as cv
# 1. 读取图⽚
img1 = cv.imread("./image/dog.jpeg")
# 2.图像缩放
# 2.1 绝对尺⼨
rows,cols = img1.shape[:2]
res = cv.resize(img1,(2*cols,2*rows),interpolation=cv.INTER_CUBIC)
# 2.2 相对尺⼨
res1 = cv.resize(img1,None,fx=0.5,fy=0.5)
# 3 图像显示
# 3.1 使⽤opencv显示图像(不推荐)
cv.imshow("orignal",img1)
cv.imshow("enlarge",res)
cv.imshow("shrink)",res1)
cv.waitKey(0)
# 3.2 使⽤matplotlib显示图像
fig,axes=plt.subplots(nrows=1,ncols=3,figsize=(10,8),dpi=100)
axes[0].imshow(res[:,:,::-1])
axes[0].set_title("绝对尺度(放⼤)")
axes[1].imshow(img1[:,:,::-1])
axes[1].set_title("原图")
axes[2].imshow(res1[:,:,::-1])
axes[2].set_title("相对尺度(缩⼩)")
plt.show()

2 图像平移
图像平移将图像按照指定⽅向和距离,移动到相应的位置。
1. API
cv.warpAffine(img,M,dsize)

 参数:

img: 输⼊图像
M 2 3 移动矩阵
对于 (x,y) 处的像素点,要把它移动到(x+t_x,y+t_y)处时, M 矩阵应如下设置:
M=\begin{bmatrix}1&0&t_x\\0&1&t_y\\\\\end{bmatrix}
注意:将 M 设置为 np.float32 类型的 Numpy 数组。
  • dsize: 输出图像的⼤⼩
注意:输出图像的⼤⼩,它应该是 ( 宽度,⾼度 ) 的形式。请记住 ,width=列数, height= ⾏数。
示例
需求是将图像的像素点移动 (50,100) 的距离:
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1. 读取图像
img1 = cv.imread("./image/image2.jpg")
# 2. 图像平移
rows,cols = img1.shape[:2]
M = M = np.float32([[1,0,100],[0,1,50]])# 平移矩阵
dst = cv.warpAffine(img1,M,(cols,rows))
# 3. 图像显示
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(img1[:,:,::-1])
axes[0].set_title("原图")
axes[1].imshow(dst[:,:,::-1])
axes[1].set_title("平移后结果")
plt.show()

3 图像旋转
图像旋转是指图像按照某个位置转动⼀定⻆度的过程,旋转中图像仍保持这原始尺⼨。图像旋转后图像的⽔平对称轴、垂直对称轴及中⼼坐标原点都可能会发⽣变换,因此需要对图像旋转中的坐标进⾏相应转换。
那图像是怎么进⾏旋转的呢?如下图所示:

假设图像逆时针旋转θ,则根据坐标转换可得旋转转换为:

其中:

带⼊上⾯的公式中,有:
\begin{bmatrix}x'&y'&1\end{bmatrix}=\begin{bmatrix}x&y&1\end{bmatrix}\begin{bmatrix}\cos\theta&-\sin\theta&0\\\sin\theta&\cos\theta&0\\0&0&1\end{bmatrix}

同时我们要修正原点的位置,因为原图像中的坐标原点在图像的左上⻆,经过旋转后图像的⼤⼩会有所变化,原点也需要修正。

假设在旋转的时候是以旋转中⼼为坐标原点的,旋转结束后还需要将坐标原点移到图像左上⻆,也就是还要进⾏⼀次变换。

\begin{gathered} \begin{bmatrix}x^{\prime\prime}&y^{\prime\prime}&1\end{bmatrix}=\begin{bmatrix}x^{\prime}&y^{\prime}&1\end{bmatrix}\begin{bmatrix}1&0&0\\0&-1&0\\left&top&1\end{bmatrix} \\ =\begin{bmatrix}x&y&1\end{bmatrix}\begin{bmatrix}\cos\theta&-\sin\theta&0\\\sin\theta&\cos\theta&0\\0&0&1\end{bmatrix}\begin{bmatrix}1&0&0\\0&-1&0\\left&top&1\end{bmatrix} \end{gathered}

OpenCV中图像旋转⾸先根据旋转⻆度和旋转中⼼获取旋转矩阵,然后根据旋转矩阵进⾏变换,即可实现任意⻆度和任意中⼼的旋转效果。
1. API
cv2.getRotationMatrix2D(center, angle, scale)
参数:
center :旋转中⼼
angle :旋转⻆度
scale :缩放⽐例
返回:
M :旋转矩阵
调⽤ cv.warpAffine 完成图像的旋转
2. 示例
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 读取图像
img = cv.imread("./image/image2.jpg")
# 2 图像旋转
rows,cols = img.shape[:2]
# 2.1 ⽣成旋转矩阵
M = cv.getRotationMatrix2D((cols/2,rows/2),90,1)
# 2.2 进⾏旋转变换
dst = cv.warpAffine(img,M,(cols,rows))
# 3 图像展示
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(img1[:,:,::-1])
axes[0].set_title("原图")
axes[1].imshow(dst[:,:,::-1])
axes[1].set_title("旋转后结果")
plt.show()

4 仿射变换
图像的仿射变换涉及到图像的形状位置⻆度的变化,是深度学习预处理中常到的功能 , 仿射变换主要是对图像的缩放,旋转,翻转和平移等操作的组合。
那什么是图像的仿射变换,如下图所示,图 1 中的点 1, 2 3 与图⼆中三个点⼀⼀映射 , 仍然形成三⻆形, 但形状已经⼤⼤改变,通过这样两组三点(感兴趣点)求出仿射变换, 接下来我们就能把仿射变换应⽤到图像中所有的点中,就完成了图像的仿射变换。

OpenCV 中,仿射变换的矩阵是⼀个 2×3 的矩阵,
M=\begin{bmatrix}A&B\end{bmatrix}=\begin{bmatrix}a_{00}&a_{01}&b_0\\a_{10}&a_{11}&b_1\end{bmatrix}
其中左边的 2×2 ⼦矩阵 $A$ 是线性变换矩阵,右边的 2×1 ⼦矩阵 $B$ 是平移项:
A=\begin{bmatrix}a_{00}&a_{01}\\a_{10}&a_{11}\end{bmatrix},B=\begin{bmatrix}b_{0}\\b_{1}\end{bmatrix}
对于图像上的任⼀位置 (x,y) ,仿射变换执⾏的是如下的操作:
T_{affine}=A\begin{bmatrix}x\\y\end{bmatrix}+B=M\begin{bmatrix}x\\y\\1\end{bmatrix}
需要注意的是,对于图像⽽⾔,宽度⽅向是 x ,⾼度⽅向是 y,坐标的顺序和图像像素对应下标⼀致。所以原点的位置不是左下⻆⽽是左上⻆, y的⽅向也不是向上, ⽽是向下。
在仿射变换中,原图中所有的平⾏线在结果图像中同样平⾏。为了创建这个矩阵我们需要从原图像中找到三个点以及他们在输出图像中的位置。然后cv2.getAf fi neTransform 会创建⼀个 2x3 的矩阵,最后这个矩阵会被传给函数 cv2.warpA ffi ne
示例
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 图像读取
img = cv.imread("./image/image2.jpg")
# 2 仿射变换
rows,cols = img.shape[:2]
# 2.1 创建变换矩阵
pts1 = np.float32([[50,50],[200,50],[50,200]])
pts2 = np.float32([[100,100],[200,50],[100,250]])
M = cv.getAffineTransform(pts1,pts2)
# 2.2 完成仿射变换
dst = cv.warpAffine(img,M,(cols,rows))
# 3 图像显示
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(img[:,:,::-1])
axes[0].set_title("原图")
axes[1].imshow(dst[:,:,::-1])
axes[1].set_title("仿射后结果")
plt.show()

5 透射变换
透射变换是视⻆变化的结果,是指利⽤透视中⼼、像点、⽬标点三点共线的条件,按透视旋转定律使承影⾯(透视⾯)绕迹线(透视轴)旋转某⼀⻆度,破坏原有的投影光线束,仍能保持承影⾯上投影⼏何图形不变的变换。

它的本质将图像投影到⼀个新的视平⾯,其通⽤变换公式为:

\begin{bmatrix}x'&y'&z'\end{bmatrix}=\begin{bmatrix}u&v&w\end{bmatrix}\begin{bmatrix}a_{00}&a_{01}&a_{02}\\a_{10}&a_{11}&a_{12}\\a_{20}&a_{21}&a_{22}\end{bmatrix}

其中, (u,v) 是原始的图像像素坐标, w 取值为 1 (x=x'/z',y=y'/z')是透射变换后的结果。后⾯的矩阵称为透视变换矩阵,⼀般情况下,我们将其分为三部分:
T=\begin{bmatrix}a_{00}&a_{01}&a_{02}\\a_{10}&a_{11}&a_{12}\\a_{20}&a_{21}&a_{22}\end{bmatrix}=\begin{bmatrix}T1&T2\\T3&a_{22}\end{bmatrix}
其中: T1 表示对图像进⾏线性变换, T2 对图像进⾏平移, T3表示对图像进⾏透射变换, a_{22} ⼀般设为 1.
opencv 中,我们要找到四个点,其中任意三个不共线,然后获取变换矩阵 T,再进⾏透射变换。通过函数 cv.getPerspectiveTransform找到变换矩阵,将 cv.warpPerspective 应⽤于此 3x3 变换矩阵。
1. 示例
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 读取图像
img = cv.imread("./image/image2.jpg")
# 2 透射变换
rows,cols = img.shape[:2]
# 2.1 创建变换矩阵
pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
pts2 = np.float32([[100,145],[300,100],[80,290],[310,300]])
T = cv.getPerspectiveTransform(pts1,pts2)
# 2.2 进⾏变换
dst = cv.warpPerspective(img,T,(cols,rows))
# 3 图像显示
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(img[:,:,::-1])
axes[0].set_title("原图")
axes[1].imshow(dst[:,:,::-1])
axes[1].set_title("透射后结果")
plt.show()

6 图像⾦字塔
图像⾦字塔是图像多尺度表达的⼀种,最主要⽤于图像的分割,是⼀种以多分辨率来解释图像的有效但概念简单的结构。
图像⾦字塔⽤于机器视觉和图像压缩,⼀幅图像的⾦字塔是⼀系列以⾦字塔形状排列的分辨率逐步降低,且来源于同⼀张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终⽌条件才停⽌采样。
⾦字塔的底部是待处理图像的⾼分辨率表示,⽽顶部是低分辨率的近似,层级越⾼,图像越⼩,分辨率越低。

1. API
cv.pyrUp(img) #对图像进⾏上采样
cv.pyrDown(img) #对图像进⾏下采样
2. 示例
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 图像读取
img = cv.imread("./image/image2.jpg")
# 2 进⾏图像采样
up_img = cv.pyrUp(img) # 上采样操作
img_1 = cv.pyrDown(img) # 下采样操作
# 3 图像显示
cv.imshow('enlarge', up_img)
cv.imshow('original', img)
cv.imshow('shrink', img_1)
cv.waitKey(0)
cv.destroyAllWindows()

总结
1. 图像缩放:对图像进⾏放⼤或缩⼩ cv.resize()
2. 图像平移:
指定平移矩阵后,调⽤ cv.warpAffine() 平移图像
3. 图像旋转:
调⽤ cv.getRotationMatrix2D 获取旋转矩阵,然后调⽤ cv.warpAffine() 进⾏旋转
4. 仿射变换:
调⽤ cv.getAffineTransform将创建变换矩阵,最后该矩阵将传递给cv.warpAffine() 进⾏变换
5. 透射变换:
通过函数 cv.getPerspectiveTransform() 找到变换矩阵,将cv.warpPerspective() 进⾏透射变换
6. ⾦字塔
图像⾦字塔是图像多尺度表达的⼀种,使⽤的 API
cv.pyrUp(): 向上采样
cv.pyrDown(): 向下采样
形态学操作
学习⽬标
理解图像的邻域,连通性
了解不同的形态学操作:腐蚀,膨胀,开闭运算,礼帽和⿊帽等,及其不同操作之间的关系
1 连通性
在图像中,最⼩的单位是像素,每个像素周围有 8 个邻接像素,常⻅的邻接关系有3 种: 4 邻接、 8 邻接和 D 邻接。分别如下图所示:

4 邻接:像素 p(x,y) 4 邻域是: (x+1,y) (x-1,y) (x,y+1) (x,y-1),⽤N_{4}(p)表示像素 p 4 邻接
D 邻接:像素 p(x,y) D 邻域是:对⻆上的点 (x+1,y+1) (x+1,y-1) ;(x-1,y+1) (x-1,y-1) ,⽤N_{D}(p)表示像素 p D 邻域
8 邻接:像素 p(x,y) 8 邻域是: 4 邻域的点 + D 邻域的点,⽤N_{8}(p)表示像素p的 8 邻域
连通性是描述区域和边界的重要概念,两个像素连通的两个必要条件是:
1. 两个像素的位置是否相邻
2. 两个像素的灰度值是否满⾜特定的相似性准则(或者是否相等)
根据连通性的定义,有 4 联通、 8 联通和 m 联通三种。
4 联通:对于具有值 V 的像素 p q ,如果 q 在集合N_{4}(p)中,则称这两个像素是4连通。
8 联通:对于具有值 V 的像素 p q ,如果 q 在集合N_{8}(p)中,则称这两个像素是8连通。

对于具有值 V 的像素 p q ,如果 :
1. q 在集合N_{4}(p)中,或
2. q 在集合{\cal N}_{D}(p)中,并且N_{4}(p)N_{4}(q)的交集为空(没有值 V 的像素)
则称这两个像素是 m 连通的,即 4 连通和 D 连通的混合连通。

2 形态学操作
形态学转换是基于图像形状的⼀些简单操作。它通常在⼆进制图像上执⾏。腐蚀和膨胀是两个基本的形态学运算符。然后它的变体形式如开运算,闭运算,礼帽⿊帽等。
2.1 腐蚀和膨胀
腐蚀和膨胀是最基本的形态学操作,腐蚀和膨胀都是针对⽩⾊部分(⾼亮部分)⽽⾔的。
膨胀就是使图像中⾼亮部分扩张,效果图拥有⽐原图更⼤的⾼亮区域;腐蚀是原图中的⾼亮区域被蚕⻝,效果图拥有⽐原图更⼩的⾼亮区域。膨胀是求局部最⼤值的操作,腐蚀是求局部最⼩值的操作。
1. 腐蚀
具体操作是:⽤⼀个结构元素扫描图像中的每⼀个像素,⽤结构元素中的每⼀个像素与其覆盖的像素做 操作,如果都为 1 ,则该像素为 1 ,否则为 0。如下图所示,结构 A 被结构 B 腐蚀后:

腐蚀的作⽤是消除物体边界点,使⽬标缩⼩,可以消除⼩于结构元素的噪声点。
API
cv.erode(img,kernel,iterations)
参数:
img: 要处理的图像
kernel: 核结构
iterations: 腐蚀的次数,默认是 1
1. 膨胀
具体操作是:⽤⼀个结构元素扫描图像中的每⼀个像素,⽤结构元素中的每⼀个像素与其覆盖的像素做 操作,如果都为 0 ,则该像素为 0 ,否则为 1。如下图所示,结构 A 被结构 B 腐蚀后:

膨胀的作⽤是将与物体接触的所有背景点合并到物体中,使⽬标增⼤,可添补⽬标中的孔洞。
API
cv.dilate(img,kernel,iterations)

参数:

img: 要处理的图像
kernel: 核结构
iterations: 膨胀的次数,默认是 1
1. 示例

我们使⽤⼀个5*5的卷积核实现腐蚀和膨胀的运算:

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 读取图像
img = cv.imread("./image/image3.png")
# 2 创建核结构
kernel = np.ones((5, 5), np.uint8)
# 3 图像腐蚀和膨胀
erosion = cv.erode(img, kernel) # 腐蚀
dilate = cv.dilate(img,kernel) # 膨胀
# 4 图像展示
fig,axes=plt.subplots(nrows=1,ncols=3,figsize=(10,8),dpi=100)
axes[0].imshow(img)
axes[0].set_title("原图")
axes[1].imshow(erosion)
axes[1].set_title("腐蚀后结果")
axes[2].imshow(dilate)
axes[2].set_title("膨胀后结果")
plt.show()

 

2.2 开闭运算
开运算和闭运算是将腐蚀和膨胀按照⼀定的次序进⾏处理。 但这两者并不是可逆的,即先开后闭并不能得到原来的图像。
1. 开运算
开运算是先腐蚀后膨胀,其作⽤是:分离物体,消除⼩区域。特点:消除噪点,去除⼩的⼲扰块,⽽不影响原来的图像。

2. 闭运算
闭运算与开运算相反,是先膨胀后腐蚀,作⽤是消除 /“ 闭合 物体⾥⾯的孔洞,
特点:可以填充闭合区域。

3. API
cv.morphologyEx(img, op, kernel)

参数:

img: 要处理的图像
op: 处理⽅式:若进⾏开运算,则设为 cv.MORPH_OPEN,若进⾏闭运算,则设为 cv.MORPH_CLOSE
Kernel : 核结构
4. 示例
使⽤ 10*10 的核结构对卷积进⾏开闭运算的实现。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 读取图像
img1 = cv.imread("./image/image5.png")
img2 = cv.imread("./image/image6.png")
# 2 创建核结构
kernel = np.ones((10, 10), np.uint8)
# 3 图像的开闭运算
cvOpen = cv.morphologyEx(img1,cv.MORPH_OPEN,kernel) # 开运算
cvClose = cv.morphologyEx(img2,cv.MORPH_CLOSE,kernel)# 闭运算
# 4 图像展示
fig,axes=plt.subplots(nrows=2,ncols=2,figsize=(10,8))
axes[0,0].imshow(img1)
axes[0,0].set_title("原图")
axes[0,1].imshow(cvOpen)
axes[0,1].set_title("开运算结果")
axes[1,0].imshow(img2)
axes[1,0].set_title("原图")
axes[1,1].imshow(cvClose)
axes[1,1].set_title("闭运算结果")
plt.show()

2.3 礼帽和⿊帽
1. 礼帽运算
原图像与 开运算 的结果图之差,如下式计算:

因为开运算带来的结果是放⼤了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了⽐原图轮廓周围的区域更明亮的区域,且这⼀操作和选择的核的⼤⼩相关。
礼帽运算⽤来分离⽐邻近点亮⼀些的斑块 。当⼀幅图像具有⼤幅的背景的时候,⽽微⼩物体⽐较有规律的情况下,可以使⽤顶帽运算进⾏背景提取。
2. ⿊帽运算
闭运算 的结果图与原图像之差。数学表达式为:

⿊帽运算后的效果图突出了⽐原图轮廓周围的区域更暗的区域,且这⼀操作和选择的核的⼤⼩相关。
⿊帽运算⽤来分离⽐邻近点暗⼀些的斑块
3. API
cv.morphologyEx(img, op, kernel)
参数:
img: 要处理的图像
op: 处理⽅式:

Kernel : 核结构
4. 示例
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
# 1 读取图像
img1 = cv.imread("./image/image5.png")
img2 = cv.imread("./image/image6.png")
# 2 创建核结构
kernel = np.ones((10, 10), np.uint8)
# 3 图像的礼帽和⿊帽运算
cvOpen = cv.morphologyEx(img1,cv.MORPH_TOPHAT,kernel) # 礼帽运算
cvClose = cv.morphologyEx(img2,cv.MORPH_BLACKHAT,kernel)# ⿊帽运算
# 4 图像显示
fig,axes=plt.subplots(nrows=2,ncols=2,figsize=(10,8))
axes[0,0].imshow(img1)
axes[0,0].set_title("原图")
axes[0,1].imshow(cvOpen)
axes[0,1].set_title("礼帽运算结果")
axes[1,0].imshow(img2)
axes[1,0].set_title("原图")
axes[1,1].imshow(cvClose)
axes[1,1].set_title("⿊帽运算结果")
plt.show()

总结
1. 连通性 邻接关系: 4 邻接, 8 邻接和 D 邻接
连通性: 4 连通, 8 连通和 m 连通
2. 形态学操作
腐蚀和膨胀:
腐蚀:求局部最⼤值
膨胀:求局部最⼩值
开闭运算:
开:先腐蚀后膨胀
闭:先膨胀后腐蚀
礼帽和⿊帽:
礼帽:原图像与开运算之差
⿊帽:闭运算与原图像之差
图像平滑
学习⽬标
  • 了解图像中的噪声类型
  • 了解平均滤波,⾼斯滤波,中值滤波等的内容
  • 能够使⽤滤波器对图像进⾏处理
1 图像噪声
由于图像采集、处理、传输等过程不可避免的会受到噪声的污染,妨碍⼈们对图像理解及分析处理。常⻅的图像噪声有⾼斯噪声、椒盐噪声等。
1.1 椒盐噪声
椒盐噪声也称为脉冲噪声,是图像中经常⻅到的⼀种噪声,它是⼀种随机出现的⽩点或者⿊点,可能是亮的区域有⿊⾊像素或是在暗的区域有⽩⾊像素(或是两者皆有)。椒盐噪声的成因可能是影像讯号受到突如其来的强烈⼲扰⽽产⽣、类⽐数位转换器或位元传输错误等。例如失效的感应器导致像素值为最⼩值,饱和的感应器导致像素值为最⼤值。
1.2 ⾼斯噪声
⾼斯噪声是指噪声密度函数服从⾼斯分布的⼀类噪声。由于⾼斯噪声在空间和频域中数学上的易处理性,这种噪声 ( 也称为正态噪声 )模型经常被⽤于实践中。⾼斯随机变量 z 的概率密度函数由下式给出:
p(z)=\frac{1}{\sqrt{2\pi}\sigma}e^{\frac{-(z-\mu)^2}{2\sigma^2}}
其中 z 表示灰度值, μ 表示 z 的平均值或期望值, σ 表示 z 的标准差。标准差的平⽅ σ 称为 z 的⽅差。⾼斯函数的曲线如图所示。

2 图像平滑简介
图像平滑从信号处理的⻆度看就是去除其中的⾼频信息,保留低频信息。因此我们可以对图像实施低通滤波。低通滤波可以去除图像中的噪声,对图像进⾏平滑。
根据滤波器的不同可分为均值滤波,⾼斯滤波,中值滤波, 双边滤波。
2.1 均值滤波
采⽤均值滤波模板对图像噪声进⾏滤除。令 表示中⼼在 (x, y) 点,尺⼨为 m×n
矩形⼦图像窗⼝的坐标组。 均值滤波器可表示为:\hat{f}\left(x,y\right)=\frac{1}{mn}\sum_{(s,t)\in Sxy}g(s,t)
由⼀个归⼀化卷积框完成的。它只是⽤卷积框覆盖区域所有像素的平均值来代替中心元素。
例如, 3x3 标准化的平均过滤器如下所示:
K=\dfrac{1}{9}\left[\begin{array}{ccc}1&1&1\\1&1&1\\1&1&1\end{array}\right]
均值滤波的优点是算法简单,计算速度较快,缺点是在去噪的同时去除了很多细节部分,将图像变得模糊。
API:
cv.blur(src, ksize, anchor, borderType)
参数 :
src :输⼊图像
ksize :卷积核的⼤⼩
anchor :默认值 (-1,-1) ,表示核中⼼
borderType :边界类型
示例:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 1 图像读取
img = cv.imread('./image/dogsp.jpeg')
# 2 均值滤波
blur = cv.blur(img,(5,5))
# 3 图像显示
plt.figure(figsize=(10,8),dpi=100)
plt.subplot(121),plt.imshow(img[:,:,::-1]),plt.title('原图')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur[:,:,::-1]),plt.title('均值滤波后结果')
plt.xticks([]), plt.yticks([])
plt.show()

2.2 ⾼斯滤波
⼆维⾼斯是构建⾼斯滤波器的基础,其概率分布函数如下所示:

G(x,y) 的分布是⼀个突起的帽⼦的形状。这⾥的 σ 可以看作两个值,⼀个是 x⽅向的标准差\sigma_{x},另⼀个是 y ⽅向的标准差\sigma_{y}

\sigma_{x}\sigma_{y}取值越⼤,整个形状趋近于扁平;当\sigma_{x}\sigma_{y}取值越小,整个形状越突起。
正态分布是⼀种钟形曲线,越接近中⼼,取值越⼤,越远离中⼼,取值越⼩。计算平滑结果时,只需要将 " 中⼼点 "作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到⼀个加权平均值。
⾼斯平滑在从图像中去除⾼斯噪声⽅⾯⾮常有效。
⾼斯平滑的流程:
⾸先确定权重矩阵
假定中⼼点的坐标是( 0,0 ),那么距离它最近的 8 个点的坐标如下:

更远的点以此类推。
为了计算权重矩阵,需要设定 σ 的值。假定 σ=1.5 ,则模糊半径为 1的权重矩阵如 下:

9 个点的权重总和等于 0.4787147 ,如果只计算这 9个点的加权平均,还必须让它们的权重之和等于 1 ,因此上⾯ 9 个值还要分别除以 0.4787147,得到最终的权重矩阵。

计算⾼斯模糊
有了权重矩阵,就可以计算⾼斯模糊的值了。
假设现有 9 个像素点,灰度值( 0-255 )如下:

每个点乘以对应的权重值:  

得到

将这 9 个值加起来,就是中⼼点的⾼斯模糊的值。
对所有点重复这个过程,就得到了⾼斯模糊后的图像。如果原图是彩⾊图⽚,可以对 RGB 三个通道分别做⾼斯平滑。
API
cv2.GaussianBlur(src,ksize,sigmaX,sigmay,borderType)

参数:

src: 输⼊图像
ksize:⾼斯卷积核的⼤⼩,注意 : 卷积核的宽度和⾼度都应为奇数,且可以不同
sigmaX: ⽔平⽅向的标准差
sigmaY: 垂直⽅向的标准差,默认值为 0 ,表示与 sigmaX 相同
borderType: 填充边界类型

示例:

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 1 图像读取
img = cv.imread('./image/dogGasuss.jpeg')
# 2 ⾼斯滤波
blur = cv.GaussianBlur(img,(3,3),1)
# 3 图像显示
plt.figure(figsize=(10,8),dpi=100)
plt.subplot(121),plt.imshow(img[:,:,::-1]),plt.title('原图')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur[:,:,::-1]),plt.title('⾼斯滤波后结果')
plt.xticks([]), plt.yticks([])
plt.show()

2.3 中值滤波
中值滤波是⼀种典型的⾮线性滤波技术,基本思想是⽤像素点邻域灰度值的中值来代替该像素点的灰度值。
中值滤波对椒盐噪声( salt-and-pepper noise)来说尤其有⽤,因为它不依赖于邻域内那些与典型值差别很⼤的值。
API
cv.medianBlur(src, ksize )
参数:
src :输⼊图像
ksize :卷积核的⼤⼩
示例:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 1 图像读取
img = cv.imread('./image/dogsp.jpeg')
# 2 中值滤波
blur = cv.medianBlur(img,5)
# 3 图像展示
plt.figure(figsize=(10,8),dpi=100)
plt.subplot(121),plt.imshow(img[:,:,::-1]),plt.title('原图')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur[:,:,::-1]),plt.title('中值滤波后结果')
plt.xticks([]), plt.yticks([])
plt.show()

 

总结
1. 图像噪声
椒盐噪声:图像中随机出现的⽩点或者⿊点
⾼斯噪声:噪声的概率密度分布是正态分布
2. 图像平滑
均值滤波:算法简单,计算速度快,在去噪的同时去除了很多细节部分, 将图像变得模糊 cv.blur()
⾼斯滤波 : 去除⾼斯噪声 cv.GaussianBlur()
中值滤波 : 去除椒盐噪声 cv.medianBlur()
直⽅图
学习⽬标
  • 掌握图像的直⽅图计算和显示
  • 了解掩膜的应⽤
  • 熟悉直⽅图均衡化,了解⾃适应均衡化
1 灰度直⽅图
1.1 原理
直⽅图是对数据进⾏统计的⼀种⽅法,并且将统计值组织到⼀系列事先定义好的 bin 当中。其中, bin 为直⽅图中经常⽤到的⼀个概念,可以译为 直条
”,其数值是从数据中计算出的特征统计量,这些数据可以是诸如梯度、⽅向、 ⾊彩或任何其他特征。
图像直⽅图(Image Histogram)是⽤以表示数字图像中亮度分布的直⽅图, 标绘了图像中每个亮度值的像素个数。这种直⽅图中,横坐标的左侧为较暗的区域,⽽右侧为较亮的区域。因此⼀张较暗图⽚的直⽅图中的数据多集中于左侧和中间部分,⽽整体明亮、只有少量阴影的图像则相反。

 

注意:直⽅图是根据灰度图进⾏绘制的,⽽不是彩⾊图像。    假设有⼀张图像的信息(灰度值 0 - 255 ,已知数字的范围包含 256 个值,于是可以按⼀定规律将这
个范围分割成⼦区域(也就是 bins )。如:
[0,255]=[0,15]\bigcup[16,30]\cdots\bigcup[240,255]
然后再统计每⼀个 bin(i) 的像素数⽬。可以得到下图(其中 x 轴表示 bin y 轴表示各个 bin 中的像素个数):

直⽅图的⼀些术语和细节:
  • dims:需要统计的特征数⽬。在上例中,dims = 1 ,因为仅仅统计了灰度值。
  • bins:每个特征空间⼦区段的数⽬,可译为直条组距,在上例中, bins = 16
  • range:要统计特征的取值范围。在上例中,range = [0, 255]
直⽅图的意义:
直⽅图是图像中像素强度分布的图形表达⽅式。它统计了每⼀个强度值所具有的像素个数。 不同的图像的直⽅图可能是相同的
1.2 直⽅图的计算和绘制
我们使⽤ OpenCV 中的⽅法统计直⽅图,并使⽤ matplotlib 将其绘制出来。
API
cv2.calcHist(images,channels,mask,histSize,ranges[,hist[,accumulate]])
参数:
images: 原图像。当传⼊函数时应该⽤中括号 [] 括起来,例如: [img]
channels: 如果输⼊图像是灰度图,它的值就是 [0];如果是彩⾊图像的话,传⼊的参数可以是 [0] [1] [2] 它们分别对应着通道 B G R
mask: 掩模图像。要统计整幅图像的直⽅图就把它设为 None。但是如果你想统计图像某⼀部分的直⽅图的话,你就需要制作⼀个掩模图像,并使⽤它。 (后边有例⼦)
histSize:BIN 的数⽬。也应该⽤中括号括起来,例如: [256]
ranges: 像素值范围,通常为 [0 256]
示例:
如下图,绘制相应的直⽅图
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
# 1 直接以灰度图的⽅式读⼊
img = cv.imread('./image/cat.jpeg',0)
# 2 统计灰度图
histr = cv.calcHist([img],[0],None,[256],[0,256])
# 3 绘制灰度图
plt.figure(figsize=(10,6),dpi=100)
plt.plot(histr)
plt.grid()
plt.show()

1.3 掩膜的应⽤
掩膜是⽤选定的图像、图形或物体,对要处理的图像进⾏遮挡,来控制图像处理的 区域。
在数字图像处理中,我们通常使⽤⼆维矩阵数组进⾏掩膜。掩膜是由 0 1组成⼀个 ⼆进制图像,利⽤该掩膜图像要处理的图像进⾏掩膜,其中 1 值的区域被处理, 0
值区域被屏蔽,不会处理。
掩膜的主要⽤途是:
提取感兴趣区域:⽤预先制作的感兴趣区掩模与待处理图像进⾏ “操作,得到感兴趣区图像,感兴趣区内图像值保持不变,⽽区外图像值都为 0
屏蔽作⽤:⽤掩模对图像上某些区域作屏蔽,使其不参加处理或不参加处理参数的计算,或仅对屏蔽区作处理或统计。
结构特征提取:⽤相似性变量或图像匹配⽅法检测和提取图像中与掩模相似的结构特征。
特殊形状图像制作
掩膜在遥感影像处理中使⽤较多,当提取道路或者河流,或者房屋时,通过⼀个掩 膜矩阵来对图像进⾏像素过滤,然后将我们需要的地物或者标志突出显示出来。
我们使⽤cv.calcHist()来查找完整图像的直⽅图。 如果要查找图像某些区域的直⽅图,该怎么办? 只需在要查找直⽅图的区域上创建⼀个⽩⾊的掩膜图像,否则创建⿊⾊, 然后将其作为掩码 mask 传递即可。
示例:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
# 1. 直接以灰度图的⽅式读⼊
img = cv.imread('./image/cat.jpeg',0)
# 2. 创建蒙版
mask = np.zeros(img.shape[:2], np.uint8)
mask[400:650, 200:500] = 255
# 3.掩模
masked_img = cv.bitwise_and(img,img,mask = mask)
# 4. 统计掩膜后图像的灰度图
mask_histr = cv.calcHist([img],[0],mask,[256],[1,256])
# 5. 图像展示
fig,axes=plt.subplots(nrows=2,ncols=2,figsize=(10,8))
axes[0,0].imshow(img,cmap=plt.cm.gray)
axes[0,0].set_title("原图")
axes[0,1].imshow(mask,cmap=plt.cm.gray)
axes[0,1].set_title("蒙版数据")
axes[1,0].imshow(masked_img,cmap=plt.cm.gray)
axes[1,0].set_title("掩膜后数据")
axes[1,1].plot(mask_histr)
axes[1,1].grid()
axes[1,1].set_title("灰度直⽅图")
plt.show()

2 直⽅图均衡化
2.1 原理与应⽤
想象⼀下,如果⼀副图像中的⼤多数像素点的像素值都集中在某⼀个⼩的灰度值值范围之内会怎样呢?如果⼀幅图像整体很亮,那所有的像素值的取值个数应该都会很⾼。所以应该把它的直⽅图做⼀个横向拉伸(如下图),就可以扩⼤图像像素值的分布范围,提⾼图像的对⽐度,这就是直⽅图均衡化要做的事情。

直⽅图均衡化是把原始图像的灰度直⽅图从⽐较集中的某个灰度区间变成在更⼴泛灰度范围内的分布。直⽅图均衡化就是对图像进⾏⾮线性拉伸,重新分配图像像素值,使⼀定灰度范围内的像素数量⼤致相同。
这种⽅法提⾼图像整体的对⽐度,特别是有⽤数据的像素值分布⽐较接近时,在 X光图像中使⽤⼴泛,可以提⾼⻣架结构的显示,另外在曝光过度或不⾜的图像中可以更好的突出细节。
使⽤ opencv 进⾏直⽅图统计时,使⽤的是:
API
dst = cv.equalizeHist(img)
参数:
img: 灰度图像
返回:
dst : 均衡化后的结果
示例:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
# 1. 直接以灰度图的⽅式读⼊
img = cv.imread('./image/cat.jpeg',0)
# 2. 均衡化处理
dst = cv.equalizeHist(img)
# 3. 结果展示
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(img,cmap=plt.cm.gray)
axes[0].set_title("原图")
axes[1].imshow(dst,cmap=plt.cm.gray)
axes[1].set_title("均衡化后结果")
plt.show()

2.2 ⾃适应的直⽅图均衡化
上述的直⽅图均衡,我们考虑的是图像的全局对⽐度。 的确在进⾏完直⽅图均衡化之后,图⽚背景的对⽐度被改变了,在猫腿这⾥太暗,我们丢失了很多信息,所以
在许多情况下,这样做的效果并不好。如下图所示,对⽐下两幅图像中雕像的画⾯,由于太亮我们丢失了很多信息。

为了解决这个问题, 需要使⽤⾃适应的直⽅图均衡化。 此时, 整幅图像会被分成很多⼩块,这些⼩块被称为 “tiles” (在 OpenCV tiles 的 ⼤⼩默认是 8x8 ),然
后再对每⼀个⼩块分别进⾏直⽅图均衡化。 所以在每⼀个的区域中, 直⽅图会集中在某⼀个⼩的区域中)。如果有噪声的话,噪声会被放⼤。为了避免这种情况的
出现要使⽤对⽐度限制。对于每个⼩块来说,如果直⽅图中的 bin 超过对⽐度的上限的话,就把其中的像素点均匀分散到其他 bins 中,然后在进⾏直⽅图均衡化。

最后,为了 去除每⼀个⼩块之间的边界,再使⽤双线性差值,对每⼀⼩块进⾏拼接。
API:  
cv.createCLAHE(clipLimit, tileGridSize)

 参数:

clipLimit: 对⽐度限制,默认是40
tileGridSize: 分块的⼤⼩,默认为8 ∗ 8
示例:
import numpy as np
import cv2 as cv
# 1. 以灰度图形式读取图像
img = cv.imread('./image/cat.jpeg',0)
# 2. 创建⼀个⾃适应均衡化的对象,并应⽤于图像
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
# 3. 图像展示
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(img,cmap=plt.cm.gray)
axes[0].set_title("原图")
axes[1].imshow(cl1,cmap=plt.cm.gray)
axes[1].set_title("⾃适应均衡化后的结果")
plt.show()

总结
1. 灰度直⽅图:
直⽅图是图像中像素强度分布的图形表达⽅式。
它统计了每⼀个强度值所具有的像素个数。
不同的图像的直⽅图可能是相同的 cv.calcHist images channels mask histSize ranges [ hist [, accumulate]]
2. 掩膜
创建蒙版,透过 mask 进⾏传递,可获取感兴趣区域的直⽅图
3. 直⽅图均衡化:增强图像对⽐度的⼀种⽅法
cv.equalizeHist(): 输⼊是灰度图像,输出是直⽅图均衡图像
4. ⾃适应的直⽅图均衡
将整幅图像分成很多⼩块,然后再对每⼀个⼩块分别进⾏直⽅图均衡化,最后进⾏拼接
clahe = cv.createCLAHE(clipLimit, tileGridSize)
边缘检测
学习⽬标
  • 了解Sobel算⼦,Scharr算⼦和拉普拉斯算⼦
  • 掌握canny边缘检测的原理及应⽤
1 原理
边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的⽬的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。
边缘的表现形式如下图所示:

图像边缘检测⼤幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。有许多⽅法⽤于边缘检测,它们的绝⼤部分可以划分为两类:基于搜索和基于零穿越。
基于搜索:通过寻找图像⼀阶导数中的最⼤值来检测边界,然后利⽤计算结果估计边缘的局部⽅向,通常采⽤梯度的⽅向,并利⽤此⽅向找到局部梯度模的最⼤值,代表算法是 Sobel 算⼦和 Scharr 算⼦。

基于零穿越:通过寻找图像⼆阶导数零穿越来寻找边界,代表算法是Laplacian 算⼦。

2 Sobel 检测算⼦
Sobel 边缘检测算法⽐较简单,实际应⽤中效率⽐ canny边缘检测效率要⾼,但是边缘不如 Canny 检测的准确,但是很多实际应⽤的场合, sobel 边缘却是⾸选, Sobel
算⼦是⾼斯平滑与微分操作的结合体,所以其抗噪声能⼒很强,⽤途较多。尤其是效率要求较⾼,⽽对细纹理不太关⼼的时候。
2.1 ⽅法
对于不连续的函数,⼀阶导数可以写作:
f'(x)=f(x)-f(x-1)

 f'(x)=f(x+1)-f(x)

所以有:

f'(x)=\frac{f(x+1)-f(x-1)}2 

假设要处理的图像为I,在两个⽅向求导:
⽔平变化: 将图像I 与奇数⼤⼩的模版进⾏卷积,结果为G_{x}。⽐如,当模板⼤⼩为3时, G_{x}为: 

G_x=\begin{bmatrix}-1&0&+1\\-2&0&+2\\-1&0&+1\end{bmatrix}*I

垂直变化: 将图像I与奇数⼤⼩的模板进⾏卷积,结果为G_{y}。⽐如,当模板⼤⼩为3时, G_{y} 为: 

G_y=\begin{bmatrix}-1&-2&-1\\0&0&0\\+1&+2&+1\end{bmatrix}*I

在图像的每⼀点,结合以上两个结果求出:

G=\sqrt{G_x^2+G_y^2} 

统计极⼤值所在的位置,就是图像的边缘。
注意:当内核⼤⼩为3时, 以上Sobel内核可能产⽣⽐较明显的误差, 为解决这⼀问题,我们使⽤Scharr函数,但该函数仅作⽤于⼤⼩为3的内核。该函数的运算与Sobel函数⼀样快,但结果却更加精确,其计算⽅法为: 

\begin{aligned}&G_x=\begin{bmatrix}-3&0&+3\\-10&0&+10\\-3&0&+3\end{bmatrix}*I\\&G_y=\begin{bmatrix}-3&-10&-3\\0&0&0\\+3&+10&+3\end{bmatrix}*I\end{aligned}

2.2 应⽤
利⽤OpenCV进⾏sobel边缘检测的API是: 

Sobel_x_or_y = cv2.Sobel(src, ddepth, dx, dy, dst, ksize, scale, delta, border)
参数:
src :传⼊的图像
ddepth: 图像的深度
dx dy: 指求导的阶数, 0 表示这个⽅向上没有求导,取值为 0 1
ksize: Sobel 算⼦的⼤⼩,即卷积核的⼤⼩,必须为奇数 1 3 5 7,默认为 3
注意:如果 ksize=-1 ,就演变成为 3x3 Scharr 算⼦。
scale :缩放导数的⽐例常数,默认情况为没有伸缩系数。
borderType :图像边界的模式,默认值为 cv2.BORDER_DEFAULT
Sobel 函数求完导数后会有负值,还有会⼤于 255 的值。⽽原图像是 uint8 ,即 8位⽆ 符号数,所以 Sobel 建⽴的图像位数不够,会有截断。因此要使⽤ 16位有符号的数 据类型,即 cv2.CV_16S 。处理完图像后,再使⽤ cv2.convertScaleAbs()函数将其转回原来的 uint8 格式,否则图像⽆法显示。
Sobel 算⼦是在两个⽅向计算的,最后还需要⽤ cv2.addWeighted( )函数将其组合起来
Scale_abs = cv2.convertScaleAbs(x) # 格式转换函数
result = cv2.addWeighted(src1, alpha, src2, beta) # 图像混合

示例:

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 1 读取图像
img = cv.imread('./image/horse.jpg',0)
# 2 计算Sobel卷积结果
x = cv.Sobel(img, cv.CV_16S, 1, 0)
y = cv.Sobel(img, cv.CV_16S, 0, 1)
# 3 将数据进⾏转换
Scale_absX = cv.convertScaleAbs(x) # convert 转换 scale 缩放
Scale_absY = cv.convertScaleAbs(y)
# 4 结果合成
result = cv.addWeighted(Scale_absX, 0.5, Scale_absY, 0.5, 0)
# 5 图像显示
plt.figure(figsize=(10,8),dpi=100)
plt.subplot(121),plt.imshow(img,cmap=plt.cm.gray),plt.title('原图')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(result,cmap = plt.cm.gray),plt.title('Sobel滤波后结果
plt.xticks([]), plt.yticks([])
plt.show()

将上述代码中计算sobel算⼦的部分中将ksize设为-1,就是利⽤Scharr进⾏边缘检测。

x = cv.Sobel(img, cv.CV_16S, 1, 0, ksize = -1)
y = cv.Sobel(img, cv.CV_16S, 0, 1, ksize = -1) 

3 Laplacian算⼦

Laplacian 是利⽤⼆阶导数来检测边缘 。 因为图像是 2 ”, 我们需要在两个⽅向求导,如下式所示:

 \Delta src=\frac{\partial^2src}{\partial x^2}+\frac{\partial^2src}{\partial y^2}

那不连续函数的⼆阶导数是:

f''(x)=f'(x+1)-f'(x)=f(x+1)+f(x-1)-2f(x)

那使⽤的卷积核是:

kernel=\begin{bmatrix}0&1&0\\1&-4&1\\0&1&0\end{bmatrix} 

API:

laplacian = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType)

 参数:

Src: 需要处理的图像,
Ddepth: 图像的深度, -1表示采⽤的是原图像相同的深度,⽬标图像的深度必须⼤于等于原图像的深度;
ksize :算⼦的⼤⼩,即卷积核的⼤⼩,必须为 1,3,5,7
示例:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 1 读取图像
img = cv.imread('./image/horse.jpg',0)
# 2 laplacian转换
result = cv.Laplacian(img,cv.CV_16S)
Scale_abs = cv.convertScaleAbs(result)
# 3 图像展示
plt.figure(figsize=(10,8),dpi=100)
plt.subplot(121),plt.imshow(img,cmap=plt.cm.gray),plt.title('原图')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(Scale_abs,cmap = plt.cm.gray),plt.title('Laplacian检
plt.xticks([]), plt.yticks([])
plt.show()

4 Canny边缘检测

Canny 边缘检测算法是⼀种⾮常流⾏的边缘检测算法,是 John F. Canny 于 1986 年提出的,被认为是最优的边缘检测算法。
4.1 原理
Canny 边缘检测算法是由 4 步构成,分别介绍如下:
第⼀步:噪声去除
由于边缘检测很容易受到噪声的影响,所以⾸先使⽤ 5*5⾼斯滤波器去除噪声,在图像平滑那⼀章节中已经介绍过。
第⼆步:计算图像梯度
对平滑后的图像使⽤ Sobel 算⼦计算⽔平⽅向和竖直⽅向的⼀阶导数( Gx Gy )。根据得到的这两幅梯度图( Gx Gy )找到边界的梯度和⽅向,公式如下 :
Edge\_Gradient\left(G\right)=\sqrt{G_x^2+G_y^2}
Angle\left(\theta\right)=tan^{-1}\left(\frac{G_y}{G_x}\right)

 

如果某个像素点是边缘,则其梯度⽅向总是与边缘垂直。梯度⽅向被归为四类:垂直,⽔平,和两个对⻆线⽅向。
第三步:⾮极⼤值抑制
在获得梯度的⽅向和⼤⼩之后,对整幅图像进⾏扫描,去除那些⾮边界上的点。对每⼀个像素进⾏检查,看这个点的梯度是不是周围具有相同梯度⽅向的点中最⼤
的。如下图所示:

A 点位于图像的边缘,在其梯度变化⽅向,选择像素点 B C ,⽤来检验 A点的梯度 是否为极⼤值,若为极⼤值,则进⾏保留,否则 A 点被抑制,最终的结果是具有 细边 的⼆进制图像。
第四步:滞后阈值
现在要确定真正的边界。 我们设置两个阈值: minVal maxVal。 当图像的灰度梯度⾼于 maxVal 时被认为是真的边界, 低于 minVal 的边界会被抛弃。如果介于两者之间的话,就要看这个点是否与某个被确定为真正的边界点相连,如果是就认为它也是边界点,如果不是就抛弃。如下图:

如上图所示, A ⾼于阈值 maxVal 所以是真正的边界点, C 虽然低于 maxVal 但⾼于 minVal 并且与 A 相连,所以也被认为是真正的边界点。⽽ B 就会被抛弃,因为 低于 maxVal ⽽且不与真正的边界点相连。所以选择合适的 maxVal minVal 对于能否得到好的结果⾮常重要。
4.2 应⽤
OpenCV 中要实现 Canny 检测使⽤的 API:
canny = cv2.Canny(image, threshold1, threshold2)
参数:
image: 灰度图,
threshold1: minval ,较⼩的阈值将间断的边缘连接起来
threshold2: maxval ,较⼤的阈值检测图像中明显的边缘
示例:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 1 图像读取
img = cv.imread('./image/horse.jpg',0)
# 2 Canny边缘检测
lowThreshold = 0
max_lowThreshold = 100
canny = cv.Canny(img, lowThreshold, max_lowThreshold) 
# 3 图像展示
plt.figure(figsize=(10,8),dpi=100)
plt.subplot(121),plt.imshow(img,cmap=plt.cm.gray),plt.title('原图')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(canny,cmap = plt.cm.gray),plt.title('Canny检测后结果
plt.xticks([]), plt.yticks([])
plt.show()

总结
1. 边缘检测的原理
基于搜索:利⽤⼀阶导数的最⼤值获取边界
基于零穿越:利⽤⼆阶导数为 0 获取边界
2. Sobel 算⼦
基于搜索的⽅法获取边界
cv.sobel()
cv.convertScaleAbs()
cv.addweights()
3. Laplacian 算⼦
基于零穿越获取边界
cv.Laplacian()
4. Canny 算法
流程:
噪声去除:⾼斯滤波
计算图像梯度: sobel 算⼦,计算梯度⼤⼩和⽅向
⾮极⼤值抑制:利⽤梯度⽅向像素来判断当前像素是否为边界点
滞后阈值:设置两个阈值,确定最终的边界
5 算⼦⽐较

模版匹配和霍夫变换
学习⽬标
  • 掌握模板匹配的原理,能完成模板匹配的应⽤
  • 理解霍夫线变换的原理,了解霍夫圆检测
  • 知道使⽤OpenCV如何进⾏线和圆的检测 
1 模板匹配
1.1 原理
所谓的模板匹配,就是在给定的图⽚中查找和模板最相似的区域,该算法的输⼊包括模板和图⽚,整个任务的思路就是按照滑窗的思路不断的移动模板图⽚,计算其与图像中对应区域的匹配度,最终将匹配度最⾼的区域选择为最终的结果。
实现流程:
准备两幅图像:
1. 原图像 (I) :在这幅图中,找到与模板相匹配的区域
2. 模板 (T) :与原图像进⾏⽐对的图像块

滑动模板图像和原图像进⾏⽐对:  

将模板块每次移动⼀个像素 ( 从左往右,从上往下 ),在每⼀个位置,都计算与模板图像的相似程度。
对于每⼀个位置将计算的相似结果保存在结果矩阵( R)中。如果输⼊图像的⼤⼩( WxH )且模板图像的⼤⼩ (wxh) ,则输出矩阵 R 的⼤⼩为(W-w + 1,H-h + 1 )将R显示为图像,如下图所示:

 

获得上述图像后,查找最⼤值所在的位置,那么该位置对应的区域就被认为是最匹配的。对应的区域就是以该点为顶点,⻓宽和模板图像⼀样⼤⼩的矩阵。
1.2 实现
我们使⽤ OpenCV 中的⽅法实现模板匹配。
API
res = cv.matchTemplate(img,template,method)
参数:
img: 要进⾏模板匹配的图像
Template :模板
method :实现模板匹配的算法,主要有:
1. 平⽅差匹配 (CV_TM_SQDIFF):利⽤模板与图像之间的平⽅差进⾏匹配, 最好的匹配是 0 ,匹配越差,匹配的值越⼤。
2. 相关匹配 (CV_TM_CCORR):利⽤模板与图像间的乘法进⾏匹配,数值越⼤表示匹配程度较⾼,越⼩表示匹配效果差。
3. 利⽤相关系数匹配 (CV_TM_CCOEFF):利⽤模板与图像间的相关系数匹配, 1 表示完美的匹配, -1 表示最差的匹配。
完成匹配后,使⽤ cv.minMaxLoc()⽅法查找最⼤值所在的位置即可。如果使⽤平⽅差作为⽐较⽅法,则最⼩值位置是最佳匹配位置。
示例:
在该案例中,载⼊要搜索的图像和模板,图像如下所示:

模板如下所示:

通过 matchTemplate 实现模板匹配,使⽤minMaxLoc定位最匹配的区域,并⽤矩形标注最匹配的区域。

 

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 1 图像和模板读取
img = cv.imread('./image/wulin2.jpeg')
template = cv.imread('./image/wulin.jpeg')
h,w,l = template.shape
# 2 模板匹配
# 2.1 模板匹配
res = cv.matchTemplate(img, template, cv.TM_CCORR)
# 2.2 返回图像中最匹配的位置,确定左上⻆的坐标,并将匹配位置绘制在图像上
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
# 使⽤平⽅差时最⼩值为最佳匹配位置
# top_left = min_loc
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv.rectangle(img, top_left, bottom_right, (0,255,0), 2)
# 3 图像显示
plt.imshow(img[:,:,::-1])
plt.title('匹配结果'), plt.xticks([]), plt.yticks([])
plt.show()

拓展:模板匹配不适⽤于尺度变换,视⻆变换后的图像,这时我们就要使⽤关键点匹配算法,⽐较经典的关键点检测算法包括 SIFT和SURF等,主要的思路是⾸先通过关键点检测算法获取模板和测试图⽚中的关键点;然后使⽤关键点匹配算法处理即可,这些关键点可以很好的处理尺度变化、视⻆变换、旋转变化、光照变化等,具有很好的不变性。
2 霍夫变换
霍夫变换常⽤来提取图像中的直线和圆等⼏何形状,如下图所示:  

2.1 原理
1. 原理
在笛卡尔坐标系中,⼀条直线由两个点 A = ( x 1 , y 1 ) B = ( x 2 , y 2 )确定,如下图所 示 :

将直线y=kx+q可写成关于(k, q)的函数表达式:  

\begin{cases}q=-kx_1+y_1\\q=-kx_2+y_2\end{cases}

 对应的变换通过图形直观的表示下:

变换后的空间我们叫做霍夫空间。即:笛卡尔坐标系中的⼀条直线,对应于霍夫空间中的⼀个点。反过来,同样成⽴,霍夫空间中的⼀条线,对应于笛卡尔坐标系中⼀个点,如下所示:

我们再来看下 A B 两个点,对应于霍夫空间的情形:

在看下三点共线的情况:  

 

可以看出如果在笛卡尔坐标系的点共线,那么这些点在霍夫空间中对应的直线交于⼀点。

如果不⽌存在⼀条直线时,如下所示:

我们选择尽可能多的直线汇成的点,上图中三条直线汇成的 A B两点,将其对应 回笛卡尔坐标系中的直线:

到这⾥我们似乎已经完成了霍夫变换的求解。但如果像下图这种情况时: 

上图中的直线是 x = 2 ,那 ( k , q ) 怎么确定呢?
为了解决这个问题,我们考虑将笛卡尔坐标系转换为极坐标。

在极坐标下是⼀样的,极坐标中的点对应于霍夫空间的线,这时的霍夫空间是不在是参数 ( k , q ) 的空间,⽽是 ( ρ , θ ) 的空间, ρ 是原点到直线的垂直距离, θ表示直线的垂线与横轴顺时针⽅向的夹⻆,垂直线的⻆度为 0 度,⽔平线的⻆度是 180 度。

我们只要求得霍夫空间中的交点的位置,即可得到原坐标系下的直线。
实现流程
假设有⼀个⼤⼩为 100 100的图⽚,使⽤霍夫变换检测图⽚中的直线,则步骤如下所示:
直线都可以使⽤ ( ρ , θ ) 表示,⾸先创建⼀个 2D数组,我们叫做累加器,初始化所有值为 0 ,⾏表示 ρ ,列表示 θ

  • 该数组的⼤⼩决定了结果的准确性,若希望⻆度的精度为1度,那就需要180列。对于ρ,最⼤值为图⽚对⻆线的距离,如果希望精度达到像素级别,⾏数应该与图像的对⻆线的距离相等。
  • 取直线上的第⼀个点(x, y),将其带⼊直线在极坐标中的公式中,然后遍历θ的取值:012...180,分别求出对应的ρ值,如果这个数值在上述累加器中存在相应的位置,则在该位置上加1.
  • 取直线上的第⼆个点,重复上述步骤,更新累加器中的值。对图像中的直线上的每个点都直线以上步骤,每次更新累加器中的值。
  • 搜索累加器中的最⼤值,并找到其对应的(ρ, θ),就可将图像中的直线表示出 来。

2.2 霍夫线检测
OpenCV 中做霍夫线检测是使⽤的 API 是:
cv.HoughLines(img, rho, theta, threshold)
参数:
img: 检测的图像,要求是⼆值化的图像,所以在调⽤霍夫变换之前⾸先要进⾏⼆值化,或者进⾏ Canny 边缘检测
rho theta: ρ θ 的精确度
threshold: 阈值,只有累加器中的值⾼于该阈值时才被认为是直线。
霍夫线检测的整个流程如下图所示,这是在 stackflow上⼀个关于霍夫线变换的解释:
示例:
检测下述图像中的直线:

import numpy as np
import random
import cv2 as cv
import matplotlib.pyplot as plt
# 1.加载图⽚,转为⼆值图
img = cv.imread('./image/rili.jpg')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray, 50, 150)
# 2.霍夫直线变换
lines = cv.HoughLines(edges, 0.8, np.pi / 180, 150)
# 3.将检测的线绘制在图像上(注意是极坐标噢)
for line in lines:
 rho, theta = line[0]
 a = np.cos(theta)
 b = np.sin(theta)
 x0 = a * rho
 y0 = b * rho
 x1 = int(x0 + 1000 * (-b))
 y1 = int(y0 + 1000 * (a))
 x2 = int(x0 - 1000 * (-b))
 y2 = int(y0 - 1000 * (a))
 cv.line(img, (x1, y1), (x2, y2), (0, 255, 0))
# 4. 图像显示
plt.figure(figsize=(10,8),dpi=100)
plt.imshow(img[:,:,::-1]),plt.title('霍夫变换线检测')
plt.xticks([]), plt.yticks([])
plt.show()

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值