opencv-python

opencv-python总结

1.简介与安装

​ 安装 opencv 3.4.1;python3.6;Pycharm 2021.1;

2.基本元素-图片

​ 学习如何加载图片,显示并保存图片

目标

  • 加载图片,显示图片,保存图片

  • OpenCV函数:cv2.imread(), cv2.imshow(), cv2.imwrite()

    大部分人可能都知道电脑上的彩色图是以RGB(红-绿-蓝,Red-Green-Blue)颜色模式显示的,但OpenCV中彩色图是以B-G-R通道顺序存储的,灰度图只有一个通道。

    图像坐标的起始点是在左上角,所以行对应的是y,列对应的是x

加载图片

​ 使用 cv2.imread()来读取一张图片:

import cv2

# 加载灰度图
img = cv2.imread('./imgs/cat.jpg', 0)  

'''
参数1:图片的文件名
如果图片放在当前文件夹下,直接写文件名就行,如'lena.jpg'
否则需要给出绝对路径,如'D:\OpenCVSamples\lena.jpg'
参数2:读入方式,省略即采用默认值
cv2.IMREAD_COLOR:彩色图,默认值(1)
cv2.IMREAD_GRAYSCALE:灰度图(0)
cv2.IMREAD_UNCHANGED:包含透明通道的彩色图(-1)
'''
print(img, type(img))  # 图片的类型:numpy.ndarray
print(img.size)  # 图片的像素点个数  207000
print(img.dtype)  # 图片的数据类型  uint8

显示图片

使用 `cv2.imshow()`来显示图片,窗口会自适应图片的大小:
cv2.imshow('lena', img)
# 参数1是窗口的名字,参数2是要显示的图片。不同窗口之间用窗口名区分,所以窗口名相同就表示是同一个窗口
cv2.waitKey(0)
# cv2.waitKey()是让程序暂停的意思,参数是等待时间(毫秒ms)。时间一到,会继续执行接下来的程序,传入0的话表示一直等待。等待期间也可以获取用户的按键输入:k = cv2.waitKey(0)

​ 我们也可以用 cv2.namedWindow()创建一个窗口,之后再显示图片:

# 先定义窗口,后显示图片
cv2.namedWindow('lena2', cv2.WINDOW_NORMAL)
# 参数1依旧是窗口的名字,参数2默认是cv2.WINDOW_AUTOSIZE,表示窗口大小自适应图片,也可以设置为cv2.WINDOW_NORMAL,表示窗口大小可调整。图片比较大的时候,可以考虑用后者。
cv2.imshow('lena2', img)
cv2.waitKey(0)

保存图片

​ 使用cv2.imwrite()保存图片,参数1是包含后缀名的文件:

cv2.imwrite('lena_gray.jpg', img)
cv2.destroyAllWindows()  # 销毁所有窗口

小结

  • cv2.imread()读取图片、cv2.imshow()显示图片、cv2.imwrite()保存图片、cv2.waitKey()程序暂停的时间、cv2.namedWindow()定义窗口、cv2.destroyAllwindows() 销毁所有窗口、type(img)图片的类型是numpy.ndarray、img.dtype图片的数据类型:uint8、img.size图片的像素大小。

3.打开摄像头

​ 学习打开摄像头捕获照片、播放本地视频、录制视频等

目标

  • 打开摄像头并捕获照片
  • 播放本地视频,录制视频
  • OpenCV函数:cv2.VideoCapture(), cv2.VideoWriter()

打开摄像头

​ 要使用摄像头,需要使用cv2.VideoCapture(0)创建VideoCapture对象,参数0指的是摄像头的编号,如果你电脑上有两个摄像头的话,访问第2个摄像头就可以传入1,依此类推。

# 打开摄像头并灰度化显示
import cv2

capture = cv2.VideoCapture(0)

while(True):
    # 获取一帧
    ret, frame = capture.read()
    # 将这帧转换为灰度图
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    cv2.imshow('frame', gray)
    if cv2.waitKey(1) == ord('q'):
        break

capture.read()函数返回的第1个参数ret(return value缩写)是一个布尔值,表示当前这一帧是否获取正确。cv2.cvtColor()用来转换颜色,这里将彩色图转成灰度图。

​ 另外,通过cap.get(propId)可以获取摄像头的一些属性,比如捕获的分辨率,亮度和对比度等。propId是从0~18的数字,代表不同的属性,完整的属性列表可以参考:VideoCaptureProperties。也可以使用cap.set(propId,value)来修改属性值。比如说,我们在while之前添加下面的代码:

# 获取捕获的分辨率
# propId可以直接写数字,也可以用OpenCV的符号表示
width, height = capture.get(3), capture.get(4)
print(width, height)

# 以原分辨率的一倍来捕获
capture.set(cv2.CAP_PROP_FRAME_WIDTH, width * 2)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, height * 2)

播放本地视频

​ 跟打开摄像头一样,如果把摄像头的编号换成视频的路径就可以播放本地视频了。回想一下cv2.waitKey(),它的参数表示暂停时间,所以这个值越大,视频播放速度越慢,反之,播放速度越快,通常设置为25或30。

# 播放本地视频
capture = cv2.VideoCapture('demo_video.mp4')  # 参数改成视频的存放位置

while(capture.isOpened()):
    ret, frame = capture.read()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    cv2.imshow('frame', gray)
    if cv2.waitKey(30) == ord('q'):
        break

录制视频

​ 之前我们保存图片用的是cv2.imwrite(),要保存视频,我们需要创建一个VideoWriter的对象,需要给它传入四个参数:

  • 输出的文件名,如’output.avi’
  • 编码方式FourCC码
  • 帧率FPS
  • 要保存的分辨率大小

​ FourCC是用来指定视频编码方式的四字节码,所有的编码可参考Video Codecs。如MJPG编码可以这样写: cv2.VideoWriter_fourcc(*'MJPG')cv2.VideoWriter_fourcc('M','J','P','G')

capture = cv2.VideoCapture(0)

# 定义编码方式并创建VideoWriter对象
fourcc = cv2.VideoWriter_fourcc(*'MJPG')  # Fourcc:视频的编码方式
outfile = cv2.VideoWriter('output.avi', fourcc, 25., (640, 480))

