直接上效果图,看你是否需求

  1. 项目要求:给定图片,自动检测图中物体,并裁剪。
  2. 文章底部附上整个项目的代码,只需改动main函数的输入图像地址,就可得到本文陈列的所有效果图。
  3. 代码详细讲解方便阅读


    左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效果

Android 照片识别自动裁剪 自动识别裁剪图片_灰度


step2: 去除图像上的噪声。

  1. 使用低通滤泼器平滑图像(9 x 9内核),这将有助于平滑图像中的高频噪声。低通滤波器的目标是降低图像的变化率。如将每个像素替换为该像素周围像素的均值。这样就可以平滑并替代那些强度变化明显的区域。
blurred = cv2.blur(gradient, (9, 9))
  1. 对模糊图像二值化。梯度图像中不大于90的任何像素都设置为0(黑色)。 否则,像素设置为255(白色)
(_, thresh) = cv2.threshold(blurred, 90, 255, cv2.THRESH_BINARY)

blurred效果

Android 照片识别自动裁剪 自动识别裁剪图片_轮廓线_02


thresh效果

Android 照片识别自动裁剪 自动识别裁剪图片_轮廓线_03


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)

Android 照片识别自动裁剪 自动识别裁剪图片_轮廓线_04


参数说明:

#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等
第四个参数是轮廓线条的颜色
第五个参数是轮廓线条的粗细

Android 照片识别自动裁剪 自动识别裁剪图片_Android 照片识别自动裁剪_05


Android 照片识别自动裁剪 自动识别裁剪图片_Android 照片识别自动裁剪_06


完整代码

#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