Python OpenCV 进阶篇

310 阅读43分钟

模板匹配

模板匹配是一种最原始、最基本的识别方法,可以在原始图像中 寻找特定图像的位置。模板匹配经常应用于简单的图像查找场景中, 例如,在集体合照中找到某个人的位置。本章将介绍如何利用 OpenCV实现模板匹配。

image.png

  1. 模板匹配方法

匹配方法的参数值 image.png

  • 在模板匹配的计算过程中,模板会在原始图像中移动。模板与重 叠区域内的像素逐个对比,最后将对比的结果保存在模板左上角像素 点索引位置对应的数组位置中。
  • 模板将原始图像中每一块区域都覆盖一遍,但结果数组的行、列数并不等于原始图像的像素的行、列数。
  • 模板移动到原始图像的边缘之后就不会继续移动了,所以模板的移动区域的边长为“原始图像边长-模板边长 +1”,最后加1是因为移动区域内的上下、左右的2个边都被模板覆盖到了,如果不加1会丢失数据。
  1. 单模板匹配

匹配过程中只用到一个模板场景叫单模板匹配。原始图像中可能 只有一个和模板相似的图像,也可能有多个。如果只获取匹配程度最 高的那一个结果,这种操作叫作单目标匹配。如果需要同时获取所有 匹配程度较高的结果,这种操作叫作多目标匹配。

  • 单目标匹配

单目标匹配只获取一个结果即可,就是匹配程度最高的结果(如 果使用平方差匹配,则为计算出的最小结果;如果使用相关匹配或相 关系数匹配,则为计算出的最大结果)。以平方差匹配为例介绍。 matchTemplate()方法的计算结果是一个二维数组,OpenCV提供了一个minMaxLoc()方法专门用来解析这个二维数组中的最大值、最小值以及这2个值对应的坐标。

平方差匹配的计算结果越小,匹配程度越高。minMaxLoc()方法 返回的minValue值就是模板匹配的最优结果,minLoc就是最优结果区 域左上角的点坐标,区域大小与模板大小一致。

# 模板匹配

#模板匹配方法
'''
result = cv2.matchTemplate(image,templ,method,mask)
image:原始图像
templ:模板图像,尺寸必须小于或等于原始图像
method:匹配的方法
mask:可选参数。掩膜,只有cv2.TM_SQDIFF和cv2.TM_CCOEFF_NORMED支持此参数,建议采用默认值。
result:计算得出的匹配结果。如果原始图像的宽、高分别为W、H,模板图像的宽高分别为w、h,
result就是一个W-w+1列、H-h+1行的32位浮点型数组。数组中每一个浮点数都是原始图像中对
应像素位置的匹配结果,其含义需要根据method参数来解读。
'''
# 单模板匹配
import cv2
# 单目标匹配
'''
minValue,maxValue,minLoc,maxLoc = cv2.minMaxLoc(src,mask)
src: matchTemplate()方法计算得出的数组。
mask:可选参数,掩膜,建议使用默认值。
minValue:数组中的最小值。
maxValue:数组中的最大值。
minLoc:最小值的坐标,格式为(x,y)
maxLoc:最大值的坐标,格式为(x,y)
'''
bgd = cv2.imread("bgd1.png")
tpl = cv2.imread("tpe1.png")
height, width, channels = tpl.shape
result = cv2.matchTemplate(bgd,tpl,cv2.TM_SQDIFF_NORMED) # 按照标准平方差方式进行匹配
# 获取匹配结果中的最小值、最大值、最小值坐标、最大值坐标
minValue,maxValue,minLoc,maxLoc = cv2.minMaxLoc(result)
resultPoint1 = minLoc  # 将最小值坐标当作最佳匹配区域的左上角坐标
# 计算出最佳匹配区域的右下角点坐标
resultPoint2 = (resultPoint1[0]+width,resultPoint1[1]+height)
# 在最佳匹配区域位置绘制红色方框,线宽为2像素
cv2.rectangle(bgd,resultPoint1,resultPoint2,(0,0,255),2)
cv2.imshow("result",bgd)# 显示结果
cv2.imshow("tpl",tpl)
cv2.waitKey()

# 从2幅图像中选择最佳的匹配结果
image = [] # 存储原始图像的列表
image.append(cv2.imread("bgd2.png"))
image.append(cv2.imread("bgd3.png"))
tpl1 = cv2.imread("tpe2.png")
index = -1 # 初始化车位编号列表的索引为-1
min = 1 # 初始化最小值
for i in range(len(image)):
    # 按照标准平方差方式匹配
    result = cv2.matchTemplate(image[i],tpl1,cv2.TM_SQDIFF_NORMED)
    # 获得最佳匹配结果的索引
    if min > any(result[0]):
        index = i
cv2.imshow("result",image[index]) # 显示最佳匹配结果
cv2.imshow("tpl1",tpl1)
cv2.waitKey()
cv2.destroyAllWindows()
  • 查找重复的图像
import cv2
import os
import sys
import numpy as np

PIC_PATH = "Y:\Pythonspace\py\OpenCV\"  # 图片路径
width, height = 100, 100  # 缩放比例

