OpenCV 从入门到精通(day_05)

1. 模板匹配

1.1 什么是模板匹配

模板匹配就是用模板图(通常是一个小图)在目标图像(通常是一个比模板图大的图片)中不断的滑动比较,通过某种比较方法来判断是否匹配成功。

1.2 匹配方法

res=cv2.matchTemplate(image, templ, method)

  • image:原图像,这是一个灰度图像或彩色图像(在这种情况下,匹配将在每个通道上独立进行)。

  • templ:模板图像,也是灰度图像或与原图像相同通道数的彩色图像。

  • method:匹配方法,可以是以下之一:

    • cv2.TM_CCOEFF

    • cv2.TM_CCOEFF_NORMED

    • cv2.TM_CCORR

    • cv2.TM_CCORR_NORMED

    • cv2.TM_SQDIFF

    • cv2.TM_SQDIFF_NORMED

    • 这些方法决定了如何度量模板图像与原图像子窗口之间的相似度。

  • 返回值res

    函数在完成图像模板匹配后返回一个结果矩阵,这个矩阵的大小与原图像相同。矩阵的每个元素表示原图像中相应位置与模板图像匹配的相似度。

    匹配方法不同,返回矩阵的值的含义也会有所区别。以下是几种常用的匹配方法及其返回值含义:

    1. cv2.TM_SQDIFFcv2.TM_SQDIFF_NORMED

      返回值越接近0,表示匹配程度越好。最小值对应的最佳匹配位置。

    2. cv2.TM_CCORRcv2.TM_CCORR_NORMED

      返回值越大,表示匹配程度越好。最大值对应的最佳匹配位置。

    3. cv2.TM_CCOEFFcv2.TM_CCOEFF_NORMED

      返回值越大,表示匹配程度越好。最大值对应的最佳匹配位置。

1.2.1 平方差匹配

cv2.TM_SQDIFF: 以模板图与目标图所对应的像素值使用平方差公式来计算,其结果越小,代表匹配程度越高,计算过程举例如下。

**注意:**模板匹配过程皆不需要边缘填充,直接从目标图像的左上角开始计算。

1.2.2 归一化平方差匹配

**cv2.TM_SQDIFF_NORMED:**与平方差匹配类似,只不过需要将值统一到0到1,计算结果越小,代表匹配程度越高,计算过程举例如下。

1.2.3 相关匹配

**cv2.TM_CCORR:**使用对应像素的乘积进行匹配,乘积的结果越大其匹配程度越高,计算过程举例如下。

1.2.4 归一化相关匹配

**cv2.TM_CCORR_NORMED:**与相关匹配类似,只不过是将其值统一到0到1之间,值越大,代表匹配程度越高,计算过程举例如下。

1.2.5 相关系数匹配

**cv2.TM_CCOEFF:**需要先计算模板与目标图像的均值,然后通过每个像素与均值之间的差的乘积再求和来表示其匹配程度,1表示完美的匹配,-1表示最差的匹配,计算过程举例如下。

1.2.6 归一化相关系数匹配

**cv2.TM_CCOEFF_NORMED:**也是将相关系数匹配的结果统一到0到1之间,值越接近1代表匹配程度越高,计算过程举例如下。

1.3 绘制轮廓

找的目标图像中匹配程度最高的点,我们可以设定一个匹配阈值来筛选出多个匹配程度高的区域。

loc=np.where(array > 0.8) : loc包含array中所有大于0.8的元素索引 的数组;

zip(*loc)

第1节知识示例:

python 复制代码
import cv2 as cv
import numpy as np

# x = list([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
# print(list(zip(*x)))
game = cv.imread('images/game.png')  # 目标图
temp = cv.imread('images/temp.png')  # 模板图
game_gray = cv.cvtColor(game, cv.COLOR_BGR2GRAY)
temp_gray = cv.cvtColor(temp, cv.COLOR_BGR2GRAY)
# print(game_gray.shape)
# print(temp_gray.shape)

