下面完整代码在github仓库:传送门


文章目录

  • 一、图像轮廓近似估计
  • 二、边界检测
  • 三、Canny算子(找轮廓)
  • 四、计算图像面积、周长、重心
  • 五、计算轮廓面积、凸包面积
  • 六、凸包和凸性检测
  • 七、图像轮廓查找与绘制
  • 八、Hough空间(形状检测)
  • 九、分水岭算法
  • 十、模版匹配
  • 十一、利用对象掩码mask
  • 十二、利用形态学操作寻找车牌
  • 十三、图像形状匹配



一、图像轮廓近似估计

import cv2
import numpy as np

# 轮廓近似:approxPolyDP()   它主要功能是把一个连续光滑曲线折线化,对图像轮廓点进行多边形拟合。
img = cv2.imread("./images/22.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# 第一个参数是寻找轮廓的图像。 cv2.RETR_TREE建立一个等级树结构的轮廓。
# cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)  # 要求传入二值图
# contours返回值首先返回一个list,list中每个元素都是图像中的一个轮廓,用numpy中的ndarray表示.
# hierarchy 这是一个ndarray,其中的元素个数和轮廓个数相同.

print(type(contours))  # <class 'list'>
print(type(contours[0]))  # <class 'numpy.ndarray'>
print(len(contours))  # 1

print(type(hierarchy))  # <class 'numpy.ndarray'>
print(hierarchy.ndim)  # 3
print(hierarchy[0].ndim)  # 2

# 轮廓近似:
# approxPolyDP(curve, epsilon, closed, approxCurve=None)
# epsilon指定逼近精度的参数。这是原始曲线与其近似值之间的最大距离。
epsilon = 20  # 精确度,越小越精确
approx = cv2.approxPolyDP(contours[0], epsilon, True)
print(np.shape(approx))  # (5, 1, 2)
print(approx)

# 绘制轮廓:直接对原图进行操作
cv2.drawContours(img, [approx], -1, (0, 0, 255), 3)

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

二、边界检测

import cv2
import numpy as np

# "边界检测: 边界矩形、最小(面积)矩形、最小外接圆以及椭圆拟合、直线拟合"

img = cv2.imread("./images/23.jpg")
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

ret, thresh = cv2.threshold(img_gray, 127, 255, 0)
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 边界矩形
x, y, w, h = cv2.boundingRect(contours[0])  # 根据轮廓点来获取边界框的坐标
img_contour = cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.imshow("img_contour", img_contour)

# 最小矩形
rect = cv2.minAreaRect(contours[0])  # 得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
print(rect)

box = cv2.boxPoints(rect)   # 获取最小外接矩形的4个顶点坐标
print(box)
print(box.dtype, box.shape)

box = np.int32(box)
print(box.dtype, box.shape)

img_contour1 = cv2.drawContours(img, [box], 0, (0, 0, 255), 2)
cv2.imshow("img_contour1", img_contour1)

# 最小外接圆
(x, y),radius = cv2.minEnclosingCircle(contours[0])  # 根据轮廓点找到最小闭合圆的中心点坐标,半径
center = (int(x), int(y))
radius = int(radius)
img_contour3 = cv2.circle(img, center, radius, (255, 0, 0), 2)
cv2.imshow("img_contour3", img_contour3)

# 椭圆拟合
ellipse = cv2.fitEllipse(contours[0])  # 根据轮廓点找到椭圆
print(ellipse)
img_contour4 = cv2.ellipse(img, ellipse, (0, 255, 255), 2)
cv2.imshow("img_contour4", img_contour4)

cv2.waitKey(0)
cv2.destroyAllWindows()

三、Canny算子(找轮廓)

import cv2

'''Canny算子:' \
'边缘:一个像素,不一定封闭,是提取轮廓的前提' \
'梯度:不一定是一个像素,有可能是多个像素' \
'轮廓:对于边缘的补充,是封闭的,属于边缘的子集'''
'''
Canny边缘检查算法步骤:
  1.彩色图转化为灰度图
  2.应用高斯滤波来平滑图像-->去除噪声
    由于边缘检测容易受到图像中噪声的影响
  3.找寻图像的强度梯度
    Canny的基本思想是找寻一幅图像中强度变化最强的位置。所谓的变化最强,即指梯度方向。
    平滑后的图像中每个像素点的梯度可以由Sobel算子来获得:
      1)首先,利用Sobel算子得到沿x轴和y轴方向的梯度G_x和G_y。
      2)由G_X和G_Y便可计算每一个像素点的梯度幅值G。
      3)接着,每一个像素点用G代替。对于变化剧烈的边界处,G值越大,对应的颜色为白色。
      4)然后,这些边界通常非常粗,难以标定边界的真正位置,还必须存储梯度的方向θ。
  4.应用非极大抑制技术来消除边误检(本来不是边缘但检测出来是)
    沿着梯度θ方向上比较该像素点,若该像素点与两侧相比最大则保留,否则抑制(置为0)。
    这一步的目的是将模糊的边界变得清晰,剔除一大部分不是边缘的点。
  5.双阈值边缘连接处理
    规则:设定两个阈值,minVal和maxVal。
      大于maxVal的边缘肯定是边缘(保留),低于minVal的边缘是非边缘(舍去)。
      对于介于两者之间的值,判断是否与真正的边界(强边界)相连,相连就保留,否则丢弃。
  6.二值化图像输出结果
'''

# 1.转化为灰度图
img = cv2.imread("./images/18.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 2.高斯模糊
gaussian = cv2.GaussianBlur(gray, (3, 3), 3)

# 3.Canny边缘提取
# canny = cv2.Canny(gray, 50, 150)
canny = cv2.Canny(gaussian, 50, 150)

cv2.imshow("img", img)
cv2.imshow("gaussian", gaussian)
cv2.imshow("canny", canny)
cv2.waitKey(0)
cv2.destroyAllWindows()
import cv2
import matplotlib.pyplot as plt

# 1.将图片转化为灰度图
img = cv2.imread("./images/18.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 对于对比度比较暗的图片,可进行高亮处理
abs = cv2.convertScaleAbs(gray, alpha=6, beta=0)

# 形态学操作(去除中间的黑色噪点)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
close = cv2.morphologyEx(abs, cv2.MORPH_CLOSE, kernel)

# 2.高斯平滑
gaussian = cv2.GaussianBlur(close, (5, 5), 0)

# 3.Canny算法
canny = cv2.Canny(gaussian, 50, 150)  # 曲线在50-150之间且大于150,或大于150保留,否则舍弃。

titles = ['img', 'gray', 'abs', 'close', 'gaussian', 'canny']
images = [img, gray, abs, close, gaussian, canny]
# plt.figure(figsize=(10, 10))
for i in range(6):
    plt.subplot(2, 3, i+1)
    plt.imshow(images[i], cmap="gray")
    plt.title(titles[i])
    plt.xticks([])
    plt.yticks([])

plt.show()

四、计算图像面积、周长、重心

import cv2

'''面积,周长,重心'''
# gray = cv2.imread("./images/21.jpg", 0)
gray = cv2.imread("./images/22.jpg", 0)

ret, binary = cv2.threshold(gray, 127, 255, 0)

contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 重心
# moments(array, binaryImage=None)
print(contours[0])  # 轮廓点的坐标
M = cv2.moments(contours[0])  # 矩
print(M)

cx = int(M['m10']) / M['m00']
cy = int(M['m01']) / M['m00']
print("重心:", cx, cy)

# 面积
# contourArea(contour, oriented=None)
area = cv2.contourArea(contours[0])
print("面积:", area)

# 周长
# arcLength(curve, closed)
perimeter = cv2.arcLength(contours[0], True)
print("周长:", perimeter)

cv2.imshow("gray", gray)
cv2.imshow("binary", binary)
cv2.waitKey(0)
cv2.destroyAllWindows()

五、计算轮廓面积、凸包面积

import cv2
import numpy as np

# 轮廓性质
img = cv2.imread("./images/23.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

# 查找轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 边界矩形
x, y, w, h = cv2.boundingRect(contours[0])
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 0, 255), 2)

# 最小面积矩形
rect = cv2.minAreaRect(contours[0])
box = cv2.boxPoints(rect)
box = np.int32(box)
cv2.drawContours(img, [box], -1, (0, 255, 0), 2)

# 最小外接圆
(x, y), radius = cv2.minEnclosingCircle(contours[0])
cv2.circle(img, (int(x), int(y)), int(radius), (255, 0, 0), 2)

# 绘制轮廓
cv2.drawContours(img, contours, -1, (255, 255, 0), 2)

# 1.边界矩形的宽高比
aspect_ratio = float(w) / h
print("边界矩形的宽高比:", aspect_ratio)

# 2.轮廓面积与边界矩形面积之比
area = cv2.contourArea(contours[0])
rect_area = w*h
extent = float(area) / rect_area
print("轮廓面积与边界矩形面积之比:", extent)

# 3.轮廓面积和凸包面积之比
hull = cv2.convexHull(contours[0])  # 凸包和凸性检测
area = cv2.contourArea(contours[0])
hull_area = cv2.contourArea(hull)
solidity = float(area) / hull_area
print("轮廓面积和凸包面积之比:", solidity)

# 4.与轮廓面积相等的圆的直径
area = cv2.contourArea(contours[0])
equi_diameter = np.sqrt(4*area / np.pi)
print("与轮廓面积相等的圆的直径:", equi_diameter)

# 5.对象的方向
ellipse = cv2.fitEllipse(contours[0])
print(ellipse)
print("对象的方向angle:", ellipse[2])

# 绘制一个圆心在(150,124)、长轴78、短轴261、线宽为2的白色椭圆
# cv2.ellipse(img, ellipse, (0, 255, 255), 2)
cv2.ellipse(img, (150, 124), (78, 261), 138, 0, 300, (0, 0, 255), thickness=2)

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

六、凸包和凸性检测

import cv2

# 凸包和凸性检测: convexHull()、isContourConvex()
# 函数 cv2.convexHull() 可以用来检测一个曲线是否具有凸性缺陷,并能纠正缺陷
# 函数 cv2.isContourConvex() 可以用来检测一个曲线是不是凸的。它只能返回 True 或 False。

img = cv2.imread("./images/23.jpg")
# img = cv2.imread("./images/22.jpg")
# img = cv2.imread("./images/21.jpg")

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

# 查找轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

hull = cv2.convexHull(contours[0])  # 凸包
print(cv2.isContourConvex(contours[0]), cv2.isContourConvex(hull))
# False True
# 说明轮廓曲线是非凸的,凸包曲线是凸的

cv2.drawContours(img, [hull], -1, (0, 0, 255), 2)

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

七、图像轮廓查找与绘制

import cv2
import numpy as np

'''轮廓查找与绘制: findContours(), drawContours()'''
# img = cv2.imread("./images/21.jpg")
img = cv2.imread("./images/22.jpg")
cv2.imshow("img", img)

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray", gray)

ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
cv2.imshow("thresh", thresh)

# 查找轮廓:包括的canny算法
# findContours(image, mode, method, contours=None, hierarchy=None, offset=None)
# image:输入图像(二值化图像)
# mode:轮廓检索方式
# method:轮廓近似方法
'''
轮廓检索方式:
cv2.RETR_EXTERNAL	只检测外轮廓
cv2.RETR_LIST	检测的轮廓不建立等级关系
cv2.RETR_CCOMP	建立两个等级的轮廓,上面一层为外边界,里面一层为内孔的边界信息
cv2.RETR_TREE	建立一个等级树结构的轮廓,包含关系
'''
'''
轮廓近似方法:
cv2.CHAIN_APPROX_NONE	存储所有边界点
cv2.CHAIN_APPROX_SIMPLE	压缩垂直、水平、对角方向,只保留端点
'''

contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours[0]))  # 点的数量396
print(np.shape(contours))  # (1, 396, 1, 2)
print(hierarchy)  # 层次树 [[[-1 -1 -1 -1]]]
cv2.imshow("thresh2", thresh)

