利用轮廓检测的试题框自动切分
一、轮廓检测
#coding:utf8
import cv2
import numpy as np
轮廓检测要点:
先将图片转化为灰度图像,cv2.canny 算子要求输入的是单通道图片
rect=cv2.minAreaRect© rect:(最小外接矩形的中心(x,y),(宽度,高度),旋转角度)1
1https://www.cnblogs.com/zhangzhihui/p/12524608.html
def contour(img,limit_range):
#检测轮廓
gray=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
canny = cv2.Canny(gray,50,150,apertureSize=3)#apertureSize是sobel算子大小,只能为1,3,5,7
contours, hier = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contour_num=0
x1_list=[]
box_list=[]
for c in contours: #遍历轮廓
flag=True
rect = cv2.minAreaRect(c) #生成最小外接矩形
box_ = cv2.boxPoints(rect)
h=max(rect[1])
w=min(rect[1])
box = cv2.boxPoints(rect) # 计算最小面积矩形的坐标
box = np.int0(box) # 将坐标规范化为整数
angle = rect[2] #获取矩形相对于水平面的角度
if angle > 0:
if abs(angle) > 45:
angle = 90 - abs(angle)
else:
if abs(angle) > 45:
angle = (90 - abs(angle))
#limit_range=np.array(limit_range)
[h2,w2,h1,w1]=limit_range
#只保留需要的轮廓
if (h > h2 or w > w2): #3000 2200
continue
if (h < h1 or w < w1): #800 700
continue
img_h=img.shape[0]
img_w=img.shape[1]
if(max(box[:,1])>img_h or min(box[:,1])<0 or max(box[:,0])>img_w or min(box[:,0])<0): #是否越界
continue
##判断是否已经画过相似检测框
x1=min(box[:,0]) #利用左上角点X判断
for x in x1_list:
if abs(x1-x)<150: #存在位置相似轮廓
flag=False
if flag==False:
continue
else:
x1_list.append(x1)
box_list.append(box)
# 绘制矩形
cv2.drawContours(img, [box], 0, (255, 0, 255), 3)
contour_num+=1
print("符合要求轮廓数量",contour_num)
cv2.namedWindow('lunkuo_img',cv2.WINDOW_NORMAL)
cv2.imshow('lunkuo_img',img)
return img,box_list
二、直线检测
问题描述:在试卷扫描时存在质量不高情况,试卷框的断断续续不连贯,甚至某侧框线缺失,如左图所示。
解决方法:利用霍夫变换进行直线检测,结合其他信息人为画出试题框再进行轮廓检测,如右图所示。
核心代码:
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize=3)
lines = cv2.HoughLines(edges,1,np.pi/180*90,500) #函数将通过步长为1的半径和步长为π/180的角来搜索所有可能的直线,当有500个像素在同一线上认为是线
lines = cv2.HoughLinesP(edges, 1,np.pi / 180, 500, minLineLength=150,maxLineGap=30)
HoughLines是霍夫直线检测,HoughLinesP函数是基于概率的霍夫概率直线检测。由于我们需要把断续的试卷框连成一条线,采用HoughLinesP函数。关键参数maxLineGap,通过调节该参数即要尽量将断续框线连起来同时避免和非框线检测为一条线。
其他
需要特别说明,HoughLines返回一个[*,1,2]数组,*是检测出直线的数量。可以通过以下代码获取直线参数。
for line in lines:
print(line)
rho,theta = line[0] #获取极值ρ长度和θ角度
a = np.cos(theta) #获取角度cos值
b = np.sin(theta) #获取角度sin值
x0 = a * rho #获取x轴值
y0 = b * rho #获取y轴值 x0和y0是直线的中点
x1 = int(x0 + 1000*(-b)) #获取这条直线最大值点x1
y1 = int(y0 + 1000*(a)) #获取这条直线最大值点y1
x2 = int(x0 - 1000 * (-b)) #获取这条直线最小值点x2
y2 = int(y0 - 1000 * (a)) #获取这条直线最小值点y2 其中*1000是内部规则
HoughLinesP返回一个[*,1,4]的数组,*是检测出直线的数量。[1,4]中参数分别为一条直线起始点的坐标x1,y1和结束点的坐标x2,y2。
整合函数代码
def line_detection(image):
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize=3)
lines = cv2.HoughLinesP(edges, 1,np.pi / 180, 500, minLineLength=150,maxLineGap=30)
#需要判断line是否为空
if len(lines)!=0:
lines1 = lines[:,0,:]
else: return -1
#找出边框四条边坐标
##找出不大于60°竖线最左最右像素点x值
min_x1=99999
max_x2=0
##找出检测到所有直线最上最下像素点y值
arr=np.hstack((lines1[:,1],lines1[:,3]))
min_y1=min(arr)
max_y2=max(arr)
for line in lines:
x1,y1,x2,y2 = line[0]
angle=abs((y2-y1) / (x2-x1))
if angle>cmath.sqrt(3): #大于60°的竖线
if y2>max_y2: max_y2=y2
if x1<min_x1: min_x1=x1
if y1<min_y1: min_y1=y1
if x2>max_x2: max_x2=x2
cv2.line(image,(min_x1,min_y1),(max_x2,min_y1),(0,0,255),2) #画最上面直线
cv2.line(image,(min_x1,max_y2),(max_x2,max_y2),(0,0,255),2) #画最下面直线
cv2.line(image,(max_x2,min_y1),(max_x2,max_y2),(0,0,255),2) #画最右边直线
cv2.line(image,(min_x1,min_y1),(min_x1,max_y2),(0,0,255),2) #画最左边直线
return image
说明:(0,0,255)代表红色。
三、裁剪
问题描述:从试卷中剪出试题框中内容。
def cut(img, box):
#从轮廓出裁剪图片
#x1, y1 #获取左上角坐标
#x2, y2 #获取右下角坐标
x1=min(box[:,0])
y1=min(box[:,1])
x2=max(box[:,0])
y2=max(box[:,1])
img_cut = img[y1:y2, x1:x2,:] #切片裁剪图像
return img_cut