while(capture.isOpened()):
    ret, frame = capture.read()

    if ret:
        outfile.write(frame)  # 写入文件
        cv2.imshow('frame', frame)
        if cv2.waitKey(1) == ord('q'):
            break
    else:
        break

小结

  • 使用cv2.VideoCapture()创建视频对象,然后在循环中一帧帧显示图像。参数传入数字时,代表打开摄像头,传入本地视频路径时,表示播放本地视频。
  • cap.get(propId)获取视频属性,cap.set(propId,value)设置视频属性。
  • cv2.VideoWriter()创建视频写入对象,用来录制/保存视频。

4. 图像基本操作

学习获取和修改像素点的值,ROI感兴趣区域,通道分离合并等基本操作。

目标

  • 访问和修改图片像素点的值
  • 获取图片的宽、高、通道等属性
  • 了解感兴趣区域 ROI
  • 分离和合并图像通道

获取和修改像素点值

import cv2

# 先读取图片
img = cv2.imread("lena.jpg") 

# 通过行列的坐标来获取像素点的值,对于彩色图,结果是 R,G,B 三个值的列表,对于灰度图或单通道图,只有一个值:
px = img[100, 90]
print(px)  # [103 98 197]

# 只获取红色 red 通道的值
px_blue = img[100, 90, 0]
pringr(px_blue)  # 103
# 行对应y,列对应x,所以其实是img[y, x]

# 修改像素的值也是同样的方式:
img[100, 90] = [255, 255, 255]
print(img[100, 90])  # [255 255 255]

# 注意:这步操作只是内存中的img像素点值变了,因为没有保存,所以原图并没有更改。

图片属性

img.shape 获取图像的形状,图片是彩色的话,返回的结果是一个包含行数(高度)、列数(宽度)和通道数的元组,灰度图只返回行数和列数:

print(img.shape)  # (263, 247, 3)  # 形状中包括行数、列数和通道数
height, width, channels = img.shape

# img是灰度图的话:
height, width = img.shape

img.dtype获取图像数据类型:

print(img.dtype)  # uint8
# 多数错误是因为数据类型不对导致的,所以健壮的代码应该对这个属性加以判断。

img.size获取图像总像素数:

print(img.size)  # 263*247*3=194883

ROI

ROI: Region of Interest,感兴趣区域。

截取ROI非常简单,指定图片的范围即可(后面我们学了特征后,就可以自动截取辣,(ง •_•)ง):

# 截取脸部ROI
face = img[100:200, 115:188]
cv2.imshow('face', face)
cv2.waitKey(0)

通道分割与合并

彩色图的BGR三个通道是可以分开单独访问的,也可以将单独的三个通道合并成一副图像。分别使用cv2.split()cv2.merge()

b, g, r = cv2.split()
img = cv2.merge((b, g, r))

split()函数比较耗时,更高效的方式是用numpy中的索引,如提取B通道:

b = img[:, :, 0]  # b:0  g:1  r:2
cv2.imshow('blue', b)
cv2.waitkey(0)

小结

  • img[y,x]获取/设置像素点值,img.shape:图片的形状(行数、列数、通道数),img.dtype:图像的数据类型。
  • img[y1:y2,x1:x2]进行ROI截取,cv2.split()/cv2.merge()通道分割/合并。更推荐的获取单通道方式:b = img[:, :, 0]

5.颜色空间转换

学习如何进行图片的颜色空间转换,视频中追踪特定颜色的物体。

目标

  • 颜色空间转换,如BGR↔Gray,BGR↔HSV等
  • 追踪视频中特定颜色的物体
  • OpenCV函数:cv2.cvtColor(), cv2.inRange()

颜色空间转换

import cv2

img = cv2.imread('lena.jpg')
# 转换为灰度图
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

cv2.imshow('img', img)
cv2.imshow('img_gray', img_gray)
cv2.waitKey(0)

cv2.cvtColor()用来进行颜色模型转换,参数1是要转换的图片,参数2是转换模式, COLOR_BGR2GRAY表示BGR→Gray,可用下面的代码显示所有的转换模式:

flags = [i for i in dir(cv2) if i.startswith('COLOR_')]  # 列表推导式得到所有的颜色转换模式
print(flags)

注意:颜色转换其实是数学运算,如灰度化最常用的是:gray=R*0.299+G*0.587+B*0.114

视频中特定颜色物体追踪

HSV 是一个常用于颜色识别的模型,相比BGR更易区分颜色,转换模式用COLOR_BGR2HSV表示。

现在,我们实现一个使用HSV来只显示视频中蓝色物体的例子,步骤如下:

  1. 捕获视频中的一帧
  2. 从 BGR 转到 HSV
  3. 只提取蓝色范围的物体
  4. 只显示蓝色物体
import cv2
import numpy as np

video = cv2.VideoCapture(0)

# 蓝色的范围
lower_blue = np.array([100, 110, 110])
upper_blue = np.array([130, 255, 255])

while True:
    # 1. 捕获视频中的一帧
    ret, frame = video.read()
    
    # 2. 从 BGR 转到 HSV
    frame_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    
    # 3. inRange(): 介于 lower/upper 之间的为白色,其余为黑色
    mask = cv2.inRange(frame_hsv, lower_blue, upper_blue)
    
    # 4. 只保留原图中的蓝色部分
    res = cv2.bitwise_and(frame, frame, mask=mask)  # 在 mask 区域内对图片进行逻辑与运算 
    
    cv2.imshow('frame', frame)
    cv2.imshow('mask', mask)
    cv2.imshow('res', res)
    
    if cv2.waitKey(1) == ord('q'):
        break

蓝色的范围获取方式:

blue = np.uint8([[[255, 0, 0]]])
hsv_blue = cv2.cvtColor(blue, cv2.COLOR_BGR2HSV)
print(hsv_blue)  # [[[120 255 255]]]

小结

  • cv2.cvtColor()函数用来进行颜色空间转换,常用BGR↔Gray,BGR↔HSV。
  • HSV颜色模型常用于颜色识别。要想知道某种颜色在HSV下的值,可以将它的BGR值用cvtColor()转换得到。

6. 阈值分割

学习使用不同的阈值方法"二值化"图像。

目标

  • 使用固定阈值、自适应阈值和Otsu阈值法"二值化"图像
  • OpenCV函数:cv2.threshold(), cv2.adaptiveThreshold()

