开始定义我们的 ShapeDetector 类。我们将跳过这里的 init 构造函数,因为不需要初始化任何东西。
# 导入必要的包
import cv2
class ShapeDetector:
def __init__(self):
pass
def detect(self, c):
# 初始化形状名称并近似轮廓
shape = "unidentified"
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.04 * peri, True)
我们的检测方法,它只需要一个参数 c,即我们试图识别的形状的轮廓。为了进行形状检测,我们将使用轮廓逼近。
顾名思义,轮廓近似是一种算法,用于减少曲线中点的数量,并减少点集——因此称为近似。
轮廓近似是基于曲线可以由一系列短线段近似的假设。这导致生成的近似曲线由原始曲线定义的点的子集组成。
轮廓近似实际上已经在 OpenCV 中通过 cv2.approxPolyDP 方法实现。
为了进行轮廓逼近,我们首先计算轮廓的周长,然后构建实际的轮廓逼近。 cv2.approxPolyDP 的第二个参数的常用值通常在原始轮廓周长的 1%-5% 范围内。
给定我们的近似轮廓,我们可以继续执行形状检测:
# 如果形状是一个三角形,它将有3个顶点
if len(approx) == 3:
shape = "triangle"
# 如果形状有4个顶点,它要么是正方形,要么是矩形
elif len(approx) == 4:
# 计算轮廓的包围框,并使用包围框计算高宽比
(x, y, w, h) = cv2.boundingRect(approx)
ar = w / float(h)
# 正方形的长宽比大约等于1,否则,形状就是矩形
shape = "square" if ar >= 0.95 and ar <= 1.05 else "rectangle"
# 如果形状是一个五边形,它将有5个顶点
elif len(approx) == 5:
shape = "pentagon"
# 否则,我们假设形状是一个圆
else:
shape = "circle"
# 返回形状的名称
return shape
重要的是要了解轮廓由顶点列表组成。我们可以检查此列表中的数目以确定对象的形状。例如,如果近似轮廓有三个顶点,
那么它一定是一个三角形。如果一条轮廓有四个顶点,那么它一定是正方形或矩形。为了确定这一点,我们计算形状的长宽比,
也就是轮廓边界框的宽度除以高度。如果长宽比是~1.0,那么我们正在检查一个正方形(因为所有的边都有大约相等的长度)。否则,形状就是矩形。如果一条等高线有五个顶点,我们可以将其标记为五边形。否则,我们可以假设我们正在检查的形状是一个圆。
最后,将标识好的形状返回给调用方法。
我们已经定义了一个utils模块。在这个模块中,我们有shapedetector .py,它将存储ShapeDetector类的实现。
最后,我们有detect_shapes.py脚本,我们将使用它从磁盘加载图像,分析它的形状,然后通过ShapeDetector类执行形状检测和识别。
现在我们的 ShapeDetector 类已经定义好了,让我们创建 detect_shapes.py 脚本:
# 导入必要的库
from utils.shapedetector import ShapeDetector
import argparse
import imutils
import cv2
# 构造参数解析并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", default="inpaint.jpg", help="path to the input image")
args = vars(ap.parse_args())
首先导入我们已经实现的ShapeDetector类,然后解析参数,下一步,我们开始预处理我们的图像
# 加载图像并将其调整图像大小,以便更好地近似形状
image = cv2.imread(args["image"])
resized = imutils.resize(image, width=300)
ratio = image.shape[0] / float(resized.shape[0])
# 将调整后的图像转换为灰度,稍微模糊它,并阈值化
gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
thresh = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# 在阈值化图像中找到轮廓并初始化形状检测器
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
sd = ShapeDetector()
首先,我们从磁盘加载图像,并调整其大小。然后,我们跟踪旧高度与调整后的新高度的比率——我们将在本教程的后面部分找到这样做的确切原因。
将调整大小后的图像转换为灰度,平滑它以减少高频噪声,最后阈值化它以显示图像中的形状。
阈值之后,我们的图像应该是这样的:
请注意我们的图像是如何被二值化的——形状显示为黑色背景下的白色前景。
最后,在二值图像中找到轮廓,基于OpenCV版本的cv2.findContours获取正确的元组值,并最终初始化我们的ShapeDetector。
最后一步是识别每个轮廓:
# 遍历所有轮廓
for c in cnts:
# 计算轮廓的中心,然后仅使用轮廓检测形状的名称
M = cv2.moments(c)
cX = int((M["m10"] / M["m00"]) * ratio)
cY = int((M["m01"] / M["m00"]) * ratio)
shape = sd.detect(c)
# 将轮廓(x, y)坐标乘以调整比例,然后在图像上绘制轮廓和形状的名称
c = c.astype("float")
c *= ratio
c = c.astype("int")
cv2.drawContours(image, [c], -1, (0, 255, 0), 2)
cv2.putText(image, shape, (cX, cY), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (255, 255, 255), 2)
# 显示输出图像
cv2.imshow("Image", image)
cv2.waitKey(0)
我们开始在每个单独的轮廓上循环。对于每一个,我们计算轮廓的中心,然后执行形状检测和识别。
由于我们正在处理从调整大小后的图像中提取的轮廓(而不是原始图像),我们需要将轮廓和中心(x, y)坐标乘以调整比率。
这将为我们提供原始图像的轮廓和质心的正确(x, y)坐标。
最后,我们在图像上绘制轮廓和识别的形状,然后显示我们的结果。
在本教程中,让我们看看如何使用 OpenCV 的轮廓来识别对象的形状和位置。
使用OpenCV的轮廓,你可以得到每个白斑的顶点的点序列(白斑被认为是多边形)。例如,对于三角形你会得到3个点(顶点),
对于四边形你会得到4个点。你可以通过多边形的顶点数来识别任何多边形。
你甚至可以通过计算和比较顶点之间的距离来识别多边形的特征,如凸性、凹性、等边等。
我们看看如何使用 OpenCV 来完成。您所需要的只是一个二进制图像,其中您的对象应该是白色的,背景应该是黑色的。
现在我将使用OpenCV C++应用程序来识别上图中的三角形、四边形和七边形。我将沿着每个确定的多边形的周长画一条线,
三角形颜色为蓝色,四边形颜色为绿色,七边形颜色为红色。
#include
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("FindingContours.png");
//show the original image
namedWindow("Raw");
imshow("Raw", img);
//converting the original image into grayscale
Mat imgGrayScale = Mat(img.size(), CV_8UC1);
cvtColor(img, imgGrayScale, COLOR_BGR2GRAY);
//thresholding the grayscale image to get better results
threshold(imgGrayScale, imgGrayScale, 128, 255, THRESH_BINARY);
//finding all contours in the image
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours(imgGrayScale, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
//iterating through each contour
for (size_t i = 0; i < contours.size(); i++)
{
vector<Point> approx;
//obtain a sequence of points of contour, pointed by the variable 'contour'
approxPolyDP(contours[i], approx, arcLength(contours[i], true) * 0.02, true);
//if there are 3 vertices in the contour(It should be a triangle)
if (approx.size() == 3)
{
//drawing lines around the triangle
for (int i = 0; i < 3; i++) {
line(img, approx[i], approx[(i+1)%3], Scalar(255, 0, 0), 4);
}
}
//if there are 4 vertices in the contour(It should be a quadrilateral)
else if (approx.size() == 4)
{
//drawing lines around the quadrilateral
for (int i = 0; i < 4; i++) {
line(img, approx[i], approx[(i + 1) % 4], Scalar(0, 255, 0), 4);
}
}
//if there are 7 vertices in the contour(It should be a heptagon)
else if (approx.size() == 7)
{
//drawing lines around the heptagon
for (int i = 0; i < 7; i++) {
line(img, approx[i], approx[(i + 1) % 7], Scalar(0, 0, 255), 4);
}
}
}
//show the image in which identified shapes are marked
namedWindow("Tracked");
imshow("Tracked", img);
waitKey(0); //wait for a key press
//cleaning up
destroyAllWindows();
return 0;
}
如您所见,三角形用蓝色标记, 四边形用绿色标记,七边形用红色标记。所以,现在很明显,这种方法能够识别形状。 首先将原始图像转换为灰度。
这是因为这种方法只适用于单通道的灰度图像。为了获得更好的结果,我使用“threshold”函数对灰度图像进行阈值处理。
您可以使用自己的方式对图像进行阈值处理。然后我找到阈值图像中的所有轮廓,并识别和跟踪所有三角形、四边形和七边形。
让我们讨论一下这个应用程序中的OpenCV 函数。
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type);
在上面的应用程序中,我使用了“ THRESH_BINARY”,因为我想在对象所在的位置分配 255(白色其他是0(黑色)。
findContours( InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset = Point());
image: 8位单通道图像。非零像素被视为1。零像素保持为0,因此图像被视为二值。你可以使用#compare #inRange #threshold,#adaptiveThreshold, #Canny,以及其他参数来创建灰度或彩色图像的二值图像。
contours: 发现的所有轮廓
hierarchy:轮廓之间的层次结构
int mode - 从图像中检索轮廓的模式,您必须选择以下之一
RETR_LIST - 检索所有轮廓并将它们放入列表中
RETR_EXTERNAL - 仅检索外轮廓
RETR_CCOMP - 检索所有轮廓并将它们组织成两级层次结构:
RETR_TREE - 检索所有轮廓并重建嵌套轮廓的完整层次结构
int method - 近似方法,您必须选择以下之一
CHAIN_APPROX_NONE - 将链码中的所有点转换为点
CHAIN_APPROX_SIMPLE - 压缩水平、垂直和对角线段,只留下它们的端点
CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS - 应用 Teh-Chin 链近似算法的一种风格。
Point offset:每个轮廓点应移动的偏移量。当我们在图像中设置 ROI(感兴趣区域)时,这很有用。通常我们将偏移量设置为 ‘Point(0,0)’
void approxPolyDP( InputArray curve, OutputArray approxCurve, double epsilon, bool closed );
https://pyimagesearch.com/2016/02/08/opencv-shape-detection/