OpenCV-Python 通过边缘检测识别物体并批量提取(大米识别为例)——minAreaRect批量生成物体的最小外接矩形(旋转矩形)并批量裁剪

OpenCV版本:4.0.0.21

算法实现思路如下:

  1. 对图像做降噪滤波处理
  2. 提取边缘
  3. 检测轮廓
  4. 检测轮廓最小外接矩形(旋转矩形)
  5. 旋转图像
  6. 裁剪

代码如下:

import cv2
import numpy as np

image = cv2.imread("rice.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)      # 转为灰度图
blurred = cv2.GaussianBlur(gray, (11, 11), 0)
edged = cv2.Canny(blurred, 30, 150)         # 用Canny算子提取边缘
(cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)      # 轮廓检测
#cv2.drawContours(image, contour, -1, (0, 255, 0), 2)    # 绘制轮廓

count = 0    # 米粒个数
margin = 5   # 裁剪边距
for i, contour in enumerate(cnts):
    ares = cv2.contourArea(contour)  # 计算包围形状的面积
    if ares < 15:  # 过滤面积小于15的形状
        continue
    count += 1
    rect = cv2.minAreaRect(contour)   # 检测轮廓最小外接矩形,得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
    box = np.int0(cv2.boxPoints(rect))   # 获取最小外接矩形的4个顶点坐标
  #  cv2.drawContours(image, [box], 0, (255, 0, 0), 2)     # 绘制轮廓最小外接矩形

    h, w = image.shape[:2]      # 原图像的高和宽
    rect_w, rect_h = int(rect[1][0]) + 1, int(rect[1][1]) + 1      # 最小外接矩形的宽和高
    if rect_w <= rect_h:
        x, y = box[1][0], box[1][1]            # 旋转中心
        M2 = cv2.getRotationMatrix2D((x, y), rect[2], 1)
        rotated_image = cv2.warpAffine(image, M2, (w * 2, h * 2))
        rotated_canvas = rotated_image[y - margin:y + rect_h + margin + 1, x - margin:x + rect_w + margin + 1]
    else:
        x, y = box[2][0], box[2][1]         # 旋转中心
        M2 = cv2.getRotationMatrix2D((x, y), rect[2] + 90, 1)
        rotated_image = cv2.warpAffine(image, M2, (w * 2, h * 2))
        rotated_canvas = rotated_image[y - margin:y + rect_w + margin + 1, x - margin:x + rect_h + margin + 1]
    print("rice #{}".format(count))
 #   cv2.imshow("rotated_canvas", rotated_canvas)
    cv2.imwrite("C:/Users/acer/Desktop/CV/RiceRecognition/rotation-results/{}.jpg".format(count), rotated_canvas)
    cv2.waitKey(0)

原图像如下:

OpenCV-Python 通过边缘检测识别物体并批量提取(大米识别为例)——minAreaRect批量生成物体的最小外接矩形(旋转矩形)并批量裁剪_第1张图片

裁剪出来的部分效果如下:

OpenCV-Python 通过边缘检测识别物体并批量提取(大米识别为例)——minAreaRect批量生成物体的最小外接矩形(旋转矩形)并批量裁剪_第2张图片

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)      # 转为灰度图
blurred = cv2.GaussianBlur(gray, (11, 11), 0)
edged = cv2.Canny(blurred, 30, 150)         # 用Canny算子提取边缘

将读取的图像转为灰度图,然后做高斯降噪滤波处理,再用Canny算子检测边缘转换为二值图,效果如下:

OpenCV-Python 通过边缘检测识别物体并批量提取(大米识别为例)——minAreaRect批量生成物体的最小外接矩形(旋转矩形)并批量裁剪_第3张图片

(cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)      # 轮廓检测
#cv2.drawContours(image, contour, -1, (0, 255, 0), 2)    # 绘制轮廓