固定阈值分割

固定阈值分割,即像素点值大于阈值的编程一类值,小于阈值的部分变成另一类值。

import cv2

# 读取灰度图
img = cv2.imread('gradient.jpg', 0)

# 阈值分割
ret, th = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
print(ret)
cv2.imshow('thresh', th)
cv2.waitKey(0)

cv2.threshold()用来实现阈值分割,ret是return value缩写,代表当前的阈值,暂时不用理会。函数有4个参数:

  • 参数1:要处理的原图,一般是灰度图
  • 参数2:设定的阈值
  • 参数3:对于 THRESH_BINARYTHRESH_BINARY_INV 阈值方法所选用的最大阈值,一般为255
  • 参数4:阈值的方式,主要有5种,详情:ThresholdTypes
import matplotlib.pyplot as plt

# 应用5种不同的阈值方法
ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, th2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
ret, th3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
ret, th4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
ret, th5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)

titles = ['Original', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, th1, th2, th3, th4, th5]

# 使用Matplotlib显示
for i in range(6):
    plt.subplot(2, 3, i + 1)
    plt.imshow(images[i], 'gray')
    plt.title(titles[i], fontsize=8)
    plt.xticks([]), plt.yticks([])  # 隐藏坐标轴

plt.show()  # 显示绘制的图像

opencv-python_第1张图片

注意:很多人误以为阈值分割就是二值化。从上图中可以发现,两者并不等同,阈值分割结果是两类值,而不是两个值。

opencv-python_第2张图片

自适应阈值

固定阈值是在整幅图片上应用一个阈值进行分割,它并不适用于明暗分布不均的图片cv2.adaptiveThreshold()自适应阈值会每次取图片的一小部分计算阈值,这样图片不同区域的阈值就不尽相同。它有5个参数,其实很好理解,先看下效果:

# 自适应阈值与固定阈值对比
img = cv2.imread('sudoku.jpg', 0)

# 固定阈值
ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)

# 自适应阈值
th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 4)
th3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 17, 6)

# 使用 pyplot 绘制图像时的标题和图片
titles = ['Original', 'Global(v = 127)', 'Adaptive Mean', 'Adaptive Gaussian']
images = [img, th1, th2, th3]

for i in range(4):
    plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i], fontsize=8)
    plt.xticks([]), plt.yticks([])
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YTzyIzSS-1634713422747)(https://i.loli.net/2021/10/12/pKT6ShD1CQcszqU.jpg)]

cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst=None)的参数说明:

  • 参数1:要处理的原图
  • 参数2:最大阈值,一般为255
  • 参数3:小区域阈值的计算方式
    • ADAPTIVE_THRESH_MEAN_C:小区域内取均值
    • ADAPTIVE_THRESH_GAUSSIAN_C:小区域内加权求和,权重是个高斯核
  • 参数4:阈值方法,只能使用THRESH_BINARYTHRESH_BINARY_INV,具体见前面所讲的阈值方法
  • 参数5:小区域的面积,如11就是11*11的小块
  • 参数6:最终阈值等于小区域计算出的阈值再减去此值

如果你没看懂上面的参数也不要紧,暂时会用就行,当然我建议你调整下参数看看不同的结果。

Otsu阈值

在前面固定阈值中,我们是随便选了一个阈值如127,那如何知道我们选的这个阈值效果好不好呢?答案是:不断尝试,所以这种方法在很多文献中都被称为经验阈值。Otsu阈值法就提供了一种自动高效的二值化方法,不过我们直方图还没学,这里暂时略过

小结

  • cv2.threshold() 用来进行固定阈值分割。固定阈值不适用光线不均匀的图片,所以用cv2,adaptiveThreshold()进行自适应阈值分割。
  • 二值化跟阈值分割并不等同。针对不同的图片,可以采用不同的阈值方法。
  • 二值化方式可以使用 Otsu 阈值法。

7. 图像几何变换

学习如何旋转、平移、缩放和翻转图片。

目标

  • 实现旋转、平移和缩放图片
  • OpenCV函数:cv2.resize(), cv2.flip(), cv2.warpAffine()

缩放图片

缩放就是调整图片的大小,使用cv2.resize()函数实现缩放。可以按照比例缩放,也可以按照指定的大小缩放:

import cv2

img = cv2.imread('drawing.jpg')

# 按照指定的宽度、高度缩放图片
res = cv2.resizez(img, (132, 150))
# 按照比例缩放,如x,y轴均放大一倍
res2 = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_LINEAR)

cv2.imshow('shrink', res)
cv2.imshow('zoom', res2)
cv2.waitKey(0)

我们也可以指定缩放方法 interpolation,更专业点叫插值方法,默认是INTER_LINEAR,全部可以参考:InterpolationFlags

翻转图片

镜像翻转图片,可以用cv2.flip()函数:

dst = cv2.flip(img, 1)  # 其中,参数2 = 0:垂直翻转(沿x轴),参数2 > 0: 水平翻转(沿y轴),参数2 < 0: 水平垂直翻转。

平移图片

要平移图片,我们需要定义下面这样一个矩阵,tx,ty是向x和y方向平移的距离:image-20211012223134642

平移是用仿射变换函数 cv2.warpAffine() 实现的:

# 平移图片
import numpy as np

rows, cols = img.shape[:2]

# 定义平移矩阵,需要是numpy的float32类型
# x轴平移100,y轴平移50
M = np.float32([[1, 0, 100], [0, 1, 50]])
# 用仿射变换实现平移
dst = cv2.warpAffine(img, M, (cols, rows))

cv2.imshow('shift', dst)
cv2.waitKey(0)

opencv-python_第3张图片

旋转图片

旋转同平移一样,也是用仿射变换实现的,因此也需要定义一个变换矩阵。OpenCV直接提供了 cv2.getRotationMatrix2D()函数来生成这个矩阵,该函数有三个参数:

  • 参数1:图片的旋转中心
  • 参数2:旋转角度(正:逆时针,负:顺时针)
  • 参数3:缩放比例,0.5表示缩小一半
# 45°旋转图片并缩小一半
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 45, 0.5)
dst = cv2.warpAffine(img, M, (cols, rows))

cv2.imshow('rotation', dst)
cv2.waitKey(0)

