本次我们将讨论OpenCV中图像轮廓的另一些特征,它们将非常有用。
边界矩形
有两类边界矩形
直边界矩形—boundingRect()
一个直矩形(就是没有旋转的矩形)。它不会考虑对象是否旋转。所以边界矩形的面积不是最小的。可以使用函数 cv2.boundingRect() 查找得到,我们来看函数原型:
x,y,w,h = cv2.boundingRect(cnt)
(x,y)为矩形左上角的坐标,(w,h)是矩形的宽和高,通常情况下,cnt代表识别的轮廓。
之后我们利用cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)画出矩形、
参数解释
第一个参数:img是原图
第二个参数:(x,y)是矩阵的左上点坐标
第三个参数:(x+w,y+h)是矩阵的右下点坐标
第四个参数:(0,255,0)是画线对应的rgb颜色
第五个参数:2是所画的线的宽度
我们来看代码:import cv2
# 读取图片并转至灰度模式
img = cv2.imread("tubao.png", 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化,取阈值为235
ret, thresh = cv2.threshold(gray, 235, 255, cv2.THRESH_BINARY)
# 寻找图像中的轮廓
contours, hierarchy = cv2.findContours(thresh, 2, 1)
cnt = contours[0]
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv2.imshow('finger', img)
cv2.waitKey()
可以看到一个外接矩形,但是它不是最小的外接矩形,接下来我们讨论最小外接矩形。
旋转的边界矩形
这个边界矩形是面积最小的,因为它考虑了对象的旋转。
用到的函数为 :
cv2.minAreaRect(cnt)。
在这里的cnt仍然跟上面的相同,该函数返回的是一个 Box2D 结构:
Box2D结构 rect:(最小外接矩形的中心(x,y),(宽度,高度),旋转角度),但是要绘制此矩形,我们需要矩形的4个角。 它是通过函cv2.boxPoints()获得的。
我们来看代码:import cv2
import numpy as np
# 读取图片并转至灰度模式
img = cv2.imread("tubao.png", 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化,取阈值为235
ret, thresh = cv2.threshold(gray, 235, 255, cv2.THRESH_BINARY)
# 寻找图像中的轮廓
contours, hierarchy = cv2.findContours(thresh, 2, 1)
cnt = contours[0]
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(img,[box],0,(0,0,255),2)
cv2.imshow('finger', img)
cv2.waitKey()
可以看到,这里得出的结果是最小外接矩形。
最小外接圆
跟之前一样,轮廓可以外接矩形,同时也可以外接圆形。接下来,我们使用函数cv.minEnclosingCircle()找到对象的外接圆。 它是一个以最小面积完全覆盖对象的圆圈。
函数原型:
center, radius = cv.minEnclosingCircle( points )
直接来看代码:import cv2
import numpy as np
# 读取图片并转至灰度模式
img = cv2.imread("tubao.png", 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化,取阈值为235
ret, thresh = cv2.threshold(gray, 235, 255, cv2.THRESH_BINARY)
# 寻找图像中的轮廓
contours, hierarchy = cv2.findContours(thresh, 2, 1)
cnt = contours[0]
(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
cv2.circle(img, center, radius, (0, 255, 0), 2)
cv2.imshow('finger', img)
cv2.waitKey()
椭圆拟合
轮廓同样也可以进行椭圆拟合,函数原型:
retval=cv.fitEllipse(points)
输入参数跟之前的外界矩形是一样的,就不一一详述了。代码:import cv2
import numpy as np
# 读取图片并转至灰度模式
img = cv2.imread("tubao.png", 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化,取阈值为235
ret, thresh = cv2.threshold(gray, 235, 255, cv2.THRESH_BINARY)
# 寻找图像中的轮廓
contours, hierarchy = cv2.findContours(thresh, 2, 1)
cnt = contours[0]
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(img,ellipse,(0,255,0),2)
cv2.imshow('finger', img)
cv2.waitKey()
直线拟合
同样,我们可以将一条直线拟合到一组点。下图包含一组白点,我们可以近似一条直线。函数原型:
output = cv2.fitLine(InputArray points, distType, param, reps, aeps)
InputArray Points: 待拟合的直线的集合,必须是矩阵形式;
distType: 距离类型。fitline为距离最小化函数,拟合直线时,要使输入点到拟合直线的距离和最小化。这里的距离的类型有以下几种:
cv2.DIST_USER : User defined distance
cv2.DIST_L1: distance = |x1-x2| + |y1-y2|
cv2.DIST_L2: 欧式距离,此时与最小二乘法相同
cv2.DIST_C:distance = max(|x1-x2|,|y1-y2|)
cv2.DIST_L12:L1-L2 metric: distance = 2(sqrt(1+x*x/2) - 1))
cv2.DIST_FAIR:distance = c^2(|x|/c-log(1+|x|/c)), c = 1.3998
cv2.DIST_WELSCH: distance = c2/2(1-exp(-(x/c)2)), c = 2.9846
cv2.DIST_HUBER:distance = |x|
param: 距离参数,跟所选的距离类型有关,值可以设置为0。
reps, aeps: 第5/6个参数用于表示拟合直线所需要的径向和角度精度,通常情况下两个值均被设定为1e-2。
output : 对于二维直线,输出output为4维,前两维代表拟合出的直线的方向,后两位代表直线上的一点。(即通常说的点斜式直线)。
代码:import cv2
import numpy as np
# 读取图片并转至灰度模式
img = cv2.imread("tubao.png", 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化,取阈值为235
ret, thresh = cv2.threshold(gray, 235, 255, cv2.THRESH_BINARY)
# 寻找图像中的轮廓
contours, hierarchy = cv2.findContours(thresh, 2, 1)
cnt = contours[0]
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)
cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
cv2.imshow('finger', img)
cv2.waitKey()
现在我们可以来一个实战部分,用椭圆拟合来进行人眼识别之后标注出来,首先我们需要安装dlib库,这种一个人脸特征检测库,接下来我们设置特征检测器,dlib有已经训练的好的需要下载,也可以自己根据需要训练:
下载链接:http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2
下载完之后解压,将路径放到根目录里面,当然了,下载是需要****的,否则下载速度非常慢。
我们使用图片:
代码:import cv2
import dlib
import numpy as np
detector = dlib.get_frontal_face_detector()
landmark_predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
img = cv2.imread("min.jpg")
faces = detector(img,1)
left_eye = []
right_eye = []
if ( len(faces) > 0):
for k,d in enumerate(faces):
shape = landmark_predictor(img,d)
for i in range(36,42):
right_eye.append([shape.part(i).x,shape.part(i).y])
for i in range(42,48):
left_eye.append([shape.part(i).x,shape.part(i).y])
ellipse_left = cv2.fitEllipse(np.array(left_eye))
ellipse_right = cv2.fitEllipse(np.array(right_eye))
cv2.ellipse(img, ellipse_left, (0,255,0), 1)
cv2.ellipse(img, ellipse_right, (0,255,0), 1)
ellipse_left
((275.1310119628906, 197.24081420898438),
(13.491097450256348, 47.203433990478516),
84.19256591796875)
center = ellipse_left[0]
size = ellipse_left[1]
angle = ellipse_left[2]
cv2.imshow('PIC',img)
cv2.waitKey(0)
可以看到,人眼已经识别出来,并用椭圆进行了拟合,是不是很有趣。
本次我们主要讨论轮廓的外界图形,事实上它们都是非常有用的,下次我们将讨论轮廓特征的一些高阶方法。