cv2.findContours()函数可以用来检测轮廓。它的第一个参数是要检测的图像,必须是二值图,即黑白的(不是灰度图),所以读取的图像要先转成灰度的,再转成二值图。第二个参数有四种,表示轮廓的检索模式:

  • cv2.RETR_EXTERNAL表示只检测外轮廓
  • cv2.RETR_LIST检测的轮廓不建立等级关系
  • cv2.RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层
  • cv2.RETR_TREE建立一个等级树结构的轮廓

第三个参数为轮廓的近似办法:

  • cv2.CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1
  • cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息

cv2.findContours()函数有两个返回值,一个是轮廓本身,还有一个是每条轮廓对应的属性。

  1. contour返回值:第一个值返回的是list,list中每个元素都是图像中的一个轮廓,用numpy中的ndarray表示。每一个ndarray里保存的是轮廓上的各个点的坐标。
  2. hierarchy返回值:此外,该函数还可返回一个可选的hiararchy结果,这是一个ndarray,其中的元素个数和轮廓个数相同,每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0]~hierarchy[i][3],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,则该值为负数。

cv2.drawContours()函数可以在图像上绘制轮廓:

  1. 第一个参数是指明在哪幅图像上绘制轮廓
  2. 第二个参数是轮廓本身,在Python中是一个list
  3. 第三个参数指定绘制轮廓list中的哪条轮廓,如果是-1,则绘制其中的所有轮廓
  4. 第四个参数是轮廓线条的颜色
  5. 第五个参数是轮廓线条的粗细

绘制轮廓效果如下:

OpenCV-Python 通过边缘检测识别物体并批量提取(大米识别为例)——minAreaRect批量生成物体的最小外接矩形(旋转矩形)并批量裁剪_第4张图片

for i, contour in enumerate(cnts):
    ares = cv2.contourArea(contour)  # 计算包围形状的面积
    print(ares)
    if ares < 15:  # 过滤面积小于15的形状
        continue
    count += 1

因为cv2.findContours()返回的第一个值是list,list中每个元素都是图像中的一个轮廓,所以就可以用for循环对返回值进行遍历(批量处理)。cv2.contourArea()可以计算包围形状的面积,当检测的轮廓是个很小的点或者不规则时,就可以设置包围形状面积的大小过滤掉(这个函数计算的面积有时候不太准确,使用须谨慎)。

rect = cv2.minAreaRect(contour)   # 检测轮廓最小外接矩形,得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
box = np.int0(cv2.boxPoints(rect))   # 获取最小外接矩形的4个顶点坐标
#cv2.drawContours(image, [box], 0, (255, 0, 0), 2)     # 绘制轮廓最小外接矩形

cv2.minAreaRect()可以得到最小面积的矩形,这个矩形是可以有偏转角度的,可以与图像的边界不平行。返回一个Box2D结构rect:(最小外接矩形的中心(x,y),(宽,高),旋转角度θ),但是要绘制这个矩形,需要通过函数cv2.boxPoints()获得矩形的顶点坐标box,返回形式[ [x0,y0], [x1,y1], [x2,y2], [x3,y3] ]。用cv2.drawContours()绘制出来的效果如下:

OpenCV-Python 通过边缘检测识别物体并批量提取(大米识别为例)——minAreaRect批量生成物体的最小外接矩形(旋转矩形)并批量裁剪_第5张图片

这里说一下cv2.minAreaRect()和cv2.boundingRect()的区别。cv2.boundingRect()得到的是包覆轮廓的最小正矩形,cv2.minAreaRect()得到的是包覆轮廓的最小外接矩形。下图蓝色框是最小外接矩形,红色框是最小正矩形:

OpenCV-Python 通过边缘检测识别物体并批量提取(大米识别为例)——minAreaRect批量生成物体的最小外接矩形(旋转矩形)并批量裁剪_第6张图片

 裁剪最小外接矩形(旋转矩形)通常的做法是通过旋转,将矩形的四个坐标点做映射,然后求出被旋转后图像的四个点的坐标再裁剪,这个方法需要通过坐标变换求出一系列的参数似乎有点麻烦。现在来看看旋转函数cv2,getRotationMatrix2D()的参数:

  1. 旋转时固定的点

  2. 旋转角度

  3. 图片缩放尺度