小结

  • cv2.resize()缩放图片,可以按指定大小缩放,也可以按比例缩放。
  • cv2.flip()翻转图片,可以指定水平/垂直/水平垂直翻转三种方式。
  • 平移/旋转是靠仿射变换cv2.warpAffine()实现的。

8. 绘图功能

学习画线、圆和矩形等多种几何形状,给图片添加文字。

目标

  • 绘制各种几何形状、添加文字
  • OpenCV函数:cv2.line(), cv2.circle(), cv2.rectangle(), cv2.ellipse(), cv2.putText()

画线

画直线只需指定起点和终点的坐标就行:

# 创建一副黑色的图片
img = np.zeros((512, 512, 3), np.uint8)
# 画一条线宽为5的蓝色直线,参数2:起点,参数3:终点
cv2.line(img, (0, 0), (512, 512), (255, 0, 0), 5)

# 注意:所有绘图函数均会直接影响原图片,这点要注意。

画圆

# 画一个填充红色的圆,参数2:圆心坐标,参数3:半径
cv2.circle(img, (447, 63), 63, (0, 0, 255), -1)

画椭圆

画椭圆需要的参数比较多,请对照后面的代码理解这几个参数:

  • 参数2:椭圆中心(x,y)
  • 参数3:x/y轴的长度
  • 参数4:angle—椭圆的旋转角度
  • 参数5:startAngle—椭圆的起始角度
  • 参数6:endAngle—椭圆的结束角度
# 在图中心画一个填充的半圆
cv2.ellipse(img, (256, 256), (100, 50), 0, 0, 180, (255, 0, 0), -1)

画多边形

画多边形需要指定一系列多边形的顶点坐标,相当于从第一个点到第二个点画直线,再从第二个点到第三个点画直线…

OpenCV中需要先将多边形的顶点坐标需要变成顶点数×1×2维的矩阵,再来绘制:

# 定义四个顶点坐标
pts = np.array([[10, 5],  [50, 10], [70, 20], [20, 30]], np.int32)
# 顶点个数:4,矩阵变成4*1*2维
pts = pts.reshape((-1, 1, 2))  # 变维数
cv2.polylines(img, [pts], True, (0, 255, 255))

cv2.polylines()的参数3如果是False的话,多边形就不闭合。

注意:如果需要绘制多条直线,使用cv2.polylines()要比cv2.line()高效很多,例如:

## 使用cv2.polylines()画多条直线
line1 = np.array([[100, 20],  [300, 20]], np.int32).reshape((-1, 1, 2))
line2 = np.array([[100, 60],  [300, 60]], np.int32).reshape((-1, 1, 2))
line3 = np.array([[100, 100],  [300, 100]], np.int32).reshape((-1, 1, 2))
cv2.polylines(img, [line1, line2, line3], True, (0, 255, 255))

添加文字

使用cv2.putText()添加文字,它的参数也比较多,同样请对照后面的代码理解这几个参数:

  • 参数2:要添加的文本
  • 参数3:文字的起始坐标(左下角为起点)
  • 参数4:字体
  • 参数5:文字大小(缩放比例)
# 添加文字
font = cv2.FONT_HERSHEY_SIMPLEX  # 文字样式
cv2.putText(img, 'ex2tron', (10, 500), font,
            4, (255, 255, 255), 2, lineType=cv2.LINE_AA)

字体可参考:HersheyFonts。另外,这里有个线型 lineType 参数,LINE_AA 表示抗锯齿线型,具体可见LineTypes

小结

  • cv2.line()画直线,cv2.circle()画圆,cv2.rectangle()画矩形,cv2.ellipse()画椭圆,cv2.polylines()画多边形,cv2.putText()添加文字。
  • 画多条直线时,cv2.polylines()要比cv2.line()高效很多。

9. 图像混合

学习图片间的数学运算,图像混合。

目标

  • 图像之间的数学运算,如相位、按位运算等
  • OpenCV函数:cv2.add(), cv2.addWeighted(), cv2.bitwise_and()

图片相加

要叠加两张照片,可以用 cv2.add() 函数,相加两幅图片的形状(高度、宽度、通道数)必须相同。numpy 中可以直接用 res = img + img1相加,但这两者的结果并不相同:

x = np.uint8([250])
y = np.uint([10])

print(cv2.add(x, y))  # 250+10 = 260 => 255, 像素值超过 255 的值,默认使用 255 代替
print(x + y)  # 250+10 = 260 % 256 = 4  像素值超过 255 的值,对 255 进行取余操作

如果是二值化图片(只有0和255两种值),两者结果是一样的(用numpy的方式更简便一些)。

图像混合

图像混合 cv2.addWeighted() 也是一种图片相加的操作,只不过两幅图片的权重不一样,γ 相当于一个修正值:

​ dst=α×img1+β×img2+γ

img1 = cv2.imread('lena_small.jpg')
img2 = cv2.imread('opencv-logo-white.png')
res = cv2.addWeighted(img1, 0.6, img2, 0.4, 0) 
'''
参数解析:
    参数1: 图片文件1
    参数2: 图片1的权重
    参数3: 图片文件2
    参数4: 图片2的权重
    参数5: γ 修正值
'''
# α和β都等于1时,就相当于图片相加。

按位操作

按位操作包括按位与/或/非/异或操作。

如果将两幅图片直接相加会改变图片的颜色,如果用图像混合,则会改变图片的透明度,所以我们需要用按位操作。首先来了解一下掩膜(mask)的概念:掩膜是用一副二值化图片对另外一幅图片进行局部的遮挡,看下图就一目了然了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YolHtPnd-1634713422753)(https://i.loli.net/2021/10/13/uxcCREpWQmAPyds.jpg)]

所以我们的思路就是把原图中要放logo的区域抠出来,再把logo放进去就行了

img1 = cv2.imread('lena.jpg')
img2 = cv2.imread('opencv-logo-white.png')  # logo 图片

# 把 logo 放在左上角,所以我们只关心这一块区域
rows, cols = img2.shape[:2]  # 获取 logo 图片的行列数
roi = img1[:rows, :clos]  # 锁定在原图上 logo 应在的位置

# 创建掩膜
img2gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)

