山東做網(wǎng)站的公司蘭州做網(wǎng)站的公司
文章目錄
- 0 簡介
- 1 項目背景
- 2 項目目的
- 3 系統(tǒng)設(shè)計
- 3.1 目標對象
- 3.2 系統(tǒng)架構(gòu)
- 3.3 軟件設(shè)計方案
- 4 圖像預(yù)處理
- 4.1 灰度二值化
- 4.2 形態(tài)學處理
- 4.3 算式提取
- 4.4 傾斜校正
- 4.5 字符分割
- 5 字符識別
- 5.1 支持向量機原理
- 5.2 基于SVM的字符識別
- 5.3 SVM算法實現(xiàn)
- 6 算法測試
- 7 系統(tǒng)實現(xiàn)
- 8 最后
0 簡介
🔥 優(yōu)質(zhì)競賽項目系列,今天要分享的是
基于機器視覺的試卷系統(tǒng) - opencv python 視覺識別
該項目較為新穎,適合作為競賽課題方向,學長非常推薦!
🧿 更多資料, 項目分享:
https://gitee.com/dancheng-senior/postgraduate
1 項目背景
機器視覺的發(fā)展對存在的作業(yè)批改問題, 提供了有效的解決方案。 通過基于機器視覺的作業(yè)批改系統(tǒng)可以對老師的教學工作進行輔助,改變傳統(tǒng)的批改作業(yè)方式,
幫助老師減輕教學壓力和工作負擔, 老師可以快速完成批改過程,及時反饋給學生。 家長同樣需要從繁重的重復(fù)性檢查作業(yè)工作中解脫出來,
將更多的精力放在關(guān)注學生的學習情況和發(fā)現(xiàn)學習問題上。 學生可以通過自我批改作業(yè)中發(fā)現(xiàn)問題、加深理解, 培養(yǎng)自主學習意識, 提高分析問題和解決問題的能力。
因此, 自動批改作業(yè)系統(tǒng)在教育領(lǐng)域的應(yīng)用表現(xiàn)出了無可比擬的教育價值和發(fā)展前景。
2 項目目的
在教育領(lǐng)域中人工智能應(yīng)用愈加廣泛, 作業(yè)在教學過程中起到重要的作用,當前作業(yè)批改存在著重復(fù)勞動、 效率低下等諸多問題,
這種傳統(tǒng)的批改作業(yè)方式占據(jù)了老師寶貴的時間。 本文設(shè)計一種作業(yè)批改視覺系統(tǒng), 將人工智能應(yīng)用到教育領(lǐng)域中, 改變老師傳統(tǒng)的批改作業(yè)方式,
實現(xiàn)自動批改數(shù)學算式作業(yè)的任務(wù)。
學長設(shè)計了一個系統(tǒng)系統(tǒng),可以協(xié)助老師和家長完成繁重和重復(fù)的作業(yè)批改和檢查工作, 提高工作效率。
3 系統(tǒng)設(shè)計
3.1 目標對象
學長這里以數(shù)學作業(yè)試卷識別為目標。
數(shù)學作業(yè)圖像中一列包含多個算式, 字符主要包括印刷體的算式題目和手寫體答案組成, 如上圖 所示為一張數(shù)學算式作業(yè)圖像。
本課題的難點在于如何有效的去除光線等外部干擾因素, 準確的提取到作業(yè)圖像中的單個算式信息;選取有效的字符識別算法,
針對印刷體字符和手寫體字符設(shè)計混合字符分類器,進行有效、 快速的識別; 選取適合的嵌入式設(shè)備, 進行軟件與硬件的系統(tǒng)集成,實現(xiàn)視覺系統(tǒng)的基本功能,
完成穩(wěn)定性的批改過程。
3.2 系統(tǒng)架構(gòu)
通過對視覺系統(tǒng)的研究以及完成作業(yè)批改解決方案的設(shè)計目標, 采取 PC 平臺與嵌入式平臺相結(jié)合的設(shè)計方案。 針對 PC 平臺進行軟件設(shè)計與算法優(yōu)化,
完成系統(tǒng)的功能要求后, 將程序移植到嵌入式系統(tǒng)中, 在嵌入式設(shè)備實現(xiàn)系統(tǒng)的便捷化應(yīng)用。 對于設(shè)計的系統(tǒng)采取多平臺測試分析, 保證系統(tǒng)在 PC
平臺準確高效的運行, 同時保證嵌入式系統(tǒng)中表現(xiàn)出穩(wěn)定的性能。 系統(tǒng)的總體結(jié)構(gòu)框圖如下。
首先按照系統(tǒng)功能需求進行分析, 確定要完成的設(shè)計任務(wù)和目標, 并對系統(tǒng)的功能和性能分析做出設(shè)計要求。 其次根據(jù)系統(tǒng)的功能劃分, 選取基于 PC
平臺的軟件設(shè)計方案完成軟件編程, 對系統(tǒng)實現(xiàn)的功能進行驗證, 測試其功能和性能是否符合設(shè)計要求。 選取視覺系統(tǒng)的嵌入式開發(fā)平臺,
進行硬件模塊設(shè)計和開發(fā)環(huán)境及軟件平臺的搭建, 將系統(tǒng)軟硬件集成在一起進行調(diào)試進行, 對系統(tǒng)存在的問題做出改進和優(yōu)化。
最后通過系統(tǒng)測試, 分別對系統(tǒng)的功能和性能進行測試驗證, 是否滿足設(shè)計的要求。 最終構(gòu)建一款多平臺應(yīng)用, 基于機器視覺的自動作業(yè)批改視覺系統(tǒng)。
3.3 軟件設(shè)計方案
該系統(tǒng)基于機器視覺的圖像處理和字符識別技術(shù), 整個系統(tǒng)的核心是軟件設(shè)計部分。 能否對作業(yè)有效和快速的批改,
很大程度上取決于軟件設(shè)計部分圖像處理的效果和字符識別的準確率。 軟件設(shè)計主要完成系統(tǒng)相關(guān)的功能操作,設(shè)計流程可分為圖 中的模塊組成。
圖像獲取是將攝像頭等設(shè)備獲取的作業(yè)圖像信息轉(zhuǎn)化為數(shù)字圖像信息; 預(yù)處理是對圖像進行二值化轉(zhuǎn)換, 去除多余噪聲, 進行每一組算式提取,
分割獲得單個清晰字符輪廓的過程; 特征提取是對預(yù)處理后的字符圖像, 進行字符特征提取, 將提取好的特征量輸入到分類器, 為字符識別做準備;
字符識別是系統(tǒng)的核心, 對字符分類器進行設(shè)計, 通過分析訓(xùn)練樣本的特征, 將待預(yù)測的樣本進行分類, 對字符完成準確識別;
結(jié)果輸出是通過公式計算器計算印刷體算式結(jié)果與手寫結(jié)果進行對比, 判斷算式作業(yè)是否作答正確完成反饋的程。
4 圖像預(yù)處理
試卷字符識別過程中, 通過攝像頭采集到的紙張作業(yè)圖像信息由于受到光線產(chǎn)生的噪聲、 書寫的污點等干擾因素, 影響字符圖像的提取效果。
為了得到完整的字符區(qū)域特征, 同時去除無關(guān)信息的干擾, 需要對圖像進行預(yù)處理操作。
4.1 灰度二值化
灰度二值化是將圖像先進行灰度處理, 再進行二值化處理。 經(jīng)過灰度二值化處理的圖像降低了像素的運算量, 同時突出圖像中算式的特征。
灰度化是將采取到的彩色圖像進行灰度值轉(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指定的插值運算來改變圖像的大小。
method的幾種可選值:
'nearest'(默認值)最近鄰插值
'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)學處理
形態(tài)學處理是通過一定形態(tài)的結(jié)構(gòu)元素, 對圖像產(chǎn)生基于形狀的操作 。它可以在保持圖像基本形狀的基礎(chǔ)上簡化數(shù)據(jù), 去除多余結(jié)構(gòu)。
形態(tài)學運算主要包括開運算和閉運算, 這兩個操作包含了膨脹和腐蝕。
算式圖像經(jīng)過形態(tài)學處理后, 實驗效果如上圖所示。 在圖中可以看出左側(cè)的算式圖像經(jīng)過形態(tài)學處理之后, 其斷裂的乘號字符在右側(cè)的算式圖像中形成了連通區(qū)域。
形態(tài)處理后字符整體趨于完整, 邊界變的平滑。
在手寫字符識別的過程中, 由于手寫字符的字跡大小、 粗細程度存在的隨意性很大, 在特征提取的過程中, 相同字符的冗余度導(dǎo)致特征向量差異很大 。
因此對獲取字符圖像要進行適當?shù)募毣幚?#xff0c; 有利于特征提取的準確性。 圖像細化指將二值圖像進行骨架化操作的運算, 細化操作過程就是剝離字符圖像上邊緣輪廓的點,
細化操作要求字符骨架保持原有的筆畫特征, 不能造成筆劃斷開, 同時具有連續(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) #由于是白底黑字,所有進行膨脹操作來去除黑色字體
cv.imshow("img_thr",img_thr)
cv.imshow("dst",dst)
cv.waitKey(0)
cv.destroyAllWindows()
4.3 算式提取
算式提取的主要任務(wù)是從紙張中找到其中一組算式的字符區(qū)域, 并將算式從所在的區(qū)域中提取出來。 經(jīng)過算式提取操作,
可以針對每一組算式進行批改,同時也便于下一步的字符分割, 算式提取準確性對作業(yè)批改效果有直接的影響。二值化處理后的算式圖像中算式的灰度值為 255,
背景的灰度值為 0。
采取基于投影的方法, 進行水平和垂直方向的投影對算式進行提取 , 由于字符圖像和背景圖像對比度較大, 背景幾乎不存在噪音干擾,
因此投影分割可以取得較好的效果。
對圖像進行列掃描, 得到垂直方向投影圖, 投影后字符間隔的白色像素點的個數(shù)為 0, 在字符區(qū)域處形成波峰。 此時根據(jù)多個連續(xù)的波峰圖像,
記錄開始和結(jié)束的位置, 就可求得算式的左右邊界, 進行分割得到僅包含一組算式區(qū)域的圖像。
4.4 傾斜校正
在圖像獲取的過程中, 由于攝像頭拍攝角度和作業(yè)圖像有時會產(chǎn)生一個傾斜角度, 此時圖像會發(fā)生垂直傾斜, 如果不對算式圖像進行傾斜校正處理,
可能會無法正確識別出字符。 因此算式提取后要對算式圖像進行傾斜校正, 采用基于 Hough 變換的方法,
其原理為圖像中的直線和曲線經(jīng)過變換映射到參數(shù)空間上的一個點, 通過累加的峰值檢驗圖像中的直線和曲線。 Hough
變換的實質(zhì)是將圖像中一定形狀元素的點進行聚類, 通過解析式將參數(shù)空間對應(yīng)的點聯(lián)系起來。
4.5 字符分割
字符分割指是將一組算式中的多個字符圖像根據(jù)字符之間的空隙, 分割成多張只包含單個字符的圖像,
字符分割需要保證對每個字符進行完整的提取。作業(yè)字符圖像是一連串的數(shù)字算式字符, 由于算式中包含除號和等號不連通的字符圖像,
因此不便采取投影法對字符進行分割。
5 字符識別
支持向量機是一種新的解決分類問題的機器學習方法, 基于統(tǒng)計學習理論,采用結(jié)構(gòu)風險最小原則。 其原理是在訓(xùn)練樣本集通過少量支持向量,
自動構(gòu)造分類函數(shù)建立一個最大間隔分類平面, 以此解決分類問題。 支持向量機不需要構(gòu)建網(wǎng)絡(luò)結(jié)構(gòu)設(shè)計, 通過非線性變換解決高維空間中樣本識別問題。
支持向量機越來越多的應(yīng)用到了字符識別中, 表現(xiàn)出較好的字符識別效果。
5.1 支持向量機原理
支持向量機(Support Vector Machine, SVM), 是 Vapnik [35] 研究小組在統(tǒng)計學習理論基礎(chǔ)上, 于 1995
年針對分類問題提出的最佳分類準則。 SVM 是一種基于統(tǒng)計學習理論的模式識別方法, 主要應(yīng)用于解決分類和回歸問題。
傳統(tǒng)的統(tǒng)計學理論基于樣本無窮大的統(tǒng)計性質(zhì), SVM 專門針對有限樣本, 算法轉(zhuǎn)化成一個二次型尋優(yōu)問題, 得到的是全局最優(yōu)解。 它具有解的唯一性,
經(jīng)過非線性變化轉(zhuǎn)化到高維特征空間, 其算法與樣本的復(fù)雜度無關(guān), 不依賴輸入空間的維數(shù),得到的最優(yōu)解優(yōu)于傳統(tǒng)的學習方法 。 因此迅速的發(fā)展起來,
在手寫字符識別領(lǐng)域取得了巨大的成功。
對于最優(yōu)間隔平面分類問題, 根據(jù)樣本分布的情況分為線性可分與非線性可分進行討論。 在線性可分的情況下, 其目標就是尋找最優(yōu)間隔超平面, 將樣本準確的分開。
根據(jù)少量支持向量確定平面, 保證樣本數(shù)據(jù)與超平面距離最大,如圖所示。
最優(yōu)分類面示意圖
5.2 基于SVM的字符識別
數(shù)學算式作業(yè)中包含印刷體字符和手寫體字符, 將這些字符全部放在一個分類器中會導(dǎo)致分類過于復(fù)雜, 類別過多會使識別速率降低。
因此按照字符的分布位置將分類器分為兩種類型: 印刷體字符分類器和手寫字符分類器。 采取一對一分類的方法對印刷體字符和手寫體字符分別設(shè)計了二分類器,
對于算式中同時包含印刷體和手寫體數(shù)字字符, 選用相應(yīng)的分類器, 會提高識別的準確性和速率。 如圖所示, 根據(jù)字符在算式中的位置, 選用對應(yīng)的分類器。
每個分類器只能將一個字符與其他字符分開, 對于手寫字符而言, 其中一類字符樣本的特征向量作為正集(標簽對應(yīng)的值為+1), 其余 9
個樣本的特征向量做負集(標簽對應(yīng)的值為-1)。 按照這種形式依次劃分, 將訓(xùn)練集依次進行訓(xùn)練, 可得到 10 個二分類器, 測試階段將未知樣本輸入到這 10
個分類器進行分類判斷, 決策結(jié)果取相應(yīng)結(jié)果的最大值。 若輸出的值為+1, 則對應(yīng)相應(yīng)類的字符。
網(wǎng)格特征是字符識別中常用的特征提取方法之一, 體現(xiàn)了字符形狀的整體分布。 其中粗網(wǎng)格特征提取的方法是將字符圖像等分成多個網(wǎng)格區(qū)域, 進行特征提取。
首先將歸一化的字符樣本圖像, 其中大小為 128 128, 等分成 16 16 個網(wǎng)格, 如下圖所示。
統(tǒng)計每個網(wǎng)格中黑色像素點占整個網(wǎng)格圖像的有效像素比例, 最后將特征值按照網(wǎng)格排列轉(zhuǎn)換為向量形式。
5.3 SVM算法實現(xiàn)
?
import numpy as npimport randomimport 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 #樣本標簽self.C = C #參數(shù)Cself.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),初始化全為0self.b = 0 #參數(shù)b,初始化為0self.eCache = np.mat(np.zeros((self.m,2))) #誤差緩存,'''函數(shù)名稱:loadData函數(shù)功能:讀取文本文件中的數(shù)據(jù),以樣本數(shù)據(jù)和標簽的形式返回輸入?yún)?shù):filename 文本文件名返回參數(shù):dataMat 樣本數(shù)據(jù)labelMat 樣本標簽'''def loadData(filename):dataMat = [];labelMat = []fr = open(filename)for line in fr.readlines(): #逐行讀取lineArr = line.strip().split('\t') #濾除行首行尾空格,以\t作為分隔符,對這行進行分解num = np.shape(lineArr)[0] dataMat.append(list(map(float,lineArr[0:num-1])))#這一行的除最后一個被添加為數(shù)據(jù)labelMat.append(float(lineArr[num-1]))#這一行的最后一個數(shù)據(jù)被添加為標簽dataMat = np.mat(dataMat)labelMat = np.mat(labelMat).Treturn dataMat,labelMat'''函數(shù)名稱:takeStep函數(shù)功能:給定alpha1和alpha2,執(zhí)行alpha1和alpha2的更新,執(zhí)行b的更新輸入?yún)?shù):i1 alpha1的標號i2 alpha2的標號dataMat 樣本數(shù)據(jù)labelMat 樣本標簽返回參數(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é)省計算量,#如果沒有歷史記錄,就計算E1if(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的輸出值u1E1 = float(u1 - y1) #誤差E1#dS.eCache[i1] = [1,E1] #存到cache中#如果E2以前被計算過,就直接從數(shù)據(jù)結(jié)構(gòu)的cache中讀取它,這樣節(jié)省計算量,#如果沒有歷史記錄,就計算E2if(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的輸出值u2E2 = float(u2 - y2) #誤差E2#dS.eCache[i2] = [1,E2] #存到cache中 s = y1*y2#計算alpha2的上界H和下界Lif(s==1): #如果y1==y2L = max(0,alpha1+alpha2-dS.C)H = min(dS.C,alpha1+alpha2)elif(s==-1): #如果y1!=y2L = max(0,alpha2-alpha1)H = min(dS.C,dS.C+alpha2-alpha1)if(L==H):print("L==H")return 0#計算學習率etak11 = 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 +k22if(eta > 0):#正常情況下eta是大于0的,此時計算新的alpha2,新的alpha2標記為a2a2 = alpha2 + y2*(E1-E2)/eta#這個公式的推導(dǎo),曾經(jīng)花費了我很多精力,現(xiàn)在寫出來卻是如此簡潔,數(shù)學真是個好東西#對a2進行上下界裁剪if(a2 < L):a2 = Lelif(a2 > H):a2 = Helse:#非正常情況下,也有可能出現(xiàn)eta《=0的情況print("eta<=0")return 0'''Lobj = Hobj = if(Lobj < Hobj-eps):a2 = Lelif(Lobj > Hobj+eps):a2 = Helse:a2 = alpha2'''#如果更新量太小,就不值浪費算力繼續(xù)算a1和b,不值得對這三者進行更新if(abs(a2-alpha2) < dS.eps*(a2+alpha2+dS.eps)):print("so small update on alpha2!")return 0#計算新的alpha1,標記為a1a1 = alpha1 + s*(alpha2 - a2)#計算b1和b2,并且更新bb1 = -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.bb2 = -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.bif(a1>0 and a1<dS.C):dS.b = b1elif(a2>0 and a2<dS.C):dS.b = b2else:dS.b = (b1 + b2) / 2#用a1和a2更新alpha1和alpha2dS.alphas[i1] = a1dS.alphas[i2] = a2#由于本次alpha1、alpha2和b的更新,需要重新計算Ecache,注意Ecache只存儲那些非零的alpha對應(yīng)的誤差validAlphasList = np.nonzero(dS.alphas.A)[0] #所有的非零的alpha標號列表dS.eCache = np.mat(np.zeros((dS.m,2)))#要把Ecache先清空for k in validAlphasList:#遍歷所有的非零alphauk = (np.multiply(dS.alphas,dS.labelMat).T).dot(np.dot(dS.dataMat,dS.dataMat[k,:].T)) + dS.byk = dS.labelMat[k,0]Ek = float(uk-yk)dS.eCache[k] = [1,Ek]print ("updated")return 1'''函數(shù)名稱:examineExample函數(shù)功能:給定alpha2,如果alpha2不滿足KKT條件,則再找一個alpha1,對這兩個乘子進行一次takeStep輸入?yún)?shù):i2 alpha的標號dataMat 樣本數(shù)據(jù)labelMat 樣本標簽返回參數(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é)省計算量,#如果沒有歷史記錄,就計算E2if(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的輸出值u2E2 = float(u2 - y2)#誤差E2#dS.eCache[i2] = [1,E2]r2 = E2*y2#如果當前的alpha2在一定容忍誤差內(nèi)不滿足KKT條件,則需要對其進行更新if((r2<-dS.toler and alpha2<dS.C) or (r2>dS.toler and alpha2>0)):'''#隨機選擇的方法確定另一個乘子alpha1,多執(zhí)行幾次可可以收斂到很好的結(jié)果,就是效率比較低i1 = random.randint(0, dS.m-1)if(takeStep(i1,i2,dS)):return 1'''#啟發(fā)式的方法確定另一個乘子alpha1nonZeroAlphasList = np.nonzero(dS.alphas.A)[0].tolist()#找到所有的非0的alphanonCAlphasList = np.nonzero((dS.alphas-dS.C).A)[0].tolist()#找到所有的非C的alphanonBoundAlphasList = list(set(nonZeroAlphasList)&set(nonCAlphasList))#所有非邊界(既不=0,也不=C)的alpha#如果非邊界的alpha數(shù)量至少兩個,則在所有的非邊界alpha上找到能夠使\E1-E2\最大的那個E1,對這一對乘子進行更新if(len(nonBoundAlphasList) > 1):maxE = 0maxEindex = 0for k in nonBoundAlphasList:if(abs(dS.eCache[k,1]-E2)>maxE):maxE = abs(dS.eCache[k,1]-E2)maxEindex = ki1 = maxEindexif(takeStep(i1,i2,dS)):return 1#如果上面找到的那個i1沒能使alpha和b得到有效更新,則從隨機開始處遍歷整個非邊界alpha作為i1,逐個對每一對乘子嘗試進行更新randomStart = random.randint(0,len(nonBoundAlphasList)-1)for i1 in range(randomStart,len(nonBoundAlphasList)):if(i1 == i2):continueif(takeStep(i1,i2,dS)):return 1for i1 in range(0,randomStart):if(i1 == i2):continueif(takeStep(i1,i2,dS)):return 1#如果上面的更新仍然沒有return 1跳出去或者非邊界alpha數(shù)量少于兩個,這種情況只好從隨機開始的位置開始遍歷整個可能的i1,對每一對嘗試更新 randomStart = random.randint(0,dS.m-1)for i1 in range(randomStart,dS.m):if(i1 == i2):continueif(takeStep(i1,i2,dS)):return 1for i1 in range(0,randomStart):if(i1 == i2):continueif(takeStep(i1,i2,dS)):return 1 '''i1 = random.randint(0,dS.m-1)if(takeStep(i1,i2,dS)):return 1 '''#如果實在還更新不了,就回去重新選擇一個alpha2吧,當前的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 = 0examineAll = 1#顯然,如果全局遍歷了一次,并且沒有任何更新,此時examineAll和numChanged都會被置零,算法終止while(numChanged > 0 or examineAll):numChanged = 0if(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):continuenumChanged += examineExample(i,dS)if(examineAll == 1):examineAll = 0elif(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ù)點、超平面,并標出支持向量輸入?yún)?shù):dS dataStruct類的數(shù)據(jù)W 超平名的法向量W 返回參數(shù):None''' def showClassifer(dS,w):#繪制樣本點dataMat = dS.dataMat.tolist()data_plus = [] #正樣本data_minus = [] #負樣本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') #負樣本散點圖#繪制直線x1 = max(dataMat)[0]x2 = min(dataMat)[0]a1, a2 = wb = float(dS.b)a1 = float(a1[0])a2 = float(a2[0])y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2plt.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,epsfor 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è)圖像實時信息傳送到計算機中, 獲取的圖像顯示在界面左側(cè)窗口, 界面運行結(jié)果如圖所示。
按鍵功能模塊, 通過算式提取按鍵, 對紙張中單個算式整體區(qū)域進行選框提取, 運行結(jié)果如圖所示,
此時算式檢測的結(jié)果在原圖像上用矩形框標記,在界面右側(cè)顯示提取到的算式效果。
圖像處理模塊, 通過檢測識別按鍵完成字符分割和識別, 在界面右側(cè)窗口顯示預(yù)處理后的圖像效果。 批改結(jié)果輸出模塊,
在界面下框中顯示字符的識別結(jié)果以及手寫的計算結(jié)果, 同時在右下角窗口顯示解答正誤, 輸出得到的批改信息。 同時對整個過程運行的時間進行統(tǒng)計,
最后保存按鍵將錯誤的批改結(jié)果保存, 便于后期修改。 此時系統(tǒng)運行界面如圖所示。
8 最后
🧿 更多資料, 項目分享:
https://gitee.com/dancheng-senior/postgraduate