那么,根据cv2,getRotationMatrix2D()的第一个参数旋转时固定的点(即旋转中心)还有cv2.boxPoints()返回矩形的四个顶点坐标,就可以以矩形的其中一个顶点坐标作为旋转中心进行旋转,然后再根据cv2.minAreaRect()函数返回的参数((宽,高),旋转角度θ)在旋转中心的坐标上加上矩形的宽和高就可以进行裁剪。(注意:这里裁剪是以矩形的左上顶点进行裁剪)

h, w = image.shape[:2]      # 原图像的长和宽
rect_w, rect_h = int(rect[1][0]) + 1, int(rect[1][1]) + 1      # 最小外接矩形的宽和高
if rect_w <= rect_h:
    x, y = box[1][0], box[1][1]            # 旋转中心
    M2 = cv2.getRotationMatrix2D((x, y), rect[2], 1)
    rotated_image = cv2.warpAffine(image, M2, (w * 2, h * 2))
    rotated_canvas = rotated_image[y - margin:y + rect_h + margin + 1, x - margin:x + rect_w + margin + 1]

因为裁剪需要的参数是整数,所以rect_w,rect_h这里设置为int类型,而int是向下取整,所以需要加1,不然裁剪的时候会损失精度。矩形旋转为竖直的时候,需要分两种情况来讨论,一是当矩形的宽<=矩形的高时,旋转矩形顺时针旋转(旋转角度θ为负),示意图如下:

OpenCV-Python 通过边缘检测识别物体并批量提取(大米识别为例)——minAreaRect批量生成物体的最小外接矩形(旋转矩形)并批量裁剪_第7张图片

 cv2,getRotationMatrix2D()返回的是个矩阵,通过cv2.warpAffine()仿射变换完成旋转,其中cv2.warpAffine()的第三个参数设置为原图的2倍,是为了防止旋转之后目标区域变换到图像外面。裁剪的时候再加1是因为切片符号取不到右边的值,所以加1。

 else:
    x, y = box[2][0], box[2][1]         # 旋转中心
    M2 = cv2.getRotationMatrix2D((x, y), rect[2] + 90, 1)
    rotated_image = cv2.warpAffine(image, M2, (w * 2, h * 2))
    rotated_canvas = rotated_image[y - margin:y + rect_w + margin + 1, x - margin:x + rect_h + margin + 1]

矩形旋转为竖直的第二种情况是,当矩形的宽>矩形的高时,旋转矩形逆时针旋转(旋转角度θ为负),示意图如下:

OpenCV-Python 通过边缘检测识别物体并批量提取(大米识别为例)——minAreaRect批量生成物体的最小外接矩形(旋转矩形)并批量裁剪_第8张图片

 当矩形的宽>矩形的高时,旋转矩形按旋转角度θ顺时针旋转为图中的绿色矩形,因为这里裁剪是以矩形的左上顶点进行裁剪,所以还需要加一个90°进行逆时针旋转为红色矩形,最后旋转矩形(蓝色)到正矩形(红色)整体看起来就是逆时针旋转。

注意:

  • 旋转角度θ是水平轴(x轴)逆时针旋转到矩形的第一条边的夹角,这个边是width,另一条边是height。这里的width与height是相对的。(特殊情况:正矩形的旋转角度θ为-0)

参考链接:

https://blog.csdn.net/u014005758/article/details/88283238

https://blog.csdn.net/weixin_39059031/article/details/103197971

https://blog.csdn.net/cliukai/article/details/102075021

https://blog.csdn.net/u014662865/article/details/105125361

https://blog.csdn.net/liqiancao/article/details/55670749

https://blog.csdn.net/lanyuelvyun/article/details/76614872

https://blog.csdn.net/chen134225/article/details/80899575

你可能感兴趣的:(OpenCV-Python,opencv,计算机视觉,cv,python)