# 保留除了 logo 外的背景
img1_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)  # 在原图的 logo 位置(即掩膜部分)
dst = cv2.add(img1_bg, img2)  # 进行融合
img1[:rows, :cols] = dst  # 融合后放在原图上

# 掩膜的概念在图像混合/叠加的场景下使用较多。

小结

  • cv2.add()用来叠加两幅图片,cv2.addWeighted()也是叠加两幅图片,但两幅图片的权重不一样。
  • cv2.bitwise_and(), cv2.bitwise_not(), cv2.bitwise_or(), cv2.bitwise_xor()分别执行按位与/或/非/异或运算。掩膜就是用来对图片进行全局或局部的遮挡。

10. 平滑图像

学习模糊/平滑图像,消除噪点。

目标

  • 模糊/平滑图片用来消除图片噪声
  • OpenCV函数:cv2.blur(), cv2.GaussianBlur(), cv2.medianBlur(), cv2.bilateralFilter()

滤波与模糊

关于滤波与模糊:

  • 他们都属于卷积,不同滤波方式之间只是卷积核不同(对线性滤波而言)
  • 低通滤波器是模糊,高通滤波器是锐化

低通滤波器就是允许低频信号通过,在图像中边缘和噪点都相当于高频部分,所以低通滤波器用于去除噪点、平滑和模糊图像;高通滤波器则相反,用来增强图片边缘,进行锐化处理

常见噪声有椒盐噪声和高斯噪声,椒盐噪声可以理解为斑点,随机出现在图像中的黑点或白点;高斯噪声可以理解为拍摄图片时由于光照等原因造成的噪声。

均值滤波

均值滤波是一种最简单的滤波处理,它取的是卷积核区域内元素的均值,用cv2.blur()实现,如3×3的卷积核:

​ kernel = 1/9 [1 1 1 1 1 1 1 1 1]

img = cv2.imread('lena.jpg')
blur = cv2.blur(img, (3, 3))  # 均值模糊

所有的滤波函数都有一个可选参数borderType,这个参数就是边框填充方式。

方框滤波

方框滤波跟均值滤波很像,如3×3的滤波核如下:

​ kernel = a[1 1 1 1 1 1 1 1 1]

cv2.boxFilter()函数实现,当可选参数 normalize 为 True 的时候,方框滤波就是均值滤波,上式中的 a 就等于1/9;normalize 为False 的时候,a=1,相当于求区域内的像素和。

# 前面的均值滤波也可以用方框滤波实现:normalize=True
blur = cv2.boxFilter(img, -1, (3, 3), normalize=True)

高通滤波

前面两种滤波方式,卷积核内的每个值都一样,也就是说图像区域中每个像素的权重也就一样。高斯滤波的卷积核权重并不相同:中间像素点权重最高,越远离中心的像素权重越小。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HmQKqhbJ-1634713422755)(https://i.loli.net/2021/10/13/Ex8kg1uRBshOyvF.jpg)]

显然这种处理元素间权值的方式更加合理一些。图像是2维的,所以我们需要使用2维的高斯函数,比如OpenCV中默认的3×3的高斯卷积核:

​ k=[0.0625 0.125 0.0625 0.125 0.25 0.125 0.0625 0.125 0.0625]

OpenCV中对应函数为cv2.GaussianBlur(src,ksize,sigmaX)

img = cv2.imread('gaussian_noise.bmp')
# 均值滤波vs高斯滤波
blur = cv2.blur(img, (5, 5))  # 均值滤波
gaussian = cv2.GaussianBlur(img, (5, 5), 1)  # 高斯滤波

参数3 σx值越大,模糊效果越明显。高斯滤波相比均值滤波效率要慢,但可以有效消除高斯噪声,能保留更多的图像细节,所以经常被称为最有用的滤波器。

中值滤波

中值又叫中位数,是所有数排序后取中间的值。中值滤波就是用区域内的中值来代替本像素值,所以那种孤立的斑点,如0或255很容易消除掉,适用于去除椒盐噪声和斑点噪声。中值是一种非线性操作,效率相比前面几种线性滤波要慢。

img = cv2.imread('salt_noise.bmp', 0)
# 均值滤波vs中值滤波
blur = cv2.blur(img, (5, 5))  # 均值滤波
median = cv2.medianBlur(img, 5)  # 中值滤波

双边滤波

模糊操作基本都会损失掉图像细节信息,尤其前面介绍的线性滤波器,图像的边缘信息很难保留下来。然而,边缘(edge)信息是图像中很重要的一个特征,所以这才有了双边滤波。用cv2.bilateralFilter()函数实现:

img = cv2.imread('lena.jpg')
# 双边滤波vs高斯滤波
gau = cv2.GaussianBlur(img, (5, 5), 0)  # 高斯滤波
blur = cv2.bilateralFilter(img, 9, 75, 75)  # 双边滤波

小结

  • 在不知道用什么滤波器好的时候,优先高斯滤波cv2.GaussianBlur(),然后均值滤波cv2.blur()
  • 斑点和椒盐噪声优先使用中值滤波cv2.medianBlur()
  • 要去除噪点的同时尽可能保留更多的边缘信息,使用双边滤波cv2.bilateralFilter()
  • 线性滤波方式:均值滤波、方框滤波、高斯滤波(速度相对快)。
  • 非线性滤波方式:中值滤波、双边滤波(速度相对慢)。

11. 边缘检测

学习使用Canny获取图像的边缘。

目标

  • Canny边缘检测的简单概念
  • OpenCV函数:cv2.Canny()

举例

Canny边缘检测方法常被誉为边缘检测的最优方法:

import cv2
import numpy as np

img = cv2.imread('handwriting.jpg', 0)
edges = cv2.Canny(img, 30, 70)  # canny边缘检测

cv2.imshow('canny', np.hstack((img, edges)))
cv2.waitKey(0)

opencv-python_第4张图片

cv2.Canny()进行边缘检测,参数2、3表示最低、高阈值,下面来解释下具体原理。

Canny边缘检测

Canny边缘提取的具体步骤如下:

  1. 使用高斯滤波消除噪声

  2. 计算图像梯度的方向:首先使用Sobel算子计算两个方向上的梯度G_x和G_y,然后算出梯度的方向:arctan(G_y/G_x)

  3. 取局部极大值

  4. 滞后阈值

    opencv-python_第5张图片

    1. 像素点的值大于最高阈值,那肯定是边缘(上图A)
    2. 同理像素值小于最低阈值,那肯定不是边缘
    3. 像素值介于两者之间,如果与高于最高阈值的点连接,也算边缘,所以上图中C算,B不算

Canny推荐的高低阈值比在2:1到3:1之间。

先进行阈值再进行检测

这样效果更好

_, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)  # Otsu阈值法
edges = cv2.Canny(thresh, 30, 70)

