Python+Opencv答題卡識(shí)別用例詳解
使用Python3和Opencv識(shí)別一張標(biāo)準(zhǔn)的答題卡。大致的過程如下:
1.讀取圖片
2.利用霍夫圓檢測,檢測出四個(gè)角的黑圓位置,從確定四個(gè)角的位置
3.利用透視變換和四個(gè)角的位置,矯正圖片(直接用的網(wǎng)上的圖片,沒有拍照,所以這一步?jīng)]有實(shí)現(xiàn))
4.裁剪四個(gè)邊框,獲取邊框上小黑格的位置
5.根據(jù)小黑格的位置確定每個(gè)涂卡區(qū)域的位置
6.將答題卡腐蝕和膨脹,遍歷所有的格子的區(qū)域,計(jì)算每個(gè)區(qū)域內(nèi)像素值為0的個(gè)數(shù),若數(shù)量達(dá)到某個(gè)值,那么就確認(rèn)這個(gè)格子是被黑筆涂過,并記錄該位置的題目選項(xiàng)。
具體的實(shí)現(xiàn)
一、讀取圖片,用是imread函數(shù)。


二、利用霍夫圓檢測位置,這里注意的是HoughCircles的param參數(shù),調(diào)的不準(zhǔn),所以檢測出來還有其他的干擾圓,最后可以通過設(shè)置半徑閾值,將四個(gè)角的圓篩選出來。最后這個(gè)函數(shù)返回四個(gè)圓的位置和半徑。
#檢測圖中的圓,并返回每個(gè)圓的位置和半徑
def detect_circles_demo(image):
? ? temp = image.copy()
? ? rows,cols,channels = temp.shape
? ? print(rows,cols)
? ? location_vcol = []
? ? dst = cv.pyrMeanShiftFiltering(image,10,100)
? ? cimage = cv.cvtColor(dst,cv.COLOR_BGR2GRAY)
? ? #不同的圖片,Parmal的值是不一樣的
? ? circles = cv.HoughCircles(cimage,cv.HOUGH_GRADIENT,1,20,param1= 27,param2=30,minRadius=0,maxRadius=0)
? ? circles = np.uint16(np.around(circles))
? ? for i in circles[0,:]:
? ? ? ? if i[2] < 20 and i[2] > 10:
? ? ? ? ? ? cv.circle(image,(i[0],i[1]),i[2],(0,0,255),2)
? ? ? ? ? ? location_vcol.append((i[0],i[1],i[2]))
? ? # 畫出圖中圓的位置
? ? cv.imshow("image",image)
? ? return location_vcol