# 获取模板图的宽高
shape_t = temp_gray.shape
h, w = shape_t

# 模板匹配cv.matchTemplate(目标图,模板图,匹配方法),返回匹配程度的矩阵
res = cv.matchTemplate(game_gray, temp_gray, cv.TM_CCOEFF_NORMED)
# print(res, "---", res.shape)
# 设定阈值
thresh = 0.8
# 找符合条件的值的坐标
loc = np.where(res >= thresh)  # 返回值中,第一个列表中装的是y值,另一个装的是x值;可以理解为坐标值
# print(list(zip(*loc[::-1])))
# print(type(loc))  # tuple

# 获取点坐标
for pt in zip(*loc[::-1]):
    # print(pt)
    cv.rectangle(game, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 1)

cv.imshow('game', game)
cv.waitKey(0)
cv.destroyAllWindows()

2. 霍夫变换

2.1 理解霍夫变换

霍夫变换常用来提取图像中的直线和圆等几何形状。

2.2 霍夫直线变换

对于一条直线(不垂直于x轴的直线),都可以用来表示,此时,x和y是横纵坐标,k和b是一个固定参数。当我们换一种方式来看待这个式子,我们就可以得到:

此时,以k和b 为横纵坐标,x和y为固定参数,变换如下图所示:

从上图可以看出,在直角坐标系下的一个直线,在变换后的空间中仅仅表示为一点,对于变换后的空间,我们称其为霍夫空间。也就是说,直角坐标系下的一条直线对应了霍夫空间中的一个点。类似的,霍夫空间中的一条直线也对应了直角坐标系中的一个点,如下图所示:

那么对于一个二值化后的图形来说,其中的每一个目标像素点(这里假设目标像素点为白色像素点)都对应了霍夫空间的一条直线,当霍夫空间中有两条直线相交时,就代表了直角坐标系中某两个点所构成的直线。而当霍夫空间中有很多条线相交于一点时,说明直角坐标系中有很多点能构成一条直线,也就意味着这些点共线,因此我们就可以通过检测霍夫空间中有最多直线相交的交点来找到直角坐标系中的直线。

然而对于x=1这种直线来说,y已经不存在了,那么就没办法使用上面的方法进行检测了,为了解决这个问题,我们就将直角坐标系转化为极坐标系,然后通过极坐标系与霍夫空间进行相互转化。

在极坐标系下是一样的,极坐标中的点对于霍夫空间中的线,霍夫空间中的点对应极坐标中的直线。并且此时的霍夫空间不再是以k为横轴、b为纵轴,而是以为θ横轴、ρ(上图中的r)为纵轴。上面的公式中,x、y是直线上某点的横纵坐标(直角坐标系下的横纵坐标),和是极坐标下的坐标,因此我们只要知道某点的x和y的坐标,就可以得到一个关于θ-ρ的表达式,如下图所示:

根据上图,霍夫空间在极坐标系下,一点可以产生一条三角函数曲线,而多条这样的曲线可能会相交于同一点。因此,我们可以通过设定一个阈值,来检测霍夫空间中的三角函数曲线相交的次数。如果一个交点的三角函数曲线相交次数超过阈值,那么这个交点所代表的直线就可能是我们寻找的目标直线。

使用API:lines=cv2.HoughLines(image, rho, theta, threshold)

  • image:输入图像,通常为二值图像,其中白点表示边缘点,黑点为背景。

  • rho:r的精度,以像素为单位,表示霍夫空间中每一步的距离增量, 值越大,考虑越多的线。

  • theta:角度θ的精度,通常以弧度为单位,表示霍夫空间中每一步的角度增量。值越小,考虑越多的线。

  • threshold:累加数阈值,只有累积投票数超过这个阈值的候选直线才会被返回。

返回值:cv2.HoughLines 函数返回一个二维数组,每一行代表一条直线在霍夫空间中的参数 (rho, theta)

示例:

python 复制代码
import cv2 as cv
import numpy as np

img = cv.imread('images/huofu.png')
shape = img.shape
# 边缘检测
dst = cv.Canny(img, 30, 70)  # 返回值边缘检测后的图像
# cv.imshow('canny', dst)
# 使用霍夫变换检查测直线,返回值是[[rho, theta]],角度单位是弧度
lines = cv.HoughLines(dst, 0.8, np.pi / 180, 90)  # 检测到四条直线
# print(lines)
# 绘制直线
for line in lines:
    rho, theta = line[0]  # 获取rho(直线到原点的垂直距离)和theta(直线与 x 轴的夹角)
    sin_theta = np.sin(theta)
    print(rho, sin_theta)
    cos_theta = np.cos(theta)
    x1 = 0
    y1 = int(rho / sin_theta)
    print(x1, y1)
    x2 = shape[1]
    y2 = int((rho - x2 * cos_theta) / sin_theta)
    cv.line(img, (x1, y1), (x2, y2), (0, 0, 255), 1)

cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

2.3 统计概率霍夫直线变换

前面的方法又称为**标准霍夫变换**,它会计算图像中的每一个点,计算量比较大,另外它得到的是整一条线(r和θ),并不知道原图中直线的端点。所以提出了**统计概率霍夫直线变换**(Probabilistic Hough Transform),是一种改进的霍夫变换,它在获取到直线之后,会检测原图中在该直线上的点,并获取到两侧的端点坐标,然后通过两个点的坐标来计算该直线的长度,通过直线长度与最短长度阈值的比较来决定该直线要不要被保留。

使用API:lines=cv2.HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=0, maxLineGap=0)

  • image:输入图像,通常为二值图像,其中白点表示边缘点,黑点为背景。

  • rho:极径分辨率,以像素为单位,表示极坐标系中的距离分辨率。

  • theta:极角分辨率,以弧度为单位,表示极坐标系中角度的分辨率。

  • threshold:阈值,用于过滤掉弱检测结果,只有累计投票数超过这个阈值的直线才会被返回。

  • lines(可选):一个可初始化的输出数组,用于存储检测到的直线参数。

  • minLineLength(可选):最短长度阈值,比这个长度短的线会被排除。

  • maxLineGap(可选):同一直线两点之间的最大距离。当霍夫变换检测到一系列接近直角的线段时,这些线段可能是同一直线的不同部分。maxLineGap参数指定了在考虑这些线段属于同一直线时,它们之间最大可接受的像素间隔。

    • 如果maxLineGap设置得较小,那么只有相邻且间距很小的线段才会被连接起来,这可能导致检测到的直线数量较多,但更准确地反映了图像中的局部直线结构。

    • 如果maxLineGap设置得较大,则线段间的间距可以更大,这样可能会合并更多的局部线段成为更长的直线,但有可能会将原本不属于同一直线的线段误连接起来。

返回值lines:cv2.HoughLinesP 函数返回一个二维数组,每个元素是一个包含4个元素的数组,分别表示每条直线的起始点和结束点在图像中的坐标(x1, y1, x2, y2)。

示例:

python 复制代码
import cv2 as cv
import numpy as np

img = cv.imread('images/huofu.png')
shape = img.shape
# 边缘检测
dst = cv.Canny(img, 30, 70)  # 返回值边缘检测后的图像
# 统计概率霍夫直线变换
lines = cv.HoughLinesP(dst, 0.8, np.pi / 180, 90, minLineLength=50, maxLineGap=10)
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv.line(img, (x1, y1), (x2, y2), (0, 0, 255), 1)

cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

2.4 霍夫圆变换

霍夫圆变换跟直线变换类似,它可以从图像中找出潜在的圆形结构,并返回它们的中心坐标和半径。只不过线是用(r,θ)表示,圆是用(x_center,y_center,r)来表示,从二维变成了三维,数据量变大了很多;所以一般使用霍夫梯度法减少计算量。

使用API:circles=cv2.HoughCircles(image, method, dp, minDist, param1, param2)

  • image:输入图像,通常是灰度图像。

  • method:使用的霍夫变换方法:霍夫梯度法,可以是 cv2.HOUGH_GRADIENT,这是唯一在OpenCV中用于圆检测的方法。

  • dp:累加器分辨率与输入图像分辨率之间的降采样比率,用于加速运算但不影响准确性。设置为1表示霍夫梯度法中累加器图像的分辨率与原图一致

  • minDist:检测到的圆心之间的最小允许距离,以像素为单位。在霍夫变换检测圆的过程中,可能会检测到许多潜在的圆心。minDist 参数就是为了过滤掉过于接近的圆检测结果,避免检测结果过于密集。当你设置一个较小的 minDist 值时,算法会尝试找出尽可能多的圆,即使是彼此靠得很近的圆也可能都被检测出来。相反,当你设置一个较大的 minDist 值时,算法会倾向于只检测那些彼此间存在一定距离的独立的圆。

    例如,如果你设置 minDist 很小,可能在真实图像中存在的一个大圆旁边的一些噪声点会被误判为多个小圆;而如果设置 minDist 较大,则可以排除这种情况,只保留明显分离的圆的检测结果。

  • param1param2:这两个参数是在使用 cv2.HOUGH_GRADIENT 方法时的特定参数,分别为:

    • param1(可选):阈值1,决定边缘强度的阈值。

    • param2:阈值2,控制圆心识别的精确度。较大的该值会使得检测更严格的圆。param2 通常被称为圆心累积概率的阈值。在使用霍夫梯度方法时,param2 设置的是累加器阈值,它决定了哪些候选圆点集合被认为是有效的圆。较高的 param2 值意味着对圆的检测更严格,只有在累加器中积累了足够高的响应值才认为是真实的圆;较低的 param2 值则会降低检测的门槛,可能会检测到更多潜在的圆,但也可能包含更多的误检结果。

      举个例子,如果你将 param2 设置得较高,那么算法只会返回那些边缘强烈符合圆形特征且周围有足够的支持像素的圆;而如果设置得较低,即使边缘特征不是很强烈,只要有一些证据支持就可能将其视为一个圆。

返回值:cv2.HoughCircles 返回一个二维numpy数组,包含了所有满足条件的圆的参数。

示例:

python 复制代码
import cv2 as cv
import numpy as np

img = cv.imread('images/huofu.png')
shape = img.shape
# 边缘检测
dst = cv.Canny(img, 30, 70)  # 返回值边缘检测后的图像
# 霍夫圆变换
circles = cv.HoughCircles(dst, cv.HOUGH_GRADIENT, 1, 20, param2=30)
# print(circles)
# 循环、遍历、绘制圆
for circle in circles:
    x, y, r = circle[:][0].astype(int)
    # print(x, y, r)
    cv.circle(img, (x, y), r, (0, 255, 0), 2)

cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

3. 图像亮度变换

我们知道,图像都是由一个个像素值组成的,图像的亮度变换实际上还是图像像素值的变换。

3.1 亮度变换

在讲解亮度时,需要和对比度一起来进行解释。

对比度调整:图像暗处像素强度变低,图像亮处像素强度变高,从而拉大中间某个区域范围的显示精度。

亮度调整:图像像素强度整体变高或者变低。

上图中:

(a) 把亮度调高,就是图片中的所有像素值加上了一个固定值;

(b) 把亮度调低,就是图片中的所有像素值减去了一个固定值;

(c) 增大像素对比度(白的地方更白,黑的地方更黑);

(d) 减小像素对比度(整幅图都趋于一个颜色);

OpenCV调整图像对比度和亮度时,公式为:。但是不能浅显的讲是控制对比度,是控制亮度的。

对比度:需要通过一起控制(仅调整只能控制像素强度0附近的对比度,而这种做法只会导致像素强度大于0的部分更亮而已,根本看不到对比度提高的效果)。

亮度:通过控制。

3.2 线性变换

使用 cv2.addWeighted() 函数,可以对图像的像素值进行加权平均,进而改变图像的整体亮度。亮度增益可以通过向每个像素值添加一个正值来实现。