# 绘制轮廓:直接对原图进行操作
# drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None)
# contourIdx 轮廓的索引(当设置为-1时,绘制所有轮廓)

img_contour = cv2.drawContours(img, contours, -1, (0, 255, 0), 2)
cv2.imshow("img contour", img_contour)

cv2.waitKey(0)
cv2.destroyAllWindows()

八、Hough空间(形状检测)

'''
1.轮廓检测算法公式检测出轮廓:使用参数作为坐标系
2.投射到Hough空间进行形状检测
  1)直线检测
    lines = cv2.HoughLines(image, rho, theta, threshold)

    参数:
    image: 单通道的二进制图像。
    rho: (ρ,θ)中ρ的精度。
    theta: (ρ,θ)中θ的精度。
    threshold: 阈值,(ρ,θ)对应的最低投票数。>=threshold被检测为一条线。
  2)圆检测
    circles = cv2.HoughCircles(image, method, dp, minDist, circles=None, param1=None, param2=None, minRadius=None, maxRadius=None)

    参数:
    method:定义检测图像中圆的方法。目前唯一实现的方法是HOUGH_GRADIENT。
    dp:累加器分辨率与图像分辨率的反比。
      dp=1,则累加器与输入图像具有相同的分辨率;dp=2,累加器有一半的宽度和高度。
    minDist:该参数是让算法能明显区分的两个不同圆之间的最小距离。
    param1 :用于Canny的边缘阈值上限,下限被置为上限的一半。
    param2:HOUGH_GRADIENT方法的累加器阈值(最低投票数)。阈值越小,检测到的圈子越多。
    minRadius :最小圆半径。
    maxRadius:最大圆半径。
'''
# 1.直线检测
import cv2
import numpy as np