cv2.imshow('canny', np.hstack((img, thresh, edges)))
cv2.waitKey(0)

12. 腐蚀与膨胀

学习常用形态学操作:腐蚀膨胀,开运算和闭运算。

目标

  • 了解形态学操作的概念
  • 学习膨胀、腐蚀、开运算和闭运算等形态学操作
  • OpenCV函数:cv2.erode(), cv2.dilate(), cv2.morphologyEx()

形态学操作其实就是改变物体的形状,比如腐蚀就是"变瘦",膨胀就是"变胖",

形态学操作一般作用于二值化图,来连接相邻的元素或分离成独立的元素。腐蚀和膨胀是针对图片中的白色部分

腐蚀

腐蚀的效果是把图片"变瘦",其原理是在原图的小区域内取局部最小值。因为是二值化图,只有0和255,所以小区域内有一个是0该像素点就为0:

opencv-python_第6张图片

这样原图中边缘地方就会变成0,达到了瘦身目的;OpenCV中用cv2.erode()函数进行腐蚀,只需要指定核的大小就行:

import cv2
import numpy as np

img = cv2.imread('j.bmp', 0)
kernel = np.ones((5, 5), np.uint8)  # 核的大小
erosion = cv2.erode(img, kernel)  # 腐蚀

这个核也叫结构元素,因为形态学操作其实也是应用卷积来实现的。结构元素可以是矩形/椭圆/十字形,可以用cv2.getStructuringElement()来生成不同形状的结构元素,比如:

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))  # 矩形结构
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))  # 椭圆结构
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))  # 十字形结构

opencv-python_第7张图片

膨胀

膨胀与腐蚀相反,取的是局部最大值,效果是把图片"变胖":

dilate = cv2.dilate(img, kernel)  # 膨胀

开、闭运算

开运算:先腐蚀后膨胀,其作用是:分离物体,消除小区域。这类形态学操作用cv2.morphologyEx()函数实现。

闭运算:先膨胀后腐蚀,消除/"闭合"物体里面的小黑洞。

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))  # 定义结构元素

img = cv2.imread('j_noise_out.bmp', 0)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)  # 开运算

img = cv2.imread('j_noise_in.bmp', 0)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)  # 闭运算

其他形态学操作

  • 形态学梯度:膨胀图减去腐蚀图,dilation - erosion,这样会得到物体的轮廓:
  • 顶帽:原图减去开运算后的图:src - opening
  • 黑帽:闭运算后的图减去原图:closing - src
img = cv2.imread('school.bmp', 0)

# 形态学梯度
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)

# 顶帽
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)

# 黑帽
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)

小结

  • 形态学操作就是改变物体的形状,如腐蚀使物体"变瘦",膨胀使物体"变胖"。
  • 先腐蚀后膨胀会分离物体,所以叫开运算,常用来去除小区域物体。
  • 先膨胀后腐蚀会消除物体内的小洞,所以叫闭运算。
  • 其他的形态学操作:cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)

13. 轮廓

学习如何寻找并绘制轮廓。

目标

  • 了解轮廓概念
  • 寻找并绘制轮廓
  • OpenCV函数:cv2.findContours(), cv2.drawContours()

轮廓

轮廓是一系列相连的点组成的曲线,代表了物体的基本外形。轮廓是连续的,边缘并不全都连续

寻找轮廓的操作一般用于二值化图,所以通常会使用阈值分割或Canny边缘检测先得到二值图。

寻找轮廓是针对白色物体的,一定要保证物体是白色,而背景是黑色,不然很多人在寻找轮廓时会找到图片最外面的一个框

寻找轮廓

使用cv2.findContours()寻找轮廓:

import cv2

img = cv2.imread('handwriting.jpg')  # 获取图片
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转成灰度图
ret, thresh = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)  # 阈值分割得到二值图

# 寻找二值化图中的轮廓
image, contours, hierarchy = cv2.findContours(
    thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) 
print(len(contours))  # 结果应该为2
  • 参数2:轮廓的查找方式,一般使用cv2.RETR_TREE,表示提取所有的轮廓并建立轮廓间的层级。更多请参考:RetrievalModes
  • 参数3:轮廓的近似方法。比如对于一条直线,我们可以存储该直线的所有像素点,也可以只存储起点和终点。使用cv2.CHAIN_APPROX_SIMPLE就表示用尽可能少的像素点表示轮廓。更多请参考:ContourApproximationModes
  • 简便起见,这两个参数也可以直接用真值3和2表示。

函数有3个返回值,image还是原来的二值化图片,hierarchy是轮廓间的层级关系,这两个暂时不用理会。我们主要看 contours,它就是找到的轮廓了,以数组形式存储,记录了每条轮廓的所有像素点的坐标(x,y)。

绘制轮廓

轮廓找出来后,为了方便观看,可以像前面图中那样用红色画出来:cv2.drawContours()

cv2.drawContours(img, contours, -1, (0, 0, 255), 2)

其中参数2就是得到的contours,参数3表示要绘制哪一条轮廓,-1表示绘制所有轮廓,参数4是颜色(B/G/R通道,所以(0,0,255)表示红色),参数5是线宽,之前在绘制图形中介绍过。

一般情况下,我们会首先获得要操作的轮廓,再进行轮廓绘制及分析:

cnt = contours[1]
cv2.drawContours(img, [cnt], 0, (0, 0, 255), 2)

小结

  • 轮廓特征非常有用,使用cv2.findContours()寻找轮廓,cv2.drawContours()绘制轮廓。

14. 轮廓特征

学习计算轮廓特征,如面积、周长、最小外接矩形等。

目标

  • 计算物体的周长、面积、质心、最小外接矩形等
  • OpenCV函数:cv2.contourArea(), cv2.arcLength(), cv2.approxPolyDP()

寻找轮廓