三、先將圖片整體二直化,再在二直化的圖片將四個(gè)邊框裁剪下來,直接根據(jù)四個(gè)邊角的位置向外或者向里增加或者較少一個(gè)半徑,確定邊框矩形的對(duì)角位置。
#獲取四個(gè)邊角的位置
location_RT = location_vcol[0]
location_RB = location_vcol[1]
location_LT = location_vcol[2]
location_LB = location_vcol[3]
?
# 先將原圖二值化,注意閾值的取范圍,再將二值化圖片轉(zhuǎn)換成BGR,方便后面標(biāo)出小方格的位置
gray = cv.cvtColor(temp, cv.COLOR_BGR2GRAY)
ret, binary = cv.threshold(gray, 100, 255, cv.THRESH_BINARY_INV)
Matimage = cv.cvtColor(binary, cv.COLOR_GRAY2BGR)
?cv.imshow("Matimage",Matimage)
?
#將圖片的四條邊裁剪出來
?roilLeft = Matimage[location_LT[1] - location_LT[2]:location_LB[1] + location_LB[2],location_LT[0] -location_LT[2]:location_LB[0] + location_LB[2]]
?roilRight = Matimage[location_RT[1] - location_RT[2]:location_RB[1] + location_RB[2],location_RT[0] - location_RT[2]:location_RB[0]+location_RB[2]]
?roilTop = Matimage[location_LT[0] - location_LT[2]:location_RT[1]+location_RT[2],location_LT[0] - location_LT[2]:location_RT[0] +location_RT[2]]
?roilBottom = Matimage[location_RB[1]-location_RB[2]:location_RB[1] + location_RB[2],location_LB[0] -location_LB[2]:location_RB[0]+location_RB[2]]
?
# 展示四條邊的圖片
cv.imshow("left",roilLeft)
cv.imshow("right", roilRight)
cv.imshow("top", roilTop)
cv.imshow("bottom", roilBottom)
四、根據(jù)裁剪的邊框確定邊框上小白格的位置。每個(gè)方位的計(jì)算方式是一樣的。每個(gè)函數(shù)返回的是小格子在源圖中的位置。
先來說底部的。這個(gè)過程是把截圖看成一個(gè)坐標(biāo)系,以行長為橫坐標(biāo),列高為中坐標(biāo),計(jì)算統(tǒng)計(jì)每個(gè)橫坐標(biāo)下,有多少個(gè)點(diǎn)是白色的,并將該數(shù)值存放于列表vcol中。根據(jù)這種算法,能夠得出這個(gè)列表中應(yīng)該是rows個(gè)元素,而且每個(gè)元素的值不超過列高,不低于0。
遍歷vcol列表,如果某一個(gè)位置為0,后一個(gè)位置不為0,那么這個(gè)位置就是小白格的起始位置。并記錄起始位置在原圖中的位置,方便后期遍歷整個(gè)涂卡區(qū)域。
其他的三個(gè)方向是同樣的道理,
#計(jì)算底部小黑格的位置
def sure_bottom(roilBottom,src,a):
? ? rows, cols, channels = roilBottom.shape
? ? src_rows,src_cols,src_channels = src.shape
? ? itemp = 0
? ? vcol = []
? ? bottom_vcol = []
? ? for i in range(0, cols):
? ? ? ? for j in range(0, rows):
? ? ? ? ? ? if roilBottom[j, i][0] == 255 and roilBottom[j, i][1] == 255 and roilBottom[j, i][2] == 255:
? ? ? ? ? ? ? ? itemp = itemp + 1
? ? ? ? vcol.append(itemp)
? ? ? ? itemp = 0
? ? for i in range(vcol.__len__()):
? ? ? ? if 0 == vcol[i] and vcol[i + 1] > 0:
? ? ? ? ? ? cv.line(roilBottom, (i, 0), (i, rows), (0, 0, 255), 2)
? ? ? ? ? ? cv.line(src, (i + a, src_rows), (i + a, src_rows - rows), (0, 255, 0), 2)
? ? ? ? ? ? bottom_vcol.append(i + a)
? ? cv.imshow("src-1", roilBottom)
? ? return bottom_vcol
?
#計(jì)算右部小黑格的位置
def sure_Right(roilRight,src,a):
? ? rows, cols, channels = roilRight.shape
? ? src_rows, src_cols, src_channels = src.shape
? ? itemp = 0
? ? vcol = []
? ? right_vcol = []
? ? for i in range(0, rows):
? ? ? ? for j in range(0, cols):
? ? ? ? ? ? if roilRight[i, j][0] == 255 and roilRight[i, j][1] == 255 and roilRight[i, j][2] == 255:
? ? ? ? ? ? ? ? itemp = itemp + 1
? ? ? ? vcol.append(itemp)
? ? ? ? itemp = 0
? ? print(vcol)
? ? for i in range(vcol.__len__()):
? ? ? ? if vcol[i] == 0 and vcol[i + 1] > 0:
? ? ? ? ? ? cv.line(roilRight, (0, i), (cols, i), (0, 0, 255), 2)
? ? ? ? ? ? cv.line(src, (src_cols - cols, i + a ), (src_cols, i + a), (0, 0, 255), 2)
? ? ? ? ? ? right_vcol.append(i + a)
? ? cv.imshow("src", src)
? ? return right_vcol
?
#計(jì)算左邊小黑格的位置
def sure_Left(roilLeft,src,a):
? ? rows, cols, channels = roilLeft.shape
? ? print(rows, cols)
? ? itemp = 0
? ? vcol = []
? ? left_vcol = []
? ? for i in range(0, rows):
? ? ? ? for j in range(0, cols):
? ? ? ? ? ? if roilLeft[i, j][0] == 255 and roilLeft[i, j][1] == 255 and roilLeft[i, j][2] == 255:
? ? ? ? ? ? ? ? itemp = itemp + 1
? ? ? ? vcol.append(itemp)
? ? ? ? itemp = 0
? ? for i in range(vcol.__len__()):
? ? ? ? if vcol[i] == 0 and vcol[i + 1] > 0:
? ? ? ? ? ? cv.line(roilLeft, (0, i), (cols, i), (0, 0, 255), 2)
? ? ? ? ? ? cv.line(src, (16, i + a), (16 + cols, i + a), (0, 0, 255), 2)
? ? ? ? ? ? left_vcol.append(i + a)
? ? cv.imshow("src", src)
? ? return left_vcol
?
# 確定頂部小黑格的位置
def sure_Top(roilTop,src,a):
? ? rows, cols, channels = roilTop.shape
? ? src_rows, src_cols, src_channels = src.shape
? ? itemp = 0
? ? vcol = []
? ? top_vcol = []
? ? for i in range(0, cols):
? ? ? ? for j in range(0, rows):
? ? ? ? ? ? if roilTop[j, i][0] == 255 and roilTop[j, i][1] == 255 and roilTop[j, i][2] == 255:
? ? ? ? ? ? ? ? itemp = itemp + 1
? ? ? ? vcol.append(itemp)
? ? ? ? itemp = 0
? ? for i in range(vcol.__len__()):
? ? ? ? if vcol[i] == 0 and vcol[i + 1] > 0:
? ? ? ? ? ? cv.line(roilTop, (i, 0), (i, rows), (0, 0, 255), 2)
? ? ? ? ? ? cv.line(src, (i + a, 12), (a + i,48), (0, 0, 255), 2)
? ? ? ? ? ? top_vcol.append(i + a)
? ? cv.imshow("src", src)
? ? return top_vcol
?
#獲取原圖位置
? ? bottom_vcol = sure_bottom(roilBottom,temp,location_LB[0] -location_LB[2])
? ? right_vcol = sure_Right(roilRight,temp,location_RT[1] - location_RT[2])
? ? left_vcol = sure_Left(roilLeft,temp,location_LT[1] - location_LT[2])
? ? top_vcol = sure_Top(roilTop,temp,location_LT[0] - location_LT[2])