img = cv2.imread("./images/24.jpg")
img = cv2.GaussianBlur(img, (5, 5), 50)

# 轮廓检测算法检测出轮廓
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 100, 150)  # 提取轮廓边缘信息
# cv2.imshow("edges", edges)

# 2.投射到Hough空间进行形状检测
# 任何一条线都可以用(ρ,θ)这两个术语表示。
# 1)先定义一个累加器,(ρ,θ)对应直线,ρ和θ都分别依次增大(根据精度),计算每对(ρ,θ)的投票数。
#    其中,ρ以像素为单位,θ以弧度为单位。rho和theta是ρ和θ的精度。
# 2)然后,根据threshold(阈值,最低投票数)来判断是否归为一条直线
lines = cv2.HoughLines(edges, 1, np.pi/30, 100)  # 隔一度的采样,距离

for line in lines:
    rho, theta = line[0]
    print(line[0])
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = rho * a
    y0 = rho * b
    # k1*k2=-1 ==> k2=-1/k1
    # k1 = tan(θ) ==> k2 = -1/tan(θ)=-cot(θ)
    x1 = int(x0 + 1000 * (b))  # 直线起点横坐标
    y1= int(y0 + 1000 * (a))  # 直线起点在纵坐标
    x2 = int(x0 - 1000 * (b))  # 直线终点横坐标
    y2 = int(y0 - 1000 * (a))  # 直线终点纵坐标
    # 画线
    cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 1)