在计算轮廓特征之前,我们先用上一节的代码把轮廓找到:

import cv2
import numpy as np

img = cv2.imread('handwriting.jpg', 0)
_, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
image, contours, hierarchy = cv2.findContours(thresh, 3, 2)

# 以数字3的轮廓为例
cnt = contours[0]

img_color1 = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
img_color2 = np.copy(img_color1)
cv2.drawContours(img_color1, [cnt], 0, (0, 0, 255), 2)

轮廓面积

area = cv2.contourArea(cnt)  # 4386.5

注意轮廓特征计算的结果并不等同于像素点的个数,而是根据几何方法算出来的,所以有小数。

轮廓周长

perimeter = cv2.arcLength(cnt, True)  # 585.7

参数2表示轮廓是否封闭,显然我们的轮廓是封闭的,所以是True。

图像矩

矩可以理解为图像的各类几何特征,详情请参考:[Image Moments]

M = cv2.moments(cnt)
# M中包含了很多轮廓的特征信息,比如M['m00']表示轮廓面积,与前面cv2.contourArea()计算结果是一样的。质心也可以用它来算:
cx, cy = M['m10'] / M['m00'], M['m01'] / M['m00']  # (205, 281)

外接矩阵

形状的外接矩形有两种,外接矩形表示不考虑旋转并且能包含整个轮廓的矩形;最小外接矩,考虑了旋转。

x, y, w, h = cv2.boundingRect(cnt)  # 外接矩形,获取左上角坐标,以及矩阵的长和宽
cv2.rectangle(img_color1, (x, y), (x + w, y + h), (0, 255, 0), 2)

rect = cv2.minAreaRect(cnt)  # 最小外接矩形
box = np.int0(cv2.boxPoints(rect))  # 矩形的四个角点取整
cv2.drawContours(img_color1, [box], 0, (255, 0, 0), 2)

# 其中np.int0(x)是把x取整的操作,比如377.93就会变成377,也可以用x.astype(np.int)。

最小外接圆

外接圆跟外接矩形一样,找到一个能包围物体的最小圆:

(x, y), radius = cv2.minEnclosingCircle(cnt)
(x, y, radius) = np.int0((x, y, radius))  # 圆心和半径取整
cv2.circle(img_color2, (x, y), radius, (0, 0, 255), 2)

拟合椭圆

ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(img_color2, ellipse, (255, 255, 0), 2)

形状匹配

cv2.matchShapes() 可以检测两个形状之间的相似度,返回值越小,越相似

img = cv2.imread('shapes.jpg', 0)
_, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
image, contours, hierarchy = cv2.findContours(thresh, 3, 2)
img_color = cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)  # 用于绘制的彩色图

图中有3条轮廓,我们用A/B/C表示:

cnt_a, cnt_b, cnt_c = contours[0], contours[1], contours[2]
print(cv2.matchShapes(cnt_b, cnt_b, 1, 0.0))  # 0.0
print(cv2.matchShapes(cnt_b, cnt_c, 1, 0.0))  # 2.17e-05
print(cv2.matchShapes(cnt_b, cnt_a, 1, 0.0))  # 0.418

可以看到BC相似程度比AB高很多,并且图形的旋转或缩放并没有影响。其中,参数3是匹配方法,详情可参考:ShapeMatchModes,参数4是OpenCV的预留参数,暂时没有实现,可以不用理会。

形状匹配是通过图像的Hu矩来实现的(cv2.HuMoments()),大家如果感兴趣,可以参考:Hu-Moments

小结

常用的轮廓特征:

  • cv2.contourArea()算面积,cv2.arcLength()算周长,cv2.boundingRect()算外接矩。
  • cv2.minAreaRect()算最小外接矩,cv2.minEnclosingCircle()算最小外接圆。
  • cv2.matchShapes()进行形状匹配。

15.直方图

学习计算并绘制直方图,直方图均衡化等。

目标

  • 计算并绘制直方图
  • (自适应)直方图均衡化
  • OpenCV函数:cv2.calcHist(), cv2.equalizeHist()

直方图

直方图就是图像中每个像素值的个数统计

有几个术语先来了解一下:

  • dims: 要计算的通道数,对于灰度图dims=1,普通彩色图dims=3
  • range: 要计算的像素值范围,一般为[0,256)
  • bins: 子区段数目,如果我们统计0~255每个像素值,bins=256;如果划分区间,比如0~15, 16~31…240~255这样16个区间,bins=16

OpenCV中直方图计算

使用cv2.calcHist(images, channels, mask, histSize, ranges)计算,其中:

  • 参数1:要计算的原图,以方括号的传入,如:[img]
  • 参数2:类似前面提到的dims,灰度图写[0]就行,彩色图B/G/R分别传入[0]/[1]/[2]
  • 参数3:要计算的区域,计算整幅图的话,写None
  • 参数4:前面提到的bins
  • 参数5:前面提到的range
import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('hist.jpg', 0)
hist = cv2.calcHist([img], [0], None, [256], [0, 256])  # 性能:0.025288 s

Numpy中计算直方图

hist, bins = np.histogram(img.ravel(), 256, [0, 256])  # 性能:0.020628 s

# 更高效的方式
hist = np.bincount(img.ravel(), minlength=256)  # 性能:0.003163 s

绘制直方图

其实Matplotlib自带了一个计算并绘制直方图的功能,不需要用到上面的函数:

plt.hist(img.ravel(), 256, [0, 256])
plt.show()

# 当然,也可以用前面计算出来的结果绘制:
plt.plot(hist)
plt.show()

直方图均衡化

一副效果好的图像通常在直方图上的分布比较均匀,直方图均衡化就是用来改善图像的全局亮度和对比度。其实从观感上就可以发现,前面那幅图对比度不高,偏灰白。对均衡化算法感兴趣的同学可参考:维基百科:直方图均衡化

# OpenCV中用cv2.equalizeHist()实现均衡化。
equ = cv2.equalizeHist(img)
cv2.imshow('equalization', np.hstack((img, equ)))  # 并排显示
cv2.waitKey(0)

opencv-python_第8张图片

opencv-python_第9张图片

可以看到均衡化后图片的亮度和对比度效果明显好于原图。

自适应均衡化

