0 簡介
?? 優(yōu)質(zhì)競賽項目系列,今天要分享的是
基于機(jī)器視覺的試卷系統(tǒng) - opencv python 視覺識別
該項目較為新穎,適合作為競賽課題方向,學(xué)長非常推薦!
?? 更多資料, 項目分享:文章來源:http://www.zghlxwxcb.cn/news/detail-732552.html
https://gitee.com/dancheng-senior/postgraduate文章來源地址http://www.zghlxwxcb.cn/news/detail-732552.html
1 項目背景
機(jī)器視覺的發(fā)展對存在的作業(yè)批改問題, 提供了有效的解決方案。 通過基于機(jī)器視覺的作業(yè)批改系統(tǒng)可以對老師的教學(xué)工作進(jìn)行輔助,改變傳統(tǒng)的批改作業(yè)方式,
幫助老師減輕教學(xué)壓力和工作負(fù)擔(dān), 老師可以快速完成批改過程,及時反饋給學(xué)生。 家長同樣需要從繁重的重復(fù)性檢查作業(yè)工作中解脫出來,
將更多的精力放在關(guān)注學(xué)生的學(xué)習(xí)情況和發(fā)現(xiàn)學(xué)習(xí)問題上。 學(xué)生可以通過自我批改作業(yè)中發(fā)現(xiàn)問題、加深理解, 培養(yǎng)自主學(xué)習(xí)意識, 提高分析問題和解決問題的能力。
因此, 自動批改作業(yè)系統(tǒng)在教育領(lǐng)域的應(yīng)用表現(xiàn)出了無可比擬的教育價值和發(fā)展前景。
2 項目目的
在教育領(lǐng)域中人工智能應(yīng)用愈加廣泛, 作業(yè)在教學(xué)過程中起到重要的作用,當(dāng)前作業(yè)批改存在著重復(fù)勞動、 效率低下等諸多問題,
這種傳統(tǒng)的批改作業(yè)方式占據(jù)了老師寶貴的時間。 本文設(shè)計一種作業(yè)批改視覺系統(tǒng), 將人工智能應(yīng)用到教育領(lǐng)域中, 改變老師傳統(tǒng)的批改作業(yè)方式,
實現(xiàn)自動批改數(shù)學(xué)算式作業(yè)的任務(wù)。
學(xué)長設(shè)計了一個系統(tǒng)系統(tǒng),可以協(xié)助老師和家長完成繁重和重復(fù)的作業(yè)批改和檢查工作, 提高工作效率。
3 系統(tǒng)設(shè)計
3.1 目標(biāo)對象
學(xué)長這里以數(shù)學(xué)作業(yè)試卷識別為目標(biāo)。
數(shù)學(xué)作業(yè)圖像中一列包含多個算式, 字符主要包括印刷體的算式題目和手寫體答案組成, 如上圖 所示為一張數(shù)學(xué)算式作業(yè)圖像。
本課題的難點在于如何有效的去除光線等外部干擾因素, 準(zhǔn)確的提取到作業(yè)圖像中的單個算式信息;選取有效的字符識別算法,
針對印刷體字符和手寫體字符設(shè)計混合字符分類器,進(jìn)行有效、 快速的識別; 選取適合的嵌入式設(shè)備, 進(jìn)行軟件與硬件的系統(tǒng)集成,實現(xiàn)視覺系統(tǒng)的基本功能,
完成穩(wěn)定性的批改過程。
3.2 系統(tǒng)架構(gòu)
通過對視覺系統(tǒng)的研究以及完成作業(yè)批改解決方案的設(shè)計目標(biāo), 采取 PC 平臺與嵌入式平臺相結(jié)合的設(shè)計方案。 針對 PC 平臺進(jìn)行軟件設(shè)計與算法優(yōu)化,
完成系統(tǒng)的功能要求后, 將程序移植到嵌入式系統(tǒng)中, 在嵌入式設(shè)備實現(xiàn)系統(tǒng)的便捷化應(yīng)用。 對于設(shè)計的系統(tǒng)采取多平臺測試分析, 保證系統(tǒng)在 PC
平臺準(zhǔn)確高效的運(yùn)行, 同時保證嵌入式系統(tǒng)中表現(xiàn)出穩(wěn)定的性能。 系統(tǒng)的總體結(jié)構(gòu)框圖如下。
首先按照系統(tǒng)功能需求進(jìn)行分析, 確定要完成的設(shè)計任務(wù)和目標(biāo), 并對系統(tǒng)的功能和性能分析做出設(shè)計要求。 其次根據(jù)系統(tǒng)的功能劃分, 選取基于 PC
平臺的軟件設(shè)計方案完成軟件編程, 對系統(tǒng)實現(xiàn)的功能進(jìn)行驗證, 測試其功能和性能是否符合設(shè)計要求。 選取視覺系統(tǒng)的嵌入式開發(fā)平臺,
進(jìn)行硬件模塊設(shè)計和開發(fā)環(huán)境及軟件平臺的搭建, 將系統(tǒng)軟硬件集成在一起進(jìn)行調(diào)試進(jìn)行, 對系統(tǒng)存在的問題做出改進(jìn)和優(yōu)化。
最后通過系統(tǒng)測試, 分別對系統(tǒng)的功能和性能進(jìn)行測試驗證, 是否滿足設(shè)計的要求。 最終構(gòu)建一款多平臺應(yīng)用, 基于機(jī)器視覺的自動作業(yè)批改視覺系統(tǒng)。
3.3 軟件設(shè)計方案
該系統(tǒng)基于機(jī)器視覺的圖像處理和字符識別技術(shù), 整個系統(tǒng)的核心是軟件設(shè)計部分。 能否對作業(yè)有效和快速的批改,
很大程度上取決于軟件設(shè)計部分圖像處理的效果和字符識別的準(zhǔn)確率。 軟件設(shè)計主要完成系統(tǒng)相關(guān)的功能操作,設(shè)計流程可分為圖 中的模塊組成。
圖像獲取是將攝像頭等設(shè)備獲取的作業(yè)圖像信息轉(zhuǎn)化為數(shù)字圖像信息; 預(yù)處理是對圖像進(jìn)行二值化轉(zhuǎn)換, 去除多余噪聲, 進(jìn)行每一組算式提取,
分割獲得單個清晰字符輪廓的過程; 特征提取是對預(yù)處理后的字符圖像, 進(jìn)行字符特征提取, 將提取好的特征量輸入到分類器, 為字符識別做準(zhǔn)備;
字符識別是系統(tǒng)的核心, 對字符分類器進(jìn)行設(shè)計, 通過分析訓(xùn)練樣本的特征, 將待預(yù)測的樣本進(jìn)行分類, 對字符完成準(zhǔn)確識別;
結(jié)果輸出是通過公式計算器計算印刷體算式結(jié)果與手寫結(jié)果進(jìn)行對比, 判斷算式作業(yè)是否作答正確完成反饋的程。
4 圖像預(yù)處理
試卷字符識別過程中, 通過攝像頭采集到的紙張作業(yè)圖像信息由于受到光線產(chǎn)生的噪聲、 書寫的污點等干擾因素, 影響字符圖像的提取效果。
為了得到完整的字符區(qū)域特征, 同時去除無關(guān)信息的干擾, 需要對圖像進(jìn)行預(yù)處理操作。
4.1 灰度二值化
灰度二值化是將圖像先進(jìn)行灰度處理, 再進(jìn)行二值化處理。 經(jīng)過灰度二值化處理的圖像降低了像素的運(yùn)算量, 同時突出圖像中算式的特征。
灰度化是將采取到的彩色圖像進(jìn)行灰度值轉(zhuǎn)換, 灰度化后的圖像去除了彩色信息, 只保留了算式字符與背景之間的亮度信息, 圖像中每個像素點都是介于 0 至 255
灰度值中的一種。
關(guān)鍵代碼
?
#3、將 RGB 轉(zhuǎn)為灰度圖
def rgb2gray(rgb):
return np.dot(rgb[…,:3], [0.299, 0.587, 0.114])
gray = rgb2gray(lena)
# 也可以用 plt.imshow(gray, cmap = plt.get_cmap('gray'))
plt.imshow(gray, cmap='Greys_r')
plt.axis('off')
plt.show()
from scipy import misc
lena_new_sz = misc.imresize(lena, 0.5) # 第二個參數(shù)如果是整數(shù),則為百分比,如果是tuple,則為輸出圖像的尺寸
plt.imshow(lena_new_sz)
plt.axis('off')
plt.show()
附上imresize的用法
功能:改變圖像的大小。
用法:
B = imresize(A,m)
B = imresize(A,m,method)
B = imresize(A,[mrows ncols],method)
B = imresize(...,method,n)
B = imresize(...,method,h)
imrersize函數(shù)使用由參數(shù)method指定的插值運(yùn)算來改變圖像的大小。
method的幾種可選值:
'nearest'(默認(rèn)值)最近鄰插值
'bilinear'雙線性插值
'bicubic'雙三次插值
B = imresize(A,m)表示把圖像A放大m倍
B = imresize(...,method,h)中的h可以是任意一個FIR濾波器(h通常由函數(shù)ftrans2、fwind1、fwind2、或fsamp2等生成的二維FIR濾波器)。
?
4.2 形態(tài)學(xué)處理
形態(tài)學(xué)處理是通過一定形態(tài)的結(jié)構(gòu)元素, 對圖像產(chǎn)生基于形狀的操作 。它可以在保持圖像基本形狀的基礎(chǔ)上簡化數(shù)據(jù), 去除多余結(jié)構(gòu)。
形態(tài)學(xué)運(yùn)算主要包括開運(yùn)算和閉運(yùn)算, 這兩個操作包含了膨脹和腐蝕。
算式圖像經(jīng)過形態(tài)學(xué)處理后, 實驗效果如上圖所示。 在圖中可以看出左側(cè)的算式圖像經(jīng)過形態(tài)學(xué)處理之后, 其斷裂的乘號字符在右側(cè)的算式圖像中形成了連通區(qū)域。
形態(tài)處理后字符整體趨于完整, 邊界變的平滑。
在手寫字符識別的過程中, 由于手寫字符的字跡大小、 粗細(xì)程度存在的隨意性很大, 在特征提取的過程中, 相同字符的冗余度導(dǎo)致特征向量差異很大 。
因此對獲取字符圖像要進(jìn)行適當(dāng)?shù)募?xì)化處理, 有利于特征提取的準(zhǔn)確性。 圖像細(xì)化指將二值圖像進(jìn)行骨架化操作的運(yùn)算, 細(xì)化操作過程就是剝離字符圖像上邊緣輪廓的點,
細(xì)化操作要求字符骨架保持原有的筆畫特征, 不能造成筆劃斷開, 同時具有連續(xù)性, 字符圖像應(yīng)盡量保留原始的結(jié)構(gòu)特征。
關(guān)鍵代碼
?
import cv2 as cv
img = cv.imread(r"C:\Users\Administrator\Desktop\chinese.png")
img_cvt = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret,img_thr = cv.threshold(img_cvt,100,255,cv.THRESH_BINARY)
kernel = cv.getStructuringElement(cv.MORPH_RECT,(30,1)) #由于是1*30的矩陣,字體會被橫向空隙的白色腐蝕掉,而下劃線橫向都是黑色,不會腐蝕
dst = cv.dilate(img_thr,kernel,iterations=1) #由于是白底黑字,所有進(jìn)行膨脹操作來去除黑色字體
cv.imshow("img_thr",img_thr)
cv.imshow("dst",dst)
cv.waitKey(0)
cv.destroyAllWindows()
4.3 算式提取
算式提取的主要任務(wù)是從紙張中找到其中一組算式的字符區(qū)域, 并將算式從所在的區(qū)域中提取出來。 經(jīng)過算式提取操作,
可以針對每一組算式進(jìn)行批改,同時也便于下一步的字符分割, 算式提取準(zhǔn)確性對作業(yè)批改效果有直接的影響。二值化處理后的算式圖像中算式的灰度值為 255,
背景的灰度值為 0。
采取基于投影的方法, 進(jìn)行水平和垂直方向的投影對算式進(jìn)行提取 , 由于字符圖像和背景圖像對比度較大, 背景幾乎不存在噪音干擾,
因此投影分割可以取得較好的效果。
對圖像進(jìn)行列掃描, 得到垂直方向投影圖, 投影后字符間隔的白色像素點的個數(shù)為 0, 在字符區(qū)域處形成波峰。 此時根據(jù)多個連續(xù)的波峰圖像,
記錄開始和結(jié)束的位置, 就可求得算式的左右邊界, 進(jìn)行分割得到僅包含一組算式區(qū)域的圖像。
4.4 傾斜校正
在圖像獲取的過程中, 由于攝像頭拍攝角度和作業(yè)圖像有時會產(chǎn)生一個傾斜角度, 此時圖像會發(fā)生垂直傾斜, 如果不對算式圖像進(jìn)行傾斜校正處理,
可能會無法正確識別出字符。 因此算式提取后要對算式圖像進(jìn)行傾斜校正, 采用基于 Hough 變換的方法,
其原理為圖像中的直線和曲線經(jīng)過變換映射到參數(shù)空間上的一個點, 通過累加的峰值檢驗圖像中的直線和曲線。 Hough
變換的實質(zhì)是將圖像中一定形狀元素的點進(jìn)行聚類, 通過解析式將參數(shù)空間對應(yīng)的點聯(lián)系起來。
4.5 字符分割
字符分割指是將一組算式中的多個字符圖像根據(jù)字符之間的空隙, 分割成多張只包含單個字符的圖像,
字符分割需要保證對每個字符進(jìn)行完整的提取。作業(yè)字符圖像是一連串的數(shù)字算式字符, 由于算式中包含除號和等號不連通的字符圖像,
因此不便采取投影法對字符進(jìn)行分割。
5 字符識別
支持向量機(jī)是一種新的解決分類問題的機(jī)器學(xué)習(xí)方法, 基于統(tǒng)計學(xué)習(xí)理論,采用結(jié)構(gòu)風(fēng)險最小原則。 其原理是在訓(xùn)練樣本集通過少量支持向量,
自動構(gòu)造分類函數(shù)建立一個最大間隔分類平面, 以此解決分類問題。 支持向量機(jī)不需要構(gòu)建網(wǎng)絡(luò)結(jié)構(gòu)設(shè)計, 通過非線性變換解決高維空間中樣本識別問題。
支持向量機(jī)越來越多的應(yīng)用到了字符識別中, 表現(xiàn)出較好的字符識別效果。
5.1 支持向量機(jī)原理
支持向量機(jī)(Support Vector Machine, SVM), 是 Vapnik [35] 研究小組在統(tǒng)計學(xué)習(xí)理論基礎(chǔ)上, 于 1995
年針對分類問題提出的最佳分類準(zhǔn)則。 SVM 是一種基于統(tǒng)計學(xué)習(xí)理論的模式識別方法, 主要應(yīng)用于解決分類和回歸問題。
傳統(tǒng)的統(tǒng)計學(xué)理論基于樣本無窮大的統(tǒng)計性質(zhì), SVM 專門針對有限樣本, 算法轉(zhuǎn)化成一個二次型尋優(yōu)問題, 得到的是全局最優(yōu)解。 它具有解的唯一性,
經(jīng)過非線性變化轉(zhuǎn)化到高維特征空間, 其算法與樣本的復(fù)雜度無關(guān), 不依賴輸入空間的維數(shù),得到的最優(yōu)解優(yōu)于傳統(tǒng)的學(xué)習(xí)方法 。 因此迅速的發(fā)展起來,
在手寫字符識別領(lǐng)域取得了巨大的成功。
對于最優(yōu)間隔平面分類問題, 根據(jù)樣本分布的情況分為線性可分與非線性可分進(jìn)行討論。 在線性可分的情況下, 其目標(biāo)就是尋找最優(yōu)間隔超平面, 將樣本準(zhǔn)確的分開。
根據(jù)少量支持向量確定平面, 保證樣本數(shù)據(jù)與超平面距離最大,如圖所示。
最優(yōu)分類面示意圖
5.2 基于SVM的字符識別
數(shù)學(xué)算式作業(yè)中包含印刷體字符和手寫體字符, 將這些字符全部放在一個分類器中會導(dǎo)致分類過于復(fù)雜, 類別過多會使識別速率降低。
因此按照字符的分布位置將分類器分為兩種類型: 印刷體字符分類器和手寫字符分類器。 采取一對一分類的方法對印刷體字符和手寫體字符分別設(shè)計了二分類器,
對于算式中同時包含印刷體和手寫體數(shù)字字符, 選用相應(yīng)的分類器, 會提高識別的準(zhǔn)確性和速率。 如圖所示, 根據(jù)字符在算式中的位置, 選用對應(yīng)的分類器。
每個分類器只能將一個字符與其他字符分開, 對于手寫字符而言, 其中一類字符樣本的特征向量作為正集(標(biāo)簽對應(yīng)的值為+1), 其余 9
個樣本的特征向量做負(fù)集(標(biāo)簽對應(yīng)的值為-1)。 按照這種形式依次劃分, 將訓(xùn)練集依次進(jìn)行訓(xùn)練, 可得到 10 個二分類器, 測試階段將未知樣本輸入到這 10
個分類器進(jìn)行分類判斷, 決策結(jié)果取相應(yīng)結(jié)果的最大值。 若輸出的值為+1, 則對應(yīng)相應(yīng)類的字符。
網(wǎng)格特征是字符識別中常用的特征提取方法之一, 體現(xiàn)了字符形狀的整體分布。 其中粗網(wǎng)格特征提取的方法是將字符圖像等分成多個網(wǎng)格區(qū)域, 進(jìn)行特征提取。
首先將歸一化的字符樣本圖像, 其中大小為 128 128, 等分成 16 16 個網(wǎng)格, 如下圖所示。
統(tǒng)計每個網(wǎng)格中黑色像素點占整個網(wǎng)格圖像的有效像素比例, 最后將特征值按照網(wǎng)格排列轉(zhuǎn)換為向量形式。
5.3 SVM算法實現(xiàn)
?
import numpy as np
import random
import matplotlib.pyplot as plt
'''
類名稱:dataStruct
功能:用于存儲一些需要保存或者初始化的數(shù)據(jù)
'''
class dataStruct:
def __init__(self,dataMatIn,labelMatIn,C,toler,eps):
self.dataMat = dataMatIn #樣本數(shù)據(jù)
self.labelMat = labelMatIn #樣本標(biāo)簽
self.C = C #參數(shù)C
self.toler = toler #容錯率
self.eps = eps #乘子更新最小比率
self.m = np.shape(dataMatIn)[0] #樣本數(shù)
self.alphas = np.mat(np.zeros((self.m,1))) #拉格朗日乘子alphas,shape(m,1),初始化全為0
self.b = 0 #參數(shù)b,初始化為0
self.eCache = np.mat(np.zeros((self.m,2))) #誤差緩存,
'''
函數(shù)名稱:loadData
函數(shù)功能:讀取文本文件中的數(shù)據(jù),以樣本數(shù)據(jù)和標(biāo)簽的形式返回
輸入?yún)?shù):filename 文本文件名
返回參數(shù):dataMat 樣本數(shù)據(jù)
labelMat 樣本標(biāo)簽
'''
def loadData(filename):
dataMat = [];labelMat = []
fr = open(filename)
for line in fr.readlines(): #逐行讀取
lineArr = line.strip().split('\t') #濾除行首行尾空格,以\t作為分隔符,對這行進(jìn)行分解
num = np.shape(lineArr)[0]
dataMat.append(list(map(float,lineArr[0:num-1])))#這一行的除最后一個被添加為數(shù)據(jù)
labelMat.append(float(lineArr[num-1]))#這一行的最后一個數(shù)據(jù)被添加為標(biāo)簽
dataMat = np.mat(dataMat)
labelMat = np.mat(labelMat).T
return dataMat,labelMat
'''
函數(shù)名稱:takeStep
函數(shù)功能:給定alpha1和alpha2,執(zhí)行alpha1和alpha2的更新,執(zhí)行b的更新
輸入?yún)?shù):i1 alpha1的標(biāo)號
i2 alpha2的標(biāo)號
dataMat 樣本數(shù)據(jù)
labelMat 樣本標(biāo)簽
返回參數(shù):如果i1==i2 or L==H or eta<=0 or alpha更新前后相差太小,返回0
正常執(zhí)行,返回1
'''
def takeStep(i1,i2,dS):
#如果選擇了兩個相同的乘子,不滿足線性等式約束條件,因此不做更新
if(i1 == i2):
print("i1 == i2")
return 0
#從數(shù)據(jù)結(jié)構(gòu)中取得需要用到的數(shù)據(jù)
alpha1 = dS.alphas[i1,0]
alpha2 = dS.alphas[i2,0]
y1 = dS.labelMat[i1]
y2 = dS.labelMat[i2]
#如果E1以前被計算過,就直接從數(shù)據(jù)結(jié)構(gòu)的cache中讀取它,這樣節(jié)省計算量,#如果沒有歷史記錄,就計算E1
if(dS.eCache[i1,0] == 1):
E1 = dS.eCache[i1,1]
else:
u1 = (np.multiply(dS.alphas,dS.labelMat)).T * np.dot(dS.dataMat,dS.dataMat[i1,:].T) + dS.b #計算SVM的輸出值u1
E1 = float(u1 - y1) #誤差E1
#dS.eCache[i1] = [1,E1] #存到cache中
#如果E2以前被計算過,就直接從數(shù)據(jù)結(jié)構(gòu)的cache中讀取它,這樣節(jié)省計算量,#如果沒有歷史記錄,就計算E2
if(dS.eCache[i2,0] == 1):
E2 = dS.eCache[i2,1]
else:
u2 = (np.multiply(dS.alphas,dS.labelMat)).T * np.dot(dS.dataMat,dS.dataMat[i2,:].T) + dS.b #計算SVM的輸出值u2
E2 = float(u2 - y2) #誤差E2
#dS.eCache[i2] = [1,E2] #存到cache中
s = y1*y2
#計算alpha2的上界H和下界L
if(s==1): #如果y1==y2
L = max(0,alpha1+alpha2-dS.C)
H = min(dS.C,alpha1+alpha2)
elif(s==-1): #如果y1!=y2
L = max(0,alpha2-alpha1)
H = min(dS.C,dS.C+alpha2-alpha1)
if(L==H):
print("L==H")
return 0
#計算學(xué)習(xí)率eta
k11 = np.dot(dS.dataMat[i1,::],dS.dataMat[i1,:].T)
k12 = np.dot(dS.dataMat[i1,::],dS.dataMat[i2,:].T)
k22 = np.dot(dS.dataMat[i2,::],dS.dataMat[i2,:].T)
eta = k11 - 2*k12 +k22
if(eta > 0):#正常情況下eta是大于0的,此時計算新的alpha2,新的alpha2標(biāo)記為a2
a2 = alpha2 + y2*(E1-E2)/eta#這個公式的推導(dǎo),曾經(jīng)花費(fèi)了我很多精力,現(xiàn)在寫出來卻是如此簡潔,數(shù)學(xué)真是個好東西
#對a2進(jìn)行上下界裁剪
if(a2 < L):
a2 = L
elif(a2 > H):
a2 = H
else:#非正常情況下,也有可能出現(xiàn)eta《=0的情況
print("eta<=0")
return 0
'''
Lobj =
Hobj =
if(Lobj < Hobj-eps):
a2 = L
elif(Lobj > Hobj+eps):
a2 = H
else:
a2 = alpha2
'''
#如果更新量太小,就不值浪費(fèi)算力繼續(xù)算a1和b,不值得對這三者進(jìn)行更新
if(abs(a2-alpha2) < dS.eps*(a2+alpha2+dS.eps)):
print("so small update on alpha2!")
return 0
#計算新的alpha1,標(biāo)記為a1
a1 = alpha1 + s*(alpha2 - a2)
#計算b1和b2,并且更新b
b1 = -E1 + y1*(alpha1 - a1)*np.dot(dS.dataMat[i1,:],dS.dataMat[i1,:].T) + y2*(alpha2 - a2)*np.dot(dS.dataMat[i1,:],dS.dataMat[i2,:].T) + dS.b
b2 = -E2 + y1*(alpha1 - a1)*np.dot(dS.dataMat[i1,:],dS.dataMat[i2,:].T) + y2*(alpha2 - a2)*np.dot(dS.dataMat[i2,:],dS.dataMat[i2,:].T) + dS.b
if(a1>0 and a1<dS.C):
dS.b = b1
elif(a2>0 and a2<dS.C):
dS.b = b2
else:
dS.b = (b1 + b2) / 2
#用a1和a2更新alpha1和alpha2
dS.alphas[i1] = a1
dS.alphas[i2] = a2
#由于本次alpha1、alpha2和b的更新,需要重新計算Ecache,注意Ecache只存儲那些非零的alpha對應(yīng)的誤差
validAlphasList = np.nonzero(dS.alphas.A)[0] #所有的非零的alpha標(biāo)號列表
dS.eCache = np.mat(np.zeros((dS.m,2)))#要把Ecache先清空
for k in validAlphasList:#遍歷所有的非零alpha
uk = (np.multiply(dS.alphas,dS.labelMat).T).dot(np.dot(dS.dataMat,dS.dataMat[k,:].T)) + dS.b
yk = dS.labelMat[k,0]
Ek = float(uk-yk)
dS.eCache[k] = [1,Ek]
print ("updated")
return 1
'''
函數(shù)名稱:examineExample
函數(shù)功能:給定alpha2,如果alpha2不滿足KKT條件,則再找一個alpha1,對這兩個乘子進(jìn)行一次takeStep
輸入?yún)?shù):i2 alpha的標(biāo)號
dataMat 樣本數(shù)據(jù)
labelMat 樣本標(biāo)簽
返回參數(shù):如果成功對一對乘子alpha1和alpha2執(zhí)行了一次takeStep,返回1;否則,返回0
'''
def examineExample(i2,dS):
#從數(shù)據(jù)結(jié)構(gòu)中取得需要用到的數(shù)據(jù)
y2 = dS.labelMat[i2,0]
alpha2 = dS.alphas[i2,0]
#如果E2以前被計算過,就直接從數(shù)據(jù)結(jié)構(gòu)的cache中讀取它,這樣節(jié)省計算量,#如果沒有歷史記錄,就計算E2
if(dS.eCache[i2,0] == 1):
E2 = dS.eCache[i2,1]
else:
u2 = (np.multiply(dS.alphas,dS.labelMat)).T * np.dot(dS.dataMat,dS.dataMat[i2,:].T) + dS.b#計算SVM的輸出值u2
E2 = float(u2 - y2)#誤差E2
#dS.eCache[i2] = [1,E2]
r2 = E2*y2
#如果當(dāng)前的alpha2在一定容忍誤差內(nèi)不滿足KKT條件,則需要對其進(jìn)行更新
if((r2<-dS.toler and alpha2<dS.C) or (r2>dS.toler and alpha2>0)):
'''
#隨機(jī)選擇的方法確定另一個乘子alpha1,多執(zhí)行幾次可可以收斂到很好的結(jié)果,就是效率比較低
i1 = random.randint(0, dS.m-1)
if(takeStep(i1,i2,dS)):
return 1
'''
#啟發(fā)式的方法確定另一個乘子alpha1
nonZeroAlphasList = np.nonzero(dS.alphas.A)[0].tolist()#找到所有的非0的alpha
nonCAlphasList = np.nonzero((dS.alphas-dS.C).A)[0].tolist()#找到所有的非C的alpha
nonBoundAlphasList = list(set(nonZeroAlphasList)&set(nonCAlphasList))#所有非邊界(既不=0,也不=C)的alpha
#如果非邊界的alpha數(shù)量至少兩個,則在所有的非邊界alpha上找到能夠使\E1-E2\最大的那個E1,對這一對乘子進(jìn)行更新
if(len(nonBoundAlphasList) > 1):
maxE = 0
maxEindex = 0
for k in nonBoundAlphasList:
if(abs(dS.eCache[k,1]-E2)>maxE):
maxE = abs(dS.eCache[k,1]-E2)
maxEindex = k
i1 = maxEindex
if(takeStep(i1,i2,dS)):
return 1
#如果上面找到的那個i1沒能使alpha和b得到有效更新,則從隨機(jī)開始處遍歷整個非邊界alpha作為i1,逐個對每一對乘子嘗試進(jìn)行更新
randomStart = random.randint(0,len(nonBoundAlphasList)-1)
for i1 in range(randomStart,len(nonBoundAlphasList)):
if(i1 == i2):continue
if(takeStep(i1,i2,dS)):
return 1
for i1 in range(0,randomStart):
if(i1 == i2):continue
if(takeStep(i1,i2,dS)):
return 1
#如果上面的更新仍然沒有return 1跳出去或者非邊界alpha數(shù)量少于兩個,這種情況只好從隨機(jī)開始的位置開始遍歷整個可能的i1,對每一對嘗試更新
randomStart = random.randint(0,dS.m-1)
for i1 in range(randomStart,dS.m):
if(i1 == i2):continue
if(takeStep(i1,i2,dS)):
return 1
for i1 in range(0,randomStart):
if(i1 == i2):continue
if(takeStep(i1,i2,dS)):
return 1
'''
i1 = random.randint(0,dS.m-1)
if(takeStep(i1,i2,dS)):
return 1
'''
#如果實在還更新不了,就回去重新選擇一個alpha2吧,當(dāng)前的alpha2肯定是有毒
return 0
'''
函數(shù)名稱:SVM_with_SMO
函數(shù)功能:用SMO寫的SVM的入口函數(shù),里面采用了第一個啟發(fā)式確定alpha2,即在全局遍歷和非邊界遍歷之間來回repeat,直到不再有任何更新
輸入?yún)?shù):dS dataStruct類的數(shù)據(jù)
返回參數(shù):None
'''
def SVM_with_SMO(dS):
#初始化控制變量,確保第一次要全局遍歷
numChanged = 0
examineAll = 1
#顯然,如果全局遍歷了一次,并且沒有任何更新,此時examineAll和numChanged都會被置零,算法終止
while(numChanged > 0 or examineAll):
numChanged = 0
if(examineAll):
for i in range(dS.m):
numChanged += examineExample(i,dS)
else:
for i in range(dS.m):
if(dS.alphas[i] == 0 or dS.alphas[i] == dS.C):continue
numChanged += examineExample(i,dS)
if(examineAll == 1):
examineAll = 0
elif(numChanged == 0):
examineAll = 1
'''
函數(shù)名稱:cal_W
函數(shù)功能:根據(jù)alpha和y來計算W
輸入?yún)?shù):dS dataStruct類的數(shù)據(jù)
返回參數(shù):W 超平名的法向量W
'''
def cal_W(dS):
W = np.dot(dS.dataMat.T,np.multiply(dS.labelMat,dS.alphas))
return W
'''
函數(shù)名稱:showClassifer
函數(shù)功能:畫出原始數(shù)據(jù)點、超平面,并標(biāo)出支持向量
輸入?yún)?shù):dS dataStruct類的數(shù)據(jù)
W 超平名的法向量W
返回參數(shù):None
'''
def showClassifer(dS,w):
#繪制樣本點
dataMat = dS.dataMat.tolist()
data_plus = [] #正樣本
data_minus = [] #負(fù)樣本
for i in range(len(dataMat)):
if dS.labelMat[i,0] > 0:
data_plus.append(dataMat[i])
else:
data_minus.append(dataMat[i])
data_plus_np = np.array(data_plus) #轉(zhuǎn)換為numpy矩陣
data_minus_np = np.array(data_minus) #轉(zhuǎn)換為numpy矩陣
plt.scatter(np.transpose(data_plus_np)[0], np.transpose(data_plus_np)[1], s=30, alpha=0.7, c='r') #正樣本散點圖
plt.scatter(np.transpose(data_minus_np)[0], np.transpose(data_minus_np)[1], s=30, alpha=0.7,c='g') #負(fù)樣本散點圖
#繪制直線
x1 = max(dataMat)[0]
x2 = min(dataMat)[0]
a1, a2 = w
b = float(dS.b)
a1 = float(a1[0])
a2 = float(a2[0])
y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2
plt.plot([x1, x2], [y1, y2])
#找出支持向量點
for i, alpha in enumerate(dS.alphas):
if abs(alpha) > 0.000000001:
x, y = dataMat[i]
plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
plt.xlabel("happy 520 day, 2018.06.13")
plt.savefig("svm.png")
plt.show()
if __name__ == '__main__':
dataMat,labelMat = loadData("testSet.txt")
dS = dataStruct(dataMat, labelMat, 0.6, 0.001, 0.01)#初始化數(shù)據(jù)結(jié)構(gòu) dataMatIn, labelMatIn,C,toler,eps
for i in range(0,1):#只需要執(zhí)行一次,效果就非常不錯
SVM_with_SMO(dS)
W = cal_W(dS)
showClassifer(dS,W.tolist())
?
6 算法測試
輸入圖像
預(yù)處理結(jié)果
識別結(jié)果
7 系統(tǒng)實現(xiàn)
系統(tǒng)主要流程如下
對在 PC 軟件平臺通過 MFC 界面中實現(xiàn)各模塊操作, 系統(tǒng)界面如圖所示。
系統(tǒng)界面采用模塊化設(shè)計, 按照界面分布分為圖像顯示模塊、 按鍵功能模塊、 圖像預(yù)處理模塊、 批改結(jié)果輸出四個模塊組成。
主要內(nèi)容包括:
- 顯示獲取作業(yè)圖像的基本信息;
- 通過按鍵控制相應(yīng)功能;
- 顯示預(yù)處理后圖像的效果;輸出識別的字符信息和批改的結(jié)果。
圖像顯示模塊, 通過打開攝像頭按鍵, 將攝像頭獲取到的紙張作業(yè)圖像實時信息傳送到計算機(jī)中, 獲取的圖像顯示在界面左側(cè)窗口, 界面運(yùn)行結(jié)果如圖所示。
按鍵功能模塊, 通過算式提取按鍵, 對紙張中單個算式整體區(qū)域進(jìn)行選框提取, 運(yùn)行結(jié)果如圖所示,
此時算式檢測的結(jié)果在原圖像上用矩形框標(biāo)記,在界面右側(cè)顯示提取到的算式效果。
圖像處理模塊, 通過檢測識別按鍵完成字符分割和識別, 在界面右側(cè)窗口顯示預(yù)處理后的圖像效果。 批改結(jié)果輸出模塊,
在界面下框中顯示字符的識別結(jié)果以及手寫的計算結(jié)果, 同時在右下角窗口顯示解答正誤, 輸出得到的批改信息。 同時對整個過程運(yùn)行的時間進(jìn)行統(tǒng)計,
最后保存按鍵將錯誤的批改結(jié)果保存, 便于后期修改。 此時系統(tǒng)運(yùn)行界面如圖所示。
8 最后
?? 更多資料, 項目分享:
https://gitee.com/dancheng-senior/postgraduate
到了這里,關(guān)于計算機(jī)競賽 機(jī)器視覺的試卷批改系統(tǒng) - opencv python 視覺識別的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!