cv2.imshow("img", img)
cv2.imshow("gray", gray)
cv2.imshow("edges", edges)

cv2.waitKey(0)
cv2.destroyAllWindows()
import cv2
import numpy as np

# 圆检测
img = cv2.imread("./images/25.jpg")

# 1.轮廓检测算法检测出轮廓
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 100)

cv2.imshow("edges", edges)

# 投影到Hough空间进行形状检测
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 100,  # 隔1度的采样、表示两个圆之间 圆心的最小距离
                           param1=90, param2=20, minRadius=20, maxRadius=300)  #双边阈值(断裂),最小最大半径

'''
cv2.HoughCircles(image, method, dp, minDist, circles, param1, param2, minRadius, maxRadius)

image为输入图像,需要灰度图

method为检测方法,常用CV_HOUGH_GRADIENT

dp为检测内侧圆心的累加器图像的分辨率于输入图像之比的倒数,如dp=1,累加器和输入图像具有相同的分辨率,如果dp=2,累计器便有输入图像一半那么大的宽度和高度

minDist表示两个圆之间圆心的最小距离

param1有默认值100,它是method设置的检测方法的对应的参数,对当前唯一的方法霍夫梯度法cv2.HOUGH_GRADIENT,它表示传递给canny边缘检测算子的高阈值,而低阈值为高阈值的一半

param2有默认值100,它是method设置的检测方法的对应的参数,对当前唯一的方法霍夫梯度法cv2.HOUGH_GRADIENT,它表示在检测阶段圆心的累加器阈值,它越小,就越可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了

minRadius有默认值0,圆半径的最小值

maxRadius有默认值0,圆半径的最大值

'''

print(np.uint16(np.around(circles[0, :])))
# 画圆
if not circles is None:
    circle = np.uint16(np.around(circles))
    for i in circle[0, :]:
        cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0), 2)

cv2.imshow("gray", gray)
cv2.imshow("edges", edges)
cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

九、分水岭算法

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

img = cv2.imread("./images/26.jpg")
cv2.imshow("img", img)

# 1.图像二值化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
cv2.imshow("thresh", thresh)
cv2.imshow("gray", gray)

kernel = np.ones((3, 3), dtype=np.uint8)

# 2.噪声去除
open = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
cv2.imshow("open", open)

# 3.确定背景区域
sure_bg = cv2.dilate(open, kernel, iterations=3)
cv2.imshow("sure_bg", sure_bg)

# 4.寻找前景区域
dist_transform = cv2.distanceTransform(open, 1, 5)  # 距离腐蚀,计算距离
cv2.imshow("dist transform", dist_transform)

