简介
使用OpenCV进行实时视频流中的四边形区域抠图主要涉及到图像处理和计算机视觉中的几个关键概念:轮廓检测、多边形近似、透视变换和图像掩码。这个算法的目标是从视频流中实时检测出四边形区域,并将该区域从背景中分离出来,以便进行进一步的处理或分析。
算法原理
1. 图像获取
首先,使用OpenCV的cv2.imread
函数获取图片。
# 灰度图
image=cv2.imread(r'test_01.png')
2. 图像预处理
对于每一帧图像,首先将其从BGR颜色空间转换为灰度图,因为灰度图足以进行边缘检测,且计算量更小。然后,对灰度图像应用高斯模糊,以减少图像噪声和细节,这有助于后续的边缘检测。
contours_img=image.copy()
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
# 高斯模糊,以减少图像噪声
blurred=cv2.GaussianBlur(gray,(5,5),0)
3. 边缘检测
使用Canny算法对模糊后的图像进行边缘检测。Canny算法是一种流行的边缘检测方法,它通过高斯滤波、梯度计算、非极大值抑制和双阈值检测来识别图像中的边缘。
# 边缘检测算法来检测图像中的边缘
edged=cv2.Canny(blurred,75,200)
cv_show('edged',edged)
4. 轮廓检测
使用cv2.findContours
函数检测边缘图像中的轮廓。这个函数返回图像中的所有轮廓,每个轮廓都是一个点集,这些点大致描述了轮廓的形状。
#找到最大轮廓,进行绘制
cnts=cv2.findContours(edged.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)
cv_show('contours_img',contours_img)
5. 轮廓筛选和排序
由于我们感兴趣的是四边形区域,因此需要对检测到的轮廓进行筛选。通常,这可以通过计算轮廓的面积并选择面积超过某个阈值的轮廓来实现。然后,根据面积对轮廓进行排序,选择最大的轮廓进行进一步处理。
doccnt=None
cnts=sorted(cnts,key=cv2.contourArea,reverse=True)
for c in cnts:
# 计算轮廓的周长
peri=cv2.arcLength(c,True)
# 近似轮廓为多边形
approx=cv2.approxPolyDP(c,0.02*peri,True)
# 检查多边形是否为四边形
if len(approx)==4:
doccnt=approx
break
6. 透视变换
一旦找到了四边形区域,就需要将其“平整”。这通过透视变换(又称为四点变换)实现,它是一种映射,可以将图像中的一个四边形区域映射到一个新的平面上。使用cv2.getPerspectiveTransform
和cv2.warpPerspective
函数来计算透视变换矩阵,并将图像中的四边形区域变换为正面视角。
# 透视变换
warped_t=four_point_transform(image,doccnt.reshape(4,2))
warped_new=warped_t.copy()
cv_show('warped',warped_t)
#二值化处理
warped=cv2.cvtColor(warped_t,cv2.COLOR_BGR2GRAY)
thresh=cv2.threshold(warped,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
thresh_contours=thresh.copy()
# 寻找轮廓
cnts=cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
warped_contours=cv2.drawContours(warped_t,cnts,-1,(0,255,0),1)
cv_show('warped_contours',warped_contours)
7. 在二值化图像中找到所有可能的问题轮廓,运用图像掩码和抠图
在透视变换后,我们得到了一个“平整”的四边形区域图像。为了得到这个区域的清晰图像,可以创建一个掩码,然后使用cv2.bitwise_and
函数将原始图像与掩码进行与运算,从而只保留四边形区域内的像素。
correct=0
for (q,i) in enumerate(np.arange(0,len(question),5)):
cnts=sort_contours(question[i:i+5])[0]
bubbled=None
for (j,c) in enumerate(cnts):
mask=np.zeros(thresh.shape,dtype='uint8')
cv2.drawContours(mask,[c],-1,255,-1)
thresh_mask_and=cv2.bitwise_and(thresh,thresh,mask=mask)
cv_show('thresh_mask_and',thresh_mask_and)
total=cv2.countNonZero(thresh_mask_and)
if bubbled is None or total>bubbled[0]:
bubbled=(total,j)
color=(0,0,255)
k=amkey[q]
if k==bubbled[1]:
color=(0,255,0)
correct+=1
cv2.drawContours(warped_new,[cnts[k]],-1,color,3)
cv_show('warpeding',warped_new)
8.显示结果
score=(correct/5.0)*100
print('{:.2f}%'.format(score))
cv2.putText(warped_new,'{:.2f}%'.format(score),(10,30),cv2.FONT_HERSHEY_SIMPLEX,0.9,(0,0,255),2)
cv2.imshow('exam',warped_new)
cv2.waitKey(0)
运行结果
完整代码
import numpy as np
import cv2
amkey={0:1,1:4,2:0,3:3,4:1}
def cv_show(name,img):
cv2.imshow(name,img)
cv2.waitKey(0)
# # 按下ESC键(ASCII码为27),则跳出循环
# if k==27:
# break
def order_points(pts):
rect = np.zeros((4,2),dtype='float32') # 创建一个4x2的数组,指定了数组中元素的数据类型为32位浮点数
# 按顺序找到对应坐标0123分别是左上、右上、右下、左下
s = pts.sum(axis=1) # 对pts矩阵的每一行进行求和操作
rect[0] = pts[np.argmin(s)]#左上角的点
rect[2] = pts[np.argmax(s)]#右下角的点
diff = np.diff(pts,axis=1) # 对pts矩阵的每一行进行差值
rect[1] = pts[np.argmin(diff)]#右上角的点
rect[3] = pts[np.argmax(diff)]#左下角的点
return rect
def four_point_transform(image,pts):
# 获取输入坐标点
rect = order_points(pts)
(tl,tr,br,bl) = rect#tl 是左上角点,tr 是右上角点,br 是右下角点,bl 是左下角点。
# 计算两个可能的宽度,取这两个宽度的最大值作为新图像的宽度。
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1]-bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1]-tl[1]) ** 2))
maxWidth = max(int(widthA) , int(widthB))
## 计算两个可能的高度,取这两个高度的最大值作为新图像的高度。
heightA = np.sqrt(((tr[0]-br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0]-bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA),int(heightB))
# 变换后对应坐标位置
dst = np.array([[0,0],
[maxWidth-1,0],
[maxWidth-1,maxHeight-1],
[0,maxHeight-1]],dtype='float32')
# 图像透视变换 cv2.getPerspectiveTransform(src,dst[,solveMethod])→ MP获得转换之间的关系
# cy2.warpPerspective(src, Mp, dsizel, dstl, flagsl, borderModel, borderValue]]1])- dst
# #参数说明:
# src:变换前图像四边形顶点坐标/第2个是原图
# MP:透视变换矩阵,3行3列
# dsize:输出图像的大小,二元元组(width,heiqht)
M = cv2.getPerspectiveTransform(rect,dst)#计算透视变换矩阵 M,它描述了如何将原始图像中的点映射到目标坐标。
warped = cv2.warpPerspective(image,M,(maxWidth,maxHeight))#透视变换
# 返回变换后的结果
return warped
def sort_contours(cnts,method='left-to-right'):
reverse=False
i=0
if method=='right-to-left' or method=='bottom-to-top':
reverse=True
if method=='top-to-bottom' or method == 'bottom-to-top':
i=1
boundingBoxes=[cv2.boundingRect(c) for c in cnts]
(cnts, boundingBoxes) = zip(*sorted(zip(cnts,boundingBoxes),key=lambda b:b[1][i],reverse=reverse))
return cnts,boundingBoxes
# 灰度图
image=cv2.imread(r'test_01.png')
contours_img=image.copy()
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
# 高斯模糊,以减少图像噪声
blurred=cv2.GaussianBlur(gray,(5,5),0)
# 边缘检测算法来检测图像中的边缘
edged=cv2.Canny(blurred,75,200)
cv_show('edged',edged)
#找到最大轮廓,进行绘制
cnts=cv2.findContours(edged.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)
cv_show('contours_img',contours_img)
doccnt=None
cnts=sorted(cnts,key=cv2.contourArea,reverse=True)
for c in cnts:
# 计算轮廓的周长
peri=cv2.arcLength(c,True)
# 近似轮廓为多边形
approx=cv2.approxPolyDP(c,0.02*peri,True)
# 检查多边形是否为四边形
if len(approx)==4:
doccnt=approx
break
# 透视变换
warped_t=four_point_transform(image,doccnt.reshape(4,2))
warped_new=warped_t.copy()
cv_show('warped',warped_t)
#二值化处理
warped=cv2.cvtColor(warped_t,cv2.COLOR_BGR2GRAY)
thresh=cv2.threshold(warped,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
thresh_contours=thresh.copy()
# 寻找轮廓
cnts=cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
warped_contours=cv2.drawContours(warped_t,cnts,-1,(0,255,0),1)
cv_show('warped_contours',warped_contours)
question=[]
for c in cnts:
(x,y,w,h)=cv2.boundingRect(c)
ar=w/float(h)
if w>=20 and h>=20 and ar>=0.9 and ar<=1.1:
question.append(c)
question=sort_contours(question,method='top-to-bottom')[0]
correct=0
for (q,i) in enumerate(np.arange(0,len(question),5)):
cnts=sort_contours(question[i:i+5])[0]
bubbled=None
for (j,c) in enumerate(cnts):
mask=np.zeros(thresh.shape,dtype='uint8')
cv2.drawContours(mask,[c],-1,255,-1)
thresh_mask_and=cv2.bitwise_and(thresh,thresh,mask=mask)
cv_show('thresh_mask_and',thresh_mask_and)
total=cv2.countNonZero(thresh_mask_and)
if bubbled is None or total>bubbled[0]:
bubbled=(total,j)
color=(0,0,255)
k=amkey[q]
if k==bubbled[1]:
color=(0,255,0)
correct+=1
cv2.drawContours(warped_new,[cnts[k]],-1,color,3)
cv_show('warpeding',warped_new)
score=(correct/5.0)*100
print('{:.2f}%'.format(score))
cv2.putText(warped_new,'{:.2f}%'.format(score),(10,30),cv2.FONT_HERSHEY_SIMPLEX,0.9,(0,0,255),2)
cv2.imshow('exam',warped_new)
cv2.waitKey(0)
结论
本实验展示了使用OpenCV进行图像中四边形区域的透视变换和答案评分的可行性。通过简单的图像处理和透视变换,可以有效地从复杂背景中提取目标区域,并对其进行分析和评分。该技术在自动化测试评分等领域具有广泛的应用前景。