东莞seo建站优化哪里好,seo网络推广员招聘,黔东南住房和城乡建设官网,多用户商城网站建设公司文章目录 前言一、轮廓检测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 代码实现 三、点集拟合最小包围三角形最小包围圆形 四、二维码检测总结 前言
在当今数字化时代计算机视觉的崛起使得目标检测成为科技领域中的一项关键技术。本文将带您快速入门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中我们通常使用最小包围三角形、最小包围圆形等方法进行点集拟合。
最小包围三角形
最小包围三角形是将一组点拟合为一个最小的包围三角形该三角形能够包含所有的离散点。
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()最小包围圆形
最小包围圆形是将一组点拟合为一个最小的包围圆形该圆形能够包含所有的离散点。
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()四、二维码检测
我们使用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()总结 首先我们简单介绍了图像轮廓的概念以及常用的轮廓检测算法。 接着我们了解了轮廓检测函数包括轮廓发现、轮廓面积、轮廓周长等功能的使用说明。代码实现部分提供了实际的操作指南使其能够在实际项目中灵活运用所学知识。 然后我们学习了轮廓的距包括几何距、中心距和Hu距等文章提供了更深层次的图像特征描述方法。 最后通过点集拟合和二维码检测两个具体案例我们展示了如何在实际应用中灵活运用轮廓检测技术更好地理解其实际应用场景。