# 根据前景像素值来确定阈值大小
ret, sure_fg = cv2.threshold(dist_transform, 0.5 * dist_transform.max(), 255, cv2.THRESH_BINARY)
cv2.imshow("sure_fg", sure_fg)

# 5.找到未知区域
sure_fg = np.uint8(sure_fg)
unknow = cv2.subtract(sure_bg, sure_fg)  # 背景减去前景,去除背景
cv2.imshow("unknow", unknow)

# 6.类别标记:计算中心
ret, markers = cv2.connectedComponents(sure_fg)  # ret表示前景标记数,markers表示标记的是背景
# 为所有的标记加1, 保证背景是0不是1
markers = markers + 1
markers[unknow == 255] = 0
print(markers)

# 7.分水岭算法
markers = cv2.watershed(img, markers)
img[markers == -1] = (0, 0, 255)
cv2.imshow("img_watershed", img)

cv2.waitKey(0)
cv2.destroyAllWindows()

十、模版匹配

import cv2
import numpy as np
'模板匹配'
'''
1)模板匹配,得到匹配灰度图
    res = cv2.matchTemplate(image, templ, method, result=None, mask=None)

    参数:
    image: 输入图像
    templ: 模板图像
    method: 模板匹配方法,包括:
    - CV_TM_SQDIFF 平方差匹配法:该方法采用平方差来进行匹配;最好的匹配值为0;匹配越差,匹配值越大。
    - CV_TM_SQDIFF_NORMED 相关匹配法:该方法采用乘法操作;数值越大表明匹配程度越好。
    - CV_TM_CCORR 相关系数匹配法:1表示完美的匹配;-1表示最差的匹配。
    - CV_TM_CCORR_NORMED 归一化平方差匹配法
    - CV_TM_CCOEFF 归一化相关匹配法
    - CV_TM_CCOEFF_NORMED 归一化相关系数匹配法
2)获取最小和最大像素值及它们的位置
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
3)最后,将匹配的区域标记出来
    cv2.rectangle(img, pt1, pt2, color, thickness=None, lineType=None, shift=None)
'''

# 1.单对象匹配:原图中仅有一个与模版匹配
img = cv2.imread("./images/16.jpg")
template = cv2.imread("./images/17.jpg")

h, w, c = template.shape
print(img.shape)  # (342, 548, 3)
print(template.shape)  # (48, 36, 3)

# 1.匹配模版,得到匹配灰度图
# res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF)  # 匹配方法:最大值是最匹配区域
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)  # 归一化为[-1, 1], 1表示100%匹配
# res = cv2.matchTemplate(img, template, cv2.TM_CCORR)  # 最大值是最匹配区域
# res = cv2.matchTemplate(img, template, cv2.TM_CCORR_NORMED)  # 归一化[0, 1], 1表示100%匹配
# res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)  # 最小值是最匹配区域
# res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF_NORMED)  # 归一化[0, 1], 0表示100%匹配

print(res.shape)

# 2.获取最小和最大像素值值及它们的位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
print(min_val)  # 最小值为:-0.6616084575653076
print(max_val)  # 最大值为:0.996796727180481
print(min_loc)  # 最小值索引:(55, 215)
print(max_loc)  # 最大值索引:(223, 85)

# 3.最后,将匹配的区域标记出来
# 匹配类型是TM_CCOEFF、TM_CCOEFF_NORMED、TM_CCORR、TM_CCORR_NORMED时,最大值是最匹配区域
cv2.rectangle(img, (max_loc[0], max_loc[1]), (max_loc[0]+w, max_loc[1]+h), color=(0, 0, 255), thickness=2)
# 匹配类型是TM_SQDIFF、TM_SQDIFF_NORMED时,最小值是最匹配区域
# cv2.rectangle(img, (min_loc[0], min_loc[1]), (max_loc[0]+w, max_loc[1]+h), color=(0, 0, 255), thickness=2)

cv2.imshow("img", img)
cv2.imshow("template", template)
cv2.waitKey(0)
cv2.destroyAllWindows()
import cv2
import numpy as np

# 多对象匹配,原图中有多个模版匹配
img = cv2.imread("./images/19.jpg")
template = cv2.imread("./images/20.jpg")

h, w, c = template.shape
print(template.shape)  # (35, 35, 3)

# 匹配模版,得到匹配灰度图
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)  # 最大值为匹配区域
print(res.shape)  # h, w  符合条件的有326*446个像素
# print(res)  # 像素值