五、在原圖中全出每個(gè)小格子。在此之前要將圖片做個(gè)預(yù)處理,腐蝕和膨脹。這個(gè)程序先圈出來的是答題區(qū)域的小格子,所以i和j 的范圍分別是底部格子數(shù)和右邊格子數(shù)。
先將每個(gè)格子的區(qū)域找出來,然后遍歷這個(gè)格子的全部像素,判斷是否為0 ,如果為0,那么就isum+1,最后遍歷結(jié)束,看看isum的個(gè)數(shù)有沒有達(dá)到總像素個(gè)數(shù)的10%,如果達(dá)到,那么就可以判定這個(gè)區(qū)域是被涂過的,記錄這個(gè)區(qū)域?qū)?yīng)的題號(hào)和選項(xiàng),以備后面計(jì)分。
dst = sure_if_fill(temp)
? ? for i in range(0,20):
? ? ? ? for j in range(10,26):
? ? ? ? ? ? rect = dst[bottom_vcol[i]:bottom_vcol[i] + 9,right_vcol[i]:right_vcol[j] + 3]
? ? ? ? ? ? rect_up = (bottom_vcol[i],right_vcol[j])
? ? ? ? ? ? rect_down = (bottom_vcol[i] + 9,right_vcol[j] + 3)
?
? ? ? ? ? ? # 判斷ROI區(qū)域是否被填充
? ? ? ? ? ? isum = 0
? ? ? ? ? ? for ii in range(rect.shape[0]):
? ? ? ? ? ? ? ? for jj in range(rect.shape[1]):
? ? ? ? ? ? ? ? ? ? if dst[ii,jj] == 0:
? ? ? ? ? ? ? ? ? ? ? ? isum = isum + 1
? ? ? ? ? ? # if isum > 0.1 * rect.shape[0] * rect.shape[1]:
? ? ? ? ? ? cv.rectangle(temp,rect_down,rect_up,(0,255,0),2)
? ? ? ? ? ? isum = 0
? ? cv.imshow("dst",temp)
?
?
#檢查空格是否被填充
def sure_if_fill(image):
? ? temp = image.copy()
? ? gray = cv.cvtColor(temp,cv.COLOR_BGR2GRAY)
? ? ret,binary = cv.threshold(gray,100,255,cv.THRESH_BINARY)
? ? kernel = cv.getStructuringElement(cv.MORPH_RECT,(3,3))
? ? dst = cv.erode(binary,kernel= kernel)
? ? cv.imshow("dst",dst)
? ? return dst這是腐蝕后的圖片

