本地环境建设网站,背景网站建设公司,微平台网站开发,安徽省高等级公路工程建设指挥部网站文章目录 前言一、轮廓检测1.1 图像轮廓的概念1.2 轮廓检测算法简介1.3 轮廓检测基本步骤1.4 轮廓检测函数说明1.4.1 轮廓发现1.4.2 轮廓面积1.4.3 轮廓周长1.4.4 轮廓外接多边形1.4.5 点到轮廓距离1.4.6 凸包检测 1.5 轮廓检测代码实现 二、轮廓的距2.1 几何距2.2 中心距2.3 H… 文章目录 前言一、轮廓检测1.1 图像轮廓的概念1.2 轮廓检测算法简介1.3 轮廓检测基本步骤1.4 轮廓检测函数说明1.4.1 轮廓发现1.4.2 轮廓面积1.4.3 轮廓周长1.4.4 轮廓外接多边形1.4.5 点到轮廓距离1.4.6 凸包检测 1.5 轮廓检测代码实现 二、轮廓的距2.1 几何距2.2 中心距2.3 Hu距2.4 代码实现 三、点集拟合3.1 最小包围三角形3.2 最小包围圆形 四、二维码检测4.1 qrcode库的使用4.2 二维码检测实战4.2.1 读取图像4.2.2 二值化处理4.2.3 均值滤波处理4.2.4 寻找轮廓4.2.5 确定三个“回”字形的位置4.2.6 确定三个“回”字中心点的顺序内积的原理内积的公式内积在确定三个点顺序中的应用 4.2.7 仿射变换4.2.8 使用qrcode库对二维码进行解码4.2.9 完整代码 总结 前言
在当今数字化时代计算机视觉的崛起使得目标检测成为科技领域中的一项关键技术。本文将带您快速入门OpenCV中的目标检测深入探讨轮廓检测、轮廓的距、点集拟合以及二维码检测等核心概念。
OpenCV作为一种强大的开源计算机视觉库为开发者提供了丰富的工具和算法使得目标检测不再是高门槛的技术难题。在本文中我们将逐步了解目标检测中的关键步骤从轮廓检测到轮廓的距再到点集拟合和二维码检测。
一、轮廓检测
1.1 图像轮廓的概念
图像轮廓是由一系列连续的边界点组成的曲线表示了图像中目标的形状和结构。这些边界点连接在一起形成了目标的外部轮廓。在计算机视觉中理解和提取图像轮廓是进行目标检测和形状分析的基础。
1.2 轮廓检测算法简介
轮廓检测的算法旨在识别图像中的显著变化即目标与背景之间的边界。常用的算法包括Sobel、Canny等边缘检测算法它们通过检测图像中的梯度变化来确定轮廓位置。
1.3 轮廓检测基本步骤
在OpenCV中轮廓检测主要使用findContours函数。该函数接受输入图像并返回轮廓的列表。通过设定合适的阈值可以在图像中找到目标的轮廓。接着可以使用drawContours函数将轮廓绘制在原始图像上使得我们能够直观地观察到目标的形状。
以下是轮廓检测的基本步骤
读取图像并将其转换为灰度图像。使用合适的边缘检测算法如Canny找到图像的边缘。应用阈值将边缘图像转换为二值图像。使用findContours函数找到图像中的轮廓。绘制轮廓以便可视化或进一步的分析。
通过深入学习轮廓检测我们为后续的目标检测过程奠定了坚实的基础。这一章节将帮助读者理解轮廓检测的核心原理以及在OpenCV中的具体实现方法。
1.4 轮廓检测函数说明
在进行轮廓检测时我们不仅仅关注轮廓的发现还要深入了解轮廓的一些重要属性。下面我们将通过Python和OpenCV代码演示如何实现轮廓检测及其相关操作。
1.4.1 轮廓发现
在计算机视觉和图像处理中轮廓是表示图像中对象边界的一种重要方式。OpenCV库提供了 findContours 函数用于在灰度图像中查找对象的轮廓。
下面是一个简单的代码示例演示如何使用OpenCV发现轮廓
# 转换为灰度图
gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 使用Canny边缘检测
edges cv2.Canny(gray, 50, 150)
# 查找轮廓
_, contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 在原图上绘制轮廓
cv2.drawContours(image, contours, -1, (0, 255, 0), 2)findContours 函数基本信息
def findContours(image, mode, method, contoursNone, hierarchyNone, offsetNone)参数解释 image: 输入的二值图像通常为一个8位单通道图像灰度图像非零像素被视为1零像素保持为0。可以使用其他OpenCV函数例如 compare、inRange、threshold、adaptiveThreshold、Canny 等将灰度图像或彩色图像转换为二值图像。如果mode参数等于RETR_CCOMP或RETR_FLOODFILL则输入也可以是一个32位整数标签图像CV_32SC1。 mode: 轮廓检索模式控制轮廓的层次关系。有几种模式可选常见的有 RETR_EXTERNAL只检测最外面的轮廓、RETR_LIST检测所有的轮廓不建立层次关系、RETR_CCOMP检测所有轮廓但只保留两个层次的轮廓信息和 RETR_TREE检测所有轮廓保留完整的层次信息。 method: 轮廓逼近方法控制轮廓的表示精度。有几种方法可选常见的有 CHAIN_APPROX_SIMPLE压缩水平、垂直和对角方向的轮廓只保留其端点、CHAIN_APPROX_TC89_L1 和 CHAIN_APPROX_TC89_KCOS。 contours: 输出参数用于存储检测到的轮廓。每个轮廓以一组点的形式存储例如 std::vectorstd::vectorcv::Point。 hierarchy: 输出参数可选用于存储图像拓扑结构的信息。对于每个轮廓hierarchy 中的一个元素是一个包含四个整数的数组分别表示在同一层次上的下一个轮廓、上一个轮廓、第一个子轮廓和父轮廓的索引。如果某个轮廓在相应的方向上没有下一个、上一个、子轮廓或父轮廓则对应的索引将为负数。 offset: 可选参数是一个偏移量用于将每个轮廓点进行偏移。这在从图像ROI提取轮廓后需要在整个图像上进行分析时很有用。 findContours 函数的主要作用是在给定的二值图像中查找对象的轮廓。它使用Suzuki算法进行轮廓检测并返回检测到的轮廓以及可选的图像拓扑结构信息。轮廓是用一组点表示的这些点描述了对象的边界。这个函数在形状分析、对象检测和识别等领域中非常有用。在检测到轮廓后你可以进一步进行轮廓的绘制、分析、过滤或者在原图像上进行标记等操作。
1.4.2 轮廓面积
轮廓面积在图像处理中具有广泛的应用。通过计算对象的轮廓面积我们可以进行目标识别、大小过滤和形状分析。例如在目标检测中我们可以通过设定一定的面积阈值来排除过小或过大的轮廓从而过滤掉不感兴趣的区域。 在OpenCV中我们可以使用cv2.contourArea(contour)函数来计算轮廓的面积其中contour是轮廓的点集。
下面是一个简单的代码示例演示如何使用OpenCV计算轮廓的面积
# 转换为灰度图
gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 进行阈值处理
_, thresh cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 寻找轮廓
contours, _ cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 计算每个轮廓的面积并输出
for contour in contours:area cv2.contourArea(contour)print(fContour Area: {area} pixels)contourArea函数是OpenCV中用于计算轮廓面积的函数。以下是该函数的参数和功能的简要说明
def contourArea(contour, orientedNone)参数 contour 输入参数表示轮廓的点集通常是一个包含2D点轮廓顶点的向量。oriented 可选参数表示是否计算有向面积。如果设置为True函数将返回一个带有方向的面积值具体取决于轮廓的方向顺时针或逆时针。默认值为False即返回面积的绝对值。 功能 该函数计算轮廓的面积使用的是Green公式。返回的面积值可能与使用drawContours或fillPoly绘制轮廓时得到的非零像素数不同。这是因为Green公式计算的是理论上的面积而绘制轮廓时计算的是像素的数量。注意对于具有自交点的轮廓该函数可能给出错误的结果。 1.4.3 轮廓周长
轮廓周长是形状描述的一个重要特征特别在目标检测和边缘检测中经常被用到。通过计算周长我们可以获取有关轮廓的详细信息例如对象的形状复杂程度。这对于区分不同形状的目标或者进行形状分析非常有帮助。
轮廓周长是指轮廓的闭合曲线的长度。在OpenCV中我们可以使用cv2.arcLength(curve, closed)函数来计算轮廓的周长其中curve是轮廓的点集而closed是一个标志指示轮廓是否闭合。
下面是一个简单的代码示例演示如何使用OpenCV计算轮廓的周长
# 计算每个轮廓的周长并输出
for contour in contours:perimeter cv2.arcLength(contour, True)print(fContour Perimeter: {perimeter})arcLength函数是OpenCV中用于计算轮廓周长的函数。以下是该函数的参数和功能的简要说明
def arcLength(curve, closed)参数 curve 输入参数表示轮廓的点集通常是一个包含2D点轮廓顶点的向量。closed 标志参数指示轮廓是否闭合。如果轮廓是封闭的则为True否则为False。 功能 该函数计算轮廓的周长或者曲线的长度。如果closed参数为True函数将计算封闭轮廓的周长如果为False则计算曲线的长度。 1.4.4 轮廓外接多边形
轮廓外接多边形提供了一种简单但有效的方式来描述和表示目标的形状。这种方法对于快速计算目标的边界框以及后续的目标跟踪和分析非常有用。通过绘制外接矩形我们可以更直观地了解目标的位置和大小。
轮廓外接多边形是指能够完全包围轮廓的最小矩形通常是一个矩形框。在OpenCV中我们可以使用cv2.boundingRect(points)函数来计算轮廓的外接矩形其中points是轮廓的点集。
下面是一个简单的代码示例演示如何使用OpenCV计算轮廓的外接矩形并在图像上绘制矩形
# 外接矩形
for contour in contours:x, y, w, h cv2.boundingRect(contour)cv2.rectangle(image, (x, y), (xw, yh), (0, 255, 0), 2)boundingRect函数是OpenCV中用于计算轮廓外接多边形的函数。以下是该函数的参数和功能的简要说明
def boundingRect(points)参数 points 输入参数表示轮廓的点集通常是一个包含2D点轮廓顶点的向量。 功能 该函数计算并返回指定点集的最小外接矩形即能够完全包围点集的矩形框。 1.4.5 点到轮廓距离
点到轮廓的距离是进行形状分析和目标识别的关键步骤之一。通过计算点到轮廓的距离我们可以判断一个点是否属于某个目标以及该点相对于目标的具体位置。这对于许多应用场景如手势识别、物体定位等都是至关重要的。
点到轮廓的距离是指一个给定点到轮廓的最短距离这可以帮助我们确定点相对于轮廓的位置关系是在轮廓内部、外部还是在轮廓上。
下面是一个简单的代码示例演示如何使用OpenCV的pointPolygonTest函数计算点到轮廓的最短距离
# 计算点到轮廓的最短距离
point (100, 100)
for contour in contours:distance cv2.pointPolygonTest(contour, point, True)print(fDistance from point to contour: {distance})pointPolygonTest函数是OpenCV中用于计算点到轮廓距离的函数。以下是该函数的参数和功能的简要说明
def pointPolygonTest(contour, pt, measureDist)参数 contour 输入参数表示轮廓的点集通常是一个包含2D点轮廓顶点的向量可以存储在std::vector或Mat中。pt 输入参数表示测试点的坐标。measureDist 输入参数表示是否测量点到轮廓的距离。如果为True函数返回点到轮廓的有向距离如果为False函数仅检查点相对于轮廓的位置关系返回1、-1或0。 功能 该函数用于执行点在轮廓内的测试。返回正值表示点在轮廓内部负值表示点在轮廓外部零值表示点在轮廓上或与轮廓上的顶点重合。当measureDist为True时返回值为点到最近轮廓边缘的有向距离。 1.4.6 凸包检测
凸包检测在图像处理和计算机视觉中广泛应用特别是在目标检测和形状分析中。通过计算凸包我们可以更好地理解目标的整体形状从而帮助进行目标识别和分析。
凸包检测是寻找一个点集的最小凸多边形的过程。在OpenCV中我们可以使用cv2.convexHull(points)函数来计算给定点集的凸包。
下面是一个简单的代码示例演示如何使用OpenCV的convexHull函数进行凸包检测并在图像上绘制凸包
# 凸包检测
for contour in contours:hull cv2.convexHull(contour)cv2.drawContours(image, [hull], 0, (0, 0, 255), 2)convexHull函数是OpenCV中用于凸包检测的函数。以下是该函数的参数和功能的简要说明
def convexHull(points, hullNone, clockwiseNone, returnPointsNone)参数 points 输入参数表示点集的2D坐标通常是一个包含2D点的向量可以存储在std::vector或Mat中。hull 输出参数表示凸包的点集或索引。可以是一个整数向量表示凸包点在原始点集中的索引也可以是一个包含凸包点的向量表示凸包的实际坐标点。clockwise 可选参数表示凸包的方向。如果为True表示凸包的方向是顺时针的如果为False表示凸包的方向是逆时针的。returnPoints 可选参数操作标志。如果为True函数返回凸包的实际坐标点如果为False函数返回凸包点在原始点集中的索引。 功能 该函数用于找到给定点集的凸包。函数返回凸包的点集或索引取决于returnPoints参数的设置。可以选择指定凸包的方向是顺时针还是逆时针。 1.5 轮廓检测代码实现
首先我们创建一个空白的图像然后定义了两个函数一个用于绘制随机椭圆另一个用于检查新椭圆是否与已存在的椭圆重叠。
# 创建一个空白的图像
width, height 800, 800
image np.zeros((height, width, 3), dtypenp.uint8)# 定义绘制椭圆的函数
def draw_random_ellipse(img):# 生成随机椭圆的参数# ...# 定义检查椭圆是否重叠的函数
def check_overlap(new_ellipse, existing_ellipses):# ...接下来通过调用draw_random_ellipse函数生成一组不重叠的椭圆并将它们绘制在图像上。
# 生成不重叠的椭圆
num_ellipses 15
ellipses []
for _ in range(num_ellipses):while True:new_ellipse ((random.randint(0, width - 1), random.randint(0, height - 1)),(random.randint(10, 100), random.randint(10, 100)),random.randint(0, 360),(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))if not check_overlap(new_ellipse, ellipses):ellipses.append(new_ellipse)break# 绘制不重叠的椭圆
for ellipse in ellipses:cv2.ellipse(image, ellipse[0], ellipse[1], ellipse[2], 0, 360, ellipse[3], -1)然后我们定义了两个参数字典用于设置绘制文本的共享参数。
# 共享的参数
shared_params {fontFace: cv2.FONT_HERSHEY_SIMPLEX,fontScale: 0.5,thickness: 2,color: (0, 0, 0),lineType: cv2.LINE_AA,
}
shared_params2 {fontFace: cv2.FONT_HERSHEY_SIMPLEX,fontScale: 0.5,thickness: 1,color: (0, 255, 0),lineType: cv2.LINE_AA,
}接下来将图像转换为灰度图并使用Canny边缘检测。
# 转换为灰度图
gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 使用Canny边缘检测
edges cv2.Canny(gray, 10, 150)通过调用cv2.findContours函数找到图像中的轮廓。
# 查找轮廓
_, contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)然后对每个轮廓进行一系列操作包括计算轮廓面积、周长、中心位置绘制外接矩形和最短距离的线段。
# 在原图上绘制轮廓并输出面积和周长信息
for contour in contours:# 计算轮廓面积和周长area int(cv2.contourArea(contour))perimeter int(cv2.arcLength(contour, True))moments cv2.moments(contour)# ...最后对每个轮廓进行凸包检测并在图像上绘制凸包。 # 凸包检测hull cv2.convexHull(contour)cv2.drawContours(image, [hull], 0, (0, 0, 255), 1)最终显示包含轮廓信息的图像。
下面是完整的代码内容
import cv2
import numpy as np
import random# 创建一个空白的图像
width, height 800, 800
image np.zeros((height, width, 3), dtypenp.uint8)# 定义绘制椭圆的函数
def draw_random_ellipse(img):# 生成随机椭圆的参数center (random.randint(0, width - 1), random.randint(0, height - 1))axes (random.randint(10, 100), random.randint(10, 100))angle random.randint(0, 360)color (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))# 绘制椭圆cv2.ellipse(img, center, axes, angle, 0, 360, color, -1)# 定义检查椭圆是否重叠的函数
def check_overlap(new_ellipse, existing_ellipses):for existing_ellipse in existing_ellipses:# 获取椭圆的掩码new_mask np.zeros_like(image, dtypenp.uint8)existing_mask np.zeros_like(image, dtypenp.uint8)cv2.ellipse(new_mask, new_ellipse[0], new_ellipse[1], new_ellipse[2], 0, 360, (255, 255, 255), -1)cv2.ellipse(existing_mask, existing_ellipse[0], existing_ellipse[1], existing_ellipse[2], 0, 360,(255, 255, 255), -1)# 检查是否有重叠的部分overlap cv2.bitwise_and(new_mask, existing_mask)if np.sum(overlap) 0:return Truereturn False# 生成不重叠的椭圆
num_ellipses 15
ellipses []
for _ in range(num_ellipses):while True:new_ellipse ((random.randint(0, width - 1), random.randint(0, height - 1)),(random.randint(10, 100), random.randint(10, 100)),random.randint(0, 360),(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))if not check_overlap(new_ellipse, ellipses):ellipses.append(new_ellipse)break# 绘制不重叠的椭圆
for ellipse in ellipses:cv2.ellipse(image, ellipse[0], ellipse[1], ellipse[2], 0, 360, ellipse[3], -1)# 共享的参数
shared_params {fontFace: cv2.FONT_HERSHEY_SIMPLEX,fontScale: 0.5,thickness: 2,color: (0, 0, 0),lineType: cv2.LINE_AA,
}
shared_params2 {fontFace: cv2.FONT_HERSHEY_SIMPLEX,fontScale: 0.5,thickness: 1,color: (0, 255, 0),lineType: cv2.LINE_AA,
}# 转换为灰度图
gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 使用Canny边缘检测
edges cv2.Canny(gray, 10, 150)# 查找轮廓
_, contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 在原图上绘制轮廓
cv2.drawContours(image, contours, -1, (0, 255, 0), 2)# 在原图上绘制轮廓并输出面积和周长信息
for contour in contours:# 计算轮廓面积area int(cv2.contourArea(contour))# 计算轮廓周长perimeter int(cv2.arcLength(contour, True))moments cv2.moments(contour)# 检查 moments[m00](轮廓的面积) 是否为零if moments[m00] ! 0:# 计算轮廓的中心位置cx int(moments[m10] / moments[m00])cy int(moments[m01] / moments[m00])# 在中心位置绘制一个点cv2.circle(image, (cx, cy), 5, (255, 255, 255), -1)cv2.circle(image, (cx, cy), 3, (0, 0, 255), -1)# 外接矩形x, y, w, h cv2.boundingRect(contour)cv2.rectangle(image, (x, y), (x w, y h), (255, 255, 0), 1)# 提取每个点的坐标将contour转换为二维数组contour_2d contour[:, 0, :]# 计算每个点到目标点的距离的平方和distances np.sum((contour_2d - np.array([cx, cy])) ** 2, axis1)# 找到最小值的索引closest_point_index np.argmin(distances)closest_point contour[closest_point_index][0]# 在图中画出这个最短距离的线段cv2.line(image, (cx, cy), (int(closest_point[0]), int(closest_point[1])), (0, 0, 255), 3)cv2.line(image, (cx, cy), (int(closest_point[0]), int(closest_point[1])), (255, 0, 0), 1)# 输出计算结果cv2.putText(image, fS {area}, (max(int(cx - 20), 0), cy 20), **shared_params)cv2.putText(image, fC {perimeter}, (max(int(cx - 20), 0), cy 35), **shared_params)cv2.putText(image, fS {area}, (max(int(cx - 20), 0), cy 20), **shared_params2)cv2.putText(image, fC {perimeter}, (max(int(cx - 20), 0), cy 35), **shared_params2)# 计算每个中心点到轮廓的最短距离dist abs(int(cv2.pointPolygonTest(contour, (cx, cy), True)))cv2.putText(image, fD_min {dist}, (max(int(cx - 20), 0), cy 50), **shared_params)cv2.putText(image, fD_min {dist}, (max(int(cx - 20), 0), cy 50), **shared_params2)# 凸包检测hull cv2.convexHull(contour)cv2.drawContours(image, [hull], 0, (0, 0, 255), 1)# 显示结果
cv2.imshow(Contours, image)
cv2.waitKey(0)
cv2.destroyAllWindows() 二、轮廓的距
在目标检测中轮廓不仅仅是一个形状的抽象表示还包含了有关形状的重要信息其中之一就是轮廓的距。距是用于描述轮廓形状和结构的一种度量它可以分为几何距、中心距和Hu距。
2.1 几何距
几何距是通过计算轮廓到某个点的距离的方式来度量轮廓的形状。常见的几何距包括轮廓的面积、周长等它们提供了关于轮廓整体尺寸的信息。 以下是几何距的一些常见计算公式
轮廓面积Area 轮廓包围的区域的面积。 Area ∫ ∫ D 1 d x d y \text{Area} \int\int_D 1 \,dx\,dy Area∫∫D1dxdy
其中 D D D 是轮廓包围的区域。
轮廓周长Perimeter 轮廓的周长即轮廓上所有点到一个参考点的距离之和。 Perimeter ∮ C d s \text{Perimeter} \oint_C ds Perimeter∮Cds
其中 C C C 是轮廓的曲线 d s ds ds 是轮廓上一点到下一点的弧长元素。
这些几何距的计算提供了有关轮廓形状和结构的基本信息是轮廓分析中的重要工具。在使用OpenCV进行轮廓分析时可以利用cv2.contourArea()和cv2.arcLength()函数分别计算轮廓的面积和周长。
2.2 中心距
中心距是通过计算轮廓中心到轮廓上所有点的距离来度量轮廓形状的一种距离。中心距包括一阶中心距、二阶中心距等它们对于形状的平移和旋转具有不变性。以下是中心距的一些常见计算公式
一阶中心距m_10 和 m_01 描述形状的平移。 m 10 ∫ ∫ D x d x d y m_{10} \int\int_D x \,dx\,dy m10∫∫Dxdxdy m 01 ∫ ∫ D y d x d y m_{01} \int\int_D y \,dx\,dy m01∫∫Dydxdy
其中 D D D 是轮廓包围的区域 x x x 和 y y y 是图像坐标。
二阶中心距m_20、m_02 和 m_11 描述形状的旋转。 m 20 ∫ ∫ D ( x − x ˉ ) 2 d x d y m_{20} \int\int_D (x - \bar{x})^2 \,dx\,dy m20∫∫D(x−xˉ)2dxdy m 02 ∫ ∫ D ( y − y ˉ ) 2 d x d y m_{02} \int\int_D (y - \bar{y})^2 \,dx\,dy m02∫∫D(y−yˉ)2dxdy m 11 ∫ ∫ D ( x − x ˉ ) ( y − y ˉ ) d x d y m_{11} \int\int_D (x - \bar{x})(y - \bar{y}) \,dx\,dy m11∫∫D(x−xˉ)(y−yˉ)dxdy
其中 x ˉ \bar{x} xˉ 和 y ˉ \bar{y} yˉ 是轮廓的质心坐标。
在OpenCV中可以使用cv2.moments()函数计算轮廓的矩进而得到一阶中心距和二阶中心距。给定一个轮廓该函数返回一个字典其中包含轮廓的一些矩的信息如质心、面积等。 这个函数的语法如下
moments cv2.moments(contour)其中contour是输入的轮廓而moments是包含轮廓矩信息的字典。
返回的字典包含以下键值对 m00: 轮廓的面积。m10, m01: 分别是x和y方向上的一阶矩。m20, m02, m11: 分别是x和y方向上的二阶矩和xy方向上的一阶矩。m30, m03, m21, m12: 分别是x和y方向上的三阶矩xy方向上的二阶矩和一阶矩。mu20, mu02, mu11: 中心矩是二阶矩关于质心的矩。mu30, mu03, mu21, mu12: 中心矩是三阶矩关于质心的矩。nu20, nu02, nu11: 归一化中心矩是中心矩除以面积的二阶矩。nu30, nu03, nu21, nu12: 归一化中心矩是中心矩除以面积的三阶矩。 2.3 Hu距
Hu距是一种通过中心距来构建的轮廓描述符具有平移、旋转和缩放不变性。Hu距是一组七个独立的距离通过对中心距的组合计算而得。以下是Hu距的计算公式 Hu距 1-7 Hu1 η 20 η 02 Hu2 ( η 20 − η 02 ) 2 4 η 11 2 Hu3 ( η 30 − 3 η 12 ) 2 ( 3 η 21 − η 03 ) 2 Hu4 ( η 30 η 12 ) 2 ( η 21 η 03 ) 2 Hu5 ( η 30 − 3 η 12 ) ( η 30 η 12 ) [ ( η 30 η 12 ) 2 − 3 ( η 21 η 03 ) 2 ] Hu6 ( η 20 − η 02 ) [ ( η 30 η 12 ) 2 − ( η 21 η 03 ) 2 ] 4 η 11 ( η 30 η 12 ) ( η 21 η 03 ) Hu7 ( 3 η 21 − η 03 ) ( η 30 η 12 ) [ ( η 30 η 12 ) 2 − 3 ( η 21 η 03 ) 2 ] − ( η 30 − 3 η 12 ) ( η 21 η 03 ) [ 3 ( η 30 η 12 ) 2 − ( η 21 η 03 ) 2 ] \begin{split} \text{Hu1} \eta_{20} \eta_{02} \\ \text{Hu2} (\eta_{20} - \eta_{02})^2 4\eta_{11}^2 \\ \text{Hu3} (\eta_{30} - 3\eta_{12})^2 (3\eta_{21} - \eta_{03})^2 \\ \text{Hu4} (\eta_{30} \eta_{12})^2 (\eta_{21} \eta_{03})^2 \\ \text{Hu5} (\eta_{30} - 3\eta_{12})(\eta_{30} \eta_{12})[(\eta_{30} \eta_{12})^2 - 3(\eta_{21} \eta_{03})^2] \\ \text{Hu6} (\eta_{20} - \eta_{02})[(\eta_{30} \eta_{12})^2 - (\eta_{21} \eta_{03})^2] 4\eta_{11}(\eta_{30} \eta_{12})(\eta_{21} \eta_{03}) \\ \text{Hu7} (3\eta_{21} - \eta_{03})(\eta_{30} \eta_{12})[(\eta_{30} \eta_{12})^2 - 3(\eta_{21} \eta_{03})^2] - (\eta_{30} - 3\eta_{12})(\eta_{21} \eta_{03})[3(\eta_{30} \eta_{12})^2 - (\eta_{21} \eta_{03})^2] \\ \end{split} Hu1η20η02Hu2(η20−η02)24η112Hu3(η30−3η12)2(3η21−η03)2Hu4(η30η12)2(η21η03)2Hu5(η30−3η12)(η30η12)[(η30η12)2−3(η21η03)2]Hu6(η20−η02)[(η30η12)2−(η21η03)2]4η11(η30η12)(η21η03)Hu7(3η21−η03)(η30η12)[(η30η12)2−3(η21η03)2]−(η30−3η12)(η21η03)[3(η30η12)2−(η21η03)2] 其中 η p q \eta_{pq} ηpq 是归一化中心距计算公式如下 η p q μ p q μ 00 1 p q 2 \eta_{pq} \frac{\mu_{pq}}{\mu_{00}^{1\frac{pq}{2}}} ηpqμ0012pqμpq
其中 μ p q \mu_{pq} μpq 是轮廓的中心距。
这七个Hu距对于图像的平移、旋转和缩放具有不变性是图像识别和匹配中常用的特征描述符。在OpenCV中可以使用cv2.HuMoments()函数计算Hu距。
2.4 代码实现
下面是使用OpenCV实现轮廓的距的代码示例
import cv2
import numpy as np
import random# 创建一个空白的图像
width, height 800, 800
image np.zeros((height, width, 3), dtypenp.uint8)# 定义绘制椭圆的函数
def draw_random_ellipse(img):# 生成随机椭圆的参数center (random.randint(0, width - 1), random.randint(0, height - 1))axes (random.randint(10, 100), random.randint(10, 100))angle random.randint(0, 360)color (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))# 绘制椭圆cv2.ellipse(img, center, axes, angle, 0, 360, color, -1)# 定义检查椭圆是否重叠的函数
def check_overlap(new_ellipse, existing_ellipses):for existing_ellipse in existing_ellipses:# 获取椭圆的掩码new_mask np.zeros_like(image, dtypenp.uint8)existing_mask np.zeros_like(image, dtypenp.uint8)cv2.ellipse(new_mask, new_ellipse[0], new_ellipse[1], new_ellipse[2], 0, 360, (255, 255, 255), -1)cv2.ellipse(existing_mask, existing_ellipse[0], existing_ellipse[1], existing_ellipse[2], 0, 360,(255, 255, 255), -1)# 检查是否有重叠的部分overlap cv2.bitwise_and(new_mask, existing_mask)if np.sum(overlap) 0:return Truereturn False# 生成不重叠的椭圆
num_ellipses 5
ellipses []
for _ in range(num_ellipses):while True:new_ellipse ((random.randint(0, width - 1), random.randint(0, height - 1)),(random.randint(10, 100), random.randint(10, 100)),random.randint(0, 360),(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))if not check_overlap(new_ellipse, ellipses):ellipses.append(new_ellipse)break# 绘制不重叠的椭圆
for ellipse in ellipses:cv2.ellipse(image, ellipse[0], ellipse[1], ellipse[2], 0, 360, ellipse[3], -1)# 转换为灰度图
gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 使用Canny边缘检测
edges cv2.Canny(gray, 50, 150)# 查找轮廓
_, contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 假设contours是你的轮廓列表
for contour in contours:# 计算轮廓的面积和周长area cv2.contourArea(contour)perimeter cv2.arcLength(contour, True)print(f轮廓面积: {area}, 周长: {perimeter})# 计算轮廓的中心位置moments cv2.moments(contour)cx int(moments[m10] / moments[m00])cy int(moments[m01] / moments[m00])print(f中心位置: ({cx}, {cy}))# 计算二阶中心距mu cv2.moments(contour)nu20 mu[nu20]nu02 mu[nu02]nu11 mu[nu11]print(f二阶中心距: nu20{nu20}, nu02{nu02}, nu11{nu11})# 计算Hu距hu_moments cv2.HuMoments(mu)print(fHu距: \n {hu_moments})这段代码演示了如何计算轮廓的面积、周长以及中心位置并且计算了二阶中心距和Hu距。通过这些距离的计算我们可以更全面地了解轮廓的形状特征为目标检测提供更多信息。
三、点集拟合
在目标检测中点集拟合是一项重要的任务它通过数学模型来逼近一组离散的点从而更好地理解和描述目标的形状。在OpenCV中我们通常使用最小包围三角形、最小包围圆形等方法进行点集拟合。
3.1 最小包围三角形
最小包围三角形是将一组点拟合为一个最小的包围三角形该三角形能够包含所有的离散点。
import cv2
import numpy as np
import random# 创建一个空白的图像
width, height 800, 800
image np.zeros((height, width, 3), dtypenp.uint8)# 定义绘制椭圆的函数
def draw_random_ellipse(img):# 生成随机椭圆的参数center (random.randint(0, width - 1), random.randint(0, height - 1))axes (random.randint(10, 100), random.randint(10, 100))angle random.randint(0, 360)color (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))# 绘制椭圆cv2.ellipse(img, center, axes, angle, 0, 360, color, -1)# 定义检查椭圆是否重叠的函数
def check_overlap(new_ellipse, existing_ellipses):for existing_ellipse in existing_ellipses:# 获取椭圆的掩码new_mask np.zeros_like(image, dtypenp.uint8)existing_mask np.zeros_like(image, dtypenp.uint8)cv2.ellipse(new_mask, new_ellipse[0], new_ellipse[1], new_ellipse[2], 0, 360, (255, 255, 255), -1)cv2.ellipse(existing_mask, existing_ellipse[0], existing_ellipse[1], existing_ellipse[2], 0, 360,(255, 255, 255), -1)# 检查是否有重叠的部分overlap cv2.bitwise_and(new_mask, existing_mask)if np.sum(overlap) 0:return Truereturn False# 生成不重叠的椭圆
num_ellipses 15
ellipses []
for _ in range(num_ellipses):while True:new_ellipse ((random.randint(0, width - 1), random.randint(0, height - 1)),(random.randint(10, 100), random.randint(10, 100)),random.randint(0, 360),(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))if not check_overlap(new_ellipse, ellipses):ellipses.append(new_ellipse)break# 绘制不重叠的椭圆
for ellipse in ellipses:cv2.ellipse(image, ellipse[0], ellipse[1], ellipse[2], 0, 360, ellipse[3], -1)# 转换为灰度图
gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 使用Canny边缘检测
edges cv2.Canny(gray, 10, 150)# 查找轮廓
_, contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 在原图上绘制轮廓并输出面积和周长信息
for contour in contours:# 寻找最小包围三角形retval, triangle cv2.minEnclosingTriangle(contour)# 绘制最小包围三角形cv2.polylines(image, [np.int32(triangle)], isClosedTrue, color(0, 255, 0), thickness2)# 计算最小包围三角形的中心坐标center np.mean(triangle, axis0, dtypenp.int32)# 将 retval 的值显示在三角形的中心font cv2.FONT_HERSHEY_SIMPLEXcv2.putText(image, fArea: {int(retval)}, tuple(center[0]), font, 0.8, (0, 0, 255), 3, cv2.LINE_AA)cv2.putText(image, fArea: {int(retval)}, tuple(center[0]), font, 0.8, (0, 255, 0), 2, cv2.LINE_AA)# 显示结果
cv2.imshow(Contours, image)
cv2.waitKey(0)
cv2.destroyAllWindows()3.2 最小包围圆形
最小包围圆形是将一组点拟合为一个最小的包围圆形该圆形能够包含所有的离散点。
import cv2
import numpy as np
import random# 创建一个空白的图像
width, height 800, 800
image np.zeros((height, width, 3), dtypenp.uint8)# 定义绘制椭圆的函数
def draw_random_ellipse(img):# 生成随机椭圆的参数center (random.randint(0, width - 1), random.randint(0, height - 1))axes (random.randint(10, 100), random.randint(10, 100))angle random.randint(0, 360)color (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))# 绘制椭圆cv2.ellipse(img, center, axes, angle, 0, 360, color, -1)# 定义检查椭圆是否重叠的函数
def check_overlap(new_ellipse, existing_ellipses):for existing_ellipse in existing_ellipses:# 获取椭圆的掩码new_mask np.zeros_like(image, dtypenp.uint8)existing_mask np.zeros_like(image, dtypenp.uint8)cv2.ellipse(new_mask, new_ellipse[0], new_ellipse[1], new_ellipse[2], 0, 360, (255, 255, 255), -1)cv2.ellipse(existing_mask, existing_ellipse[0], existing_ellipse[1], existing_ellipse[2], 0, 360,(255, 255, 255), -1)# 检查是否有重叠的部分overlap cv2.bitwise_and(new_mask, existing_mask)if np.sum(overlap) 0:return Truereturn False# 生成不重叠的椭圆
num_ellipses 15
ellipses []
for _ in range(num_ellipses):while True:new_ellipse ((random.randint(0, width - 1), random.randint(0, height - 1)),(random.randint(10, 100), random.randint(10, 100)),random.randint(0, 360),(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))if not check_overlap(new_ellipse, ellipses):ellipses.append(new_ellipse)break# 绘制不重叠的椭圆
for ellipse in ellipses:cv2.ellipse(image, ellipse[0], ellipse[1], ellipse[2], 0, 360, ellipse[3], -1)# 转换为灰度图
gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 使用Canny边缘检测
edges cv2.Canny(gray, 10, 150)# 查找轮廓
_, contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 在原图上绘制轮廓并输出面积和周长信息
for contour in contours:# 寻找最小包围三角形(x, y), radius cv2.minEnclosingCircle(contour)# 绘制一个圆cv2.circle(image, (int(x), int(y)), int(radius), (255, 255, 255), 2)# 显示结果
cv2.imshow(Minimum Enclosing Circle, image)
cv2.waitKey(0)
cv2.destroyAllWindows()四、二维码检测
4.1 qrcode库的使用
我们使用qrcode库生成并解码二维码 首先检查两个库
pip install --upgrade opencv-python3.4.4.19
pip install qrcode[pil]qrcode库的使用方式这里不做深入探讨但是需要注意的是生成的二维码是PILPython Imaging Library图像对象。在后续的代码中我们需要将PIL图像转换为OpenCV图像。
其次使用cv2.QRCodeDetector()进行解码这是OpenCV的二维码解码器。在解码过程中获取了二维码的信息、顶点坐标和直线形式的二维码。确保理解这些输出的含义以便根据需要进行处理。
codeinfo, points, straight_qrcode qr_detector.detectAndDecode(img_read)import cv2
import qrcode
import numpy as np# 获取OpenCV版本信息
cv_version cv2.__version__# 将版本字符串转换为数字列表
version_numbers [int(num) for num in cv_version.split(.)]# 检查版本是否小于3.4.4
if version_numbers [3, 4, 4]:print(OpenCV版本过低请升级至3.4.4或更高版本。 pip install --upgrade opencv-python3.4.4.19)# 随机生成一个二维码
data Hello, QR Code!
qr qrcode.QRCode(version1,error_correctionqrcode.constants.ERROR_CORRECT_L,box_size10,border4,
)
qr.add_data(data)
qr.make(fitTrue)
img qr.make_image(fill_color( 0 , 0 , 0 ), back_color( 255 , 255 , 255 ))# 将PIL图像转换为OpenCV格式
img_np np.array(img)
img_read cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)# 使用cv2.QRCodeDetector()进行解码
qr_detector cv2.QRCodeDetector()
# 解码
codeinfo, points, straight_qrcode qr_detector.detectAndDecode(img_read)# 描绘轮廓
cv2.drawContours(img_read, [np.int32(points)], 0, (0, 0, 255), 2)
print(QR Code: %s % codeinfo)
# 添加文字
cv2.putText(img_read, QR Code: str(codeinfo), (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
cv2.imshow(QR Code, img_read)cv2.waitKey(0)
cv2.destroyAllWindows()4.2 二维码检测实战
接下来我们尝试更加艰巨的任务。
4.2.1 读取图像
# 读取图像
img_read cv2.imread(QRPhoto.jpg) # 二值化
img_read cv2.resize(img_read, (800, 800))
img cv2.resize(img_read.copy(), (800, 800))
img_gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)我们首先读取一张图像然后进行二值化处理将图像的大小调整为800x800像素并将其转换为灰度图像。
4.2.2 二值化处理
# 二值化处理
_, img_bin cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
img_bin 255 - img_bin # 反转二值图像接着我们对灰度图像进行二值化处理并对二值图像进行反转。这一步是为了准备后续的轮廓处理操作。
4.2.3 均值滤波处理
# 应用均值滤波
kernel_size (3, 3)
blurred_image cv2.blur(img_bin, kernel_size)然后我们使用均值滤波对二值图像进行模糊处理以平滑图像有助于后续的轮廓检测。
4.2.4 寻找轮廓
# 寻找轮廓
_, contours, _ cv2.findContours(blurred_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)接下来利用OpenCV的findContours函数寻找图像中的轮廓。
# 在原图上绘制所有轮廓
img_contours img.copy()
cv2.drawContours(img, contours, -1, (0, 255, 0), 2)
cv2.drawContours(img_contours, contours, -1, (0, 255, 0), 2)然后我们在原图上绘制所有找到的轮廓用绿色表示。
4.2.5 确定三个“回”字形的位置
根据上面绿色框线的特点一个“回”字形被三个绿色方框所确定。因此我们需要用三个for循环嵌套找到被三个框包围的“回”字形中心点。
# 存储需要的轮廓和中心点
selected_contours []
selected_center []接着我们准备存储我们需要的轮廓和轮廓的中心点。我们遍历每一个找到的轮廓并计算其包围盒。
# 遍历每个轮廓
for contour_a in contours:# 计算当前轮廓 a 的包围盒x_a, y_a, w_a, h_a cv2.boundingRect(contour_a)# 遍历其他轮廓看是否有包含当前轮廓 a 的情况is_contained_a Falsefor contour_b in contours:if contour_b is not contour_a:# 计算轮廓 b 的包围盒x_b, y_b, w_b, h_b cv2.boundingRect(contour_b)# 判断当前轮廓 a 是否完全被轮廓 b 包含if x_a x_b and y_a y_b and x_a w_a x_b w_b and y_a h_a y_b h_b:is_contained_b False# 遍历其他轮廓看是否有包含当前轮廓 b 的情况for contour_c in contours:if contour_c is not contour_b and contour_c is not contour_a:# 计算轮廓 c 的包围盒x_c, y_c, w_c, h_c cv2.boundingRect(contour_c)# 判断当前轮廓 b 是否完全被轮廓 c 包含if x_b x_c and y_b y_c and x_b w_b x_c w_c and y_b h_b y_c h_c:is_contained_a Truebreak上面这个部分我们检查当前轮廓是否被其他轮廓包含如果是将其标记为需要的轮廓。 # 如果当前轮廓 a 完全被包含则将其添加到需要的轮廓列表中if is_contained_a:selected_contours.append(contour_a)# 计算中心点坐标center_x x_a w_a // 2center_y y_a h_a // 2# 在原图上绘制中心点cv2.circle(img, (center_x, center_y), 5, (255, 0, 0), -1)selected_center.append([center_x, center_y])如果当前轮廓被包含我们将其添加到需要的轮廓列表中并计算其中心点坐标然后在原图上绘制中心点。
# 在原图上绘制需要的轮廓
cv2.drawContours(img, selected_contours, -1, (0, 0, 255), 2)最后在原图上绘制我们需要的轮廓用红色表示。
4.2.6 确定三个“回”字中心点的顺序
# 将三个点连接成三角形
pt1 selected_center[0]
pt2 selected_center[1]
pt3 selected_center[2]# 计算三角形的边
side_a np.linalg.norm(np.array(pt2) - np.array(pt3))
side_b np.linalg.norm(np.array(pt1) - np.array(pt3))
side_c np.linalg.norm(np.array(pt1) - np.array(pt2))接下来我们将中心点连接起来形成一个三角形。然后计算三角形的三条边的长度。
# 找到最大边对应的点
if side_a side_b and side_a side_c:max_side_pt pt1angle_point pt1other_points [pt2, pt3]
elif side_b side_a and side_b side_c:max_side_pt pt2angle_point pt2other_points [pt1, pt3]
else:max_side_pt pt3angle_point pt3other_points [pt1, pt2]接着我们找到三角形中最长的边对应的点并确定三角形的顶点和其他两个点。
# 计算三个点的向量
vector1 np.array(other_points[0]) - np.array(angle_point)
vector2 np.array(other_points[1]) - np.array(angle_point)然后我们计算三个点的向量。
# 计算内积
inner_product np.dot(vector1, vector2)接下来我们计算这两个向量的内积。
# 根据内积的正负来确定点的顺序
if inner_product 0:src_points np.float32([angle_point, other_points[0], other_points[1]])
else:src_points np.float32([angle_point, other_points[1], other_points[0]])然后根据内积的正负确定三个点的顺序。
内积的原理
内积也称为点积或数量积是线性代数中的一种运算用于衡量两个向量之间的相似度。在上述代码中通过计算两个向量的内积我们可以确定三个点的顺序。下面我们来深入了解内积的原理和公式以及为什么内积能够帮助确定三个点的顺序。
内积的原理基于余弦定理。余弦定理表达了两个向量之间的夹角与它们的内积之间的关系。具体而言对于两个向量 a \mathbf{a} a 和 b \mathbf{b} b它们之间的夹角 θ \theta θ 可以通过以下公式计算 cos ( θ ) a ⋅ b ∥ a ∥ ⋅ ∥ b ∥ \cos(\theta) \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \cdot \|\mathbf{b}\|} cos(θ)∥a∥⋅∥b∥a⋅b
其中 ⋅ \cdot ⋅ 表示内积 ∥ a ∥ \|\mathbf{a}\| ∥a∥ 和 ∥ b ∥ \|\mathbf{b}\| ∥b∥ 分别表示向量 a \mathbf{a} a 和 b \mathbf{b} b 的模。
内积的公式
内积的计算公式为 a ⋅ b a 1 ⋅ b 1 a 2 ⋅ b 2 … a n ⋅ b n \mathbf{a} \cdot \mathbf{b} a_1 \cdot b_1 a_2 \cdot b_2 \ldots a_n \cdot b_n a⋅ba1⋅b1a2⋅b2…an⋅bn
其中 a [ a 1 , a 2 , … , a n ] \mathbf{a} [a_1, a_2, \ldots, a_n] a[a1,a2,…,an] 和 b [ b 1 , b 2 , … , b n ] \mathbf{b} [b_1, b_2, \ldots, b_n] b[b1,b2,…,bn] 是两个 n 维向量。
内积在确定三个点顺序中的应用
在上述代码中我们通过计算两个向量的内积实际上是在比较它们的方向是否一致。考虑三个点 P 1 , P 2 , P 3 P_1, P_2, P_3 P1,P2,P3我们可以将它们看作两个向量 P 1 P 2 → v 1 \overrightarrow{P_1P_2} \mathbf{v_1} P1P2 v1 和 P 1 P 3 → v 2 \overrightarrow{P_1P_3} \mathbf{v_2} P1P3 v2。
如果 v 1 ⋅ v 2 0 \mathbf{v_1} \cdot \mathbf{v_2} 0 v1⋅v20则说明两个向量的方向大致一致这时我们选择 P 3 P_3 P3 作为顶点如果 v 1 ⋅ v 2 0 \mathbf{v_1} \cdot \mathbf{v_2} 0 v1⋅v20则说明两个向量的方向相反这时我们选择 P 2 P_2 P2 作为顶点。这样我们就能够确定三个点的顺序。
这种方法基于内积的方向性质因为内积的正负与向量夹角的余弦值的正负一致。因此通过比较两个向量的内积我们可以判断它们的方向关系从而确定三个点的顺序。
4.2.7 仿射变换
# 定义仿射变换的目标点
dst_points np.float32([[200, 200], [500, 200], [200, 500]])接下来我们根据三个点的顺序定义仿射变换的目标点。
# 计算仿射变换矩阵
affine_matrix cv2.getAffineTransform(src_points, dst_points)然后我们利用源点和目标点计算仿射变换矩阵。
# 画出三角形
cv2.line(img, tuple(pt1), tuple(pt2), (255, 0, 0), 2)
cv2.line(img, tuple(pt2), tuple(pt3), (255, 0, 0), 2)
cv2.line(img, tuple(pt3), tuple(pt1), (255, 0, 0), 2)接着在原图上画出我们找到的三角形。
# 进行仿射变换
warp_read cv2.warpAffine(img_read, affine_matrix, (800, 800))
warp cv2.warpAffine(img, affine_matrix, (800, 800))然后我们对原图和原图的二值化版本进行仿射变换。
4.2.8 使用qrcode库对二维码进行解码
# 使用cv2.QRCodeDetector()进行解码
qr_detector cv2.QRCodeDetector()result_read_gray cv2.cvtColor(warp_read, cv2.COLOR_BGR2GRAY)_, result_read_bin cv2.threshold(result_read_gray, 127, 255, cv2.THRESH_BINARY)接着我们使用cv2.QRCodeDetector()进行二维码的解码准备工作包括将仿射变换后的图像转为灰度图并进行二值化处理。
# 解码
codeinfo, points, straight_qrcode qr_detector.detectAndDecode(result_read_bin)
print(QR Code: %s % codeinfo)然后我们调用detectAndDecode方法解码二维码并输出解码结果。
# 添加文字
cv2.putText(warp_read, QR Code: str(codeinfo), (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)接下来我们在仿射变换后的图像上添加解码结果的文字信息。 通过这个实例我们深入了解了图像处理、轮廓检测、仿射变换和二维码解码等OpenCV的关键功能实现了一个完整的目标检测任务。
4.2.9 完整代码
import cv2
import numpy as npdef preprocess_image(image_path):# 读取图像img cv2.imread(image_path)resized_img cv2.resize(img, (800, 800))# 转换为灰度图像img_gray cv2.cvtColor(resized_img, cv2.COLOR_BGR2GRAY)# 二值化_, binaried_img cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)binaried_img 255 - binaried_img# 均值滤波kernel_size (3, 3)blurred_image cv2.blur(binaried_img, kernel_size)return resized_img, binaried_img, blurred_imagedef find_contours_and_center(resized_img,img_bin):# 寻找轮廓_, contours, _ cv2.findContours(img_bin, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# 用于存储需要的轮廓selected_contours []selected_center []# 遍历每个轮廓for contour_a in contours:# 计算当前轮廓 a 的包围盒x_a, y_a, w_a, h_a cv2.boundingRect(contour_a)# 遍历其他轮廓看是否有包含当前轮廓 a 的情况is_contained_a Falsefor contour_b in contours:if contour_b is not contour_a:# 计算轮廓 b 的包围盒x_b, y_b, w_b, h_b cv2.boundingRect(contour_b)# 判断当前轮廓 a 是否完全被轮廓 b 包含if x_a x_b and y_a y_b and x_a w_a x_b w_b and y_a h_a y_b h_b:is_contained_b False# 遍历其他轮廓看是否有包含当前轮廓 b 的情况for contour_c in contours:if contour_c is not contour_b and contour_c is not contour_a:# 计算轮廓 c 的包围盒x_c, y_c, w_c, h_c cv2.boundingRect(contour_c)# 判断当前轮廓 b 是否完全被轮廓 c 包含if x_b x_c and y_b y_c and x_b w_b x_c w_c and y_b h_b y_c h_c:is_contained_a Truebreak# 如果当前轮廓 a 完全被包含则将其添加到需要的轮廓列表中if is_contained_a:selected_contours.append(contour_a)# 计算中心点坐标center_x x_a w_a // 2center_y y_a h_a // 2# 在原图上绘制中心点cv2.circle(resized_img, (center_x, center_y), 5, (255, 0, 0), -1)selected_center.append([center_x, center_y])# 在原图上绘制需要的轮廓cv2.drawContours(resized_img, selected_contours, -1, (0, 0, 255), 2)return selected_centerdef calculate_triangle_points(selected_center):# 将三个点连接成三角形pt1, pt2, pt3 selected_center# 计算三角形的边side_a np.linalg.norm(np.array(pt2) - np.array(pt3))side_b np.linalg.norm(np.array(pt1) - np.array(pt3))side_c np.linalg.norm(np.array(pt1) - np.array(pt2))# 找到最大边对应的点if side_a side_b and side_a side_c:angle_point pt1other_points [pt2, pt3]elif side_b side_a and side_b side_c:angle_point pt2other_points [pt1, pt3]else:angle_point pt3other_points [pt1, pt2]return angle_point, other_pointsdef determine_points_order(angle_point, other_points):# 计算三个点的向量vector1 np.array(other_points[0]) - np.array(angle_point)vector2 np.array(other_points[1]) - np.array(angle_point)# 计算内积inner_product np.dot(vector1, vector2)# 根据内积的正负来确定点的顺序if inner_product 0:src_points np.float32([angle_point, other_points[0], other_points[1]])else:src_points np.float32([angle_point, other_points[1], other_points[0]])return src_pointsdef perform_affine_transform(img, src_points, dst_points):# 计算仿射变换矩阵affine_matrix cv2.getAffineTransform(src_points, dst_points)# 进行仿射变换rst cv2.warpAffine(img, affine_matrix, (800, 800))return rstdef detect_and_decode_qr_code(result):# 使用cv2.QRCodeDetector()进行解码qr_detector cv2.QRCodeDetector()result_gray cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)_, result_bin cv2.threshold(result_gray, 127, 255, cv2.THRESH_BINARY)# 解码codeinfo, _, _ qr_detector.detectAndDecode(result_bin)print(QR Code: %s % codeinfo)# 添加文字cv2.putText(result, QR Code: str(codeinfo), (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)return resultif __name__ __main__:# 图像预处理img_resize, img_bin, image_blur preprocess_image(QRPhoto.jpg)# 寻找轮廓并绘制中心点selected_center find_contours_and_center(img_resize.copy(),img_bin)# 计算三角形的顶点angle_point, other_points calculate_triangle_points(selected_center)# 确定顶点的顺序src_points determine_points_order(angle_point, other_points)# 定义仿射变换的目标点dst_points np.float32([[200, 200], [500, 200], [200, 500]])# 进行仿射变换result perform_affine_transform(img_resize, src_points, dst_points)# 检测并解码QR码result_with_qr detect_and_decode_qr_code(result)# 显示结果cv2.imshow(Original Image, img_resize)cv2.imshow(Result with QR Code, result_with_qr)cv2.waitKey(0)cv2.destroyAllWindows()运行结果1 运行结果2
上面代码还无法应对更加严峻的畸变挑战有待后续的研究和优化。 总结 首先我们简单介绍了图像轮廓的概念以及常用的轮廓检测算法。 接着我们了解了轮廓检测函数包括轮廓发现、轮廓面积、轮廓周长等功能的使用说明。代码实现部分提供了实际的操作指南使其能够在实际项目中灵活运用所学知识。 然后我们学习了轮廓的距包括几何距、中心距和Hu距等文章提供了更深层次的图像特征描述方法。 最后通过点集拟合和二维码检测两个具体案例我们展示了如何在实际应用中灵活运用轮廓检测技术更好地理解其实际应用场景。