# 当匹配像素值>=0.95,我们认为是匹配的。
locs = np.where(res >= 0.95)  # 返回匹配程度大于0.95的数组。
print(locs)  # (array([230, 230, 230, 230, 230], dtype=int64), array([125, 163, 200, 310, 384], dtype=int64))
print(*locs[::-1])  # w,h -> x, y     [125 163 200 310 384] [230 230 230 230 230]

for pt in zip(*locs[::-1]):
    print(pt[0], pt[1])

    # 最后,将所有匹配的区域标记出来
    cv2.rectangle(img, (pt[0], pt[1]), (pt[0] + w, pt[1] + h), color=(0, 0, 255), thickness=1)

cv2.imshow("img", img)
cv2.imshow("template", template)
cv2.waitKey(0)
cv2.destroyAllWindows()

十一、利用对象掩码mask

import cv2
import numpy as np

# 对象掩码mask
img = cv2.imread("./images/23.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 0, 255, 0 | 8)

contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

mask = np.zeros(img.shape, np.uint8)
cv2.drawContours(mask, contours, -1, (255, 0, 0), -1)

pixel_points = np.transpose(np.nonzero(mask))
print(pixel_points)

cv2.imshow("img", img)
cv2.imshow("mask", mask)
cv2.waitKey(0)
cv2.destroyAllWindows()

十二、利用形态学操作寻找车牌

import cv2

# 读取图片
raw_image = cv2.imread("./images/27.jpg")

# 高斯模糊,将图片平滑化,去掉干扰的噪声
image = cv2.GaussianBlur(raw_image, (3, 3), 0)

# 图片灰度化
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Sobel算子(X方向)
Sobel_x = cv2.Sobel(image, cv2.CV_16S, 1, 0)
Sobel_y = cv2.Sobel(image, cv2.CV_16S, 0, 1)
absX = cv2.convertScaleAbs(Sobel_x)  # 转回uint8
absY = cv2.convertScaleAbs(Sobel_y)
dst = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
image = dst

cv2.imshow("image", image)

# 二值化:图像的二值化,就是将图像上的像素点的灰度值设置为0或255,图像中显示出明显的有黑和白
ret, image = cv2.threshold(image, 0, 255, cv2.THRESH_OTSU)
cv2.imshow("image1", image)

# 闭操作:闭操作可以将目标区域连成一个整体,便于后续轮廓的提取
kernel_X = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel_X)
cv2.imshow("image2", image)

# 膨胀腐蚀(形态学处理)
kernel_X = cv2.getStructuringElement(cv2.MORPH_RECT, (20, 1))
kernel_Y = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 20))

image = cv2.dilate(image, kernel_X)  # 在x轴上膨胀,填充车牌漏洞,会在y轴上连续
image = cv2.erode(image, kernel_X)  # 在x轴上腐蚀,变回原状
image = cv2.erode(image, kernel_Y)  # 在y轴上腐蚀,切断x轴上数据
image = cv2.dilate(image, kernel_Y)  # 在y轴上膨胀,返回原型
image = cv2.medianBlur(image, 15)  # 平滑处理,中值滤波
cv2.imshow("image3", image)

# 查找轮廓
contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
for item in contours:
    rect = cv2.boundingRect(item)  # 根据轮廓点来找到矩形框
    x = rect[0]
    y = rect[1]
    w = rect[2]
    h = rect[3]
    if w > (h * 2):
        # 裁剪区域图片
        chepai = raw_image[y:y+h, x:x+w]
        cv2.imshow("chepai"+str(x), chepai)

# 绘制轮廓
image = cv2.drawContours(raw_image, contours, -1, (0, 0, 255), 3)
cv2.imshow("image4", image)

cv2.waitKey(0)
cv2.destroyAllWindows()

十三、图像形状匹配

import cv2

# 形状匹配:matchShapes()
img1 = cv2.imread("./images/21.jpg")
img2 = cv2.imread("./images/22.jpg")

gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
ret1, binary1 = cv2.threshold(gray1, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
contours1, _ = cv2.findContours(binary1, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
ret2, binary2 = cv2.threshold(gray2, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
contours2, _ = cv2.findContours(binary2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

ret = cv2.matchShapes(contours1[0], contours2[0], cv2.CONTOURS_MATCH_I1, 0.0)
print(ret)  # 值越小,越匹配

cv2.imshow("img1", img1)
cv2.imshow("img2", img2)
cv2.waitKey(0)
cv2.destroyAllWindows()