直方图均衡化是应用于整幅图片的,而自适应均衡化,它在每一个小区域内(默认8×8)进行直方图均衡化。当然,如果有噪点的话,噪点会被放大,需要对小区域内的对比度进行了限制,所以这个算法全称叫:对比度受限的自适应直方图均衡化

# 自适应均衡化,参数可选
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
cl1 = clahe.apply(img)

小结

  • 直方图是一种分析图像的手段。
  • cv2.calcHist()numpy.bincount()均可用来计算直方图,使用Matplotlib绘制直方图。
  • 均衡化用来使图像的直方图分布更加均匀,提升亮度和对比度。

16. 模板匹配

学习使用模板匹配在图像中寻找物体。

目标

  • 使用模板匹配在图像中寻找物体
  • OpenCV函数:cv2.matchTemplate(), cv2.minMaxLoc()

模板匹配

模板匹配就是用来在大图中找小图,也就是说在一副图像中寻找另外一张模板图像的位置:

cv2.matchTemplate()实现模板匹配。首先我们来读入图片和模板:

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('lena.jpg', 0)
template = cv2.imread('face.jpg', 0)
h, w = template.shape[:2]  # rows->h, cols->w

# 匹配函数返回的是一副灰度图,最白的地方表示最大的匹配。使用cv2.minMaxLoc()函数可以得到最大匹配值的坐标,以这个点为左上角角点,模板的宽和高画矩形就是匹配的位置了:

# 相关系数匹配方法:cv2.TM_CCOEFF
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

left_top = max_loc  # 左上角
right_bottom = (left_top[0] + w, left_top[1] + h)  # 右下角
cv2.rectangle(img, left_top, right_bottom, 255, 2)  # 画出矩形位置

opencv-python_第10张图片

匹配多个物体

前面我们是找最大匹配的点,所以只能匹配一次。我们可以设定一个匹配阈值来匹配多次:

# 1.读入原图和模板
img_rgb = cv2.imread('mario.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('mario_coin.jpg', 0)
h, w = template.shape[:2]

# 2.标准相关模板匹配
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8 

# 3.这边是Python/Numpy的知识,后面解释
loc = np.where(res >= threshold)  # 匹配程度大于%80的坐标y,x np.where()在这里返回res中值大于0.8的所有坐标
for pt in zip(*loc[::-1]):  # *号表示可选参数 zip函数拼接在一起。
    right_bottom = (pt[0] + w, pt[1] + h)
    cv2.rectangle(img_rgb, pt, right_bottom, (0, 0, 255), 2)

小结

  • 模板匹配用来在大图中找小图。
  • cv2.matchTemplate()用来进行模板匹配。

17.霍夫变换

学习使用霍夫变换识别出图像中的直线和圆。

目标

  • 理解霍夫变换的实现
  • 分别使用霍夫线变换和圆变换检测图像中的直线和圆
  • OpenCV函数:cv2.HoughLines(), cv2.HoughLinesP(), cv2.HoughCircles()

霍夫直线变换

OpenCV中用cv2.HoughLines()在二值图上实现霍夫变换,函数返回的是一组直线的(r,θ)数据:

import cv2
import numpy as np

# 1.加载图片,转为二值图
img = cv2.imread('shapes.jpg')
drawing = np.zeros(img.shape[:], dtype=np.uint8)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)

# 2.霍夫直线变换
lines = cv2.HoughLines(edges, 0.8, np.pi / 180, 90)

函数中:

  • 参数1:要检测的二值图(一般是阈值分割或边缘检测后的图)
  • 参数2:距离r的精度,值越大,考虑越多的线
  • 参数3:角度θ的精度,值越小,考虑越多的线
  • 参数4:累加数阈值,值越小,考虑越多的线
# 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))

    cv2.line(drawing, (x1, y1), (x2, y2), (0, 0, 255))

统计概率霍夫直线变换

前面的方法又称为标准霍夫变换,它会计算图像中的每一个点,计算量比较大,另外它得到的是整一条线(r和θ),并不知道原图中直线的端点。所以提出了统计概率霍夫直线变换(Probabilistic Hough Transform),是一种改进的霍夫变换:

drawing = np.zeros(img.shape[:], dtype=np.uint8)
# 3.统计概率霍夫线变换
lines = cv2.HoughLinesP(edges, 0.8, np.pi / 180, 90,
                        minLineLength=50, maxLineGap=10)

前面几个参数跟之前的一样,有两个可选参数:

  • minLineLength:最短长度阈值,比这个长度短的线会被排除
  • maxLineGap:同一直线两点之间的最大距离
# 3.将检测的线画出来
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(drawing, (x1, y1), (x2, y2), (0, 255, 0), 1, lineType=cv2.LINE_AA)
# cv2.LINE_AA在之前绘图功能中讲解过,表示抗锯齿线型。

霍夫圆变换

霍夫圆变换跟直线变换类似,只不过线是用(r,θ)表示,圆是用(x_center,y_center,r)来表示,从二维变成了三维,数据量变大了很多;所以一般使用霍夫梯度法减少计算量,对该算法感兴趣的同学可参考:Circle Hough Transform

drawing = np.zeros(img.shape[:], dtype=np.uint8)
# 2.霍夫圆变换
circles = cv2.HoughCircles(edges, cv2.HOUGH_GRADIENT, 1, 20, param2=30)
circles = np.int0(np.around(circles))

其中,

  • 参数2:变换方法,一般使用霍夫梯度法,详情:HoughModes
  • 参数3 dp=1:表示霍夫梯度法中累加器图像的分辨率与原图一致
  • 参数4:两个不同圆圆心的最短距离
  • 参数5:param2跟霍夫直线变换中的累加数阈值一样
# 将检测的圆画出来
for i in circles[0, :]:
    cv2.circle(drawing, (i[0], i[1]), i[2], (0, 255, 0), 2)  # 画出外圆
    cv2.circle(drawing, (i[0], i[1]), 2, (0, 0, 255), 3)  # 画出圆心

小结

  • 霍夫变换用来提取图像中的直线和圆等几何形状。
  • 霍夫直线变换:cv2.HoughLines()(整条直线), cv2.HoughLinesP()
  • 霍夫圆变换:cv2.HoughCircles()

总结

参考网址:http://codec.wang/#/opencv/

你可能感兴趣的:(opencv-python,opencv,python)