cv2.addWeighted(src1, alpha, src2, beta, gamma)

  • src1:第一张输入图像,它将被赋予权重 alpha

  • alpha:第一个输入图像的权重。

  • src2:第二张输入图像,它将被赋予权重 beta

  • beta:第二个输入图像的权重。

  • gamma:一个标量,将被添加到权重求和的结果上,可用于调整总体亮度。

    计算公式为: dst = src1 * alpha + src2 * beta + gamma

示例:

python 复制代码
import cv2 as cv
import numpy as np

img = cv.imread('images/1.jpg')
img2 = np.zeros_like(img)

img_n = cv.addWeighted(img, 1.5, img2, 0, 0)
cv.imshow('img', img)
cv.imshow('img_n', img_n)
cv.waitKey(0)
cv.destroyAllWindows()

3.3 直接像素值修改

如果只需要增加或减少固定的亮度值,可以直接遍历图像像素并对每个像素值进行加减操作。

使用的API:**numpy.clip(a, a_min, a_max)**用于对数组中的元素进行限定,将超出指定范围的元素值截断至指定的最小值和最大值之间

  • a:输入数组。

  • a_min:指定的最小值,数组中所有小于 a_min 的元素将被替换为 a_min

  • a_max:指定的最大值,数组中所有大于 a_max 的元素将被替换为 a_max

示例:

python 复制代码
import cv2 as cv
import numpy as np

# 为滑动条添加窗口
winname = 'Trackbar_win'  # 窗口名
cv.namedWindow(winname)  # 为滑条创建的窗口


def change(x):  # x是像素值的变化量
    # 把值映射到(-255, 255)
    x = x / 255 * (255 - (-255)) - 255
    # print(x)  # -55
    # 读取图像
    img = cv.imread('images/1.jpg')
    img_new = np.uint8(np.clip(img + x, 0, 255))  # 原像素值加上一个像素值,变化后的像素值范围是0-255
    cv.imshow('img', img)
    cv.imshow('img_new', img_new)


maxval = 255  # 滑条最大值
trackval = 100  # 滑条初始值
change(trackval)
# 创建滑条cv.createTrackbar(滑条名, 显示滑条的窗口名, 滑条初始值, 滑条的最大值, 关联函数)
cv.createTrackbar('Trackbar', winname, trackval, maxval, change)
cv.waitKey(0)
cv.destroyAllWindows()
相关推荐
阿坡RPA9 小时前
手搓MCP客户端&服务端:从零到实战极速了解MCP是什么?
人工智能·aigc
用户277844910499310 小时前
借助DeepSeek智能生成测试用例:从提示词到Excel表格的全流程实践
人工智能·python
机器之心10 小时前
刚刚,DeepSeek公布推理时Scaling新论文,R2要来了?
人工智能
算AI12 小时前
人工智能+牙科:临床应用中的几个问题
人工智能·算法
凯子坚持 c13 小时前
基于飞桨框架3.0本地DeepSeek-R1蒸馏版部署实战
人工智能·paddlepaddle
你觉得20513 小时前
哈尔滨工业大学DeepSeek公开课:探索大模型原理、技术与应用从GPT到DeepSeek|附视频与讲义下载方法
大数据·人工智能·python·gpt·学习·机器学习·aigc
8K超高清13 小时前
中国8K摄像机:科技赋能文化传承新图景
大数据·人工智能·科技·物联网·智能硬件
hyshhhh14 小时前
【算法岗面试题】深度学习中如何防止过拟合?
网络·人工智能·深度学习·神经网络·算法·计算机视觉
薛定谔的猫-菜鸟程序员14 小时前
零基础玩转深度神经网络大模型:从Hello World到AI炼金术-详解版(含:Conda 全面使用指南)
人工智能·神经网络·dnn
币之互联万物14 小时前
2025 AI智能数字农业研讨会在苏州启幕,科技助农与数据兴业成焦点
人工智能·科技