0.杂七杂八
-
解决matplotlib中文显示乱码或方块问题
-
代码头部加上此段代码即可
-
def set_ch(): from pylab import mpl mpl.rcParams['font.sans-serif'] =['FangSong']# 指定默认字体 mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题 set_ch()
-
-
OpenCV是BGR通道,plt默认RGB通道,若使用cv2.imread()读入图像,用plt.imshow()展示原始图像或者展示对读入图像进行一系列操作后的图像时,需要进行通道转换,在展示灰度图像时,需要使用
plt.imshow(img, camp="gray")
-
ndarray:(x, y, z[, …]):最外层为0维,向里依次递增,表示0维有x个1维的元素,1维有y个2维的元素,2维有z个3维元素
-
arr.reshape(x,y):表示先将arr数组平展开,然后按顺序(从左到右,从上到下)填入一个x*y的二维数组中
-
pts.sum(axis=1)表示将pts(ndarray:(4,2))中第1维的元素相加最后形成一个s数组(ndarray:(4,))
-
np.diff(pts, axis = 1)表示将pts(ndarray:(4,2))中第1维的中元素依次后一个减去前一个最后形成一个diff数组(ndarray:(4,1))
1. 数字图像
1. 位数
平常接触的图像都是8位数图像,包含0-255灰度,其中0代表最黑,255代表最白
2. 分类
二值图像
二值图像的二维矩阵仅由0、1两个值构成,“0”代表黑色,“1”代白色,或0、255两个值构成
灰度图
单通道表示,每个像素点只有一种颜色灰色,根据亮度的不同,有区间0-255。用(H, W, 1)的二维矩阵构成
彩色图
三通道表示,每个像素点可以为任意颜色,根据颜色和通道的不同,有区间0-255。用(H, W, 3)的二维矩阵构成。RGB图像的数据类型一般为8位无符号整形,通常用于表示和存放真彩色图像,用((H, W, 3), np.uint8)
设置
注意
- OpenCV中读取通道顺序为(B, G, R)
- matplotlib中读取通道顺序为(R, G, B)
2. OpenCV的模块
- core模块:实现了最核心的数据结构及其基本运算,如绘图函数、数组操作相关函数等
- highgui模块:实现了视频与图像的读取、显示、存储等接口
- imgproc模块:实现了图像处理的基础方法,包括图像滤波、图像的几何变换、平滑、闻值分割、形态学处理、边缘检测、目标检测、运动分析和对象跟踪等
- features2d模块:用于提取图像特征以及特征匹配
- obidetect模块:实现了一些目标检测的功能,经典的基于Haar、LBP特征的人脸检测,基于HOG的行人、汽车等目标检测,分类器使用Cascade Classification(级联分类)和Latent SVM等
- stitching模块:实现了图像拼接功能
- FLANN模块:包含快速近似最近邻搜索FLANN和聚类Clustering算法
- photo模块:包含图像修复和图像去噪两部分
- video模块:针对视频处理,如背景分离,前景检测、对象跟踪等
- G-API模块:包含超高效的图像处理pipeline引擎
3. 图像基本操作
1. 图像的IO操作
"""
读取图像
cv.imread("图像地址", 0|1|-1)
1表示:cvIMREAD*COLOR:以彩色模式加载图像,任何图像的透明度都将被忽略,默认为1
0表示:cv.IMREAD*GRAYSCALE:以灰度模式加载图像
-1表示:CvIMREAD_UNCHANGED:包括alpha通道的加载图像模式
显示图像
cv.imshow('显示窗口名称', xxx)
xxx表示:需要加载显示的图像
cv.waitKey(0)
防止显示图像出现闪退现象
保存图像
cv.imwrite("文件名,想要保存在哪里", xxx)
xxx表示:想要保存的图像
"""
import numpy as np
import cv2 as cv
img = cv.imread("G:/Pycharm_workspace/OpenCV_Study/data/color.jpg", 1)
cv.imshow("img_color", img)
cv.waitKey(0)
cv.imwrite("G:/Pycharm_workspace/OpenCV_Study/data/color1.jpg", img)
2. 绘制图形
在OpenCV中图像坐标系:左上角为原点,y轴向下,x轴向右,用于表示坐标点,在代码中的二维数组中,行(高)对应y轴,列(宽)对应x轴
注意:
- 当通过numpy数组创建图像时(
img_black = np.zeros((512, 512, 3), np.uint8)
),其中必须标明np.uint8
,表示数组元素大小只能在0-255之间- 创建矩阵(H, W, 3)时用的是
( )
,创建修改BGR三原色[B, G, R]时用的是[ ]
"""
绘制直线
cv.line(img, start, end, color, thickness)
img:要绘制直线的图像
start, end:直线的起点和终点
color:线条的颜色
thickness:线条宽度
绘制圆形
cv.circle(img, centerpoint, r, color, thickness)
img:要绘制圆形的图像
centerpoint, r:圆心和半径
color:线条的颜色
thickness:线条宽度,为-1时生成闭合图案并填充颜色
绘制方形
cv.rectangle(img, pt1, pt2, color, thickness)
img:要绘制方形的图像
pt1, pt2:方形的左上角和右下角的坐标
color:线条的颜色
thickness:线条宽度
添加文字
cv.putText(img, text, org, fontFace, fontScale, color, thickness=None,lineType=None, bottomLeftOrigin=None)
img:要添加文字的图像
text:添加文字的文本内容
org:文字的位置
fontFace, fontScale:字体样式、字体大小
color:线条的颜色
thickness:线条宽度
"""
import cv2 as cv
import numpy as np
# 创建黑色图像 (512, 512, 3)表示三个大小为512*512的二维数组,分别用来表示RGB三通道
img_black = np.zeros((512, 512, 3), np.uint8)
# 绘制图形
cv.line(img_black, (0, 0), (511, 511), (255, 0, 0), 5) # opencv中图像的存储是以BGR形式存储,(255, 0, 0)表示蓝色
cv.rectangle(img_black, (384, 0), (510, 128), (0, 255, 0), 3)
cv.circle(img_black, (447, 63), 63, (0, 0, 255), -1)
font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img_black, "opencv", (10, 500), font, 4, (255, 255, 255), 2, cv.LINE_AA)
# 展示图像
cv.imshow("opencv_img", img_black)
cv.waitKey(0)
3. 获取并修改图像中的像素点
"""
通过行和列的坐标值获取该像素点的像素值
对于BGR图像,它返回一个蓝,绿,红值的数组
对于灰度图像,仅返回相应的强度值
使用相同的方法对像素值进行修改
"""
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img_black = np.zeros((512, 512, 3), np.uint8)
plt.imshow(img_black[:,:,::-1])
# 获取某个像素点
img_black[100, 100] # array([0, 0, 0], dtype=uint8)
# 仅获取蓝色通道的强度值
img_black[100, 100, 0] # 0
# 修改某个位置的像素值
img_black[100, 100] = [255, 255, 255]
plt.imshow(img_black[:,:,::-1])
# 获取某个像素点
img_black[100, 100] # array([255, 255, 255], dtype=uint8)
4. 获取图像的属性
"""
形状:img.shape
图像大小:img.size
数据类型:img.dtyte
"""
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img_black = np.zeros((512, 512, 3), np.uint8)
# 形状:img.shape
img_black.shape # (512, 512, 3)
# 数据类型:img.dtyte
img_black.dtype # dtype('uint8')
# 图像大小:img.size
img_black.size # 512*512*3 = 786432
5. 图像通道的拆分和合并
- 需要在B,G,R通道图像上单独工作时,可以将BGR图像分割为单个通道
- 需要将这些单独的通道合并到BGR图像
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img_color = cv.imread("G:/Pycharm_workspace/OpenCV_Study/data/color.jpg", 1)
# 通道拆分
b, g, r =cv.split(img_color)
print(b)
print(g)
print(r)
# 通道合并
img_color = cv.merge((b, g, r))
6. 色彩空间的改变
"""
cv.cvtColor(input_image, flag)
flag:
CV.COLOR_BGR2GRAY: BGR->Gray
CV.COLOR BGR2HSV: BGR->HSV
"""
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img_color = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/color.jpg", 1)
plt.imshow(img_color[:,:,::-1])
gray_img = cv.cvtColor(img_color, cv.COLOR_BGR2GRAY)
plt.imshow(gray_img,cmap=plt.cm.gray)
hsv_img = cv.cvtColor(img_color, cv.COLOR_BGR2HSV)
plt.imshow(hsv_img)
7. 算数操作
注意:无论是加法、减法、混合操作,要求两个图像具有相同的大小和类型,或者第二个图像可以是标量值
推荐使用OpenCV提供的加减API
- Opencv加法和Numpy加法之间存在差异。OpenCV的加法是饱和操作,而Numpy的加法是模运算
- Opencv减法和Numpy减法之间存在差异。OpenCV的减法是保底为0(负数变成0),而Numpy减法是遇负相加(负数+256)
图像的加法
可以使用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 = 255
print(x + y) # 250+ 10 = 260 260 % 256 = 4 -
import cv2 as cv import numpy as np import matplotlib.pyplot as plt img_view = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/view.jpeg", 1) img_rain = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/rain.jpeg", 1) plt.imshow(img_view[:,:,::-1]) plt.imshow(img_rain[:,:,::-1]) # cv形式的相加 img_cv_add = cv.add(img_view, img_rain) plt.imshow(img_cv_add[:,:,::-1]) # numpy数组形式的相加 img_np_add = img_view + img_rain plt.imshow(img_np_add[:,:,::-1])
-
图像的减法
可以使用OpenCV的cv.subtract
函数把两幅图像相加,或者可以简单地通过numpy操作添加两个图像,如res=img1-img2
减法一般用于图像的背景消除
注意
-
Opencv减法和Numpy减法之间存在差异。OpenCV的减法是保底为0(负数变成0),而Numpy减法是遇负相加(负数+256)
-
x = np.uint8([250])
y = np.uint8([255])
print(cv.subtract(x, y)) # 250 - 255 = 0
print(x - y) # 250 - 255 = -5 -5 + 256 = 251 -
import cv2 as cv import numpy as np import matplotlib.pyplot as plt img_view = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/view.jpeg", 1) img_rain = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/rain.jpeg", 1) plt.imshow(img_view[:,:,::-1]) plt.imshow(img_rain[:,:,::-1]) #cv形式的相减 img_cv_subtract = cv.subtract(img_view, img_rain) plt.imshow(img_cv_subtract[:,:,::-1]) #numpy数组形式的相加相减 img_np_subtract = img_view - img_rain plt.imshow(img_np_subtract[:,:,::-1])
-
图像的混合
混合即是加法,不同的是两幅图像的权重不同,图像混合的计算公式如下:
g
(
x
)
=
(
1
−
a
)
f
1
(
x
)
+
a
f
2
(
x
)
g(x) = (1-a)f_1(x)+af_2(x)
g(x)=(1−a)f1(x)+af2(x)
利用cv.addWeighted(img1, a, img2, b, y)
方法进行混合
注意:
cv.addWeighted(img1, a, img2, b, y)
- 其中:
- a为img1的权重,b为img2的权重,a + b 必须等于1
- y为偏移权重,普通的图片混合时,一般设置为0
#混合
img_cv_addWeighted = cv.addWeighted(img_view, 0.7, img_rain, 0.3, 0)
plt.imshow(img_cv_addWeighted[:,:,::-1])
4. 图像处理操作
1. 几何变换
图像缩放
cv2.resize(src,dsize,fx=0,fy=0,interpolation=cV2.INTER LINEAR)
-
src:输入图像
-
dsize: 绝对尺寸,直接指定调整后图像的大小
-
fx,fy: 相对真实宽高尺寸,将dsize设置为None,然后将x和fy设置为比例因子即可
-
interpolation:插值方法
-
插值 含义 CV2.INTER LINEAR 双线性插值法 CV2INTER NEAREST 最近邻插值 CV2INTER AREA 像素区域重采样(默认) CV2.INTER CUBIC 双三次插值
-
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img_color = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/color.jpg", 1)
print(img_color.shape) # (666, 1000, 3)
#绝对尺寸,shape[:2]因为shape为(h,w,3),取前两个即可
rows, cols = img_color.shape[:2]
res1 = cv.resize(img_color, (2*cols, 2*rows), interpolation=cv.INTER_CUBIC)
plt.imshow(res1[:,:,::-1])
print(res1.shape) # (1332, 2000, 3)
res2 = cv.resize(img_color, None, fx=0.5, fy=0.5, interpolation=cv.INTER_CUBIC)
plt.imshow(res2[:,:,::-1])
print(res2.shape) # (333, 500, 3)
图像平移
图像平移将图像按照指定方向和距离,移动到相应的位置
cv.warpAffine(img,M,dsize)
-
img:输入图像
-
M:2*3移动矩阵
-
对于(x,y)处的像素点,要把它移动到(x + t_x, y +t_y)处时,M矩阵应如下设置:
-
M = [ 1 0 t x 0 1 t y ] M=\left[\begin {array}{c} 1 &0 &t_x \\ 0 &1 &t_y \\ & & \\ \end{array}\right] M= 1001txty
-
注意:将M设置为np.float32类型的Numpy数组
-
-
dsize: 输出图像的大小
- 输出图像的大小,它应该是(宽度,高度)的形式。width=列数,height=行数
- 这里的输出图像大小指的是坐标系大小,不是图像大小
- 如果坐标系大小依旧为原始图像大小,则平移后图像超出的部分自动截断
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
def set_ch(): # set_ch()解决matplotlib图像显示乱码或方块问题
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong']# 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
img_color = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/color.jpg", 1)
print(img_color.shape)
#图像平移
rows, cols = img_color.shape[:2]
M = np.float32([[1, 0, 100], [0, 1, 50]]) #平移矩阵
img_color_dst = cv.warpAffine(img_color, M, (cols, rows))
#图像显示
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10,8), dpi=100)
axes[0].imshow(img_color[:,:,::-1])
axes[0].set_title(u"原图")
axes[1].imshow(img_color_dst[:,:,::-1])
axes[1].set_title(u"移动后")
图像旋转
图像旋转是指图像按照某个位置转动一定角度的过程,旋转中图像仍保持这原始尺寸。图像旋转后图像的水平对称轴、垂直对称轴及中心坐标原点都可能会发生变换,因此需要对图像旋转中的坐标进行相应转换
调用cv2.getRotationMatrix2D(center,angle, scale)
方法,获得旋转矩阵M,然后调用cv.warpAffine完成图像的旋转
- center:旋转中心
- angle:旋转角度,正数为逆时针旋转,负数为顺时针旋转
- scale:缩放比例
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
img_color = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/color.jpg", 1)
print(img_color.shape)
rows, cols = img_color.shape[:2]
#生成旋转矩阵
M = cv.getRotationMatrix2D((cols/2,rows/2), -90, 1)
#进行旋转变换
img_color_dst = cv.warpAffine(img_color, M, (cols, rows))
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10,8), dpi=100)
axes[0].imshow(img_color[:,:,::-1])
axes[0].set_title(u"原图")
axes[1].imshow(img_color_dst[:,:,::-1])
axes[1].set_title(u"移动后")
仿射变换
仿射变换主要是对图像的缩放,旋转,翻转和平移等操作的组合
一般用于当深度学习数据集不够时,对数据集进行仿射变换,以达到扩充数据集的作用
仿射变换矩阵为2*3的矩阵
M
=
[
A
B
]
=
[
a
00
a
01
b
0
a
10
a
11
b
1
]
M =\left[\begin {array}{c} A &B\\ \end{array}\right] =\left[\begin {array}{c} a_{00} &a_{01} &b_{0} \\ a_{10} &a_{11} &b_{1} \\ \end{array}\right]
M=[AB]=[a00a10a01a11b0b1]
其中左边的2x2子矩阵A是线性变换矩阵,右边2x1子矩阵是平移项
A
=
[
a
00
a
01
a
10
a
11
]
,
B
=
[
b
0
b
1
]
A =\left[\begin {array}{c} a_{00} &a_{01} \\ a_{10} &a_{11} \\ \end{array}\right], B =\left[\begin {array}{c} b_{0} \\ b_{1} \\ \end{array}\right]
A=[a00a10a01a11],B=[b0b1]
对于图像上的任意位置(x,y),仿射变换执行的是如下操作:
T
a
f
f
i
n
e
=
A
[
x
y
]
+
B
=
M
[
x
y
1
]
T_{affine} =A\left[\begin {array}{c} x \\ y \\ \end{array}\right]+B =M\left[\begin {array}{c} x \\ y \\ 1 \\ \end{array}\right]
Taffine=A[xy]+B=M
xy1
在仿射变换中,原图中所有的平行线在结果图像中同样平行,从原图像中找到三个点以及他们在输出图像中的位置,利用cv2.getAfineTransform(pts1, pts2)
API创建一个2x3的M矩阵,最后将M矩阵传如cv2.warpAffine
其中:
- pts1:变换前在图像中找到的三个点的坐标
- pts2:变换后在图像中找到的三个点的坐标
- pts形式:
pts=([[x1, y1], [x2,y2], [x3, y3]])
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong']# 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
img_color = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/color.jpg", 1)
print(img_color.shape)
rows, cols = img_color.shape[:2]
#仿射变换
pts1 = np.float32([[50,50],[200,50],[50,200]])
pts2 = np.float32([[100,100],[200,50],[100,450]])
M = cv.getAffineTransform(pts1, pts2)
img_color_dst = cv.warpAffine(img_color, M, (rows, cols))
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10,8), dpi=100)
axes[0].imshow(img_color[:,:,::-1])
axes[0].set_title(u"原图")
axes[1].imshow(img_color_dst[:,:,::-1])
axes[1].set_title(u"移动后")
透射变换
将图像投影到一个新的视平面的过程,称为透射变换,通用透射变换公式:
[
x
‘
y
‘
z
‘
]
=
[
u
v
w
]
[
a
00
a
01
a
02
a
10
a
11
a
12
a
20
a
21
a
22
]
\left[\begin {array}{c} x^` &y^` &z^`\\ \end{array}\right] =\left[\begin {array}{c} u &v &w\\ \end{array}\right] \left[\begin {array}{c} a_{00} &a_{01} &a_{02} \\ a_{10} &a_{11} &a_{12} \\ a_{20} &a_{21} &a_{22} \\ \end{array}\right]
[x‘y‘z‘]=[uvw]
a00a10a20a01a11a21a02a12a22
其中(u,v)是原始图像像素坐标,w取值为1,(x=x’/z’, y=y’/z’)是透射变换后的结果
透射变换矩阵一般分为三部分:
T
=
[
a
00
a
01
a
02
a
10
a
11
a
12
a
20
a
21
a
22
]
=
[
T
1
T
2
T
3
a
22
]
T =\left[\begin {array}{c} a_{00} &a_{01} &a_{02} \\ a_{10} &a_{11} &a_{12} \\ a_{20} &a_{21} &a_{22} \\ \end{array}\right] =\left[\begin {array}{c} T1 &T2 \\ T3 &a_{22} \\ \end{array}\right]
T=
a00a10a20a01a11a21a02a12a22
=[T1T3T2a22]
其中:
- T1表示对图像进行线性变换
- T2表示对图像进行平移
- T3表示对图像进行投射变换
- a 2 2 a_22 a22一般设置为1
投射变换时需要找四个点,其中3个点不共线即可,通过cv.getPerspectiveTransform(pst1, pst2)
得到转换矩阵M,然后将转换矩阵M传入cv.warpPerspective
其中:
- pts1:变换前在图像中找到的四个点的坐标
- pts2:变换后在图像中找到的四个点的坐标
- pts形式:
pts=([[x1, y1], [x2,y2], [x3, y3], [x4, y4]])
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong']# 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
img_road = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/road.jpeg", 1)
print(img_road.shape)
rows, cols = img_road.shape[:2]
plt.imshow(img_road[:,:,::-1])
#透射变换
pst1 = np.float32([[80, 600], [990, 600], [400, 450], [720, 450]])
pst2 = np.float32([[200, 650], [800, 650], [200, 300], [800, 300]])
M = cv.getPerspectiveTransform(pst1, pst2)
img_road_dst = cv.warpPerspective(img_road, M, (rows, cols))
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10,8), dpi=100)
axes[0].imshow(img_road[:,:,::-1])
axes[0].set_title(u"原图")
axes[1].imshow(img_road_dst[:,:,::-1])
axes[1].set_title(u"移动后")
注意:
- 仿射变换需要两个1×3维矩阵,分别表示变换前和变换后点的坐标,每个1×3维矩阵里面嵌套三个1×2维矩阵,分别表示3个点的坐标
- 透射变换需要两个1×4维矩阵,分别表示变换前和变换后点的坐标,每个1×4维矩阵里面嵌套三个1×2维矩阵,分别表示4个点的坐标
图像金字塔
图像金字塔是图像多尺度表达的一种,最主要用于图像的分割,是一种以多分辨率来解释图像的有效但概念简单的结构
一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样
金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似,层级越高,图像越小,分辨率越低
cv.pyrUp(img)
:对图像进行上采样(提高图像分辨率)cv.pyrDown(img)
:对图像进行下采样(降低图像分辨率)
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong']# 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
img_color = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/color.jpg", 1)
print(img_color.shape)
rows, cols = img_color.shape[:2]
img_color_Up = cv.pyrUp(img_color) # 只进行一次上采样过程,一般是将分辨率扩大2倍
img_color_Down = cv.pyrDown(img_color) # 只进行一次下采样过程,一般是将分辨率缩小1/2倍
plt.imshow(img_color[:,:,::-1])
plt.imshow(img_color_Up[:,:,::-1])
plt.imshow(img_color_Down[:,:,::-1])
一般下采样至图像大小64×64或者任意一条边小于64时停止下采样
边界填充
cv.BORDER_REPLICATE
:复制法,也就是复制最边缘像素cv.BORDER_REFLECT
:反射法,对感兴趣的图像中的像素在两边进行复制,例如:fedcba<-abcdefgh->hgfedcb
cv.BORDER_REFLECT101
:反射法,也就是以最边缘像素为轴,对称,例如:gfedcb<-abcdefgh->gfedcba
cv.BORDER_WRAP
:外包装法,例如:cdefgh<-abcdefgh->abcdefg
cv.BORDER_CONSTANT
: 常量法,常数值填充,value
设置常数值
top_size,bottom_size,left_size,right_size= (50,50,50,50)
replice = cv.copyMakeBorder(img_color, top_size, bottom_size, left_size, right_size, cv.BORDER_REPLICATE)
reflect = cv.copyMakeBorder(img_color, top_size, bottom_size, left_size, right_size, cv.BORDER_REFLECT)
reflect_101 = cv.copyMakeBorder(img_color, top_size, bottom_size, left_size, right_size, cv.BORDER_REFLECT101)
wrap = cv.copyMakeBorder(img_color, top_size, bottom_size, left_size, right_size, cv.BORDER_WRAP)
constant = cv.copyMakeBorder(img_color, top_size, bottom_size, left_size, right_size, cv.BORDER_CONSTANT, value=0)
2. 形态学
形态学转换是基于图像形状的一些简单操作,通常在二值图上进行
连通性
在图像中,最小的单位是像素,每个像素周围有8个邻接像素,常见的邻接关系有3种:4邻接、8邻接和D邻接
- 4邻接:像素p(x,y)的4邻域是: (x+1,y);(x-1,y);(x,y+1);(x,y1),用N_4§表示像素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的D邻域
- 8邻接:像素p(x,y)的8邻域是: 4邻域的点 + D域的点,用N_8§表示像素p的8邻域
连通性是描述区域和边界的重要概念,两个像素连通的两个必要条件是:
- 两个像素的位置是否相邻,即相互在对方的邻域中
- 两个像素的灰度值是否满足特定的相似性准则 (或者是否相等)
根据连通性的定义,有4联通、8联通和m联通三种:
- 4联通:对于具有值V的像素p和q,如果q在集合N_4§中,则称这两个像素是4连通
- 8联通(重要):对于具有值V的像素p和q,如果q在集合N_8§中,则称这两个像素是8连通
- m联通
- 对于具有值V的像素p和q,如果:
- q集合N_4§中 或 q在集合N_D§中,并且N_4§与N_4(g)的交集为空 (没有值V的像素)
- 则称这两个像素是m连通的,即4连通和D连通的混合连通
腐蚀、膨胀
腐蚀和膨胀为两个基本的形态学运算符,变体形式有开运算、闭运算、礼帽、黑帽等
腐蚀和膨胀都是针对白色部分(高亮部分)而言的
- 膨胀就是使图像中高亮部分扩张,效果图拥有比原图更大的高亮区域
- 腐蚀是原图中的高亮区域被蚕食,效果图拥有比原图更小的高亮区域
- 膨胀是求局部最大值的操作,腐蚀是求局部最小值的操作
腐蚀
具体操作是:用一个结构元素扫描图像中的每一个像素,用结构元素中的每一个像素与其覆盖的像素做“与”操作,如果都为1,则该像素为1,否则为0
如下图所示,结构A被结构B腐蚀后:
用结构B的中心点去扫描结构A的每一个像素点,每次比较时,用结构B中所有像素值为1的与结构A中对应点做与操作,做完与操作后,如果所有结果都为1,则此次比较结果为1,此时赋值给中心点像素值为1,依次比较所有像素点即可
注意只添加结构B的中心点像素
腐蚀的作用是消除物体边界点,是目标缩小,可以消除小于结构元素的噪声点
cv.erode(img, kernel, iterations)
- img:要处理的图像
- kernel:核结构
- iterations:腐蚀的次数,默认为1
膨胀
具体操作是:用一个结构元素扫描图像中的每一个像素,用结构元素中的每一个像素与其覆盖的像素做“与”操作,如果都为0,则该像素为0,否则为1
如下图所示,结构A被结构B腐蚀后:
用结构B的中心点去扫描结构A的每一个像素点,每次比较时,用结构B中所有像素值为1的与结构A中对应点做与操作,做完与操作后,如果所有结果不都为0,则此次比较结果为1,此时赋值给中心点像素值为1,依次比较所有像素点即可
注意只添加结构B的中心点像素
膨胀的作用是将与物体接触的所有背景点合并到物体中,是目标增大,可填补目标中的孔洞(4邻域都有像素,中心没有像素->中心填补像素)
cv.dilate(img, kernel, iterations)
- img:要处理的图像
- kernel:核结构
- iterations:腐蚀的次数,默认为1
膨胀和腐蚀
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
#读取图像、图像二值化
img_color = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/color.jpg", 0)
ret2,img_color_mask_OTSU=cv.threshold(img_color,0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
#创建核结构
kernerl = np.ones((5, 5), np.uint8)
#腐蚀与膨胀
img_color_erosion = cv.erode(img_color_mask_OTSU, kernerl, iterations=1)
img_color_dilate = cv.dilate(img_color_mask_OTSU, kernerl, iterations=1)
#灰度显示二值化图像
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10,8), dpi = 100)
axes[0].imshow(img_color_mask_OTSU, cmap='gray')
axes[0].set_title("原图")
axes[1].imshow(img_color_erosion, cmap='gray')
axes[1].set_title("腐蚀后") # 高亮区变小
axes[2].imshow(img_color_dilate, cmap='gray')
axes[2].set_title("膨胀后") # 高亮区变大
plt.show()
开运算、闭运算
开运算和闭运算是将腐蚀和膨胀按照一定的次序进行处理。 两者不可逆,即先开后闭并不能得到原来的图像
开运算
开运算是先腐蚀后膨胀
作用是:分离物体,消除小区域
特点:消除噪点,去除小的干扰块,而不影响原来的图像
kernel使用的是9×9的绿色十字结构
闭运算
闭运算是先膨胀后腐蚀
作用是消除“闭合”物体里面的孔洞
特点:可以填充闭合区域
kernel使用的是9×9的绿色十字结构
开运算和闭运算
cv.morphologyEx(img,op, kernel)
参数:
- img:要处理的图像
- op:处理方式
- 若进行开运算,则设为
cv.MORPH_OPEN
- 若进行闭运算,则设为
cv.MORPH_CLOSE
- 若进行开运算,则设为
- Kernel:核结构
#%%
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
#读取图像
img_num_open = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/img_num_open.png")
img_num_close = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/img_num_close.png")
#kenrnel
kernel = np.ones((10, 10), np.uint8)
img_num_open_open = cv.morphologyEx(img_num_open, cv.MORPH_OPEN, kernel)
img_num_close_close = cv.morphologyEx(img_num_close, cv.MORPH_CLOSE, kernel)
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(10,8), dpi = 100)
axes[0,0].imshow(img_num_open)
axes[0,0].set_title("img_num_open原图")
axes[0,1].imshow(img_num_open_open)
axes[0,1].set_title("img_num_open开运算后") # 消除了噪点
axes[1,0].imshow(img_num_close)
axes[1,0].set_title("img_num_close原图")
axes[1,1].imshow(img_num_close_close)
axes[1,1].set_title("img_num_close_close闭运算后") # 填补了孔洞
礼帽、黑帽
礼帽运算
原图像与“开运算“的结果图之差,dst = tophat(src, element) = src - open(src, element)
因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中 减去 开运算后的图 得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关
礼帽运算作用:分离比邻近点亮一些的斑块
黑帽运算
原图像与“闭运算“的结果图之差,dst = blackhat(src, element)= close(src, element)- src
因为闭运算带来的结果是填充了闭合区域中的孔洞,因此,从闭运算后的图 减去 原图 得到的效果图突出了原图中闭合区域的孔洞,突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关
黑帽运算作用:分离比邻近点暗一些的斑块
礼帽运算和黑帽运算
cv.morphologyEX(img, op, kernel)
-
img:要处理的图像,注意是原图像不是开运算或闭运算后的图像
-
op:处理方式
-
参数 功能 CUMORPH_CLOSE
闭运算 CVMORPH_OPEN
开运算 CMORPH_TOPHAT
礼帽运算 CVMORPH_BLACKHAT
黑帽运
-
-
kernel:核结构
#%%
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
#读取图像
img_num_open = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/img_num_open.png")
img_num_close = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/img_num_close.png")
#kenrnel
kernel = np.ones((9, 9), np.uint8)
img_num_open_open = cv.morphologyEx(img_num_open, cv.MORPH_OPEN, kernel)
img_num_close_close = cv.morphologyEx(img_num_close, cv.MORPH_CLOSE, kernel)
img_num_topHat = cv.morphologyEx(img_num_open, cv.MORPH_TOPHAT, kernel)
img_num_blackHat = cv.morphologyEx(img_num_close, cv.MORPH_BLACKHAT, kernel)
fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10,8), dpi = 100)
axes[0,0].imshow(img_num_open)
axes[0,0].set_title("img_num_open原图")
axes[0,1].imshow(img_num_open_open)
axes[0,1].set_title("img_num_open开运算后")
axes[0,2].imshow(img_num_topHat)
axes[0,2].set_title("礼帽运算后")
axes[1,0].imshow(img_num_close)
axes[1,0].set_title("img_num_close原图")
axes[1,1].imshow(img_num_close_close)
axes[1,2].set_title("img_num_close_close闭运算后")
axes[1,2].imshow(img_num_blackHat)
axes[1,2].set_title("黑帽运算后")
3. 梯度运算
图像梯度代表图像灰度值变化的速度,对于一副图像而言,其边缘部分两侧灰度值相差较大,梯度值大,所以对一副图像求梯度可以突出图像边缘的信息。由于像素值为离散值,所以其梯度其是就是差分运算,其本质上也就是一种空间滤波。
#%%
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
#读取图像
img_circle = cv.imread("G:/Pycharm_workspace/OpenCV_Study/image/circle.jpg")
kernel = np.ones((3,3), np.uint8)
img_circle_dilate = cv.dilate(img_circle, kernel, iterations=5)
img_circle_erosion = cv.erode(img_circle, kernel, iterations=5)
# 组合膨胀后图像和腐蚀后图像
res = np.hstack((img_circle_dilate, img_circle_erosion))
# 梯度运算 = 膨胀 - 腐蚀 默认iterations=1
img_circle_gradient = cv.morphologyEx(img_circle, cv.MORPH_GRADIENT, kernel)
fig, axes = plt.subplots(nrows=3, ncols=2, figsize=(10,10), dpi = 100)
axes[0,0].imshow(img_circle)
axes[0,0].set_title("img_circle")
axes[0,1].imshow(img_circle)
axes[0,1].set_title("img_circle")
axes[1,0].imshow(img_circle_dilate)
axes[1,0].set_title("img_circle_dilate")
axes[1,1].imshow(img_circle_erosion)
axes[1,1].set_title("img_circle_erosion")
axes[2,0].imshow(res)
axes[2,0].set_title("res")
axes[2,1].imshow(img_circle_gradient)
axes[2,1].set_title("img_circle_gradient")
resule = cv.subtract(img_circle_dilate, img_circle_erosion) # cv图像相减方法
plt.imshow(resule)
plt.title("subtract")
Sobel算子
G x = A ∗ [ − 1 0 + 1 − 2 0 + 2 − 1 0 + 1 ] a n d G y = A ∗ [ − 1 − 2 − 1 0 0 0 + 1 + 2 + 1 ] G_x =A* \left[\begin {array}{c} -1 &0 &+1\\ -2 &0 &+2 \\ -1 &0 &+1 \\ \end{array}\right] \quad and\quad G_y =A* \left[\begin {array}{c} -1 &-2 &-1\\ 0 &0 &0 \\ +1 &+2 &+1 \\ \end{array}\right] Gx=A∗ −1−2−1000+1+2+1 andGy=A∗ −10+1−20+2−10+1
注意:这里的*是卷积运算, 默认步长为1,不是矩阵乘法
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
- ddepth:图像的深度,默认值-1
- dx和dy:分别表示水平和竖直方向,取值为0或1,表示是沿x方向还是y方向
- ksize:Sobel算子的大小,一般为3*3大小,即设置为ksize=3
# sobel算子
# convertScaleAbs取绝对值,当白到黑是正数,黑到白则为负数(为防止负数截断为0,此时需要去绝对值)
# 水平方向求梯度时,是原图的右边减左边
# 竖直方向求梯度时,是原图的下边减上边
img_circle_sobel_x = cv.Sobel(img_circle, cv.CV_64F, 1, 0, ksize=3)
img_circle_sobel_x_abs = cv.convertScaleAbs(img_circle_sobel_x)
img_circle_sobel_y = cv.Sobel(img_circle, cv.CV_64F, 0, 1, ksize=3)
img_circle_sobel_y_abs = cv.convertScaleAbs(img_circle_sobel_y)
# 先分别求x方向和y方向的梯度,然后进行求和
img_circle_sobel_xy_ABS = cv.addWeighted(img_circle_sobel_x_abs, 0.5, img_circle_sobel_y_abs, 0.5, 0)
# 直接求x方向和y方向的梯度和
img_circle_sobel_xy_abs = cv.Sobel(img_circle, cv.CV_64F, 1, 1, ksize=3)
"""
建议:
先分别求x方向和y方向的梯度,然后进行求和,比,直接求x方向和y方向的梯度和,的效果要好
"""
fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(10,20), dpi = 100)
axes[0,0].imshow(img_circle)
axes[0,0].set_title("img_circle")
axes[0,1].imshow(img_circle)
axes[0,1].set_title("img_circle")
axes[1,0].imshow(img_circle_sobel_x)
axes[1,0].set_title("img_circle_sobel_x")
axes[1,1].imshow(img_circle_sobel_y)
axes[1,1].set_title("img_circle_sobel_y")
axes[2,0].imshow(img_circle_sobel_x_abs)
axes[2,0].set_title("img_circle_sobel_x_abs")
axes[2,1].imshow(img_circle_sobel_y_abs)
axes[2,1].set_title("img_circle_sobel_y_abs")
axes[3,0].imshow(img_circle_sobel_xy_ABS)
axes[3,0].set_title("img_circle_sobel_xy_ABS")
axes[3,1].imshow(img_circle_sobel_xy_abs)
axes[3,1].set_title("img_circle_sobel_xy_abs")
Scharr算子
G x = A ∗ [ − 3 0 + 3 − 10 0 + 10 − 3 0 + 3 ] a n d G y = A ∗ [ − 3 − 10 − 3 0 0 0 + 3 + 10 + 3 ] G_x =A* \left[\begin {array}{c} -3 &0 &+3\\ -10 &0 &+10 \\ -3 &0 &+3 \\ \end{array}\right] \quad and\quad G_y =A* \left[\begin {array}{c} -3 &-10 &-3\\ 0 &0 &0 \\ +3 &+10 &+3 \\ \end{array}\right] Gx=A∗ −3−10−3000+3+10+3 andGy=A∗ −30+3−100+10−30+3
相较于Sobel算子对梯度变化更明显
cv.Scharr(src, ddepth, dx, dy)
- ddepth:图像的深度,默认值-1
- dx和dy:分别表示水平和竖直方向,取值为0或1,表示是沿x方向还是y方向
Laplacian算子
G = A ∗ [ 0 − 1 0 1 − 4 1 0 1 0 ] G =A* \left[\begin {array}{c} 0 &-1 &0\\ 1 &-4 &1 \\ 0 &1 &0 \\ \end{array}\right] G=A∗ 010−1−41010
相当于二阶导,对梯度变化更明显,对噪音点更敏感
cv.Laplacian(src, ddepth)
- ddepth:图像的深度,默认值-1
三个算子差距
4. 图像噪声
常见的图像噪声有高斯噪声、椒盐噪声等
椒盐噪声
椒盐噪声(也称为脉冲噪声),是一种随机出现的白点或者黑点,可能是亮的区域有黑色像素或是在暗的区域有白色像素(或是两者皆有)
椒盐噪声的成因可能是影像讯号受到突如其来的强烈干扰而产生、类比数位转换器或位元传输错误等。例如失效的感应器导致像素值为最小值,饱和的感应器导致像素值为最大值
高斯噪声
高斯噪声(也称为正态噪声),是指噪声密度函数服从高斯分布的一类噪声,在空间和频域中数学上的易处理性。
高斯随机变量z的概率密度函数:
p
(
z
)
=
1
2
π
σ
e
−
(
z
−
μ
)
2
σ
2
p(z)=\frac{1}{\sqrt{2π}σ}e{\frac{-(z-μ)}{2σ^2}}
p(z)=2πσ1e2σ2−(z−μ)
- z:表示灰度值
- μ:表示z的平均值或期望值
- σ:表示z的标准差
- σ^2:表示z的方差
5. 平滑处理
均值滤波
cv.blur(img, (x,y))
- (x,y):设置一个x*y的矩阵,对img进行卷积操作,对值求均值,然后存入
方框滤波
cv.boxFilter(img, -1, (x,y), normalize=True)
- (x,y):设置一个x*y的矩阵,对img进行卷积操作
- normalize:
- 值为True时进行均值操作,和均值滤波作用一致
- 值为False时不进行均值操作,此时卷积操作后的值为多少就存入多少,如果大于255,则存入255即可
高斯滤波
高斯滤波器是一种线性滤波器,能够有效的抑制噪声,平滑图像。其作用原理和均值滤波器类似,都是取滤波器窗口内的像素的均值作为输出。但其窗口模板的系数和均值滤波器不同,均值滤波器的模板系数都是相同的为1,而高斯滤波器的模板系数则随着距离模板中心的增大而减小。所以,高斯滤波器相比于均值滤波器对图像个模糊程度较小
cv.GaussianBlur(img, (x,y), sigma)
- (x,y):设置一个x*y的矩阵,设置设置中间的卷积核值为1,设置周围卷积核值为相较于中间值的比例,然后对图像进行卷积操作,最后存入值
- sigma:标准差
中值滤波
cv.medianBlur(img, ksize)
- ksize:卷积核大小,找到图像在卷积矩阵的中间值,当做最终值存入
综合
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
img_people_nosie = cv.imread("../../image/img_people_salt_noise.jpg")
# 均值滤波
# 简单的平均卷积操作
blur = cv.blur(img_people_nosie, (5,5))
# 方框滤波
# 基本和均值一样,可以选择归一化
box_True = cv.boxFilter(img_people_nosie, -1, (5,5), normalize=True)
box_False = cv.boxFilter(img_people_nosie, -1, (5,5), normalize=False)
# 高斯滤波
# 高斯模糊的卷积核里的数值是满足高斯分布,相当于更重视中间
guassian = cv.GaussianBlur(img_people_nosie, (5,5), 0.1)
# 中值滤波
# 相当于用中值代替
median = cv.medianBlur(img_people_nosie, 5)
plt.imshow(median[:,:,::-1])
fig, axes = plt.subplots(nrows=1, ncols=5, figsize=(10,20), dpi=100)
axes[0].imshow(img_people_nosie[:,:,::-1])
axes[0].set_title("img_people_nosie")
axes[1].imshow(blur[:,:,::-1])
axes[1].set_title("blur")
axes[2].imshow(box_True[:,:,::-1])
axes[2].set_title("box_True")
axes[3].imshow(guassian[:,:,::-1])
axes[3].set_title("guassian")
axes[4].imshow(median[:,:,::-1])
axes[4].set_title("median")
6. 图像阈值(二值化)
图像的阈值处理又称为二值化,将一幅图转换为感兴趣的部分(前景)和不感兴趣的部分(背景),二值化可以剔除掉图像中一些低于或高于一定值(即阈值)的像素,从而提取图像中的物体,通常将超过阈值的像素作为前景。阈值又称为临界值,它的目的是确定出一个范围,然后这个范围内的像素点使用同一种方法处理,而阈值之外的部分则使用另一种处理方法或保持原样
ret, dst = cv2.threshold(src, thresh, maxval, type)
- ret:获得设置的阈值值
- dst:输出图
- src:输入图,只能输入单通道图像,通常来说为灰度图
- thresh:阈值
- maxval:当像素值超过了闻值(或者小于值,根据type来决定),所赋予的值
- type:二化操作的类型,包含以下5类型
CV2.THRESH_BINARY
:大于阈值的部分取值为maxval,小于阈值的部分取值为0CV2.THRESH_BINARY_INV
:CV2.THRESH_BINARY
的反转CV2.THRESH_TRUNC
:大于阈值的部分取值为thresh,小于阈值的部分取值不变CV2.THRESH_TOZERO
:大于阈值的部分取值为不变,小于阈值的部分取值为threshCV2.THRESH_TOZERO_INV
:CV2.THRESH_TOZERO
的反转
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
img_sketch = cv.imread("../../image/img_sketch.jpg")
ret, thresh1 = cv.threshold(img_sketch, 127, 255, cv.THRESH_BINARY)
ret, thresh2 = cv.threshold(img_sketch, 127, 255, cv.THRESH_BINARY_INV)
ret, thresh3 = cv.threshold(img_sketch, 127, 255, cv.THRESH_TRUNC)
ret, thresh4 = cv.threshold(img_sketch, 127, 255, cv.THRESH_TOZERO)
ret, thresh5 = cv.threshold(img_sketch, 127, 255, cv.THRESH_TOZERO_INV)
fig, axes = plt.subplots(nrows=1, ncols=6, figsize=(10,20), dpi = 100)
axes[0].imshow(img_sketch)
axes[0].set_title("img_sketch")
axes[1].imshow(thresh1)
axes[1].set_title("thresh1")
axes[2].imshow(thresh2)
axes[2].set_title("thresh2")
axes[3].imshow(thresh3)
axes[3].set_title("thresh3")
axes[4].imshow(thresh4)
axes[4].set_title("thresh4")
axes[5].imshow(thresh5)
axes[5].set_title("thresh5")
7. Canny边缘检测
- 使用高斯滤波器,以平滑图像,滤除噪声
- 计算图像中每个像素点的梯度强度和方向
- 应用非极大值 (Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应
- 应用双阈值(Double-Threshold) 检测来确定真实的和潜在的边缘
- 通过抑制孤立的弱边缘最终完成边缘检测
高斯滤波器
e = H ∗ A = [ h 11 h 12 h 13 h 21 h 22 h 23 h 31 h 32 h 33 ] ∗ [ a b c d e f g h i ] = s u m ( [ a ∗ h 11 b ∗ h 12 c ∗ h 13 d ∗ h 21 e ∗ h 22 f ∗ h 23 g ∗ h 31 h ∗ h 32 i ∗ h 33 ] ) e =H*A =\left[\begin {array}{c} h_{11} &h_{12} &h_{13}\\ h_{21} &h_{22} &h_{23}\\ h_{31} &h_{32} &h_{33}\\ \end{array}\right] * \left[\begin {array}{c} a &b &c\\ d &e &f\\ g &h &i\\ \end{array}\right] =sum( \left[\begin {array}{c} a*h_{11} &b*h_{12} &c*h_{13}\\ d*h_{21} &e*h_{22} &f*h_{23}\\ g*h_{31} &h*h_{32} &i*h_{33}\\ \end{array}\right] ) e=H∗A= h11h21h31h12h22h32h13h23h33 ∗ adgbehcfi =sum( a∗h11d∗h21g∗h31b∗h12e∗h22h∗h32c∗h13f∗h23i∗h33 )
梯度和方向
G = G x 2 + G y 2 θ = a r c t a n ( G y / G x ) G = \sqrt{G^2_x+G^2_y} \quad \quad θ=arctan(G_y/G_x) \quad \quad G=Gx2+Gy2θ=arctan(Gy/Gx)
S x = [ − 1 0 + 1 − 2 0 + 2 − 1 0 + 1 ] S y = [ − 1 − 2 − 1 0 0 0 + 1 + 2 + 1 ] S_x= \left[\begin {array}{c} -1 &0 &+1\\ -2 &0 &+2 \\ -1 &0 &+1 \\ \end{array}\right] \quad \quad S_y= \left[\begin {array}{c} -1 &-2 &-1\\ 0 &0 &0 \\ +1 &+2 &+1 \\ \end{array}\right] Sx= −1−2−1000+1+2+1 Sy= −10+1−20+2−10+1
G x = S x ∗ A = [ − 1 0 + 1 − 2 0 + 2 − 1 0 + 1 ] ∗ [ a b c d e f g h i ] = s u m ( [ − a 0 c − 2 d 0 2 f − g 0 i ] ) G_x =S_x*A =\left[\begin {array}{c} -1 &0 &+1\\ -2 &0 &+2 \\ -1 &0 &+1 \\ \end{array}\right] * \left[\begin {array}{c} a &b &c\\ d &e &f\\ g &h &i\\ \end{array}\right] =sum( \left[\begin {array}{c} -a &0 &c\\ -2d&0 &2f\\ -g &0 &i\\ \end{array}\right] ) Gx=Sx∗A= −1−2−1000+1+2+1 ∗ adgbehcfi =sum( −a−2d−g000c2fi )
G y = S y ∗ A = [ − 1 − 2 − 1 0 0 0 + 1 + 2 + 1 ] ∗ [ a b c d e f g h i ] = s u m ( [ a 2 b c 0 0 0 − g − 2 h − i ] ) G_y =S_y*A =\left[\begin {array}{c} -1 &-2 &-1\\ 0 &0 &0 \\ +1 &+2 &+1 \\ \end{array}\right] * \left[\begin {array}{c} a &b &c\\ d &e &f\\ g &h &i\\ \end{array}\right] =sum( \left[\begin {array}{c} a&2b &c\\ 0&0 &0\\ -g &-2h&-i\\ \end{array}\right] ) Gy=Sy∗A= −10+1−20+2−10+1 ∗ adgbehcfi =sum( a0−g2b0−2hc0−i )
非极大值抑制
线性插值法:设g1的梯度幅值M(g1),g2的梯度幅值M(g2),则dtmp1可以很得到:M(dtmp1)=w * M(g2) + (1-w) * M(g1)
其中w=distance(dtmp1,g1)/distance(g1,g2),distance(g1,g2) 表示两点之间的距离,如果c比dtmp1、dtmp2的值都大,则c是极大值保留下来
上图中,在已知梯度方向为CAB时,求离A最近的两个像素点的值,如果A比B、C的值都大,则判定A为极大值,即为边界
双阈值检测
- 梯度值>maxVal:则处理为边界
- minVal<梯度值<maxVal:连有边界则保留,否则舍弃
- 梯度值<minVal:则舍弃
综合
cv.Canny(img, minVal, maxVal)
- minVal,maxVal:梯度值的最小和最大值
- 在minVal相同的情况下,maxVal越大,显示的边界信息更少
- 在maxVal相同的情况下,minVal越小,显示的边界信息更多
- 一般设置minVal值比较小,maxVal值比较小,此时显示的边界信息比较多
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
img_sketch = cv.imread("../../image/img_people.jpg")
img_sketch_canny1 = cv.Canny(img_sketch, 80, 150)
img_sketch_canny2 = cv.Canny(img_sketch, 50, 100)
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10,20), dpi=100)
axes[0].imshow(img_sketch[:,:,::-1])
axes[0].set_title("img_sketch")
axes[1].imshow(img_sketch_canny1)
axes[1].set_title("img_sketch_canny1")
axes[2].imshow(img_sketch_canny2)
axes[2].set_title("img_sketch_canny2")
8. 图像轮廓
获取轮廓信息
contours, hierarchy = cv2.findcontours(img,mode,method)
- mode:轮廓检索模式
- RETR_EXTERNAL:只检索最外面的轮廓
- RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中
- RETR_CCOMP:检索所有的轮廓,并将他们组织为两层: 顶层是各部分的外部边界,第二层是空洞的边界
- RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次,一般推荐只用这种模式
- method:轮廓逼近方法
- CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)
- CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分
- contours:返回的list表,保存轮廓信息
- hierarchy:返回的是层级结构
利用获取到的轮廓信息画轮廓
cv.drawContours(img, contours, x, (0,0,255), pix)
- img:在哪张图上画轮廓
- contours:获取到的轮廓信息
- x:
- -1:默认,表示画出全部轮廓
- 0~…:表示画第几个轮廓
- (0,0,255):(B,G,R)表示用那个颜色画轮廓
- pix:线条像素
- 注意:这种方法是在原始图像的基础上画轮廓,输出画好轮廓的图像,并且原始图像也会画上轮廓,所以一般先将原始图像copy,然后在copy的图像基础上画轮廓,此时就不会对原始图像进行修改
步骤:
- 读取图像
- 转化图像->灰度图->二值图
- 将二值图传入
cv.findContours
方法,获取contours(轮廓信息)和hierarchy(层级信息)两个值- 利用``copy`方法复制原始图像
- 将copy图像传入
cv.drawContours
方法,在copy的图像上画轮廓
#%%
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
img_contours = cv.imread("../../image/img_contours.jpg")
img_contours_gray = cv.cvtColor(img_contours, cv.COLOR_BGR2GRAY)
ret, img_contours_gray_thresh = cv.threshold(img_contours_gray,127, 255, cv.THRESH_BINARY)
plt.imshow(img_contours_gray_thresh, cmap="gray")
contours, hierarchy = cv.findContours(img_contours_gray_thresh, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
img_contours_copy1 = img_contours.copy()
img_contours_gray_thresh_contours_res1 = cv.drawContours(img_contours_copy1, contours, -1, (0,0,255), 2)
img_contours_copy2 = img_contours.copy()
img_contours_gray_thresh_contours_res2 = cv.drawContours(img_contours_copy2, contours, 0, (0,0,255), 2)
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8,8), dpi=100)
axes[0,0].imshow(img_contours[:,:,::-1])
axes[0,0].set_title("img_contours")
axes[0,1].imshow(img_contours[:,:,::-1])
axes[0,1].set_title("img_contours")
axes[1,0].imshow(img_contours_gray_thresh_contours_res1[:,:,::-1])
axes[1,0].set_title("img_contours_gray_thresh_contours_res1")
axes[1,1].imshow(img_contours_gray_thresh_contours_res2[:,:,::-1])
axes[1,1].set_title("img_contours_gray_thresh_contours_res2")
轮廓特征
步骤:
- 在获取的contours中取响应的轮廓
cont = contours[x]
- 利用
cv.contourArea(cont)
获取轮廓面积 - 利用
cv.arcLength(cont, True)
获取轮廓周长,True表示闭合 epslion = x*cv.arcLength(contours_xx[0], True)
- x值越小,越近似轮廓,不能大于1,
approx = cv.approxPolyDP(contours_xx[0], epslion, True)
- 复制原图像
cv.drawContours(img_copy, [approx], -1, (0,0,255), 2)
# 轮廓近似
img_circle = cv.imread("../../image/circle.jpg")
img_circle_gray = cv.cvtColor(img_circle, cv.COLOR_BGR2GRAY)
ret, img_circle_gray_thresh = cv.threshold(img_circle_gray, 125, 255, cv.THRESH_BINARY)
contours_xx, hierarchy_xx = cv.findContours(img_circle_gray_thresh, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
img_circle_copy = img_circle.copy()
res = cv.drawContours(img_circle_copy, contours_xx[0], -1, (0,0,255), 2)
epslion = 0.1*cv.arcLength(contours_xx[0], True)
approx = cv.approxPolyDP(contours_xx[0], epslion, True)
img_circle_copy1 = img_circle.copy()
res1 = cv.drawContours(img_circle_copy1, [approx], -1, (0,0,255), 2)
epslion = 0.005*cv.arcLength(contours_xx[0], True)
approx = cv.approxPolyDP(contours_xx[0], epslion, True)
img_circle_copy2 = img_circle.copy()
res2 = cv.drawContours(img_circle_copy2, [approx], -1, (0,0,255), 2)
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8,8), dpi=100)
axes[0,0].imshow(res[:,:,::-1])
axes[0,0].set_title("res")
axes[0,1].imshow(res[:,:,::-1])
axes[0,1].set_title("res")
axes[1,0].imshow(res1[:,:,::-1])
axes[1,0].set_title("res1")
axes[1,1].imshow(res2[:,:,::-1])
axes[1,1].set_title("res2")
9. 模板匹配
模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在penc里有6种,然后将每次计算的结果放入一个短阵里,作为结果输出。假如原图形是AxB大小,而模板是axb大小,则输出结里的短阵是(A-a+1)x(B-b+1)
min_val, max_val, min_loc, max_loc = cv.matchTemplate(img, img_template, penc)
- penc:
- TM_SQDIFF:计算平方不同,计算出来的值越小,越相关
- TM_CCORR:计算相关性,计算出来的值越大,越相关
- TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
- TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关
- TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关
- TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关
- min_val、min_loc:像素差的最小值,以及最小值的位置
- max_val、max_loc:像素差的最大值,以及最大值的位置
- 一般位置指的是矩形框的左上角,然后再利用
h, w = template.shape[:2]
进行切片,获得模板的长和宽,然后画矩形框即可
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
def set_ch():
from pylab import mpl
mpl.rcParams['font.sans-serif'] =['FangSong'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号-显示为方块的问题
set_ch()
# set_ch()解决matplotlib图像显示乱码或方块问题
img_sketch = cv.imread("../../image/img_sketch.jpg")
img_sketch_part = cv.imread("../../image/img_sketch_part.jpg")
h, w = img_sketch_part.shape[:2]
methods = ['cv.TM_CCOEFF','cv.TM_CCOEFF_NORMED',
'cv.TM_CCORR','cv.TM_CCORR_NORMED',
'cv.TM_SQDIFF','cv.TM_SQDIFF_NORMED']
for meth in methods:
img_sketch_copy1 = img_sketch.copy()
method = eval(meth)
res = cv.matchTemplate(img_sketch, img_sketch_part, method)
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
# 如果是平方差匹配TM_SQDIFF或归一化平方差匹配TM_SQDIFF_NORMED,取最小值
if method in ['TM_SQDIFF','TM_SQDIFF_NORMED']:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
cv.rectangle(img_sketch_copy1, top_left, bottom_right, (0,0,255), 2)
fig, axes = plt.subplots(nrows=1, ncols=4, figsize=(10,20), dpi=100)
axes[0].imshow(img_sketch)
axes[0].set_title("原图")
axes[1].imshow(img_sketch_part)
axes[1].set_title("截取部分")
axes[2].imshow(res, cmap="gray")
axes[2].set_title("res")
axes[3].imshow(img_sketch_copy1[:,:,::-1])
axes[3].set_title(meth)
# 匹配多个对象
img_game = cv.imread("../../image/img_game.jpg")
img_game_gray = cv.cvtColor(img_game, cv.COLOR_BGR2GRAY)
img_game_template = cv.imread("../../image/img_game_part.jpg")
img_game_template_gray = cv.cvtColor(img_game_template, cv.COLOR_BGR2GRAY)
h, w = img_game_template.shape[:2]
res = cv.matchTemplate(img_game_gray, img_game_template_gray, cv.TM_CCOEFF_NORMED)
threshold = 0.8
# 取匹配程度大于80%的坐标
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):
bottom_right1 = (pt[0]+w, pt[1]+h)
cv.rectangle(img_game, pt, bottom_right1, (0,0,255), 1)
plt.imshow(img_game[:,:,::-1])
5. 识别项目
步骤:
- 读取图像
- 灰度化
- 方便以后的二值化、礼帽、黑帽、梯度、开运算、闭运算使用
- 二值化
- 作用是:剔除掉图像中一些低于或高于一定值(即阈值)的像素,从而提取图像中的物体
ret, dst= cv2.threshold(src, thresh, maxval, type)
- dst:二值化后返回的图像,如果没有设置,则默认返回src
- thresh:最低阈值
- maxval:最高阈值
- type:二化操作的类型,包含以下5类型
CV2.THRESH_BINARY
:大于阈值的部分取值为maxval,小于阈值的部分取值为0CV2.THRESH_BINARY_INV
:CV2.THRESH_BINARY
的反转CV2.THRESH_TRUNC
:大于阈值的部分取值为thresh,小于阈值的部分取值不变CV2.THRESH_TOZERO
:大于阈值的部分取值为不变,小于阈值的部分取值为threshCV2.THRESH_TOZERO_INV
:CV2.THRESH_TOZERO
的反转
- 形态学操作
cv.morphologyEX(img, op, kernel)
- 膨胀
- 作用:填补图像中的孔洞
- op:
cv.MORPH_DILATE
- 腐蚀
- 作用:消除物体边界点,是目标缩小,可以消除小于结构元素的噪声点
- op:
cv.MORPH_ERODE
- 开运算
- 作用:先腐蚀后膨胀,分离物体,消除小区域,消除噪点,去除小的干扰块,而不影响原来的图像
- op:
cv.MORPH_OPEN
- 闭运算
- 作用:先膨胀后腐蚀,消除“闭合”物体里面的孔洞
- op:
cv.MORPH_CLOSE
- 礼帽运算
- 作用:原图像与“开运算“的结果图之差,显示高亮部分,突出了比原图轮廓周围的区域更明亮的区域
- op:
cv.MORPH_TOPHAT
- 黑帽运算
- 作用:原图像与“闭运算“的结果图之差,显示孔洞部分,突出了比原图轮廓周围的区域更暗的区域
- op:
cv.MORPH_BLACKHAT
- 梯度运算
- 作用:膨胀减去腐蚀后的效果,代表图像灰度值变化的速度,对于一副图像而言,其边缘部分两侧灰度值相差较大,梯度值大,对一副图像求梯度可以突出图像边缘的信息
- op:
cv.MORPH_GRADIENT
- 一般使用
cv.Sobel(src, ddepth, dx, dy, ksize)
而不使用cv.morphologyEX(img, op, kernel)
- ddepth一般设置为-1
- dx、dy:表示求x方向或y方向的梯度
- ksize:卷积核的大小一般设置为3
- 一般使用Sobel算子先求x、y方向上的梯度,然后通过
convertScaleAbs
方法分对两梯度求绝对值,然后通过cv.addWeighted(gradX, 0.5, gradY, 0.5, 0)
求总的梯度
- 平滑操作
- 必要时需要平滑操作,模糊图像,减少噪声
- 高斯平滑:高斯滤波器是一种线性滤波器,能够有效的抑制噪声,平滑图像
cv.GaussianBlur(img, (x,y), sigma)
- (x,y):设置一个x*y的矩阵,设置设置中间的卷积核值为1,设置周围卷积核值为相较于中间值的比例,然后对图像进行卷积操作,最后存入值
- sigma:标准差
- 膨胀
- 轮廓运算
- 找轮廓
contours, hierarchy = cv2.findcontours(img,mode,method)
- contours:表示图像的轮廓信息
- mode:轮廓检索模式
- RETR_EXTERNAL:只检索最外面的轮廓
- RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中
- RETR_CCOMP:检索所有的轮廓,并将他们组织为两层: 顶层是各部分的外部边界,第二层是空洞的边界
- RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次,一般推荐用这种模式
- method:轮廓逼近方法
- CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)
- CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分
- 画轮廓
cv.drawContours(img, contours, x, (0,0,255), pix)
- x:
- -1:默认,表示画出全部轮廓
- 0~…:表示画第几个轮廓
- x:
- 注意:轮廓计算中,必须先通过
img.copy()
将原图像复制,在两个方法中传入的是复制后的图像不是原图像(为了防止直接修改原图像)
- 找轮廓
- 作用:先腐蚀后膨胀,分离物体,消除小区域,消除噪点,去除小的干扰块,而不影响原来的图像
- op:cv.MORPH_OPEN
4. 闭运算
- 作用:先膨胀后腐蚀,消除“闭合”物体里面的孔洞
- op:cv.MORPH_CLOSE
5. 礼帽运算
- 作用:原图像与“开运算“的结果图之差,显示高亮部分,突出了比原图轮廓周围的区域更明亮的区域
- op:cv.MORPH_TOPHAT
6. 黑帽运算
- 作用:原图像与“闭运算“的结果图之差,显示孔洞部分,突出了比原图轮廓周围的区域更暗的区域
- op:cv.MORPH_BLACKHAT
7. 梯度运算
- 作用:膨胀减去腐蚀后的效果,代表图像灰度值变化的速度,对于一副图像而言,其边缘部分两侧灰度值相差较大,梯度值大,对一副图像求梯度可以突出图像边缘的信息
- op:cv.MORPH_GRADIENT
- 一般使用cv.Sobel(src, ddepth, dx, dy, ksize)
而不使用cv.morphologyEX(img, op, kernel)
- ddepth一般设置为-1
- dx、dy:表示求x方向或y方向的梯度
- ksize:卷积核的大小一般设置为3
- 一般使用Sobel算子先求x、y方向上的梯度,然后通过convertScaleAbs
方法分对两梯度求绝对值,然后通过cv.addWeighted(gradX, 0.5, gradY, 0.5, 0)
求总的梯度
8. 平滑操作
- 必要时需要平滑操作,模糊图像,减少噪声
- 高斯平滑:高斯滤波器是一种线性滤波器,能够有效的抑制噪声,平滑图像
-cv.GaussianBlur(img, (x,y), sigma)
- (x,y):设置一个x*y的矩阵,设置设置中间的卷积核值为1,设置周围卷积核值为相较于中间值的比例,然后对图像进行卷积操作,最后存入值
- sigma:标准差
- 轮廓运算
- 找轮廓
contours, hierarchy = cv2.findcontours(img,mode,method)
- contours:表示图像的轮廓信息
- mode:轮廓检索模式
- RETR_EXTERNAL:只检索最外面的轮廓
- RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中
- RETR_CCOMP:检索所有的轮廓,并将他们组织为两层: 顶层是各部分的外部边界,第二层是空洞的边界
- RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次,一般推荐用这种模式
- method:轮廓逼近方法
- CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)
- CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分
- 画轮廓
cv.drawContours(img, contours, x, (0,0,255), pix)
- x:
- -1:默认,表示画出全部轮廓
- 0~…:表示画第几个轮廓
- x:
- 注意:轮廓计算中,必须先通过
img.copy()
将原图像复制,在两个方法中传入的是复制后的图像不是原图像(为了防止直接修改原图像)
- 找轮廓