直接上效果图,看你是否需求
- 项目要求:给定图片,自动检测图中物体,并裁剪。
- 文章底部附上整个项目的代码,只需改动main函数的输入图像地址,就可得到本文陈列的所有效果图。
- 代码详细讲解方便阅读
左1:根据cv2.findContours()找到多个轮廓,中间:根据最大轮廓原则选择最大的2个轮廓并求出相应最小外接矩阵,右1:根据外接矩阵提供的坐标进行裁剪。
step1: 加载图片,转成灰度图,用Sobel算子计算x,y方向上的梯度,之后在x方向上减去y方向上的梯度,通过这个减法,我们留下具有高水平梯度和低垂直梯度的图像区域。
image = cv2.imread(img_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gradX = cv2.Sobel(gray, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradY = cv2.Sobel(gray, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=-1)
# subtract the y-gradient from the x-gradient
gradient = cv2.subtract(gradX, gradY)
gradient = cv2.convertScaleAbs(gradient)
gradient效果
step2: 去除图像上的噪声。
- 使用低通滤泼器平滑图像(9 x 9内核),这将有助于平滑图像中的高频噪声。低通滤波器的目标是降低图像的变化率。如将每个像素替换为该像素周围像素的均值。这样就可以平滑并替代那些强度变化明显的区域。
blurred = cv2.blur(gradient, (9, 9))
- 对模糊图像二值化。梯度图像中不大于90的任何像素都设置为0(黑色)。 否则,像素设置为255(白色)
(_, thresh) = cv2.threshold(blurred, 90, 255, cv2.THRESH_BINARY)
blurred效果
thresh效果
step3: 找出车子的轮廓。
(cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print('轮廓框的个数:{}'.format(len(cnts)))
all_box_img = copy.deepcopy(image)
cv2.drawContours(all_box_img, cnts, -1, (0, 255, 0), 1)
参数说明:
#1. cv2.findContours()函数第一个参数是要检索的图片,必须是为二值图,即黑白的(不是灰度图),所以读取的图像要先转成灰度的,再转成二值图,我们在第二步用cv2.threshold()函数已经得到了二值图。第二个参数表示轮廓的检索模式。第三个参数为轮廓的近似方法。
#2. cv2.findContours()的第二个参数是轮廓的检索模式,有以下4种:
cv2.RETR_EXTERNAL表示只检测外轮廓
cv2.RETR_LIST检测的轮廓不建立等级关系
cv2.RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。
cv2.RETR_TREE建立一个等级树结构的轮廓。
#3. cv2.findContours()的第二个参数是轮廓的近似方法,即采用多少个点来描述一个轮廓:
cv2.CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1#
cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
#4. cv2.findContours()返回的两个参数:一个是轮廓本身,还有一个是每条轮廓对应的属性,通常我们只使用第一个参数
(cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
其中cnts是一个list,通过print(len(list))可以查看共检测出多少个轮廓,如下图轮廓框的个数=16,
print(cnts[0])打印出第一个轮廓相应的描述点
step4: 在检测到的所有轮廓中选择最大的两个,并用cv2.minAreaRect求其相应的最小外接矩阵,根据外接矩阵提供的坐标,对原图裁剪
通过cv2.findContours可以找到多个轮廓,如上图轮廓框的个数=16,那么对我们裁剪来讲,小的轮廓就是一种干扰,有用的就是最大的那几个。因为不同的输入图片会检测出不同个数的轮廓,这里我们只利用了最大的两个轮廓信息。
if len(cnts)>=2:
c = sorted(cnts, key=cv2.contourArea, reverse=True)#将轮廓点从小到达排序,选择最大的两个
rect0 = cv2.minAreaRect(c[0])
rect1 = cv2.minAreaRect(c[1])
box0 = np.int0(cv2.boxPoints(rect0))
box1 = np.int0(cv2.boxPoints(rect1))
# draw a bounding box arounded the detected barcode and display the image
top2_box_img = copy.deepcopy(image)
cv2.drawContours(top2_box_img, [box0,box1], -1, (0, 255, 0), 1)
x_box0 = [i[0] for i in box0]
x_box1 = [i[0] for i in box1]
y_box0 = [i[1] for i in box0]
y_box1 = [i[1] for i in box1]
xs=x_box0+x_box1
ys=y_box0+y_box1
x1 = 0 if min(xs)< 0 else min(xs)
x2 = 0 if max(xs)< 0 else max(xs)
y1 = 0 if min(ys)< 0 else min(ys)
y2 = 0 if max(ys)< 0 else max(ys)
cropImg = image[y1:y2, x1:x2]
cv2.imshow("blurred", blurred)
cv2.imshow("gradient", gradient)
cv2.imshow("thresh", thresh)
#查看参数:
# print(box0,box1)
print('x1:{} x2:{} y1:{} y2:{}'.format(x1,x2,y1,y2))#横坐标是x,纵坐标是y
cv2.imshow("all_box_img", all_box_img)
cv2.imshow("top2_box_image1", top2_box_img)
cv2.imshow("corped_image", cropImg)
# cv2.imwrite("corped_image.jpg", image)
cv2.waitKey(0)
else:
c = sorted(cnts, key=cv2.contourArea, reverse=True) # 将轮廓点从小到达排序,选择最大的两个
rect0 = cv2.minAreaRect(c[0])
box0 = np.int0(cv2.boxPoints(rect0))
# draw a bounding box arounded the detected barcode and display the image
top2_box_img = copy.deepcopy(image)
cv2.drawContours(top2_box_img, [box0], -1, (0, 255, 0), 1)
x_box0 = [i[0] for i in box0]
y_box0 = [i[1] for i in box0]
xs = x_box0
ys = y_box0
x1 = 0 if min(xs) < 0 else min(xs)
x2 = 0 if max(xs) < 0 else max(xs)
y1 = 0 if min(ys) < 0 else min(ys)
y2 = 0 if max(ys) < 0 else max(ys)
cropImg = image[y1:y2, x1:x2]
# 查看参数:
# print(box0,box1)
print('x1:{} x2:{} y1:{} y2:{}'.format(x1, x2, y1, y2)) # 横坐标是x,纵坐标是y
cv2.imshow("all_box_img", all_box_img)
cv2.imshow("top2_box_image1", top2_box_img)
cv2.imshow("corped_image", cropImg)
# cv2.imwrite("corped_image.jpg", image)
cv2.waitKey(0)
参数说明:
1. 利用rect0=cv2.minAreaRect()函数求得包含点集最小面积的矩形,这个矩形是可以有偏转角度的,可以与图像的边界不平行。
2. box0 = np.int0(cv2.boxPoints(rect0))返回的box0是对应矩阵的4个坐标
3. 这里我们利用box0,box1的坐标信息,求出包含这两个矩形的最合理的坐标
4. cv2.drawContours(all_box_img, cnts, -1, (0, 255, 0), 1)
cv2.drawContours(top2_box_img, [box0,box1], -1, (0, 255, 0), 1)
第一个参数是指明在哪幅图像上绘制轮廓
第二个参数是是轮廓本身(.dtype=list)(如上面step3得到的cnts就是形状不规则的轮廓,step4得到的[box0,box1]就是最小外接矩阵,看上、下面图中的绿色框子)
第三个参数指定绘制轮廓list中的哪条轮廓,如果是-1,则绘制其中的所有轮廓,可以输出0,1,2等
第四个参数是轮廓线条的颜色
第五个参数是轮廓线条的粗细
完整代码
#good
import numpy as np
import cv2
import copy
def crop_single_picture(img_path):
#step1:加载图片,转成灰度图,并用Sobel算子计算x,y方向上的梯度,之后在x方向上减去y方向上的梯度,通过这个减法,我们留下具有高水平梯度和低垂直梯度的图像区域。
image = cv2.imread(img_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gradX = cv2.Sobel(gray, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradY = cv2.Sobel(gray, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=-1)
# subtract the y-gradient from the x-gradient
gradient = cv2.subtract(gradX, gradY)
gradient = cv2.convertScaleAbs(gradient)
#step2:去除图像上的噪声。
## 1.使用低通滤泼器平滑图像(9 x 9内核),这将有助于平滑图像中的高频噪声。低通滤波器的目标是降低图像的变化率。如将每个像素替换为该像素周围像素的均值。这样就可以平滑并替代那些强度变化明显的区域。
## 2.对模糊图像二值化。梯度图像中不大于90的任何像素都设置为0(黑色)。 否则,像素设置为255(白色)
# blur and threshold the image
blurred = cv2.blur(gradient, (9, 9))
(_, thresh) = cv2.threshold(blurred, 90, 255, cv2.THRESH_BINARY)
#step3:找出图像所有的轮廓
#cv2.findContours()函数第一个参数是要检索的图片,必须是为二值图,即黑白的(不是灰度图),所以读取的图像要先转成灰度的,再转成二值图,我们在第三步用cv2.threshold()函数已经得到了二值图。第二个参数表示轮廓的检索模式
#第三个参数为轮廓的近似方法
#cv2.CHAIN_APPROX_NONE存储所有的轮廓点(cnts[0]有257个点),cv2.CHAIN_APPROX_SIMPLE cnts[0]有98个点
(cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print('轮廓框的个数:{}'.format(len(cnts)))
all_box_img = copy.deepcopy(image)
cv2.drawContours(all_box_img, cnts, -1, (0, 255, 0), 1)
#step4:在图像所有的轮廓中选择最大的两个,并用cv2.minAreaRect求其相应的最小外接矩阵,根据外接矩阵提供的坐标,对原图裁剪
if len(cnts)>=2:
c = sorted(cnts, key=cv2.contourArea, reverse=True)#将轮廓点从小到达排序,选择最大的两个
rect0 = cv2.minAreaRect(c[0])
rect1 = cv2.minAreaRect(c[1])
box0 = np.int0(cv2.boxPoints(rect0))
box1 = np.int0(cv2.boxPoints(rect1))
# draw a bounding box arounded the detected barcode and display the image
top2_box_img = copy.deepcopy(image)
cv2.drawContours(top2_box_img, [box0,box1], -1, (0, 255, 0), 1)
x_box0 = [i[0] for i in box0]
x_box1 = [i[0] for i in box1]
y_box0 = [i[1] for i in box0]
y_box1 = [i[1] for i in box1]
xs=x_box0+x_box1
ys=y_box0+y_box1
x1 = 0 if min(xs)< 0 else min(xs)
x2 = 0 if max(xs)< 0 else max(xs)
y1 = 0 if min(ys)< 0 else min(ys)
y2 = 0 if max(ys)< 0 else max(ys)
cropImg = image[y1:y2, x1:x2]
cv2.imshow("blurred", blurred)
cv2.imshow("gradient", gradient)
cv2.imshow("thresh", thresh)
#查看参数:
# print(box0,box1)
print('x1:{} x2:{} y1:{} y2:{}'.format(x1,x2,y1,y2))#横坐标是x,纵坐标是y
cv2.imshow("all_box_img", all_box_img)
cv2.imshow("top2_box_image1", top2_box_img)
cv2.imshow("corped_image", cropImg)
# cv2.imwrite("corped_image.jpg", image)
cv2.waitKey(0)
else:
c = sorted(cnts, key=cv2.contourArea, reverse=True) # 将轮廓点从小到达排序,选择最大的两个
rect0 = cv2.minAreaRect(c[0])
box0 = np.int0(cv2.boxPoints(rect0))
# draw a bounding box arounded the detected barcode and display the image
top2_box_img = copy.deepcopy(image)
cv2.drawContours(top2_box_img, [box0], -1, (0, 255, 0), 1)
x_box0 = [i[0] for i in box0]
y_box0 = [i[1] for i in box0]
xs = x_box0
ys = y_box0
x1 = 0 if min(xs) < 0 else min(xs)
x2 = 0 if max(xs) < 0 else max(xs)
y1 = 0 if min(ys) < 0 else min(ys)
y2 = 0 if max(ys) < 0 else max(ys)
cropImg = image[y1:y2, x1:x2]
# 查看参数:
# print(box0,box1)
print('x1:{} x2:{} y1:{} y2:{}'.format(x1, x2, y1, y2)) # 横坐标是x,纵坐标是y
cv2.imshow("all_box_img", all_box_img)
cv2.imshow("top2_box_image1", top2_box_img)
cv2.imshow("corped_image", cropImg)
# cv2.imwrite("corped_image.jpg", image)
cv2.waitKey(0)
if __name__ == '__main__':
path = 'car.png'
crop_single_picture(path)
参考1参考2