這是圈出所有答題區(qū)域的圖片

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Python接口自動(dòng)化淺析如何處理動(dòng)態(tài)數(shù)據(jù)
- Python接口自動(dòng)化淺析如何處理接口依賴
- python接口自動(dòng)化測試數(shù)據(jù)和代碼分離解析
- python中playwright結(jié)合pytest執(zhí)行用例的實(shí)現(xiàn)
- Python教程之pytest命令行方式運(yùn)行用例
- Python接口自動(dòng)化之文件上傳/下載接口詳解
- python+pytest接口自動(dòng)化之token關(guān)聯(lián)登錄的實(shí)現(xiàn)
- python+pytest接口自動(dòng)化之日志管理模塊loguru簡介
- Python自動(dòng)化實(shí)戰(zhàn)之接口請(qǐng)求的實(shí)現(xiàn)
- Python接口自動(dòng)化?之用例讀取方法總結(jié)
相關(guān)文章
Anaconda+vscode+pytorch環(huán)境搭建過程詳解
這篇文章主要介紹了Anaconda+vscode+pytorch環(huán)境搭建過程詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
Python實(shí)現(xiàn)列表轉(zhuǎn)換成字典數(shù)據(jù)結(jié)構(gòu)的方法
這篇文章主要介紹了Python實(shí)現(xiàn)列表轉(zhuǎn)換成字典數(shù)據(jù)結(jié)構(gòu)的方法,結(jié)合實(shí)例形式分析了Python數(shù)值類型轉(zhuǎn)換的相關(guān)技巧,需要的朋友可以參考下2016-03-03
Python OpenCV之圖片縮放的實(shí)現(xiàn)(cv2.resize)
這篇文章主要介紹了Python OpenCV之圖片縮放的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
python基礎(chǔ)教程之簡單入門說明(變量和控制語言使用方法)
這篇文章主要介紹了開始學(xué)習(xí)python的第一步需要知道的知識(shí)(變量和控制語言使用方法),需要的朋友可以參考下2014-03-03
Python實(shí)現(xiàn)從URL地址提取文件名的方法
這篇文章主要介紹了Python實(shí)現(xiàn)從URL地址提取文件名的方法,涉及OS模塊中basename方法的使用技巧,需要的朋友可以參考下2015-05-05
python Bamboolib庫加速Pandas數(shù)據(jù)分析過程詳解
這篇文章主要介紹了python Bamboolib庫加速Pandas數(shù)據(jù)分析過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
python?sns.countplot()?繪畫條形圖詳情
這篇文章主要介紹了python?sns.countplot()繪畫條形圖詳情,sns.countplot()用于畫類別特征的頻數(shù)條形圖,更多相關(guān)內(nèi)容需要的朋友可以參考一下2022-06-06
python裝飾器實(shí)現(xiàn)對(duì)異常代碼出現(xiàn)進(jìn)行自動(dòng)監(jiān)控的實(shí)現(xiàn)方法
這篇文章主要介紹了python裝飾器實(shí)現(xiàn)對(duì)異常代碼出現(xiàn)進(jìn)行自動(dòng)監(jiān)控的實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09

