OpenCV-Python -- Contour Features

学习目标

  • 寻找不同的轮廓特征,比如面积,周长,重心,边框
  • 与轮廓相关的函数

矩(Moments)

图像矩可以帮助计算一些特征,比如目标对象的质心,对象的面积等。cv2.moments()返回一个字典,包含计算的矩值,下面介绍该函数的使用:

def moments(array, binaryImage=None): # real signature unknown; restored from __doc__
    """
       @brief 计算最大到3阶矩或者栅格形状(rasterized shape).
       
       The function computes moments, up to the 3rd order, of a vector shape or a 
       rasterized shape. The results are returned in the structure cv::Moments.
       该函数计算图像的矩(最大到3阶),或者向量的形状,或者栅格化形状。
       
       @param array Raster image (single-channel, 8-bit or floating-point 2D array) or an 
       array (1xN,或者Nx1的2D点),通常是cv2.findContours返回的轮廓信息.
    
       @param binaryImage If it is true, all non-zero image pixels are treated as 1's. The 
       parameter is used for images only.
       @returns moments.  
       
       @note Only applicable to contour moments calculations from Python bindings: Note 
       that the numpy type for the input array should be either np.int32 or np.float32.
       python的接口仅仅用于轮廓矩的计算。
    """
    pass

下面具体看下例子:

img1 = cv2.imread('star.png', 1)
img = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(img, 127, 255, 0)
contours, hierarchy = cv2.findContours(thresh, 1, 2)
print(len(contours))
cnt = contours[1]
M = cv2.moments(cnt)
print(M)

# 计算图像的质心
x1 = M['m10']/M['m00']
y1 = M['m01']/M['m00']
print(x1)
print(y1)

image = cv2.drawContours(img1, contours, -1, (255, 0, 0), 3)
cv2.imshow('image', image)
cv2.waitKey(0)

下面是M的打印结果:

{'m00': 10174.5, 'm10': 1274781.0, 'm01': 1171712.0,
 'm20': 170342228.25, 'm11': 146795270.54166666, 'm02': 145563982.25,
 'm30': 24004385811.4, 'm21': 19614391639.716667, 'm12': 18235939976.05,
 'm03': 19211850096.9, 'mu20': 10622674.663976133, 'mu11': -10586.755694389343,
 'mu02': 10627718.950181842, 'mu30': 41250.74350357056, 'mu21': 155917.34777283669,
 'mu12': 410996.8116209507, 'mu03': 653829.4448013306, 'nu20': 0.10261426257635634,
 'nu11': -0.0001022672879496034, 'nu02': 0.10266299001323984, 'nu30': 3.9504729315724535e-06,
 'nu21': 1.4931785699470972e-05, 'nu12': 3.9360060967885616e-05, 'nu03': 6.261549015059886e-05}

画出找到的2个轮廓,前面介绍过寻找轮廓和画轮廓:
在这里插入图片描述

轮廓面积

计算的函数如下,当然也可以使用图像矩计算,M['m00'],使用如下:

area = cv2.contourArea(cnt)  # cnt是返回的轮廓信息,参考上面的例子
# 输出为:10174.5

图像的面积统计如下:
在二值图像内,非零像素标记为1,零像素标记为0,那么所有的非零元素求和则为面积,其实就是轮廓内的所有1求和。可以用规则的多边形测试,即可验证

轮廓周长(Contour Perimeter)

计算函数为:cv2.arcLength(),第二个参数指明轮廓是否封闭(true),或者是曲线:

perimeter = cv2.arcLength(cnt,True)

图像的周长统计如下:
在二值图像内,非零像素标记为1,零像素标记为0,将轮廓所有的元素求和,即是周长。

轮廓近似(Contour Approximation)

在特定的精度要求下,可以选择用更少的轮廓点近似另一个形状。应用的算法为 Douglas-Peucker algorithm

为了理解这个算法,假设我们尝试在图像中寻找方形,由于某些原因,无法得到完美的方形,而是一个不好的形状。那么我们可以用形状近似该形状。函数的介绍如下:

approx = cv2.approxPolyDP(cnt,epsilon,True)
	"""
		功能:得到图像中多边形的近似轮廓点。
		cnt:表示输入的形状(轮廓)
		epsilon:近似的精度,表示近似轮廓与原轮廓的最大距离
		布尔值:true表示轮廓是封闭的,否则不是封闭的
		approx:返回值,与输入轮廓属性一样
	"""

应用示例如下:

    img = cv2.imread('s2.png')
    print(img.shape)
    cv2.imshow('ori image', img)

    img1 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, thresh = cv2.threshold(img1, 127, 255, 0)
    contours, hierarchy = cv2.findContours(thresh, 1, 2)
    cnt = contours[0]
    print(len(contours))
    # 原始的轮廓
    # image_ori = cv2.drawContours(img, contours, -1, (0, 0, 255), 2)
    # cv2.imshow('ori contour', image_ori)
    epsilon = 0.1*cv2.arcLength(cnt, True)
    print(epsilon)
    approx = cv2.approxPolyDP(cnt, epsilon, True)
    print(len(approx))
    image = cv2.drawContours(img, approx, -1, (0, 0, 255), 3)

    cv2.imshow('image', image)
    cv2.waitKey(0)

在这里插入图片描述在这里插入图片描述

轮廓凸包(Convex Hull)

轮廓凸包与轮廓近似有些类似,但是事实上是不一样的(在某些特殊情况下可能结果看着一样)。这里,函数cv2.convexHull()用于检查曲线的凸包缺陷,并纠正。一般来说,凸曲线通常是向外凸的,或者至少也是平坦的。如果曲线向内凸,那么称之为凸包缺陷(convexity defects)。比如,下面的图像,红色的线表示手的凸包,双向箭头表示凸包缺陷,他是凸包到轮廓的局部最大偏差。

在这里插入图片描述

hull = cv2.convexHull(points, hull=None, clockwise=None, returnPoints=None )

功能:寻找目标的凸包
points:输入的2D点,通常是轮廓点。
hull:输入的凸包点。
clockwise:方向的标志,如果为True,则顺时针输出,否则逆时针。
returnPoints:默认是True,输出凸包点的左边。如果为False,返回凸包点在轮廓点中的索引。

hull = cv2.convexHull(cnt)

如果需要寻找凸包的凹陷点,那么returnPoints=False

示例如下:

import cv2
import numpy as np


def get_contour(img):
    """获取连通域

    :param img: 输入图片
    :return: 最大连通域
    """
    # 灰度化, 二值化, 连通域分析
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, img_bin = cv2.threshold(img_gray, 50, 255, cv2.THRESH_BINARY)
    #
    cv2.imshow('img', img_bin)
    # cv2.waitKey(0)

    contours, hierarchy = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

    return img_gray, contours


def main():

    # 1.导入图片
    img_src = cv2.imread("Mat2D.bmp")  # Mat2D.bmp  binary1
    print(img_src)

    # 2.获取连通域
    img_gray, contour = get_contour(img_src)

    # 3.轮廓外边缘点
    mask = np.zeros(img_gray.shape, np.uint8)
    print(len(contour))
    mask = cv2.drawContours(img_src, contour, -1, (255, 0, 255), 3)

    cnt = contour[0]
    hull=cv2.convexHull(cnt, returnPoints=False)
    defects=cv2.convexityDefects(cnt, hull)
    print(defects.shape[0])
    # for i in range(defects.shape[0]):
    for i in range(5):
        s, e, f, d=defects[i, 0]
        start=tuple(cnt[s][0])
        end=tuple(cnt[e][0])
        far=tuple(cnt[f][0])
        cv2.line(img_src, start, end, [0, 255, 0], 2)
        cv2.circle(img_src, far, 5, [255, 0, 0], -1)

    cv2.imshow('img1', img_src)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    # cv2.imshow('mask', mask)
    # cv2.waitKey(0)

    # # 4.计算极值点
    # left_most = tuple(contour[contour[:, :, 0].argmin()][0])
    # right_most = tuple(contour[contour[:, :, 0].argmax()][0])
    # top_most = tuple(contour[contour[:, :, 1].argmin()][0])
    # bottom_most = tuple(contour[contour[:, :, 1].argmax()][0])
    # print("left_most=", left_most)
    # print("right_most=", right_most)
    # print("top_most=", top_most)
    # print("bottom_most=", bottom_most)
    #
    # # 5.绘制文字
    # img_result = img_src.copy()
    # font = cv2.FONT_HERSHEY_SIMPLEX
    # cv2.putText(img_result, "A", left_most, font, 1, (0, 0, 255), 2)
    # cv2.putText(img_result, "B", right_most, font, 1, (0, 0, 255), 2)
    # cv2.putText(img_result, "C", top_most, font, 1, (0, 0, 255), 2)
    # cv2.putText(img_result, "D", bottom_most, font, 1, (0, 0, 255), 2)
    #
    # # 6.显示图片
    # cv2.imshow("img_src", img_src)
    # cv2.imshow("img_result", img_result)
    #
    # cv2.waitKey()
    # cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

运行结果如下:
在这里插入图片描述

检查凸凹性(Checking Convexity)

可以使用函数检查曲线是否是凸的,cv2.isContourConvex(). 返回 True或者False.

边界矩形(Bounding Rectangle)

总共有两种类型的边界矩形。

常规边框(Straight Bounding Rectangle)

常规的边框,目标对象没有旋转。所以边框的面积不会存在最小值。函数为cv2.boundingRect()
令(x,y)是矩形的左上角坐标,(w,h)是宽和高。

x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)

旋转矩形(Rotated Rectangle)

这里,边框需要是最小区域,所以考虑对象的旋转。函数为cv2.minAreaRect(),返回边框的2D结构,具体包括(左上角坐标(x,y),(width,height),旋转角度)。但是画该边框我们需要边框的四个角,获取方式如下:

rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int(box)
im = cv2.drawContours(im,[box],0,(0,0,255),2)

下面的图像是运行的结果,绿色框表示正常的边框,红色表示旋转框:

在这里插入图片描述

最小闭合圆(Minimum Enclosing Circle)

下面我们目标的最小包围圆,函数为cv2.minEnclosingCircle(),使用最小的圆形区域包围目标对象,具体使用如下:

(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img = cv2.circle(img,center,radius,(0,255,0),2)

运行结果如下:
在这里插入图片描述

拟合椭圆(Fitting an Ellipse)

下面使用椭圆拟合目标,例子如下:

ellipse = cv2.fitEllipse(cnt)
im = cv2.ellipse(im,ellipse,(0,255,0),2)

在这里插入图片描述

拟合直线(Fitting a Line)

类似,我们可以用直线拟合点,下面的图像包含一系列白色的点,我们可以用直线拟合,例子如下:

rows,cols = img.shape[:2]
[vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
img = cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用PythonOpenCV实现手势识别的基本步骤如下: 1. 采集手势图像数据集,包括手势的前景和背景样本。 2. 对采集的图像进行预处理,包括图像增强、去噪等操作,以提高手势识别的准确率。 3. 提取手势特征,例如手指数量、手指位置等。 4. 训练分类器,使用机器学习算法对特征进行分类,得到手势分类器。 5. 使用分类器对新采集的手势图像进行分类,判断手势的类型。 下面是一个简单的实现手势识别的Python示例代码: ``` python import cv2 import numpy as np import sklearn from sklearn import svm #采集手势图像数据集 def collect_dataset(): dataset = [] cap = cv2.VideoCapture(0) while True: ret, img = cap.read() cv2.rectangle(img, (300,300), (100,100), (0,255,0),0) crop_img = img[100:300, 100:300] grey = cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY) value = (35, 35) blurred = cv2.GaussianBlur(grey, value, 0) _, thresh1 = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) cv2.imshow('Thresholded', thresh1) cv2.imshow('Gesture', img) dataset.append(thresh1) if cv2.waitKey(1) == ord('q'): break cap.release() cv2.destroyAllWindows() return dataset #提取手势特征 def get_contour_precedence(contour, cols): tolerance_factor = 10 origin = cv2.boundingRect(contour) return ((origin[1] // tolerance_factor) * tolerance_factor) * cols + origin[0] def get_hand_contour(img): contours, hierarchy = cv2.findContours(img.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) cnt = max(contours, key=lambda x: cv2.contourArea(x)) if cv2.contourArea(cnt) > 10000: cnt = max(contours, key=lambda x: get_contour_precedence(x, img.shape[1])) return (cnt) else: return None def get_hand_feature(img): cnt = get_hand_contour(img) if cnt: hull = cv2.convexHull(cnt, returnPoints=False) defects = cv2.convexityDefects(cnt, hull) if defects is not None: cnt = max(defects, key=lambda x: x[0][3]) start, end, _, _ = cnt[0] farthest = tuple(cnt[0][2]) return (start, end, farthest, cnt, hull) return None #训练分类器 def train_classifier(): dataset = collect_dataset() features = [] labels = [] for i in range(len(dataset)): img = dataset[i] feature = get_hand_feature(img) if feature: start, end, farthest, cnt, hull = feature features.append([start, end, farthest]) labels.append(i) clf = svm.SVC(kernel='linear', C=1) clf.fit(features, labels) return clf #使用分类器对新采集的手势图像进行分类 def predict_gesture(clf, img): feature = get_hand_feature(img) if feature: start, end, farthest, _, _ = feature p = clf.predict([start, end, farthest]) return p else: return None #主函数 if __name__ == '__main__': clf = train_classifier() cap = cv2.VideoCapture(0) while True: ret, img = cap.read() cv2.rectangle(img, (300,300), (100,100), (0,255,0),0) crop_img = img[100:300, 100:300] grey = cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY) value = (35, 35) blurred = cv2.GaussianBlur(grey, value, 0) _, thresh1 = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) cv2.imshow('Thresholded', thresh1) p = predict_gesture(clf, thresh1) if p is not None: print(p) cv2.imshow('Gesture', img) if cv2.waitKey(1) == ord('q'): break cap.release() cv2.destroyAllWindows() ``` 这个示例代码中实现了手势图像数据集的采集、手势特征提取、分类器训练和手势识别等功能,可以作为实现手势识别的一个参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值