pic_file = os.listdir(PIC_PATH)  # 所有照片文件列表
pic_file = [f for f in pic_file if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff'))]  # 过滤支持的图片格式
same_pic_index = []  # 相同图像的索引列表
imgs = []  # 缩放后的图像对象列表
has_same = set()  # 相同图像的集合
count = len(pic_file)  # 照片数量

if count == 0:
    print("没有图像")
    sys.exit(0)  # 停止程序

for file_name in pic_file:  # 遍历所有照片文件
    pic_name = os.path.join(PIC_PATH, file_name)  # 拼接完整文件名
    img = cv2.imread(pic_name)  # 创建文件的图像
    if img is None:
        print(f"无法读取图像: {pic_name}")
        continue
    img = cv2.resize(img, (width, height))  # 缩放图像为统一大小
    imgs.append(img)  # 按文件顺序保存图像对象

# 更新图像数量
count = len(imgs)

for i in range(count - 1):  # 遍历所有图像文件,不遍历最后一个图像
    if i in has_same:  # 如果此图像已经找到相同的图像
        continue  # 跳过此图像
    templ = imgs[i]  # 取出模板图像
    same = [i]  # 与templ内容相同的图像索引列表
    for j in range(i + 1, count):  # 从templ的下一个位置开始遍历
        if j in has_same:  # 如果此图像已经找到相同的图像
            continue  # 跳过此图像
        pic = imgs[j]  # 取出对照图像
        # 使用cv2.norm计算两幅图像的差异
        norm_diff = cv2.norm(templ, pic, cv2.NORM_L2)
        similarity = 1 - norm_diff / (width * height * 3)
        if similarity > 0.9:  # 如果相似度大于0.9,则认为两幅图像相同
            same.append(j)  # 记录对照图像的索引
            has_same.add(i)  # 模板图像已找到相同图像
            has_same.add(j)  # 对照图像已找到相同图像
    if len(same) > 1:  # 如果模板图像找到了至少一幅与自己相同的图像
        same_pic_index.append(same)  # 记录相同图像的索引

for same_list in same_pic_index:  # 遍历相同图像的索引
    text = "相同的照片:"
    for same in same_list:
        text += str(pic_file[same]) + ", "  # 拼接相同图像的文件名
    print(text)
  • 多目标匹配

多目标匹配需要将原始图像中所有与模板相似的图像都找出来, 使用相关匹配或相关系数匹配可以很好地实现这个功能。如果计算结 果大于某值(例如0.999),则认为匹配区域的图案和模板是相同的。

# 多目标匹配
import cv2
# 为原始图像中所有匹配成功的图案绘制红框
img1 = cv2.imread("bgd4.png")
tpl1 = cv2.imread("tpe3.png")
height,width,channels = tpl1.shape
results = cv2.matchTemplate(img1,tpl1,cv2.TM_CCOEFF_NORMED)# 按照标准相关系数匹配
for y in range(len(results)):# 遍历结果数组的行
    for x in range(len(results[y])):# 遍历结果数组的列
        if results[y][x]>0.99:# 如果相关系数大于0.99,则认为匹配成功
            cv2.rectangle(img1,(x,y),(x+width,y+height),(0,0,255),2)
cv2.imshow("result1",img1)
cv2.imshow("tpl1",tpl1)
cv2.waitKey()

# 统计一条快轨线路的站台总数
img2 = cv2.imread("bgd5.png")
tpl2 = cv2.imread("tpe4.png")
height,width,channels = tpl2.shape
results = cv2.matchTemplate(img2,tpl2,cv2.TM_CCOEFF_NORMED)
station_Num = 0
for y in range(len(results)):
    for x in range(len(results[y])):
        if results[y][x]>0.99:
            # 在最佳匹配结果位置绘制蓝色矩形边框
            cv2.rectangle(img2,(x,y),(x+width,y+height),(255,0,0),2)
            station_Num += 1
# 在原始图像中绘制统计结果
cv2.putText(img2,"站台数:"+str(station_Num),(0,30),cv2.FONT_HERSHEY_COMPLEX_SMALL,1,(0,0,255),1)
cv2.imshow("result2",img2)
cv2.waitKey()

# 优先选择直线距离最短的地铁站
import numpy as np
import math

# 读取原始图像
image = cv2.imread("bgd6.png")
# 读取模板图像
templ = cv2.imread("tpe5.png")
# 获取模板图像的高、宽和通道数
height, width, c = templ.shape

# 按照标准化相关系数匹配
results = cv2.matchTemplate(image, templ, cv2.TM_CCOEFF_NORMED)

# 用于存储最佳匹配结果左上角坐标的列表
point_X = []
point_Y = []

# 遍历结果矩阵的行
for y in range(results.shape[0]):
    # 遍历结果矩阵的列
    for x in range(results.shape[1]):
        # 如果相关系数大于 0.8 则认为匹配成功
        if results[y, x] > 0.98:  # 降低阈值以匹配最短路径,过低则精确度低,匹配失真
            # 在最佳匹配结果位置绘制蓝色方框
            cv2.rectangle(image, (x, y), (x + width, y + height), (255, 0, 0), 2)
            # 把最佳匹配结果左上角的横坐标添加到列表中
            point_X.append(x)
            # 把最佳匹配结果左上角的纵坐标添加到列表中
            point_Y.append(y)

# 确保找到至少两个匹配点
if len(point_X) >= 2 and len(point_Y) >= 2:
    # 出发点的横、纵坐标
    start_X = 122
    start_Y = 302

    # 计算出发点到第一个匹配点的距离
    place_Square = np.array([point_X[0], point_Y[0]])
    place_Start = np.array([start_X, start_Y])
    minus_SS = place_Start - place_Square
    start_Square = math.hypot(minus_SS[0], minus_SS[1])

    # 计算出发点到第二个匹配点的距离
    place_Highroad = np.array([point_X[1], point_Y[1]])
    minus_HS = place_Highroad - place_Start
    start_Highroad = math.hypot(minus_HS[0], minus_HS[1])

    # 在图像上显示第一个匹配点的距离
    cv2.putText(image, f"Distance1: {start_Square:.2f}", (point_X[0], point_Y[0] - 10),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

    # 在图像上显示第二个匹配点的距离
    cv2.putText(image, f"Distance2: {start_Highroad:.2f}", (point_X[1], point_Y[1] - 10),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

    # 画出距离较短的路线
    if start_Square < start_Highroad:
        cv2.line(image, (start_X, start_Y), (point_X[0], point_Y[0]), (0, 255, 0), 2)
    else:
        cv2.line(image, (start_X, start_Y), (point_X[1], point_Y[1]), (0, 255, 0), 2)

    # 显示结果图像
    cv2.imshow("result", image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()  # 释放所有窗口

else:
    print("未找到足够的匹配点。")
  1. 多模板匹配

匹配过程中同时查找多个模板的操作叫多模板匹配。多模板匹配实际上就是进行了n次“单模板多目标匹配”操作,n的数量为模板总数。

# 多模板匹配
import cv2
# 自定义方法:获取模板匹配成功后所有红框位置的坐标
def myMatchTemplate(img, templ, threshold=0.99):
    height, width, channels = templ.shape
    results = cv2.matchTemplate(img, templ, cv2.TM_CCORR_NORMED)
    loc = []
    # 使用 zip 组合坐标
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(results)
    yloc, xloc = (results >= threshold).nonzero()

    for (x, y) in zip(xloc, yloc):
        loc.append((x, y, x + width, y + height))
    return loc

img = cv2.imread("bgd4.png")
templs = [
    cv2.imread("tpe3.png"),
    cv2.imread("tpe6.png"),
    cv2.imread("tpe7.png")
]

loc = []
for t in templs:
    loc += myMatchTemplate(img, t)

for (x1, y1, x2, y2) in loc:
    cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.imshow("img", img)
cv2.waitKey()

# 使用多模板匹配让控制台判断4辆车分别停在了哪个车位上
image = cv2.imread("carbgd.png")
tmps = [
    cv2.imread("car1.png"),
    cv2.imread("car2.png"),
    cv2.imread("car3.png"),
    cv2.imread("car4.png")
]
# 定义每个车位的区域(可以根据实际情况调整边界)
parking_slots = [
    (0, 271),  # 车位1的范围
    (271, 634),  # 车位2的范围
    (634, 963),  # 车位3的范围
    (963, float('inf'))  # 车位4的范围
]
car_positions = []
for idx, car in enumerate(tmps):
    results = cv2.matchTemplate(image, car, cv2.TM_CCORR_NORMED)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(results)
    # 如果匹配结果的相关系数大于阈值,判断车所在的车位
    if max_val > 0.99:
        car_position = max_loc[0]  # 获取匹配位置的x坐标
        for slot_idx, (left, right) in enumerate(parking_slots, 1):
            if left <= car_position < right:
                car_positions.append((idx + 1, slot_idx))
                print(f"车{idx + 1}停在了车位{slot_idx}")
                break
# 如果需要,可以将car_positions用于后续处理
cv2.destroyAllWindows()
  • 小结

模板匹配包括单模板匹配和多模板匹配,单模板匹配又包括单目 标匹配和多目标匹配。实现这些内容的基础方法就是模板匹配方法, 即matchTemplate()方法。其中,读者朋友重点掌握模板匹配方法的6个 参数值。此外,为了实现单目标匹配,除了需要使用模板匹配方法 matchTemplate()外,还要使用minMaxLoc()方法,这个方法返回的就是 单目标匹配的最优结果。对于多目标匹配,读者朋友要将它和多模板匹配区分开:多目标匹配只有一个模板,而多模板匹配则有多个模板。

  1. 滤波器

在尽量保留原图像信息的情况下,去除图像内噪声、降低细节层 次信息等一系列过程,叫作图像的平滑处理(或图像的模糊处理)。 实现平滑处理最常用的工具就是滤波器。通过调节滤波器的参数,可 以控制图像的平滑程度。OpenCV提供了种类丰富的滤波器,每种滤 波器使用的算法均不同,但都能对图像中的像素值进行微调,让图像 呈现平滑效果。本章将介绍均值滤波器、中值滤波器、高斯滤波器和 双边滤波器的使用方法。

image.png

  • 均值滤波核

图像中可能会出现这样一种像素,该像素与 周围像素的差别非常大,导致从视觉上就能看出 该像素无法与周围像素组成可识别的图像信息, 降低了整个图像的质量。这种“格格不入”的像素就 是图像的噪声。如果图像中的噪声都是随机的纯 黑像素或者纯白像素,这样的噪声称作“椒盐噪声” 或“盐噪声”。

以一个像素为核心,其周围像素可以组成一个n行n列(简称 n×n)的矩阵,这样的矩阵结构在滤波操作中被称为“滤波核”。

均值滤波器(也称为低通滤波器)可以把图像中的每一个像素都 当成滤波核的核心,然后计算核内所有像素的平均值,最后让核心像 素值等于这个平均值。

  • 中值滤波器

中值滤波器的原理与均值滤波器非常相似,唯一的不同就是不计 算像素的平均值,而是将所有像素值排序,把最中间的像素值取出, 赋值给核心像素。

高斯滤波也被称为高斯模糊或高斯平滑,是目前应用最广泛的平 滑处理算法。高斯滤波可以很好地在降低图片噪声、细节层次的同时 保留更多的图像信息,经过处理的图像呈现“磨砂玻璃”的滤镜效果。

  • 双边滤波器

不管是均值滤波、中值滤波还是高斯滤波,都会使整幅图像变得 平滑,图像中的边界会变得模糊不清。双边滤波是一种在平滑处理过 程中可以有效保护边界信息的滤波操作方法。 双边滤波器自动判断滤波核处于“平坦”区域还是“边缘”区域:如 果滤波核处于“平坦”区域,则会使用类似高斯滤波的算法进行滤波; 如果滤波核处于“边缘”区域,则加大“边缘”像素的权重,尽可能地让这些像素值保持不变。

  • 小结

噪声指的是一幅图像内部的、高亮度的像素点。图像平滑处理是 指在尽量保留原图像信息的情况下,去除图像内部的这些高亮度的像 素点(也就是“噪声”)。为了实现图像平滑处理,需要的工具就是滤 波器。本章主要讲解了OpenCV中的4种滤波器,虽然每种滤波器的实 现原理都不同,但是每种滤波器都能对图像进行图像平滑处理。读者 朋友在掌握这4种滤波器的实现方法的同时,也要熟悉这4种滤波器的 实现原理。

# 图像滤波器
import cv2
# 均值滤波器
'''
dst = cv2.blur(src,ksize,anchor,borderType)
src:被处理的图像。
ksize:滤波核大小,其格式为(高度,宽度),建议使用等宽、高相等的奇数边长。滤波核越大,处理之后的图像就越模糊。
anchor:滤波核的锚点,建议采用默认值,可以自动计算锚点。
borderType:边界样式,建议采用默认值。
dst:经过均值滤波处理后的图像。
'''
img = cv2.imread("elysia.png")
dst1 = cv2.blur(img,(3,3))
dst2 = cv2.blur(img,(5,5))
dst3 = cv2.blur(img,(9,9))
cv2.imshow("img",img)
cv2.imshow("dst1",dst1)
cv2.imshow("dst2",dst2)
cv2.imshow("dst3",dst3)
cv2.waitKey()

# 中值滤波器
'''
dst = cv2.medianBlur(src,ksize)
src:被处理的图像。
ksize:滤波核大小,其格式为(高度,宽度),建议使用奇数3、5、7、9。滤波核越大,处理之后的图像就越平滑。
dst:经过中值滤波处理后的图像。
'''
dst4 = cv2.medianBlur(img,3)
dst5 = cv2.medianBlur(img,5)
dst6 = cv2.medianBlur(img,9)
cv2.imshow("dst4",dst4)
cv2.imshow("dst5",dst5)
cv2.imshow("dst6",dst6)
cv2.waitKey()

# 高斯滤波器
'''
dst = cv2.GaussianBlur(src,ksize,sigmaX,sigmaY,borderType)
src:被处理的图像
ksize:滤波核大小,其格式为(高度,宽度),建议使用等宽、高相等的奇数边长。滤波核越大,处理之后的图像就越模糊。
sigmaX:卷积核水平方向的标准差。
sigmaY:卷积核竖直方向的标准差。
修改sigmaX或sigmaY的值都可以改变卷积核中的权重比例。
如果不知道如何设计这2个参数值,就直接把这2个参数的值
写成0,该方法就会根据滤波核的大小自动计算合适的权重比
例。
borderType:边界样式,建议采用默认值。
dst:经过高斯滤波处理后的图像。
'''
dst7 = cv2.GaussianBlur(img,(5,5),0,0)
dst8 = cv2.GaussianBlur(img,(9,9),0,0)
dst9 = cv2.GaussianBlur(img,(15,15),0,0)
cv2.imshow("dst7",dst7)
cv2.imshow("dst8",dst8)
cv2.imshow("dst9",dst9)
cv2.waitKey()

# 双边滤波器
'''
dst = cv2.bilateralFilter(src,d,sigmaColor,sigmaSpace,borderType)
src:被处理的图像。
d:以当前像素为中心的整个滤波区域的直径。如果d<0,则
自动根据sigmaSpace参数计算得到。该值与保留的边缘信息
数量成正比,与方法运行效率成反比。
sigmaColor:参与计算的颜色范围,这个值是像素颜色值与
周围颜色值的最大差值,只有颜色值之差小于这个值时,周
围的像素才进行滤波计算。值为255时,表示所有颜色都参与
计算。
sigmaSpace::坐标空间的σ(sigma)值,该值越大,参与计
算的像素数量就越多。
dst:经过双边滤波处理后的图像。
'''
dst10 = cv2.GaussianBlur(img,(15,15),0,0)
dst11 = cv2.bilateralFilter(dst10,15,120,100)
cv2.imshow("GaussianBlur",dst10)
cv2.imshow("bilateralFilter",dst11)
cv2.waitKey()
cv2.destroyAllWindows()
  1. 腐蚀与膨胀

腐蚀和膨胀是图像形态学中的两种核心操作,通过这两种操作可 以清除或强化图像中的细节。合理使用腐蚀和膨胀,还可以实现图像 开运算、闭运算、梯度运算、顶帽运算和黑帽运算等极具特点的操 作。

image.png

  • 腐蚀

腐蚀操作可以让图像沿着自己的边界向内收缩。OpenCV通过“核” 来实现收缩计算。“核”的英文名为kernel,在形态学中可以理解为“由n 个像素组成的像素块”,像素块包含一个核心(核心通常在中央位置, 也可以定义在其他位置)。像素块在图像的边缘移动,在移动过程 中,核会将图像边缘那些与核重合但又没有越过核心的像素点都抹除,就像削土豆皮一样,将图像一层层地“削薄”。

image.png

  • 膨胀

膨胀操作与腐蚀操作相反,膨胀操作可以让图像沿着自己的边界 向内扩张。同样是通过核来计算,当核在图像的边缘移动时,核会将 图像边缘填补新的像素,就像在一面墙 上反反复复地涂水泥,让墙变得越来越厚。

image.png

  • 开运算

开运算是将图像先进行腐蚀操作,再进行膨胀操作。开运算可以 用来抹除图像外部的细节(或者噪声)。

  • 闭运算

闭运算是将图像先进行膨胀操作,再进行腐蚀操作。闭运算可以 抹除图像内部的细节(或者噪声)。

  • 形态学运算

腐蚀和膨胀是形态学的基础操作,除了开运算和闭运算以外,形 态 学 中 还 有 几 种 比 较 有 特 点 的 运 算 。

image.png

  • 梯度运算

这里的梯度是指图像梯度,可以简单地理解为像素的变化程度。 如果几个连续的像素,其像素值跨度越大,则梯度值越大。让原图的膨胀图减原图的腐 蚀图。因为膨胀图比原图大,腐蚀图比原图小,利用腐蚀图将膨胀图 掏空,就得到了原图的轮廓图。 梯度运算中得到的轮廓图只是一个大概轮廓,不精准。

  • 顶帽运算

顶帽运算的运算让原图减原图的开运算图。 因为开运算抹除图像的外部细节,“有外部细节”的图像减去“无外部细 节”的图像,得到的结果就只剩外部细节了。

  • 黑帽运算

黑帽运算的运算让原图的闭运算图减去原 图。因为闭运算抹除图像的内部细节,“无内部细节”的图像减去“有内 部细节”的图像,得到的结果就只剩内部细节了。

  • 小结

本章介绍的基础内容是腐蚀和膨胀。读者掌握了其用法,就能轻 而易举地实现开运算和闭运算。其中,开运算是对图像先进行腐蚀操 作,再进行膨胀操作,其作用是抹除图像外部的细节;而闭运算是对 图像先进行膨胀操作,再进行腐蚀操作,其作用是抹除图像内部的细 节。此外,形态学运算也是构建在腐蚀和膨胀的基础上的。其中,梯 度运算是让原图的膨胀图减原图的腐蚀图,得到的结果是原图的轮 廓;顶帽运算是让原图减原图的开运算图,得到的结果是图像的外部 细节;黑帽运算是让原图的闭运算图减去原图,得到的结果是图像的内部细节。

# 膨胀与腐蚀
import cv2
import numpy as np
# 腐蚀
'''
dst = cv2.erode(src, kernel, anchor, iterations, borderType, borderValue)
src:原始图像
kernel:腐蚀使用的核
anchor:核的锚点位置
iterations:腐蚀操作的迭代次数,默认值为1
borderType:边界样式,建议默认
borderValue:边界填充值,默认值为0
dst:腐蚀后的图像
在OpenCV做腐蚀或其他形态学操作时,通常使用numpy模块来创建核数组
import numpy as np
k = np.ones((5,5),np.uint8)
'''
img = cv2.imread("elysia.png")
k = np.ones((3,3),np.uint8)
cv2.imshow("img",img)
dst = cv2.erode(img,k)
cv2.imshow("dst",dst)
cv2.waitKey()

# 膨胀
'''
dst = cv2.dilate(src,kernel,anchor,iterations,borderType,borderValue)
src:原始图像
kernel:膨胀使用的核
anchor:核的锚点位置
iterations:膨胀操作的迭代次数,默认值为1
borderType:边界样式,建议默认
borderValue:边界填充值,默认值为0
dst:膨胀后的图像
'''
# 将图像加工成近视眼效果
k1 = np.ones((9,9),np.uint8)
dst1 = cv2.dilate(img,k1)
cv2.imshow("dst1",dst1)
cv2.waitKey()

# 开运算
k2 = np.ones((5,5),np.uint8)
dst2 = cv2.dilate(dst,k2)
cv2.imshow("dst2",dst2)
cv2.waitKey()

# 闭运算
k3 = np.ones((15,15),np.uint8)
dst3 = cv2.erode(dst1,k3)
cv2.imshow("dst3",dst3)
cv2.waitKey()

# 形态学运算
'''
dst = cv2.morphologyEx(src,op,kernel,anchor,iterations,borderType,borderValue)
src:原始图像
op:操作类型
kernel:操作过程中使用的核
anchor:核的锚点位置
iterations:操作的迭代次数,默认值为1
borderType:边界样式,建议默认
borderValue:边界填充值,默认值为0
dst:操作后的图像
'''

# 梯度运算
dst4 = cv2.morphologyEx(img,cv2.MORPH_GRADIENT,k2)
cv2.imshow("dst4",dst4)
cv2.waitKey()

# 顶帽运算
dst5 = cv2.morphologyEx(img,cv2.MORPH_TOPHAT,k2)
cv2.imshow("dst5",dst5)
cv2.waitKey()

# 黑帽运算
dst6 = cv2.morphologyEx(img,cv2.MORPH_BLACKHAT,k2)
cv2.imshow("dst6",dst6)
cv2.waitKey()
cv2.destroyAllWindows()
  1. 图形检测

图形检测是计算机视觉的一项重要功能。通过图形检测可以分析 图像中可能存在的形状,然后对这些形状进行描绘,如搜索并绘制图 像的边缘,定位图像的位置,判断图像中有没有直线、圆形等。虽然 图形检测涉及非常深奥的数学算法,但OpenCV已经将这些算法封装 成简单的方法,开发者只要学会如何调用方法、调整参数即可很好地 实现检测功能。

image.png

  • 图像的轮廓

轮廓是指图像中图形或物体的外边缘线条。简单的几何图形轮廓 是由平滑的线构成的,容易识别,但不规则图形的轮廓可能由许多个 点构成,识别起来比较困难。

image.png

image.png

  • 轮廓拟合

拟合是指将平面上的一系列点,用一条光滑的曲线连接起来。轮 廓的拟合就是将凹凸不平的轮廓用平整的几何图形体现出来。

  • 矩形包围框

矩形包围框是指图像轮廓的最小矩形边界。OpenCV提供的 boundingRect()方法可以自动计算轮廓最小矩形边界的坐标、宽和高。

  • 圆形包围框

圆形包围框与矩形包围框一样,是图像轮廓的最小圆形边界。 OpenCV提供的minEnclosingCircle ()方法可以自动计算轮廓最小圆形边 界的圆心和半径。

  • 凸包

之前介绍了矩形包围框和圆形包围框,这2种包围框虽然已经逼近 了图形的边缘,但这种包围框为了保持几何形状,与图形的真实轮廓 贴合度较差。如果能找出图形最外层的端点,将这些端点连接起来, 就可以围出一个包围图形的最小包围框,这种包围框叫凸包。 凸包是最逼近轮廓的多边形,凸包的每一处都是凸出来的,也就 是任意3个点组成的内角均小于180°。

  • Canny边缘检测

Canny边缘检测算法是John F. Canny于1986年开发的一个多级边缘 检测算法,该算法根据像素的梯度变化寻找图像边缘,最终可以绘制 十分精细的二值边缘图像。

  • 霍夫变换直线检测

霍夫直线变换是通过霍夫坐标系的直线与笛卡儿坐标系的点之间 的映射关系来判断图像中的点是否构成直线。OpenCV将此算法封装 成两个方法,分别是cv2.HoughLines()和cv2.HoughLinesP(),前者用于 检测无限延长的直线,后者用于检测线段。 HoughLinesP()方法名称最后有一个大写的P,该方法只能检测二 值灰度图像,也就是只有两种像素值的黑白图像。该方法最后把找出 的所有线段的两个端点坐标保存成一个数组。

  • 霍夫变换圆形检测

霍夫圆环变换的原理与霍夫直线变换类似。OpenCV提供的 HoughCircles()方法用于检测图像中的圆环,该方法在检测过程中进行 两轮筛选:第一轮筛选找出可能是圆的圆心坐标,第二轮筛选计算这 些圆心坐标可能对应的半径长度。该方法最后将圆心坐标和半径封装 成一个浮点型数组。

  • 小结

图像轮廓指的是将图像的边缘连接起来形成的一个整体,它是图 像的一个重要的特征信息,通过对图像的轮廓进行操作,能够得到这 幅图像的大小、位置和方向等信息,用于后续的计算。为此, OpenCV提供findContours()方法,通过计算图像的梯度,判断图像的 轮廓。为了绘制图像的轮廓,OpenCV又提供了drawContours()方法。 但需要注意的是,Canny()方法虽然能够检测出图像的边缘,但这个边 缘是不连续的。霍夫变换需降噪。

# 图形检测
import cv2
import numpy as np
#轮廓
'''
找轮廓的函数是cv2.findContours()。
contours,hierarchy = cv2.findContours(image,mode,methode)
image: 被检测的图像,必须是8位单通道二值图像。如果原始图像是彩色图像,必须为灰度图,并经过二值化处理。
mode:轮廓的检索模式
methode:轮廓的近似办法
contours:检测出的所有轮廓,list类型,每一个元素都是某一个轮廓的像素坐标数组
hierarchy:轮廓之间的层次关系。

画轮廓的函数是cv2.drawContours()。
image:被绘制轮廓的原始图像,可以是多通道图像。
contours:findContours()函数返回的轮廓数组(列表)。
contourIdx:绘制轮廓的索引,如果为-1,则绘制所有轮廓。
color:轮廓的颜色。
thickness:轮廓的粗细,如果为-1,则表示填充。
lineType:线条的类型。
hierarchy:findContours()函数返回的层次关系。
maxLevel:绘制轮廓的层次深度,最深绘制第maxLevel层的轮廓。
offset:偏移量,可以改变绘制结果的位置。
image:同参数中的image,执行后原始图像就包含绘制的轮廓了,可以不使用此返回值保存结果。
'''
# 绘制几何图像的轮廓1
img = cv2.imread("elysia.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret1,binary1 = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)#二值化
# 检测图像中出现的所有轮廓,记录轮廓的每一点
contours1, hierarchy1 = cv2.findContours(binary1, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# 绘制所有轮廓,宽度为5,颜色为红色
cv2.drawContours(img, contours1, -1, (0,0,255), 5)
cv2.imshow("img", img)
cv2.waitKey()

# 绘制几何图像的轮廓2
img = cv2.medianBlur(img, 5)#中值滤波
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret2,binary2 = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)#二值化
cv2.imshow("binary2", binary2)
# 获取二值化图像中的轮廓及轮廓层次
contours2, hierarchy2 = cv2.findContours(binary2, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(img, contours2, -1, (0,0,255), 2)
cv2.imshow("countours2", img)
cv2.waitKey()

# 轮廓拟合
# 矩形包围框
'''
retval = cv2.boundingRect(array)
array:轮廓数组(countours)
etval:元组类型,包含4个整数值,分别是最小矩形包围框
的:左上角顶点的横坐标、左上角顶点的纵坐标、矩形的宽
和高。所以也可以写成x, y, w, h = cv2.boundingRect (array)的
形式。
'''
img1 = cv2.imread("bgd7.png")
gray = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
ret3,binary3 = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
# 获取二值化图像中的轮廓及轮廓层次
contours3, hierarchy3 = cv2.findContours(binary3, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
x,y,w,h = cv2.boundingRect(contours3[0])# 获取第一个轮廓的矩形包围框,记录坐标、宽、高
cv2.rectangle(img1,(x,y),(x+w,y+h),(0,0,255),2)# 绘制红色矩形包围框
cv2.imshow("img1", img1)
cv2.waitKey()
# 圆形包围框
'''
center,radius = cv2.minEnclosingCircle(points)
points:轮廓数组(countours)
center:元组类型,包含2个浮点值,是最小圆形包围框的圆心横纵坐标。
radius:浮点值,是最小圆形包围框的半径。
'''
center,radius = cv2.minEnclosingCircle(contours3[0])
x = int(round(center[0]))
y = int(round(center[1]))
cv2.circle(img1,(x,y),int(radius),(0,255,0),2)
cv2.imshow("img1", img1)
cv2.waitKey()

# 凸包
'''
hull = cv2.convexHull(points,clockwise,returnPoints)
points:轮廓数组(countours)
clockwise:布尔类型,是否顺时针排列点。
returnPoints:布尔类型,是否返回点坐标。
hull:凸包的点阵数组。
'''
img2 = cv2.imread("bgd7.png")
gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
ret4,binary4 = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
# 检测图像中出现的所有轮廓,记录轮廓的每一点
contours4, hierarchy4 = cv2.findContours(binary4, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
hull = cv2.convexHull(contours4[0])
cv2.polylines(img2,[hull],True,(0,255,0),2)
cv2.imshow("img2",img2)
cv2.waitKey()

# Canny边缘检测
'''
edges = cv2.Canny(image,threshold1,threshold2,apertureSize,L2gradient)
image:检测的原始图像
threshold1:计算过程中使用的第一个阈值,可以是最小阈
值,也可以是最大阈值,通常用来设置最小阈值。
threshold2:计算过程中使用的第二个阈值,通常用来设置
最大阈值。
apertureSize:Sobel算子的孔径大小,默认为3。
L2gradient:可选参数,计算图像梯度的标识,默认值为
False。值为True时采用更精准的算法进行计算。
edges:计算后得出的边缘图像,是一个二值灰度图像。
'''
img3 = cv2.imread("elysia.png")
ret5 = cv2.Canny(img3,10,50)
ret6 = cv2.Canny(img3,100,200)
ret7 = cv2.Canny(img3,400,600)
cv2.imshow("ret5",ret5)
cv2.imshow("ret6",ret6)
cv2.imshow("ret7",ret7)
cv2.waitKey()

# 霍夫变换
# 直线检测
'''
lines = cv2.HoughLinesP(image,rho,theta,threshold,minLineLength,maxLineGap)
image:检测的原始图像
rho:检测直线使用的半径不长,值为1时,表示检测所有可能的半径步长。
theta:搜索直线的角度,值为pi/180°时,表示检测所有角度。
threshold:阈值,该值越小,检测出的直线就越多。
minLineLength:线段的最小长度,小于该长度的线段不记录到结果中。
maxLineGap:线段之间的最小距离。
lines:一个数组,元素为所有检测出的线段,每条线段是一
个数组,代表线段两个端点的横、纵坐标,格式为[[[x1, y1,
x2, y2], [x1, y1, x2, y2]]]。
'''
img4 = cv2.imread("elysia.png")
o = img4.copy()
o = cv2.medianBlur(o,5)
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)
binary5 = cv2.Canny(o,50,150)
# 检测直线,精度为1,全角度,阈值为15,线段最短100,最小间隔为18
lines = cv2.HoughLinesP(binary5,1,np.pi/180,15,minLineLength=100,maxLineGap=18)
for line in lines:
    x1,y1,x2,y2 = line[0]
    cv2.line(img4,(x1,y1),(x2,y2),(0,0,255),2)
cv2.imshow("canny",binary5)
cv2.imshow("lines",img4)
cv2.waitKey()

# 圆环检测
'''
circles = cv2.HonghCircle(image,method,dp,minDist,param1,param2,minRadius,maxRadius)
image:检测的原始图像.
method:检测方法。
dp:累加器分辨率与原始图像分辨率之比的倒数。值为1
时,累加器与原始图像具有相同的分辨率;值为2时,累加器
的分辨率为原始图像的1/2。通常使用1作为参数。
minDist:圆心之间的最小距离。
param1:Canny边缘检测器的最高阈值。
param2:可选参数,检测圆环结果的投票数。第一轮筛选时
投票数超过该值的圆环才会进入第二轮筛选。值越大,检测
出的圆环越少,但越精准。
minRadius:圆环的最小半径。
maxRadius:圆环的最大半径。
circles:一个数组,元素为所有检测出的圆环,每个圆环也
是一个数组,内容为圆心的横、纵坐标和半径长度,格式
为:[[[x1 ,y1, r1], [x2 ,y2, r2]]]。
'''
# 检测硬币图像中出现的圆环
img5 = cv2.imread("coin.png")
coin = img5.copy()
coin = cv2.medianBlur(coin,5)
gray = cv2.cvtColor(coin,cv2.COLOR_BGR2GRAY)
# 检测圆环,圆心最小间距为70,Canny最大阈值为100,投票数超过25,最小半径为83,最大半径为86
circles = cv2.HoughCircles(gray,cv2.HOUGH_GRADIENT,1,70,param1=100,param2=25,minRadius=83,maxRadius=86)
circles = np.uint(np.around(circles))
for c in circles[0]:
    x,y,r = c
    cv2.circle(coin,(x,y),r,(0,0,255),3)
    cv2.circle(coin,(x,y),2,(0,0,255),3)
cv2.imshow("coin",coin)
cv2.waitKey()
cv2.destroyAllWindows()
  1. 视频处理

OpenCV不仅能够处理图像,还能够处理视频。视频是由大量的 图像构成的,这些图像以固定的时间间隔从视频中获取。这样,就能 够使用图像处理的方法对这些图像进行处理,进而达到处理视频的目 的。要处理视频,需要先对视频进行读取、显示和保存等相关操作。 为此,OpenCV提供了VideoCapture类和VideoWriter类的相关方法。

image.png

  • 读取并显示摄像头视频

视频是由大量的图像构成的,把这些图像称作帧。

# 读取并显示摄像头视频
'''
capture = cv2.VideoCapture(index)
capture: 要打开的摄像头。
index:摄像头的设备索引。
摄像头的数量及其设备索引的先后顺序由操作系统决定,因为
OpenCV没有提供查询摄像头的数量及其设备索引的任何方法。

retval = cv2.VideoCapture.isOpened()
retval:isOpen()方法的返回值。如果摄像头初始化成功,则返回true,否则返回false。
在VideoCapture()的语法格式基础上,isOpened()方法的语法格
式可以简写为retval = cap.isOpened()。

retval,image = cv2.VideoCapture.read()
可简写为retval,image = capture.read()
retval:是否读取到帧。
image:读取到的帧。

cv2.VideoCapture.release()
可简写为capture.release()
释放摄像头资源。
'''
import cv2
capture = cv2.VideoCapture(0,cv2.CAP_DSHOW)#,cv2.CAP_DSHOW出错要加上
while(capture.isOpened()):
    retval,image = capture.read()
    image_Gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    if retval == True:
        cv2.imshow("capture",image)
        cv2.imshow("capture_Gray",image_Gray)
    key = cv2.waitKey(1)#等待1ms,按下任意键退出循环
    if key == 32:#按下空格键截图并保存
        capture.release()#按下空格键时释放摄像头资源
        cv2.destroyAllWindows()
        cv2.imwrite("capture.png",image)#保存按下空格键时截取的图片
        cv2.imshow("capture",image)
        cv2.waitKey()
        break
cv2.destroyAllWindows()
  • 播放视频文件

VideoCapture类及其方法除了能够读取并显示摄像头视频外,还 能够读取并显示视频文件。当窗口根据视频文件的时长显示视频文件 时,便实现了播放视频文件的效果。

image.png

# 播放视频文件
# 读取并显示视频文件
'''
video = cv2.VideoCapture(filename)
video:要打开的视频
filename:视频文件路径文件名
OpenCV中的VideoCapture类虽然支持各种格式的视频文件,但
是这个类在不同的操作系统中,支持的视频文件格式不同。尽管如
此,VideoCapture类能够在不同的操作系统中支持后缀名为.avi的视
频文件。
'''
import cv2

video = cv2.VideoCapture("sq.mp4")
fps = video.get(cv2.CAP_PROP_FPS)
frame_Num = 1
while(video.isOpened()):
    retval,frame = video.read()
    video_Gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    cv2.namedWindow("video",0)#0表示窗口大小可变
    cv2.resizeWindow("video",420,300)
    cv2.namedWindow("video_Gray", 0)
    cv2.resizeWindow("video_Gray", 420, 300)
    if retval == True:
        # 显示当前帧数
        cv2.putText(frame,"Frame:"+str(frame_Num),(0,100),cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,2,(0,0,255),5)
        # 该帧对应视频第几秒
        cv2.putText(frame,"Time:"+str(round(frame_Num/fps,2))+"s",(0,200),cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,2,(0,0,255),5)
        cv2.imshow("video_Gray",video_Gray)
        cv2.imshow("video",frame)
    else:
        break
    key = cv2.waitKey(30)#会影响视频播放速度,根据视频帧数调整
    frame_Num += 1
    if key == 32:
        cv2.waitKey(0)
        continue
    if key == 27:
        break
video.release()
cv2.destroyAllWindows()

# 读取视频文件的属性
'''
retval = cv2.VideoCapture.get(propId)
retval:获取与propId对应的属性值
propId:视频文件的属性值。
'''
video = cv2.VideoCapture("sq.mp4")
fps = video.get(cv2.CAP_PROP_FPS)  # 获取视频文件的帧速率
frame_Count = video.get(cv2.CAP_PROP_FRAME_COUNT)  # 获取视频文件的总帧数
frame_Width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))  # 获取视频文件的帧宽度
frame_Height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))  # 获取视频文件的帧高度
print("帧速率:", fps)
print("帧数:", frame_Count)
print("帧宽度:", frame_Width)
print("帧高度:", frame_Height)
  • 保存视频文件

image.png

# 保存视频文件
'''
<VideoWriter object> = cv2.VideoWriter(filename,fourcc,fps,frameSize)
VideoWriter object:VideoWriter类对象
filename:保存视频时的路径(含有文件名)
fourcc:用4个字符表示的视频编码格式。在 Windows 操 作 系 统 下 , fourcc 的 值 为
cv2.VideoWriter_fourcc('X', 'V', 'I', 'D') , 帧 速 率 为 20 , 帧 大 小 为
640×480。
fps:帧速率
frameSize:视频的尺寸(宽,高),每一帧的大小。

为了保存一段视频,除需要使用VideoWriter类的构造方法外,还
需要使用VideoWriter类提供的write()方法。write()方法的作用是在创建
好的VideoWriter类对象中写入读取到的帧。
cv2.VideoWriter.write(frame)
frame:读取到的帧,要写入的帧。

使用write()方法时,需要由VideoWriter类对象进行调用。例
如,在创建好的VideoWriter类对象output中写入读取到的帧frame,
关键代码如下:
 output.write(frame)

 当不需要使用VideoWriter类对象时,需要将其释放掉。
 output.release()
'''
import cv2
capture = cv2.VideoCapture(0,cv2.CAP_DSHOW)
fourcc = cv2.VideoWriter_fourcc("X","V","I","D")
output = cv2.VideoWriter("output.avi",fourcc,20,(640,480))
while (capture.isOpened()):
    retval,frame = capture.read()
    if retval == True:
        output.write(frame)
        cv2.imshow("frame",frame)
    key = cv2.waitKey(1)
    if key == 27:
        break
capture.release()
output.release()
cv2.destroyAllWindows()

# 保存10秒的视频
import cv2
capture = cv2.VideoCapture(0,cv2.CAP_DSHOW)
fourcc = cv2.VideoWriter_fourcc("X","V","I","D")
fps = 20
output_ten = cv2.VideoWriter("output_ten.avi",fourcc,fps,(640,480))
frame_Num = 10*fps# 10秒的帧数
while(capture.isOpened() and frame_Num>0):
    retval,frame = capture.read()
    if retval == True:
        output_ten.write(frame)
        cv2.imshow("frame",frame)
    key = cv2.waitKey(1)
    frame_Num -= 1
capture.release()
output_ten.release()
cv2.destroyAllWindows()

# 保存视频文件
import cv2
video = cv2.VideoCapture("sq.mp4")
fps = video.get(cv2.CAP_PROP_FPS)
size = (int(video.get(cv2.CAP_PROP_FRAME_WIDTH)),
        int(video.get(cv2.CAP_PROP_FRAME_HEIGHT)))
fourcc = cv2.VideoWriter_fourcc("X","V","I","D")
output = cv2.VideoWriter("copy_sq.avi",fourcc,fps,size)
while(video.isOpened()):
    ret,frame = video.read()
    if ret == True:
        output.write(frame)
    else:
        break
print("视频拷贝在当前路径下的copy_sq.avi。")
video.release()
output.release()
cv2.destroyAllWindows()

# 保存视频文件中的前10秒
import cv2
video = cv2.VideoCapture("sq.mp4")
fps = video.get(cv2.CAP_PROP_FPS)
size = (int(video.get(cv2.CAP_PROP_FRAME_WIDTH)),
        int(video.get(cv2.CAP_PROP_FRAME_HEIGHT)))
fourcc = cv2.VideoWriter_fourcc("X","V","I","D")
output = cv2.VideoWriter("sq_10s.avi",fourcc,fps,size)
frame_Num = 10*fps# 10秒的帧数
while(video.isOpened() and frame_Num>0):
    ret,frame = video.read()
    if ret == True:
        output.write(frame)
        frame_Num -= 1
    else:
        break
print("视频拷贝在当前路径下的sq_10s.avi。")
video.release()
output.release()
cv2.destroyAllWindows()
  1. 人脸检测和人脸识别

人脸识别是基于人的脸部特征信息进行身份识别的一种生物识别 技术,也是计算机视觉重点发展的技术。机器学习算法诞生之后,计 算机可以通过摄像头等输入设备自动分析图像中包含的内容信息,随 着技术的不断发展,现在已经有了多种人脸识别的算法。本章将介绍 OpenCV自带的多种图像跟踪技术和3种人脸识别技术的用法。

image.png

  • 人脸检测

人脸检测是让计算机在一幅画面中找出人脸的位置。毕竟计算机 还达不到人类的智能水平,所以计算机在检测人脸的过程中实际上是 在做“分类”操作,例如,计算机发现图像中有一些像素组成了眼睛的 特征,那这些像素就有可能是“眼睛”;如果“眼睛”旁边还有“鼻子”和 “另一只眼睛”的特征,那这3个元素所在的区域就很有可能是人脸区 域;但如果“眼睛”旁边缺少必要的“鼻子”和“另一只眼睛”,那就认为 这些像素并没有组成人脸,它们不是人脸图像的一部分。

  • 级联分类器

将一系列简单的分类器按照一定顺序级联到一起就构成了级联分 类器,使用级联分类器的程序可以通过一系列简单的判断来对样本进 行识别。

OpenCV提供了一些已经训练好的级联分类器,这些级联分类器 以XML文件的方式保存在以下路径中: ...\Python\Lib\site-packages\cv2\data\

image.png

image.png

  • 方法

OpenCV实现人脸检测需要做两步操作:加载级联分类器和使用 分类器识别图像。这两步操作都有对应的方法。

# 加载级联分析器
'''
<CascadeClassifier object> = cv2.CascadeClassifier(filename)
filename: 级联分类器的XML文件
object: 级联分类器对象

objects = cascade.detectMultiScale(image, scaleFactor, minNeighbors, flags, minSize, maxSize)
cascade: 级联分类器对象
image: 待检测的图像
scaleFactor: 缩放因子,默认值为1.1
minNeighbors: 最少的邻近数目,默认值为3
flags: 附加的标志,默认值为0
minSize: 最小的对象尺寸,默认值为(30, 30)
maxSize: 最大的对象尺寸,默认值为(0, 0)
objects:捕捉到的目标区域数组,数组中每一个元素都是一
个目标区域,每一个目标区域都包含4个值,分别是:左上角
点横坐标、左上角点纵坐标、区域宽、区域高。object的格式
为:[[244 203 111 111] [432 81 133 133]]。
'''
# 分析人脸位置
# 在图像的人脸位置绘制红框
import cv2
img = cv2.imread('face.jpg')
# 加载人脸的级联分类器
faceCascade = cv2.CascadeClassifier(r"Y:\PYTHON\Lib\site-packages\cv2\data\haarcascade_frontalface_default.xml")
faces = faceCascade.detectMultiScale(img, 1.1, 3)
for (x, y, w, h) in faces:
    cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),2)
cv2.imshow('img',img)
cv2.waitKey()
cv2.destroyAllWindows()

# 戴墨镜特效
import cv2

# 覆盖图像函数
def overlay_img(img, img_over, img_over_x, img_over_y):
    img_h, img_w = img.shape[:2]
    img_over_h, img_over_w, img_over_c = img_over.shape

    for w in range(0, img_over_w):
        for h in range(0, img_over_h):
            if img_over_c == 4:  # 如果是4通道图像,检查Alpha通道
                if img_over[h, w, 3] == 0:  # Alpha值为0表示完全透明,跳过此像素
                    continue
            x = img_over_x + w
            y = img_over_y + h
            if x >= img_w or y >= img_h:
                continue
            img[y, x] = img_over[h, w, :3]  # 仅拷贝BGR通道

    return img

face_img = cv2.imread('face.jpg')
glasses_img = cv2.imread('glasses.jpg', cv2.IMREAD_UNCHANGED)

# 检查图像是否加载成功
if face_img is None:
    print("Error: Cannot load image 'face.jpg'. Please check the file path.")
    exit()
if glasses_img is None:
    print("Error: Cannot load image 'glasses.png'. Please check the file path.")
    exit()

height, width, channels = glasses_img.shape

# 加载人脸的级联分类器
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
grayframe = cv2.cvtColor(face_img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(grayframe, 1.1, 3)

for (x, y, w, h) in faces:
    # 计算戴墨镜的位置
    gw = w
    gh = int(height * w / width)
    glasses_img_resized = cv2.resize(glasses_img, (gw, gh))
    face_img = overlay_img(face_img, glasses_img_resized, x, y + int(h * 1/3))

cv2.imshow('img', face_img)
cv2.waitKey()
cv2.destroyAllWindows()
  • 眼睛检测
# 眼睛检测
import cv2
img = cv2.imread("face.jpg")
# 加载识别眼睛的级联分类器
eyeCascade = cv2.CascadeClassifier(r"Y:\PYTHON\Lib\site-packages\cv2\data\haarcascade_eye.xml")
eyes = eyeCascade.detectMultiScale(img, 2, 1)
for(x, y, w, h) in eyes:
    cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),1)
cv2.imshow("Eyes", img)
cv2.waitKey()
cv2.destroyAllWindows()
  • 猫脸检测
# 猫脸检测
import cv2
img = cv2.imread("catface.png")
# 加载识别猫脸的级联分析器
catFaceCascade = cv2.CascadeClassifier(r"Y:\PYTHON\Lib\site-packages\cv2\data\haarcascade_frontalcatface.xml")
catFace = catFaceCascade.detectMultiScale(img,1.15,4)
for (x,y,w,h) in catFace:
    cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),2)
cv2.imshow("Cat Face",img)
cv2.waitKey()
cv2.destroyAllWindows()
  • 行人检测
# 在图像中找到行人的位置
import cv2
img = cv2.imread("suseiki1.png")
# 加载识别类人体的级联分类器
bodyCascade = cv2.CascadeClassifier(r"Y:\PYTHON\Lib\site-packages\cv2\data\haarcascade_fullbody.xml")
bodys = bodyCascade.detectMultiScale(img, 1.1, 2)
for (x, y, w, h) in bodys:
    cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()
  • 车牌检测
# 车牌检测
import cv2
img = cv2.imread("pai.jpg")
# 加载识别车牌的级联分类器
plateCascade = cv2.CascadeClassifier(r"Y:\PYTHON\Lib\site-packages\cv2\data\haarcascade_russian_plate_number.xml")
plates = plateCascade.detectMultiScale(img,1.15,4)
for (x,y,w,h) in plates:
    cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),2)
cv2.imshow("Plates",img)
cv2.waitKey()
cv2.destroyAllWindows()
  • 人脸识别

OpenCV提供了3种人脸识别方法,分别是Eigenfaces、Fisherfaces 和LBPH。这3种方法都是通过对比样本的特征最终实现人脸识别。因 为这3种算法提取特征的方式不一样,侧重点不同,所以不能分出孰优 孰劣,只能说每种方法都有各自的识别风格。

  • Eigenfaces人脸识别器

Eigenfaces也叫作“特征脸”。Eigenfaces通过PCA(主成分分析) 方法将人脸数据转换到另外一个空间维度做相似性计算。在计算过程 中,算法可以忽略一些无关紧要的数据,仅识别一些具有代表性的特 征数据,最后根据这些特征识别人脸。

# 人脸识别器:Eigenfaces
'''
recognizer = cv2.face.EigenFaceRecognizer_create(num_components,threshold)
num_components:PCA方法中保留的分量个数,建议使用默认值。
threshold:阈值,用于判断是否是同一个人,建议使用默认值。
recognizer:创建的Eigenfaces人脸识别器对象。

训练Eigenfaces人脸识别器:
recognizer.train(src,labels)
recognizer:已有的Eigenfaces人脸识别器对象。
src:用来训练的图片数据集。list格式,样本图像必须宽高一致。
labels:样本对应的标签,格式为数组,元素类型为整数。
数组长度必须与样本列表长度相同。样本与标签按照插入顺序一一对应。

识别人脸,识别器的predict()方法:
label,confidence = recognizer.predict(src)
recognizer:已有的Eigenfaces人脸识别器对象。
src:需要识别的人脸图像,改图宽高必须与样本一致。
label:与样本匹配程度最高的标签值。
confidence:匹配程度最高的信用度评分。评分小于5000匹
配程度较高,0分表示2幅图像完全一样。
'''
import cv2
import numpy as np

photos = []
labels = []

# 定义所有图像的固定尺寸
img_size = (200, 200)

# 加载和调整图像大小,然后分配标签
image_files = [("suseiki1.png", 0), ("suseiki2.png", 0), ("suseiki3.png", 0),
               ("haixiu7.png", 1), ("haixiu8.png", 1), ("haixiu9.png", 1)]

for file_name, label in image_files:
    img = cv2.imread(file_name, 0)  # 以灰度模式加载图像
    if img is None:
        print(f"错误: 无法加载图像 {file_name}")
    else:
        resized_img = cv2.resize(img, img_size)  # 调整图像大小
        photos.append(resized_img)
        labels.append(label)

    recognizer = cv2.face.EigenFaceRecognizer_create()
# 训练人脸识别器
recognizer.train(photos, np.array(labels))

# 对新图像进行预测
i = cv2.imread("suseiki.png", 0)
if i is not None:
    resized_i = cv2.resize(i, img_size)  # 调整预测图像的大小
    label, confidence = recognizer.predict(resized_i)
    names = {0: "suseiki", 1: "haixiu"}
    print(f"置信度 = {confidence}")
    print(f"识别结果: {names.get(label, '未知')}")
else:
    print("错误: 无法加载预测图像。")
  • Fisherfaces人脸识别器

Fisherfaces是由Ronald Fisher最早提出的,这也是Fisherfaces名字 的由来。Fisherfaces通过LDA(线性判别分析技术)方法将人脸数据 转换到另外一个空间维度做投影计算,最后根据不同人脸数据的投影 距离判断其相似度。 该人脸识别结果错误,用于对比两种方法的差异

# Fisherfaces人脸识别器
'''
recognizer = cv2.face.FisherFaceRecognizer_create(num_components,threshold)
num_components:通过Fisherface方法进行判断分
析时保留的分量个数,建议使用默认值。
threshold:阈值,用来控制识别率,越大越严格,建议使用默认值。
recognizer:创建Fisherface人脸识别器对象。

训练Fisherface人脸识别器:
recognizer.train(src,labels)
recognizer:已有的Fisherface人脸识别器对象。
src:用来训练的人脸图像样本列表,格式为list。样本图像必须宽高一致。
lables:样本对应的标签,格式为数组,元素类型为整数。
数组长度必须与样本列表长度相同。样本与标签按照插入顺
序一一对应。

识别人脸:
label, confidence = recognizer.predict(src)
recognizer:已有的Fisherface人脸识别器对象。
src:需要识别的人脸图像,该图像宽、高必须与样本一致,格式为numpy.ndarray。
label:与样本匹配程度最高的标签值。
confidence:与样本匹配程度最高的置信度值。评分小于5000程
度较高,0分表示2幅图像完全一样。
'''
import cv2
import numpy as np

photos = []
labels = []
# 加载和调整图像大小,然后分配标签
image_files = [("suseiki1.png", 0), ("suseiki2.png", 0), ("suseiki3.png", 0),
               ("haixiu7.png", 1), ("haixiu8.png", 1), ("haixiu9.png", 1)]

for file_name, label in image_files:
    img = cv2.imread(file_name, 0)  # 以灰度模式加载图像
    if img is None:
        print(f"错误: 无法加载图像 {file_name}")
    else:
        photos.append(img)
        labels.append(label)

    recognizer = cv2.face.FisherFaceRecognizer_create()
# 训练人脸识别器
recognizer.train(photos, np.array(labels))

# 对新图像进行预测
i = cv2.imread("suseiki.png", 0)
if i is not None:
    label, confidence = recognizer.predict(i)
    names = {0: "suseiki", 1: "haixiu"}
    print(f"置信度 = {confidence}")
    print(f"识别结果: {names.get(label, '未知')}")
else:
    print("错误: 无法加载预测图像。")
  • Local Binary Pattern Histogram人脸识别器

Local Binary Pattern Histogram简称LBPH,即局部二进制模式直方 图,这是一种基于局部二进制模式算法,这种算法善于捕获局部纹理 特征。

# Local Binary Pattern Histogram人脸识别器
'''
recognizer = cv2.face.LBPHFaceRecognizer_create(radius,neighbors,grid_x,grid_y,threshold)
radius:可选参数,圆形局部二进制模式的半径,建议使用默认值。
neighbors:可选参数,圆形局部二进制模式的采样点数目,建议使用默认值。
grid_x:可选参数,水平方向上的单元格数,建议使用默认值。
grid_y:可选参数,垂直方向上的单元格数,建议使用默认值。
threshold:可选参数,人脸识别时使用的阈值,建议使用默认值。

训练人脸数据集:
recognizer.train(src, labels)
recognizer:已有的LBPH人脸识别器对象。
src:用来训练的人脸图像样本列表,格式为list。样本图像必须宽、高一致。
labels:样本对应的标签,格式为数组,元素类型为整数。
数组长度必须与样本列表长度相同。样本与标签按照插入顺序一一对应。

识别人脸:
label, confidence = recognizer.predict(src)
recognizer:已有的LBPH人脸识别器对象。
src:需要识别的人脸图像,该图像宽、高必须与样本一致。
label:与样本匹配程度最高的标签值。
confidence:匹配程度最高的信用度评分。评分小于50匹配
程度较高,0分表示2幅图像完全一样。
'''
import cv2
import numpy as np

photos = []
labels = []
# 加载和调整图像大小,然后分配标签
image_files = [("suseiki1.png", 0), ("suseiki2.png", 0), ("suseiki3.png", 0),
               ("haixiu7.png", 1), ("haixiu8.png", 1), ("haixiu9.png", 1)]

for file_name, label in image_files:
    img = cv2.imread(file_name, 0)  # 以灰度模式加载图像
    if img is None:
        print(f"错误: 无法加载图像 {file_name}")
    else:
        photos.append(img)
        labels.append(label)

    recognizer = cv2.face.LBPHFaceRecognizer_create()
# 训练人脸识别器
recognizer.train(photos, np.array(labels))

# 对新图像进行预测
i = cv2.imread("suseiki.png", 0)
if i is not None:
    label, confidence = recognizer.predict(i)
    names = {0: "suseiki", 1: "haixiu"}
    print(f"置信度 = {confidence}")
    print(f"识别结果: {names.get(label, '未知')}")
else:
    print("错误: 无法加载预测图像。")
  • 小结

人脸检测和人脸识别是相辅相成的,这是因为在进行人脸识别前,要先判断当前图像内是否出现了人脸,这个判断过程需要由人脸 检测完成。只有在当前图像内检测到人脸,才能判断出这张人脸属于哪个人,这个判断是由人脸识别器完成的。因此,人脸识别指的是程序先在图像内检测人脸,再识别这张人脸属于哪个人的过程。本章讲 解了3种人脸识别器,读者要熟练掌握这3种人脸识别器的实现方法和实现原理。

以上内容摘自明日科技《Python OpenCV从入门到精通》一书