USTC-NP2016秋季網絡程序設計
這門課的第一堂,老師說要“玩個大的”,因爲自己基礎不太好,便打了退堂鼓,但是由於一直以來接受的都是傳統的“灌輸式”教學方法,孟寧老師這種類似團隊做項目,讓同學們自己來學習分享的教學方法十分吸引我,於是選擇了這門課,果然受益匪淺。
課程主要目標
這門課程的主要目標是通過學習神經網絡和深度學習等機器學習算法來實現出一個完整的對血常規檢驗報告的OCR識別、學習與分析系統,主要對年齡與性別進行了預測。
//項目的最後效果就是,用戶上傳一張血常規報告單的圖片,後臺首先進行OCR識別出圖片中的項目,將其存入MongoDB,然後會根據機器學習算法生成的模型對用戶數據進行預測。
項目地址
項目總覽
本項目分兩大部分,前端展示和後臺OCR及預測;三大模塊,web模塊,圖像OCR模塊,學習預測模塊。
項目環境配置及運行
環境配置
安裝numpy,python的數據科學庫
1 $ sudo apt-get install python-numpy
安裝opencv,
1 $ sudo apt-get install python-opencv
安裝pytesseract( OCR識別庫pytesseract開源項目: pytesseract)
$ sudo apt-get install tesseract-ocr
$ sudo pip install pytesseract
$ sudo apt-get install python-tk
$ sudo pip install pillow
安裝Flask框架
$ sudo pip install Flask
安裝mongodb
$ sudo apt-get install mongodb # 如果提示no module name mongodb, 先執行sudo apt-get update
$ sudo service mongodb started
$ sudo pip install pymongo
安裝Tensorflow,當前最流行的深度學習平臺
$ sudo apt-get install python-numpy
$ sudo apt-get install python-imaging
$ pip install --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.12.0rc0-cp27-none-linux_x86_64.whl
運行
$ cd BloodTestReportOCR
$ python view.py # upload圖像,在瀏覽器打開http://yourip:8080
各模塊說明
Web模塊
view.py:
Web 端上傳圖片到服務器,存入mongodb並獲取oid,目前並不完善。
圖像識別模塊
imageFilter.py
實現了圖像的透視,剪裁,識別等,OCR進行了簡單的封裝,以便於模塊間的交互,規定適當的接口
imageFilter = ImageFilter() # 可以傳入一個opencv格式打開的圖片
num = 22
print imageFilter.ocr(num)
ocr函數
模塊主函數返回識別數據
用於對img進行ocr識別,他會先進行剪切,之後進一步做ocr識別,返回一個json對象 如果剪切失敗,則返回None @num 規定剪切項目數
perspect函數
初步的矯正圖片
用於透視image,他會緩存一個透視後的opencv numpy矩陣,並返回該矩陣 透視失敗,則會返回None,並打印不是報告 @param 透視參數
具體處理過程:
1,先對圖像進行灰度化,高斯平滑,開閉運算,輪廓識別
#灰度化
img_gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY)
#高斯平滑
img_gb = cv2.GaussianBlur(img_gray, (gb_param,gb_param), 0)
#閉運算
closed = cv2.morphologyEx(img_gb, cv2.MORPH_CLOSE, kernel)
#開運算
opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel)
#canny算子邊緣檢測
edges = cv2.Canny(opened, canny_param_lower , canny_param_upper)
2.由於我們上傳的報告單中既含有中文,英文還有數字,符號,直接識別十分困難,因此我們將圖片分割成不同的小塊,定位於是成了關鍵。我們將黑線當成識別的特徵
# 調用findContours提取輪廓
contours, hierarchy = cv2.findContours(edges,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
#求最小外接矩形
def getbox(i):
rect = cv2.minAreaRect(contours[i])
box = cv2.cv.BoxPoints(rect)
box = np.int0(box)
return box
比較最小外接矩形相鄰兩條邊的長短
以兩條短邊的中點作爲線的兩端
所有的線兩兩進行比較篩選
# 由三條線來確定表頭的位置和表尾的位置
line_upper, line_lower = findhead(line[2],line[1],line[0])
# 由表頭和表尾確定目標區域的位置
# 利用叉乘的不可交換性確定起始點
total_width = line_upper[1]-line_upper[0]
total_hight = line_lower[0]-line_upper[0]
cross_prod = cross(total_width, total_hight)
if cross_prod <0:
temp = line_upper[1]
line_upper[1] = line_upper[0]
line_upper[0] = temp
temp = line_lower[1]
line_lower[1] = line_lower[0]
line_lower[0] = temp
如果圖像拍的不完全正,就進行透視變換
#使用透視變換將表格區域轉換爲一個1000*760的圖
PerspectiveMatrix = cv2.getPerspectiveTransform(points,standard)
self.PerspectiveImg = cv2.warpPerspective(self.img,PerspectiveMatrix, (1000, 760))
這樣得到的圖就是1000*760的相同大小圖,能夠進行裁剪成每個小方塊。之後調用ocr識別庫進行識別。就能夠得到訓練的數據。
關於param
參數的形式爲[p1, p2, p3 ,p4 ,p5]。 p1,p2,p3,p4,p5都是整型,其中p1必須是奇數。
p1是高斯模糊的參數,p2和p3是canny邊緣檢測的高低閾值,p4和p5是和篩選有關的乘數。
如果化驗報告單放在桌子上時,有的邊緣會稍微翹起,產生比較明顯的陰影,這種陰影有可能被識別出來,導致定位失敗。 解決的方法是調整p2和p3,來將陰影線篩選掉。但是如果將p2和p3調的比較高,就會導致其他圖裏的黑線也被篩選掉了。 參數的選擇是一個問題。 我在getinfo.default中設置的是一個較低的閾值,p2=70,p3=30,這個閾值不會屏蔽陰影線。 如果改爲p2=70,p3=50則可以屏蔽,但是會導致其他圖片識別困難。
就現在來看,得到較好結果的前提主要有三個:
- 化驗單儘量平整
- 圖片中應該包含全部的三條黑線
- 圖片儘量不要包含化驗單的邊緣,如果有的話,請儘量避開有陰影的邊緣。
filter函數
過濾掉不合格的或非報告圖片
返回img經過透視過後的PIL格式的Image對象,如果緩存中有PerspectivImg則直接使用,沒有先進行透視 過濾失敗則返回None @param filter參數
autocut函數
將圖片中性別、年齡、日期和各項目名稱數據分別剪切出來
用於剪切ImageFilter中的img成員,剪切之後臨時圖片保存在out_path, 如果剪切失敗,返回-1,成功返回0 @num 剪切項目數 @param 剪切參數
剪切出來的圖片在BloodTestReportOCR/temp_pics/ 文件夾下
函數輸出爲data0.jpg,data1.jpg……等一系列圖片,分別是白細胞計數,中性粒細胞記數等的數值的圖片。
classifier.py
用於判定裁剪矯正後的報告和裁剪出檢測項目的編號
imgproc.py
將識別的圖像進行處理二值化等操作,提高識別率 包括對中文和數字的處理
digits
將該文件替換Tesseract-OCR\tessdata\configs中的digits
深度學習和機器學習模塊
這一部分主要就是對數據進行處理,這一部分剛開始我想跟着用TensorFlow,但是由於某些問題,電腦配置不成功,於是換了scikit-learn來實現對於年齡與性別的預測。
神經網絡原理
神經網絡由能夠互相通信的節點構成,赫布理論解釋了人體的神經網絡是如何通過改變自身的結構和神經連接的強度來記憶某種模式的。而人工智能中的神經網絡與此類似。請看下圖,最左一列藍色節點是輸入節點,最右列節點是輸出節點,中間節點是隱藏節點。該圖結構是分層的,隱藏的部分有時候也會分爲多個隱藏層。如果使用的層數非常多就會變成我們平常說的深度學習了。
一個簡單的神經網絡
每一層(除了輸入層)的節點由前一層的節點加權加相加加偏置向量並經過激活函數得到,公式如下:
f是激活函數,b是偏置向量
神經網絡屬於監督學習,那麼多半就三件事,決定模型參數,通過數據集訓練學習,訓練好後就能到分類工具/識別系統用了。數據集可以分爲2部分(訓練集,驗證集),也可以分爲3部分(訓練集,驗證集,測試集),訓練集可以看作平時做的習題集(可反覆做)。通過不斷的訓練減少損失,我們就可以得到最優的參數,即偏置向量和權重。
遞歸神經網絡
RNN通常用於描述動態時間行爲序列,將狀態在自身網絡中循環傳遞,可以接受更爲廣泛的時序序列結構輸入。不同於前饋深層神經網絡,RNN更重視網絡的反饋作用。由於存在當前狀態和過去狀態的連接,RNN可以具有一定的記憶功能。
當前代表性的遞歸神經網絡包括傳統RNN模型,長短期記憶神經網絡(long short-term memory, LSTM) [Hochreiter97]以及GRU(gated recurrent unit)模型[Cho14],
卷積神經網絡
在課程上通過同學的分享我瞭解了很多機器學習的方法,例如感知機、支持向量機、決策樹、隨機森林以及不同的神經網絡算法。這裏簡單介紹一下其中我比較熟悉的卷積神經網絡。
卷積神經網絡是人工神經網絡的一種,是一種前饋型網絡,它已經成爲了當前語音分析和圖像識別領域的研究熱點,它的權值共享網絡結構使之更類似於生物神經網絡,降低了網絡模型的複雜度,減少了權值的數量。它使得圖像可以直接作爲網路的輸入,避免了傳統識別算法中的特徵提取和數據重建過程。卷積神經網絡相比傳統的神經網絡模型,主要區別是加入了卷積層和抽樣層。
卷積層的一個特徵圖(feature map)和輸入層通過一組權重集(3×3的卷積核)相連,這樣每個特徵圖中的神經元對應的輸入區域也爲 3×3的區域,通過和卷積核進行卷積運算得到該神經元的輸入。輸入層中虛線矩形框範圍內的 3×3個輸入層節點和卷積核進行卷積運算,其結果和一個偏差相加之後作爲特徵圖第一個節點的輸入。具體卷積操作如下圖所示。由於同一個特徵圖的節點共享同一個卷積核作爲連接,所以這種連接方式和全連接構成的神經網絡模型相比,待訓練的權重大量減少。卷積核一般有多個,每個對應輸入圖的一個特徵,通過卷積操作得到一個卷積層特徵圖。因此一個卷積層的特徵圖數目比其輸入層的特徵圖數目多很多,這種特徵轉換使得特徵的維數快速上升,爲了避免維數太多,可以引入抽樣層對卷積層特徵圖進行特徵篩選,抽樣層特徵圖的每個神經元節點的輸入是前一層對應特徵圖中一個窗形區域內的節點集以某種方式篩選所得。通過卷積層和抽樣層處理後的數據會被送到類似傳統神經網絡的全連接層,最後到輸出層。
TensorFlow
最終還是用了tensorflow全連接網絡
預測流程:
tf_predict.py
先進行數據歸一化預處理:
def normalized(a,b):
for i in range(22):
tmp = np.mean(a[:, i])
a[:, i] = a[:, i] - tmp
b[:, i] = b[:, i] - tmp
if np.min(a[:, i]) != np.max(a[:, i]):
b[:, i] = 2 * (b[:, i] - np.min(a[:, i])) / (np.max(a[:, i]) - np.min(a[:, i])) - 1
else:
b[:, i] = 0
return b
然後構建神經網絡模型並進行預測。這裏的模型採用了四層網絡結構,分別是1個輸入層,2個隱藏層,1個輸出層,激活函數採用的是relu函數。本次項目採用了機器學習庫tensorflow進行預測模塊的開發。
設置學習率、每層的單元數等參數:
learning_rate = 0.005
display_step = 100
n_input = 22
n_hidden_1_age = 32
n_hidden_2_age = 16
n_classes_age = 1
構建神經網絡模型的部分主要代碼:
def multilayer_perceptron_age(x_age, weights_age, biases_age):
# Hidden layer with RELU activation
layer_1 = tf.add(tf.matmul(x_age, weights_age['h1']), biases_age['b1'])
layer_1 = tf.nn.relu(layer_1)
# Hidden layer with RELU activation
layer_2 = tf.add(tf.matmul(layer_1, weights_age['h2']), biases_age['b2'])
layer_2 = tf.nn.relu(layer_2)
# Output layer with linear activation
out_layer = tf.matmul(layer_2, weights_age['out']) + biases_age['out']
return out_layer
weights_age = {
'h1': tf.Variable(tf.random_normal([n_input, n_hidden_1_age])),
'h2': tf.Variable(tf.random_normal([n_hidden_1_age, n_hidden_2_age])),
'out': tf.Variable(tf.random_normal([n_hidden_2_age, n_classes_age]))
}
biases_age = {
'b1': tf.Variable(tf.random_normal([n_hidden_1_age])),
'b2': tf.Variable(tf.random_normal([n_hidden_2_age])),
'out': tf.Variable(tf.random_normal([n_classes_age]))
}
項目運行
啓動
$ cd BloodTestReportOCR
$ python view.py
訪問 http://0.0.0.0:8080/
這樣就進入了以下頁面:
上傳圖片
我上傳的是這張圖:
經過上面提到的ImageFilter模塊的處理,上傳成功後是這樣的:
生成報告
點擊預測
課程心得
之前就對孟老師有所耳聞,之前可惜沒能選上孟寧老師的高級軟件工程課程,我很欣賞孟寧老師這種課程教學方式,不是老師讀PPT的傳統式教學,給予學生充分的自主權。引導我們進行團隊開發做工程。
這門課對我來說的確是很難,不僅是因爲之前代碼量少,而且也是因爲要學習一門新的語言與大量的理論知識。學習分享非常棒,調動了大家學習的熱情,而且相互之間的分享討論也減輕了自己學習的負擔,很不好意思的是沒能在這個工程裏多多貢獻,但是這門課的確是讓我對各個方面包括工程,團隊合作,圖像處理以至於神經網絡等方面有了更進一步的瞭解。之前一直覺得理論知識學的足夠了才能能動手敲代碼,這門課也糾正了我這個想法,只有理論與實踐結合起來才能更快更有效率的學習到應用上學到的 東西,兩者相輔相成,老師的教學方法我認爲十分適合計算機這門學科,應該被廣泛應用到計算機教學中,及保證了理論知識的學習,又沒有放鬆動手能力的培養,實在是巧妙。
我最大的收穫就是要立即行動,勤於動手。機器學習這纔是剛剛開始。