基於影像視覺詞彙的文字分類方法(完整專案)
向AI轉型的程式設計師都關注了這個號???
大資料探勘DT資料分析 公眾號: datadw
一年多以前我腦子一熱,想做一款移動應用:一款給學生朋友用的“錯題集”應用,可以將錯題拍照,記錄影像的同時,還能自動分類。比如拍個題目,應用會把它自動分類為"物理/力學/曲線運動"。當然,這個專案其實不靠譜,市場上已經有太多“搜題”類應用了。但過程很有趣,導致我過了一年多,清理磁碟垃圾時,還捨不得刪掉這個專案的“成果”。
本文程式碼及樣本資料在公眾號 datadw 裡 回覆 文字分類 即可獲取。
這個專案,核心要解決的問題就是文字分類。所以最初想到的方案是先 OCR 圖片轉文字,然後分詞,再計算 tf-idf,最後用 SVM 分類。但這個方案的問題是:開源 OCR 普遍需要自己訓練,且需要做大量的優化、調校和訓練,才能在中文識別上有不錯的效果,加上影像上還會有公式、幾何圖形,這些特徵也會決定分類,這又提高了對 OCR 的要求。所以我最終選擇的方案是,不使用 OCR,而是直接從影像中尋找有區分性的、魯棒的特徵,作為視覺詞彙。之後再通過傳統文字分類的方法,訓練分類器。
下面將展示整個訓練過程,訓練的樣本來自《2016 B版 5年高考3年模擬:高考理數》,並手工標註了14個分類,每個分類下約50個樣本,每個樣本為一個題目, 影像為手機拍攝。
本文中大部分演算法庫來自numpy、scipy、opencv、skimage、sklearn。
預處理
為了獲取到穩定的特徵,我們需要對影像進行預處理,包括調整影像大小,將影像縮放到合適尺寸;旋轉影像,或者說調整成水平;二值化,去除色彩資訊,產生黑白影像。
1.1. 調整影像大小
調整的目的是為了讓影像中文字的尺寸保持大致相同的畫素尺寸。這裡做了一個簡單假設,即:影像基本是一段完整的文字,比如一個段落,或者一頁文件,那麼不同的影像中,每行文字的字數相差不會很大。這樣我就可以從我所瞭解的、少得可憐的影像工具庫裡找到一個工具了:直線擬合。即通過擬合的直線(線段)長度與影像寬度的比例,調整影像的大小。下圖為兩張不同尺寸影像,經過多次擬合+調整大小後的結果,其中紅色演算法檢查到的直線(線段)。
下面是使用 opencv 直線擬合的程式碼:
# Canny演算法提取邊緣特徵, image是256灰度影像
image = cv2.Canny(image, 50, 200)
# 霍夫線變換提取直線
lines = cv2.HoughLinesP(image, 2, math.pi / 180.0, 40, numpy.array([]), 50, 10)[0]
1.2. 影像二值化
二值演算法選用skimage.filters.threshold_adaptive
(區域性自適應閥值的二值化), 試下來針對這種場景,這個演算法效果最好,其他演算法可以去scikit-image文件瞭解。下圖為全域性閥值和區域性自適應閥值的效果對比:
相關程式碼如下:
# 全域性自適應閥值binary_global = image > threshold_otsu(image)
binary_global = numpy.array(binary_global, 'uint8') * 255
binary_global = cv2.bitwise_not(binary_global)
#反轉黑白# 區域性自適應閥值
adaptive = threshold_adaptive(image, 41, offset=10)
adaptive = numpy.array(adaptive, 'uint8') * 255
adaptive = cv2.bitwise_not(adaptive)
#反轉黑白
1.3. 旋轉影像
從第一步獲取到的直線,可以計算出影像的傾斜角度,針對只是輕微傾斜的影像,可以反向旋轉進行調整。由於可能存在干擾線條,所以這裡取所有直線傾斜角度的中值比平均值更合適。下圖展示了影像旋轉跳轉前後的效果:
相關程式碼如下:
# 先計算所有線條的角度angles = []for line in lines:
x = (line[2] - line[0])
y = (line[3] - line[1])
xy = (x ** 2 + y ** 2) ** 0.5
if 0 == xy: continue
sin = y / xy
angle = numpy.arcsin(sin) * 360. / 2. / numpy.pi
angles += [angle] # 計算中值
angle = numpy.median(angles)
# 旋轉影像
image = ndimage.rotate(image, angle)
2. 提取特徵
這裡的思路是,首先通過形態學處理,可以分割出文字行(的影像),再從文字行中分割出詞彙(的影像),然後從"詞彙"中提取特徵。但這裡的需要克服的困難是:
很多漢字分左右部,容易被錯分,比如
你好
, 可能被分割成以4塊影像:亻
、爾
、女
、子
。獨立的“字”並不適合於文字分類,還需能學習出詞彙。
針對以上問題的解決方案是:
將小的影像塊進行組合,組合後的新影像塊和原來的小塊影像一起作為原始影像的特徵,如
你好
將得到10個特徵:亻
、你
、你女
,你好
,爾
、爾女
、爾好
、女
、好
、子
。得益於上面的方案,詞彙資訊也被保留了下來,所以第二個問題也就解決了,同時增加了演算法的魯棒性。
下面將介紹具體實現。
2.1. 提取文字行
由於預處理過程中已經將樣本的影像尺寸基本調整一致,所以可以比較容易的利用形態學的處理方法,分割出文字行。過程如下:
# cv2.Canny 可提取邊緣,並去除噪點# image為調整過大小,但沒有調整水平和二值化的影像
# 二值化後會影響 cv2.Canny 演算法效果,所以這裡用還沒有二值化的圖片
image = cv2.Canny(image, 100, 200)
# 二值化後調整水平image = ndimage.rotate(image, slope)
# 進行四次膨脹和腐蝕操作# 水平方向膨脹和腐蝕,聯通字與字之間的空間
# 垂直方向做較小的膨脹和腐蝕,填補行內的空隙
image = cv2.dilate(image, cv2.getStructuringElement(cv2.MORPH_RECT, (40, 3)))
image = cv2.erode(image, cv2.getStructuringElement(cv2.MORPH_RECT, (40, 3)))
image = cv2.erode(image, cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)))
image = cv2.dilate(image, cv2.getStructuringElement(cv2.MORPH_RECT, (6, 5)))
下圖展示了每一步的變化:
接下來可以利用scipy庫中的measurements.label
方法,標記出不同的的區域,下圖展示了標註後的效果,不同區域以不同的灰度表示。
相關程式碼如下:
# image 為上一步形態學處理後的影像
image = 1 * (image > 64)
# 只保留灰度>64的區域,可以去除一些躁點
labeled, count = measurements.label(image)
# labeled為一個和影像尺寸一致的矩陣,矩陣中每個元素的值即這個畫素位置所屬的區域索引
# count為區域數量figure()
gray()
imshow(labeled)
show()
接下來根據標記的區域,可從影像中裁剪出每行的資料,如下圖:
相關程式碼如下:
def bounding_box(src):
'''
矩陣中非零元素的邊框
'''
B = numpy.argwhere(src) if B.size == 0: return [0, 0, 0, 0]
(ystart, xstart), (ystop, xstop) = B.min(0), B.max(0) + 1
return [xstart, ystart, xstop - xstart, ystop - ystart] def clip_lines(image, labeled, count)
lines = [] for i in range(1, count + 1):
temp = image.copy()
temp[labeled != i] = 0
box = bounding_box(temp)
x, y, w, h = box
data = temp[y:y + h, x:x + w]
lines.append(data)
return lines
2.2. 提取特徵(視覺詞彙)
裁剪出單行文字影像後,我們可以將影像中各列的畫素的值各自累加,得到一個一緯陣列,此陣列中的每個區域性最小值所在的位置,即為文字間的空隙。如下圖所示,其中藍色線為畫素值的累加值,綠色線為其通過高斯濾波平滑後的效果,紅色線為最終檢測到的分割點。
詳細過程見下面程式碼:
# 1. 將影像中每一列的所有畫素的值累加orisum = image.sum(axis=0) / 255.0
# 2. 累加後的陣列通過高斯濾波器做平滑處理,減少干擾
filtered = filters.gaussian_filter(orisum, 8)
# 3. 找出拐點(上升轉下降、下降轉上升的點)trend = False
# False 下降,True上升preval = 0
# 上一個值points = [] # 拐點pos = 0for i in filtered:
if preval != i:
if trend != (i > preval):
trend = (i > preval)
points += [[pos if pos == 0 else pos - 1, preval, orisum[pos]]]
pos = pos + 1
preval = i
# 4. 下降轉上升的拐點即為分割點
... 程式碼略 ...
將單行的影像按上述方法獲取的分割點進行裁剪,裁剪出單個字元,然後再把相鄰的單個字元進行組合,得到最終的特徵資料。組合相鄰字元是為了使特徵中保留詞彙資訊,同時增加魯棒性。下圖為最終獲得的特徵資訊:
本文中使用的所有樣本,最終能提取出約30萬個特徵。
2.3. 選擇特徵描述子
選擇合適的特徵描述子通常需要直覺+運氣+不停的嘗試(好吧我承認這裡沒有什麼經驗可分享),經過幾次嘗試,最終選中了HOG(方向梯度直方圖)描述子。HOG 最讓人熟悉的應用領域應該是行人檢測了,它很適合描述鋼性物體的邊緣特徵(方向),而印刷字型首先是剛性的,其次其關鍵資訊都包含在邊緣的方向上,所以理論上也適合用 HOG 描述。更多關於HOG的介紹請點選這裡
https://link.jianshu.com/?t=http://scikit-image.org/docs/dev/auto_examples/features_detection/plot_hog.html#sphx-glr-auto-examples-features-detection-plot-hog-py
下圖為文字影像及其 HOG 描述子的視覺化:
程式碼如下:
# 提取邊緣canny = cv2.Canny(numpy.uint8(img), 50, 200)
# 計算描特徵描述子desc, hog_image = hog(
canny,
orientations=6,
pixels_per_cell=(4, 4),
cells_per_block=(2, 2),
visualise=True)
3. 訓練詞彙分類器
對詞彙進行人工標註工作量太大,所以最好能做到自動分類。我的做法是先聚類,再基於聚類的結果訓練分類器。但有個問題,主流的聚類演算法中,除了 K-Means 外,其他都不適合處理大量樣本(目前有30萬+樣本),但 K-Means 在這個場景上聚類效果不佳,高頻但不相關的詞彙容易被聚成一類,而 DBSCAN 效果很好,但樣本數一多,所需時間幾何級增長(在我的機器上,超過兩萬個樣本就需要耗費數個小時)。下圖來自sklearn 文件,對各聚類演算法做了比較:
2017/09/21 修改:原此處選擇的聚類方法(即先使用先用 K-Means 做較少的分類然後對每個分類單獨使用 DBSCAN 聚類並單獨訓練 SVC 分類器),準確率保持在70%左右,很難提高,故改用了下面描述的新方法。
本文來自 微信公眾號 datadw 【大資料探勘DT資料分析】
為解決這一問題,我的做法是:
1. 先對每類樣本下的詞彙用 DBSCAN 聚類(約1萬個詞彙樣本),得到一級分類。
2. 聚類後,計算每個一級分類的中心,然後以所有中心為樣本再用DBSCAN聚類,得到二級分類。完成後,原一級分類中心的新分類,即代表其原一級分類下所有元素的分類。
聚類的過程為,使用前面提取的 HOG 特徵,先 PCA 降緯,再 DBSCAN 聚類。這裡注意,計算二級分類時,PCA應使用全域性樣本計算。
分類器使用SGDClassifier
,原因是其支援分批計算,不至於導致記憶體不足。
本文中使用的樣本,最終得到3000+詞彙型別。下圖為分類效果,其中每一行為一個分類:
4. 訓練文字分類器
有了詞彙分類器,我們終於可以識別出每個文字樣本上所包含的詞彙了(事實上前面步驟的中間過程也能得到每個樣本的詞彙資訊),於是我們可以給每個樣本計算一個詞袋模型(即用每個詞出現的次數表示一篇文字),再通過池袋模型計算TF-IDF模型(即用每個詞的 TF*IDF 值表示一篇文字),並最終訓練 SVM 分類器。下面展示了此過程的主要程式碼:
Fitting the classifier to the training set
done in 0.034s
score : 0.918639053254Predicting on the test set
done in 0.004s
precision recall f1-score support
物理-電學-靜電場 1.00 0.67 0.80 3
物理-力學-互相作用 0.56 1.00 0.71 5
物理-機械振動和機械波 0.83 1.00 0.91 5
物理-電學-電磁感應 0.71 1.00 0.83 5
物理-電學-恆定電流 1.00 1.00 1.00 5
物理-力學-曲線運動 0.88 0.78 0.82 9
物理-機械能及其守恆定律 0.62 0.56 0.59 9
物理-光學 1.00 0.50 0.67
2物理-力學-萬有引力與航天 1.00 0.75 0.86 4
物理-力學-牛頓運動定律 0.62 0.71 0.67 7
物理-電學-交變電流 1.00 1.00 1.00 1
物理-電學-磁場 1.00 0.25 0.40 4
物理-熱學 1.00 1.00 1.00
2物理-力學-質點的直線運動 0.86 0.86 0.86 7
avg / total 0.81 0.78 0.77 68
[[2 0 0 0 0 0 1 0 0 0 0 0 0 0]
[0 5 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 5 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 5 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 5 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 7 0 0 0 0 0 0 0 1]
[0 2 0 0 0 1 5 0 0 1 0 0 0 0]
[0 0 0 0 0 0 0 1 0 1 0 0 0 0]
[0 0 0 0 0 0 1 0 3 0 0 0 0 0]
[0 1 0 0 0 0 1 0 0 5 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 1 0 0 0]
[0 0 0 2 0 0 0 0 0 1 0 1 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 2 0]
[0 0 1 0 0 0 0 0 0 0 0 0 0 6]]
測試集上正確率 81%,召回率 78%。個別分類正確率較低,可能是因為樣本數太少,另外訓練過程大多使用預設引數,若進行細緻調校,應該還有提高空間。
https://www.jianshu.com/p/f774e273a883
人工智慧大資料與深度學習
搜尋新增微信公眾號:weic2c
長按圖片,識別二維碼,點關注
大資料探勘DT資料分析
搜尋新增微信公眾號:datadw
教你機器學習,教你資料探勘
長按圖片,識別二維碼,點關注
相關文章
- 「影像分類」 實戰影像分類網路的視覺化視覺化
- 視覺化影像處理 | 視覺化訓練器 | 影像分類視覺化
- 基於spark2.0文字分詞+多分類模型Spark分詞模型
- 基於python----影像的表示和視覺化Python視覺化
- 目標檢測和影像分類及其相關計算機視覺的影像分佈計算機視覺
- 完整的牛津3000詞彙表及牛津5000詞彙表
- Flutter 基於BLoC完整Flutter App專案FlutterBloCAPP
- 紅色視覺網,一個基於thinkphp的視覺類圖片網站視覺PHP網站
- 基於Tensorflow + Opencv 實現CNN自定義影像分類OpenCVCNN
- 分詞工具Hanlp基於感知機的中文分詞框架HanLP中文分詞框架
- [計算機視覺]基於內容的影像搜尋實現計算機視覺
- Nodejs CMS——基於 NestJS/NuxtJS 的完整開源專案NodeJSUX
- 中文分詞工具之基於字標註法的分詞中文分詞
- 【火爐煉AI】機器學習051-視覺詞袋模型+極端隨機森林建立影像分類器AI機器學習視覺模型隨機森林
- 基於支援向量機的影像分類系統(MATLAB GUI介面版)MatlabGUI
- 影像分類:來自13個Kaggle專案的經驗總結
- 如何用Python從PDF檔案中提取文字詞彙Python
- 關於視覺化程式設計分類的民間智慧 – drossbucket視覺化程式設計ROS
- Swift 專案總結 02 常用分類方法Swift
- 基於hanlp的es分詞外掛HanLP分詞
- 【VIP視訊網站專案上線】基於Nodejs的Express框架開發的VIP視訊網站專案及完整程式碼分享...網站NodeJSExpress框架
- 音樂檔案一鍵整理分類指令碼(按照歌手-專輯進行分類)--基於python指令碼Python
- 史上最全的iOS開源專案分類彙總沒有之一iOS
- 人類視覺系統對影像的認知 與 影像壓縮的關係視覺
- MapReduce實現與自定義詞典檔案基於hanLP的中文分詞詳解HanLP中文分詞
- 基於PaddlePaddle的影像分類實戰 | 深度學習基礎任務教程系列(一)深度學習
- 設計一個基於 LSTM 神經網路的文字分類器神經網路文字分類
- TensorFlow2基礎:CNN影像分類CNN
- 基於 HanLP 的 ES 中文分詞外掛HanLP中文分詞
- [Python] 基於 jieba 的中文分詞總結PythonJieba中文分詞
- ArcGIS中的影像分類
- 帶你讀論文丨基於視覺匹配的自適應文字識別視覺
- 計算機視覺 | Matlab實現單目視覺里程計基於SURF特徵(程式碼類)計算機視覺Matlab特徵
- NLP《詞彙表示方法(二)詞嵌入表示》
- 基於.NetCore開發部落格專案 StarBlog - (8) 分類層級結構展示NetCore
- 自動化專案基類實踐--視訊演示
- 文字分類-TextCNN文字分類CNN
- 文字分類模型文字分類模型