基于python+opencv的图像目标区域自动提取(本项目为提取纸张中的内容)

要点:
该教程为基于python+opencv的图像目标区域自动提取,实现自动提取一张照片中的纸张内容
环境配置:
Wn10+CPU i7-6700
Pycharm2018
opencv-python 3.4.2.17
numpy 1.14.5
笔者信息:Next_Legend QQ:1219154092 人工智能 自然语言处理 图像处理 神经网络
——2018.8.12于天津大学

该项目的代码在笔者的资源仓库中,代码地址:
基于python+opencv的图像目标区域自动提取


一、项目背景

一张照片中的感兴趣区域总是沿着x,y,z三个轴都有一定倾斜(如下图),要想把照片翻转到平行位置,需要进行透视变换,而透视变换需要同一像素点变换前后的坐标。由此可以想到,提取矩形区域四个角的坐标作为变换前的坐标,变换后的坐标可以设为照片的四个角落,经过投影变换,矩形区域将会翻转并充满图像。
因此我们要解决的问题变为:提取矩形的四个角落、进行透视变换。基于python+opencv的图像目标区域自动提取(本项目为提取纸张中的内容)_第1张图片

二、提取矩形角落坐标

矩形的检测主要是提取边缘,图片显示部分的亮度通常高于周围环境,我们可以将图片阈值化,将图片部分与周围环境明显的分别开来,这对后边的边缘检测非常有帮助。
检测矩形并提取坐标需要对图像进行预处理、边缘检测、提取轮廓、检测凸包、角点检测。

1、预处理转为灰度图

由于手机拍摄的照片像素可能会很高,为了加快处理速度,我们首先将图像转化为灰度图

image = cv2.imread(Config.src)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
srcWidth, srcHeight, channels = image.shape
print(srcWidth, srcHeight)

2、中值滤波

binary = cv2.medianBlur(gray,7)

3、转化为二值图像

ret, binary = cv2.threshold(binary, Config.threshold_thresh, 255, cv2.THRESH_BINARY)
cv2.imwrite("1-threshold.png", binary, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])

此时图片已经变成了这个样子:
基于python+opencv的图像目标区域自动提取(本项目为提取纸张中的内容)_第2张图片
可见纸张页面部分已经与背景环境分离开来。

4、边缘检测与轮廓处理

我们用Canny算子边缘检测,提取轮廓

# canny提取轮廓
binary = cv2.Canny(binary, 0, 60, apertureSize = 3)
cv2.imwrite("3-canny.png", binary, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])

基于python+opencv的图像目标区域自动提取(本项目为提取纸张中的内容)_第3张图片
提取轮廓后,拟合外接多边形(矩形)

# 提取轮廓后,拟合外接多边形(矩形)
_,contours,_ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print("len(contours)=%d"%(len(contours)))

5、提取面积最大的轮廓并用多边形将轮廓包围

for idx,c in enumerate(contours):
    if len(c) < Config.min_contours:
        continue

    epsilon = Config.epsilon_start
    while True:
        approx = cv2.approxPolyDP(c,epsilon,True)
        print("idx,epsilon,len(approx),len(c)=%d,%d,%d,%d"%(idx,epsilon,len(approx),len(c)))
        if (len(approx) < 4):
            break
        if math.fabs(cv2.contourArea(approx)) > Config.min_area:
            if (len(approx) > 4):
                epsilon += Config.epsilon_step
                print("epsilon=%d, count=%d"%(epsilon,len(approx)))
                continue
            else:
                #for p in approx:
                #    cv2.circle(binary,(p[0][0],p[0][1]),8,(255,255,0),thickness=-1)
                approx = approx.reshape((4, 2))
                # 点重排序, [top-left, top-right, bottom-right, bottom-left]
                src_rect = order_points(approx)

                cv2.drawContours(image, c, -1, (0,255,255),1)
                cv2.line(image, (src_rect[0][0],src_rect[0][1]),(src_rect[1][0],src_rect[1][1]),color=(100,255,100))
                cv2.line(image, (src_rect[2][0],src_rect[2][1]),(src_rect[1][0],src_rect[1][1]),color=(100,255,100))
                cv2.line(image, (src_rect[2][0],src_rect[2][1]),(src_rect[3][0],src_rect[3][1]),color=(100,255,100))
                cv2.line(image, (src_rect[0][0],src_rect[0][1]),(src_rect[3][0],src_rect[3][1]),color=(100,255,100))

                # 获取最小矩形包络
                rect = cv2.minAreaRect(approx)
                # rect = cv2.maxAreaRect(approx)
                box = cv2.boxPoints(rect)
                box = np.int0(box)
                box = box.reshape(4,2)
                box = order_points(box)
                print("approx->box")
                print(approx)
                print(src_rect)
                print(box)
                w,h = point_distance(box[0],box[1]), \
                      point_distance(box[1],box[2])
                print("w,h=%d,%d"%(w,h))

6、 透视变换

dst_rect = np.array([
                    [0, 0],
                    [w - 1, 0],
                    [w - 1, h - 1],
                    [0, h - 1]],
                    dtype="float32")
                M = cv2.getPerspectiveTransform(src_rect, dst_rect)
                warped = cv2.warpPerspective(image, M, (w, h))
                cv2.imwrite("transfer%d.png"%idx, warped, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
                break

总结

本项目利用了照片背景亮度较高的特点,通过二值化突出轮廓提取坐标,进行透视变换。但是局限性在于,如果矩形的亮度与背景相差不大,就很难用这种方法检测到轮廓还需要算法优化。该项目的代码在笔者的资源仓库中,代码地址:
基于python+opencv的图像目标区域自动提取

你可能感兴趣的:(python,图像处理)