Python+Opencv答題卡識別用例詳解

使用Python3和Opencv識別一張標準的答題卡。大致的過程如下:

1.讀取圖片

2.利用霍夫圓檢測,檢測出四個角的黑圓位置,從確定四個角的位置

3.利用透視變換和四個角的位置,矯正圖片(直接用的網上的圖片,沒有拍照,所以這一步沒有實現)

4.裁剪四個邊框,獲取邊框上小黑格的位置

5.根據小黑格的位置確定每個塗卡區域的位置

6.將答題卡腐蝕和膨脹,遍歷所有的格子的區域,計算每個區域內像素值為0的個數,若數量達到某個值,那麼就確認這個格子是被黑筆塗過,並記錄該位置的題目選項。

具體的實現

一、讀取圖片,用是imread函數。

二、利用霍夫圓檢測位置,這裡註意的是HoughCircles的param參數,調的不準,所以檢測出來還有其他的幹擾圓,最後可以通過設置半徑閾值,將四個角的圓篩選出來。最後這個函數返回四個圓的位置和半徑。

#檢測圖中的圓,並返回每個圓的位置和半徑
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

三、先將圖片整體二直化,再在二直化的圖片將四個邊框裁剪下來,直接根據四個邊角的位置向外或者向裡增加或者較少一個半徑,確定邊框矩形的對角位置。

#獲取四個邊角的位置
location_RT = location_vcol[0]
location_RB = location_vcol[1]
location_LT = location_vcol[2]
location_LB = location_vcol[3]
 
# 先將原圖二值化,註意閾值的取范圍,再將二值化圖片轉換成BGR,方便後面標出小方格的位置
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)

四、根據裁剪的邊框確定邊框上小白格的位置。每個方位的計算方式是一樣的。每個函數返回的是小格子在源圖中的位置。

先來說底部的。這個過程是把截圖看成一個坐標系,以行長為橫坐標,列高為中坐標,計算統計每個橫坐標下,有多少個點是白色的,並將該數值存放於列表vcol中。根據這種算法,能夠得出這個列表中應該是rows個元素,而且每個元素的值不超過列高,不低於0。

遍歷vcol列表,如果某一個位置為0,後一個位置不為0,那麼這個位置就是小白格的起始位置。並記錄起始位置在原圖中的位置,方便後期遍歷整個塗卡區域。

其他的三個方向是同樣的道理,

#計算底部小黑格的位置
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
 
#計算右部小黑格的位置
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
 
#計算左邊小黑格的位置
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])

五、在原圖中全出每個小格子。在此之前要將圖片做個預處理,腐蝕和膨脹。這個程序先圈出來的是答題區域的小格子,所以i和j 的范圍分別是底部格子數和右邊格子數。

先將每個格子的區域找出來,然後遍歷這個格子的全部像素,判斷是否為0 ,如果為0,那麼就isum+1,最後遍歷結束,看看isum的個數有沒有達到總像素個數的10%,如果達到,那麼就可以判定這個區域是被塗過的,記錄這個區域對應的題號和選項,以備後面計分。

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區域是否被填充
            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

這是腐蝕後的圖片

這是圈出所有答題區域的圖片

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: