OpenCV形状检测
求则得之,舍则失之 人气:01.基于OpenCV的形状检测Python版本
目录结构
1.1.定义我们的形状检测器类ShapeDetector
开始定义我们的 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类执行形状检测和识别。
1.2.基于OpenCV的形状检测器
现在我们的 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)坐标。
最后,我们在图像上绘制轮廓和识别的形状,然后显示我们的结果。
2.基于OpenCV的形状检测C++版本
在本教程中,让我们看看如何使用 OpenCV 的轮廓来识别对象的形状和位置。
使用OpenCV的轮廓,你可以得到每个白斑的顶点的点序列(白斑被认为是多边形)。例如,对于三角形你会得到3个点(顶点),
对于四边形你会得到4个点。你可以通过多边形的顶点数来识别任何多边形。
你甚至可以通过计算和比较顶点之间的距离来识别多边形的特征,如凸性、凹性、等边等。
我们看看如何使用 OpenCV 来完成。您所需要的只是一个二进制图像,其中您的对象应该是白色的,背景应该是黑色的。
现在我将使用OpenCV C++应用程序来识别上图中的三角形、四边形和七边形。我将沿着每个确定的多边形的周长画一条线,
三角形颜色为蓝色,四边形颜色为绿色,七边形颜色为红色。
2.1代码实现
#include <opencv2/opencv.hpp> 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”函数对灰度图像进行阈值处理。
您可以使用自己的方式对图像进行阈值处理。然后我找到阈值图像中的所有轮廓,并识别和跟踪所有三角形、四边形和七边形。
2.2主要函数解析
让我们讨论一下这个应用程序中的OpenCV 函数。
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type);
src:源图像(8位单通道)
dst:与src具有相同大小相同类型的目标图像
thresh:阈值
maxval:滿足條件的像素替换为这个值
type:阈值化方法,
THRESH_BINARY
- dst(x,y)=max, if src(x,y) > ThreshVal
- dst(x,y)=0, if src(x,y) < ThreshVal
THRESH_BINARY_INV
- dst(x,y)=0,如果 src(x,y) > ThreshVal
- dst(x,y)=max,如果 src(x,y) < ThreshVal
THRESH_TOZERO
- dst(x,y)=src(x,y), 如果 src(x,y) > ThreshVal
- dst(x,y)=0, 如果 src(x,y) < ThreshVal
THRESH_TOZERO_INV
- dst(x,y)=0,如果 src(x,y) > ThreshVal
- dst(x,y)=src(x,y),如果 src(x,y) < ThreshVal
THRESH_TRUNC
- dst(x,y)=threshVal,如果 src(x,y) > ThreshVal
- dst(x,y)=src(x,y), if src(x,y) < ThreshVal
在上面的应用程序中,我使用了“ 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 );
- curve: 存储2D点的std::vector或Mat
- approxCurve: 多边形近似的结果。类型应该与输入类型相匹配。
- epsilon: 指定近似精度的参数。这是原始轮廓与其近似值之间的最大距离。
- closed: 如果为真,逼近曲线是闭合的(它的第一个和最后一个顶点是连接的)。否则不闭合。
2.3结果展示
加载全部内容