01.07 基於Tesseract的OCR識別小程序

一、背景

先說下開發背景,今年有次搬家找房子(2020了應該叫去年了),發現每天都要對著各種租房廣告打很多電話。(當然網上也找了實地也找),每次基本都是對著牆面看電話號碼然後撥打,次數一多就感覺非常麻煩,如果沒看清還容易輸錯一個號碼。

基於Tesseract的OCR識別小程序

圖片來自於網絡

當時就想現在OCR技術那麼流行,為什麼不能做個程序來解決這個問題。因為租房電話有部分還是手寫號碼,所以也要解決手寫識別的問題。同時租房信息其實也有很多是中介或者其他詐騙類等等。所以有部分並不是我們所需要的,為什麼這塊信息就不能做個平臺進行共享,類似於手機裡面識別和提交詐騙電話一樣。然後自己也搜索了下微信小程序裡有沒有類似的小程序,發現基本沒有,有些OCR相關程序但是使用相對繁瑣,並多數需要收費或開通會員等。

所以自己就想著還是自己來開發一個相關的小程序,功能簡單,通過相機拍照獲取電話號碼,進行識別;可直接在裡面調用電話進行撥號。同時允許提交該識別後的號碼作為標記,標記成詐騙、中介等。當某個號碼被人標記後,下次任何人再識別該號碼後就能顯示對應的標記信息。

二、核心問題

本次核心問題可能就是手寫識別了,普通的打印文字識別。已經很常見也很成熟了。我們主要是識別數字,所以只針對數字手寫識別。這些年第三方的例如BAT其實都有類似的服務接口提供,我也研究了下,對接很簡單。識別率還算行,能夠達到70%左右。畢竟手寫每個人千差萬別。但是第三方的終究都是要收費的,當然有一定的免費額度。我開發這個軟件也沒打算收費什麼的,所以第三方的暫且放棄使用了。

MNIST 機器學習

現在比較熱門的都是基於機器學習的手寫識別,機器學習框架有很多常用的比如TensorFlow ,在手寫數字識別裡面MNIST 數據集是最常用的,甚至流行到一般機器學習框架都使用MNIST 做入門教程了。關於MNIST我在這裡不做具體介紹,有興趣的可以自行上網瞭解。

然後我就網上找python機器學習手寫數字識別之類的,學習了很久。最後感覺使用MNIST數據集做識別更多還是一種識別算法的比較。因為MNIST裡本身就包含了樣本和測試數據集。通過機器學習將樣本的進行學習然後生成一個模型數據,然後根據模型數據去讀取樣本數據進行比較以此來查識別成功率。並且網上的案例基本都是類似,即根據數據集來學習和識別正確率。

我還找到了.NET Core使用ML.NET 基於MNIST數據集的手寫數字識別,運行結果如下圖所示。原理和過程和Python處理是一樣的,包括輸出結果。

基於Tesseract的OCR識別小程序

MNIST數據集是每張均為28*28像素的黑白圖片,並且是一個字符佔用一張圖片。我們平時OCR識別的時候都是一張圖一起識別所有文字(數字)。所以如果使用MNIST我們必須要對圖片文字(數字)進行精準分割再把圖像進行二值化保存為20*28像素的。手寫數字很多可能連載在一起,在此基礎上做分割還是有一定難度。

所以這裡暫時放棄使用了,後續抽時間會繼續研究使用MNIST進行識別。

Tesseract

Tesseract 是一個相對於比較有名的開源OCR識別軟件早期由惠普實驗室開發,現在是由Google在開發和維護。支持的平臺有Windows、linux、macos。支持的很多常用語言識別多達幾十種;還可以自己訓練文字庫,如果使用手寫識別所以需要自己去訓練字庫進行識別。

具體我就不過多闡述介紹了,感興趣的自行了解。我本次開發就是選用的 Tesseract 進行識別。

GitHub: https://github.com/tesseract-ocr/tesseract

三、前端開發

本次前端使用的是微信小程序,小程序因為不用安裝並且完全跨平臺。自己不是專業前端開發,所以不過多介紹,只介紹小程序裡如何使用相機拍照並且裁剪指定區域等。

1、拍照

相機拍照界面,因為只識別一個號碼所以拍照區域不需要很大,如果單純的把相機縮小又感覺很醜的樣子。所以有點類似微信掃描那樣的,需要的部分全亮其他部分半透明的效果。

如下圖所示

基於Tesseract的OCR識別小程序

一開始一直在想小程序的相機是否提供類似的功能,讓我通過設置甚直接達到這樣的效果。但是很可惜沒找到,相機上面加文字圖片都是可以的。其實這個一部分透明一般分半透明就是一個圖片。整個背景是個圖片,這也是自己想好了好久才想到的,然後自己通過PS把一張圖做成透明和半透明的效果。

代碼如下:

<code> 1 <camera>
2 <cover-view>
3 <cover-image> /<cover-image>
4 <cover-view>請對準電話號碼掃描/<cover-view>
5 <cover-view>*支持打印和手寫號碼識別/<cover-view>
6 <cover-view>
7 <cover-image>
8 <cover-view>
9 <cover-image>
10 /<cover-view>
11 /<cover-view>
12 /<cover-view>
13 /<camera>
/<code>

具體詳細代碼,請看文章結尾github地址。

2、裁剪

拍照界面實現了,下一步就是要實現拍照功能了,拍照代碼簡單,不做過多闡述。因為我們是要取完全透明的那一塊區域的圖像。也就是照片的指定位置,在拍照的api裡面微信沒有提供類似的獲取指定區域圖像的功能,所以我們要實現這功能就需要自己針對一個完整的圖片信息進行裁剪。

基於Tesseract的OCR識別小程序

如何截取這部分圖像呢?目前我的做法是根據框框的位置去找出在整個圖像中的位置即對應的X,Y。因為我們整個框框本來就是一張背景圖的一部分,所以它在代碼中是沒有一個實際座標位置的。我們需要將裁剪後的圖像展示出來,所以在另個頁面還需要一個canvas用來展示裁剪後的圖像。

下面代碼看到了我使用了延遲和出錯重試機制,因為在實際真機測試中偶爾還是會出現canvasToTempFilePath 方法報錯問題。原因就是在調用canvasToTempFilePath 前面需要繪製一個矩形框用於展示我們截取後的圖片。但是canvas.draw()方法是異步的,這樣就會導致前面還沒繪製完下面canvasToTempFilePath方法報錯。

裁剪主要代碼如下:

<code> 1   canvasToTempFile: function() {
2 var that = this;
3 setTimeout(function() {
4 wx.canvasToTempFilePath({ // 裁剪對參數
5 canvasId: "image-canvas",
6 x: that.data.image_x, // 畫布x軸起點
7 y: that.data.image_y, // 畫布y軸起點
8 width: that.data.width, // 畫布寬度
9 height: that.data.image_height, // 畫布高度
10 destWidth: that.data.width, // 輸出圖片寬度
11 destHeight: that.data.image_height, // 輸出圖片高度
12 canvasId: 'image-canvas',
13 success: function(res) {
14 that.filePath = res.tempFilePath;
15 // 清除畫布上在該矩形區域內的內容。
16 that.canvas.clearRect(0, 0, that.data.width, that.data.height);
17 that.canvas.drawImage(that.filePath, that.data.image_x, that.data.image_y, that.data.width - 20, that.data.image_height);
18 that.canvas.draw();
19 wx.hideLoading();
20 // 開始請求識別接口
21 that.startDiscern(res.tempFilePath);
22 // 開始獲取標記類型
23 that.getMarkType();
24 },
25 fail: function(e) {
26 // console.log("出錯了:" + e);
27 wx.hideLoading()
28 wx.showToast({
29 title: '請稍後...',
30 icon: 'loading'
31 })
32 // 出錯後繼續執行一次。
33 that.canvasToTempFile();
34 }
35 });
36 }, 1000);
37 }
/<code>

具體詳細代碼,請看文章結尾github地址。

四 、Tesseract 訓練字庫

首先Tesseract為了提高識別效果,可以允許我們自己訓練自己的字庫。即當Tesseract識別不正確時我們對它進行人工矯正。或者當他完全無法識別時,我們可以自己去標記要被識別的文字座標和正確的結果值。

使用的工具是 jTessBoxEditor, 使用之前需要安裝Java。

1、圖片轉換成tif格式

訓練樣板必須為TIFF格式,所以第一步就是需要轉成TIFF格式文件,同時對於多張圖片是可以合併成一個TIFF文件格式。這樣就能在一個文件裡保存多個圖片。在jTessBoxEditor 裡Tools->Merge即可進行,選擇多張圖片即可。但是保存為TIFF文件是格式一定要注意是

[lang].[fontname].exp[num].tif

lang為語言名稱(即自己訓練後的語言名稱),

fontname為字體名稱,

num為序號,可自定義。

2、生成BOX文件

下一步生成BOX文件,這一步其實就是使用Tesseract做一個基本的識別,識別後會生成一個box文件,這個文件保存著識別結果和結果對應的座標信息。

這一步一定要在電腦上安裝tesseract,不然無法執行。

生成BOX文件命令(注意要在剛生成tif文件目錄中)

tesseract num.font.exp0.tif num.font.exp0 batch.nochop makebox

3、jTessBoxEditor矯正錯誤並訓練

打開jTessBoxEditor,選擇Box Editor->Open 打開剛剛生成的tif文件所在目錄。該目錄必須包含上一步已經生成的box文件。選擇打開的是tif圖像文件,而不是box文件。

基於Tesseract的OCR識別小程序

打開後會看到初步識別的結果,如果不對自行修改矯正。一個是修改座標即藍色框框對應的矩形位置,另個就是修改識別出來的字符。

基於Tesseract的OCR識別小程序

修改矯正完成後,按Ctrl+S 或者上面的Save按鈕保存。

4、創建字體文件

創建一個文件名為font_properties的文件,放到同一個目錄下,注意沒有擴展名。

內容是:font 0 0 0 0 0

如下圖

基於Tesseract的OCR識別小程序

其中每個0對應的是各種字體。

分別為:

斜體,黑體,默認字體,襯線字體, 德文黑字體

0代表無1代表有

5、執行批處理

將下面代碼複製到一個txt文件中放到相同目錄下,修改擴展名為bat。

代碼如下:

<code> 1 echo Run Tesseract for Training..   
2 tesseract.exe num.font.exp0.tif num.font.exp0 nobatch box.train
3
4 echo Compute the Character Set..
5 unicharset_extractor.exe num.font.exp0.box
6 mftraining -F font_properties -U unicharset -
7 O num.unicharset num.font.exp0.tr
8
9
10 echo Clustering..
11 cntraining.exe num.font.exp0.tr
12
13 echo Rename Files..
14 rename normproto num.normproto
15 rename inttemp num.inttemp
16 rename pffmtable num.pffmtable
17 rename shapetable num.shapetable
18
19 echo Create Tessdata..
20 combine_tessdata.exe num.
21
22 echo. & pause
/<code>

最後執行此批處理即可,執行後會生成很多文件,如下圖,我們只需要拷貝目錄下的num.traineddata文件到項目的tessdata目錄中。

基於Tesseract的OCR識別小程序

五、後端開發

後端這塊主要使用.NET Core 寫了一個webapi,小程序將拍照並截取後的圖像轉換成base64格式傳入到後臺,後臺這邊通過調用Tesserac進行識別。前面我提到能對識別的號碼進行提交標記和獲取標記等。所以後臺這邊目前使用的是MongoDB進行相關數據的存儲和讀取。

普通接口和MongoDB等操作很簡單,這裡不做介紹。主要說下Tesseract識別相關實現。

安裝Tesseract依賴

Tesseract目前最新版本是4.1.0,

Nuget裡面對應最新版是Genesis.Tesseract4。

所以下載時注意別下載錯了。

基於Tesseract的OCR識別小程序

Tesseract 提高識別率主要兩個方面,第一個就是訓練更多的相關字庫,第二個就是圖像的處理。圖像這一塊原圖重要性最高,在我們這裡就是用戶拍攝圖片,圖片拍攝的清晰可見,文字後面沒有其他干擾等最好。

在圖像識別領域,圖像處理最常用的就是灰度化、二值化、圖像校正等。

灰度化

在RGB模型中,如果R=G=B時,則彩色表示一種灰度顏色,其中R=G=B的值叫灰度值,說通俗點就是把圖像處理成黑白圖像。

C# 代碼如下:

<code> 1 /// <summary>  
2 /// 圖像灰度化
3 /// /<summary>
4 /// <param>
5 /// <returns>
6 public static Bitmap ToGray(Bitmap bmp)
7 {
8 for (int i = 0; i < bmp.Width; i++)
9 {
10 for (int j = 0; j < bmp.Height; j++)
11 {
12 //獲取該點的像素的RGB的顏色
13 Color color = bmp.GetPixel(i, j);
14 //利用公式計算灰度值
15 int gray = (int)(color.R * 0.3 + color.G * 0.59 + color.B * 0.11);
16 Color newColor = Color.FromArgb(gray, gray, gray);
17 bmp.SetPixel(i, j, newColor);
18 }

19 }
20 return bmp;
21 }
/<code>

具體詳細代碼,請看文章結尾github地址。

二值化

二值化就是將大於某個值的像素點都修改為255,小於該值的修改為0

即0和1,其實是灰度圖像的0~255的簡版,0表示白色,1表示黑色

二值化裡最重要的就是閾值的選取,一般分為固定閾值和自適應閾值。 比較常用的二值化方法則有:雙峰法、P參數法、迭代法和OTSU法等。

c# 代碼如下:

<code> 1 /// <summary>  
2 /// 圖像二值化(迭代法)
3 /// /<summary>
4 /// <param>
5 /// <returns>
6 public static Bitmap ToBinaryImage(Bitmap bmp)
7 {
8 int[] histogram = new int[256];
9 int minGrayValue = 255, maxGrayValue = 0;
10 //求取直方圖
11 for (int i = 0; i < bmp.Width; i++)
12 {
13 for (int j = 0; j < bmp.Height; j++)
14 {
15 Color pixelColor = bmp.GetPixel(i, j);
16 histogram[pixelColor.R]++;
17 if (pixelColor.R > maxGrayValue) maxGrayValue = pixelColor.R;
18 if (pixelColor.R < minGrayValue) minGrayValue = pixelColor.R;
19 }

20 }
21 //迭代計算閥值
22 int threshold = -1;
23 int newThreshold = (minGrayValue + maxGrayValue) / 2;
24 for (int iterationTimes = 0; threshold != newThreshold &&iterationTimes< 100; iterationTime
25 {
26 threshold = newThreshold;
27 int lP1 = 0;
28 int lP2 = 0;
29 int lS1 = 0;
30 int lS2 = 0;
31 //求兩個區域的灰度的平均值
32 for (int i = minGrayValue; i < threshold; i++)
33 {
34 lP1 += histogram[i] * i;
35 lS1 += histogram[i];
36 }
37 int mean1GrayValue = (lP1 / lS1);
38 for (int i = threshold + 1; i < maxGrayValue; i++)
39 {
40 lP2 += histogram[i] * i;
41 lS2 += histogram[i];
42 }
43 int mean2GrayValue = (lP2 / lS2);
44 newThreshold = (mean1GrayValue + mean2GrayValue) / 2;
45 }
46
47 //計算二值化
48 for (int i = 0; i < bmp.Width; i++)
49 {
50 for (int j = 0; j < bmp.Height; j++)
51 {
52 Color pixelColor = bmp.GetPixel(i, j);
53 if (pixelColor.R > threshold) bmp.SetPixel(i, j, Color.FromArgb(255, 255, 255));
54 else bmp.SetPixel(i, j, Color.FromArgb(0, 0, 0));
55 }
56 }
57 return bmp;
58 }
/<code>

具體詳細代碼,請看文章結尾github地址。

下圖是我在這期間為了測試圖像處理效果及對比寫的一個工具。

灰度化

基於Tesseract的OCR識別小程序

二值化

基於Tesseract的OCR識別小程序

完整圖像處理代碼 https://github.com/cfan1236/ImageManipulation

無論灰度和二值化,都是為了強化和突出要被識別的區域,比如文字。弱化背景和其他干擾項,在最後的測試實驗中,發現並不是所有原圖經過一系列的圖像處理後都能得到更好的結果,有些圖片不做任何處理反而結果會更好。

所以我在實際處理中,使用三個線程同時處理識別3種情況,第一個使用原圖識別、第二個使用灰度化後識別、第三個使用二值化後識別。最後取識別結果最多的一個。

代碼如下:

<code>  1 /// <summary>
2 /// 數字識別
3 /// /<summary>
4 /// <param>
5 /// <param>
6 /// <returns>
7 public PhoneDiscernResult DiscernNumber(string base64_image, string image_url)
8 {
9 PhoneDiscernResult result = new PhoneDiscernResult();
10 string imageFile = GetImageFileName();
11 if (!string.IsNullOrEmpty(image_url))
12 {
13 Utils.DownLoadWebImage(image_url, imageFile);
14 }
15 else
16 {
17 Utils.SaveBase64Image(base64_image, imageFile);
18 }
19 if (File.Exists(imageFile))
20 {

21 string[] taskResult = new string[3];
22 // 三個線程同時去處理執行
23 // 每個線程處理的圖片都不一樣 取結果最好的一個
24 Task[] tk = new Task[] {
25 Task.Factory.StartNew(()=>
26 {
27 // 原圖識別
28 taskResult[0]=Discern(imageFile);
29 }),
30 Task.Factory.StartNew(()=>
31 {
32 // 灰度處理後識別
33 taskResult[1]=GrayDiscern(imageFile);
34 }),
35 Task.Factory.StartNew(()=>
36 {
37 // 二值化處理後識別
38 taskResult[2]=BinaryzationDiscern(imageFile);
39 }),
40 };
41 // 超時1分鐘
42 int timeout = (1000 * 60) * 1;
43 Task.WaitAll(tk, timeout);
44 var number_str = taskResult[0];
45 if (taskResult[1].Length > number_str.Length)
46 {
47 number_str = taskResult[1];
48 }
49 if (taskResult[2].Length > number_str.Length)
50 {
51 number_str = taskResult[2];
52 }
53 result.text = number_str;
54 if (number_str.Length == 11)
55 {
56 result.message = "識別成功";
57 }
58 else
59 {
60 result.message = "當前識別的電話可能有誤,請注意辨別";
61 }
62
63 }

64 return result;
65 }
66
67
68 /// <summary>
69 /// 直接識別
70 /// /<summary>
71 /// <param>
72 /// <returns>
73 private string Discern(string imageFile)
74 {
75 string number_str = "";
76 // 這裡可以選擇不同的語言包 可以是自己訓練的 可以是Tesseract 訓練好的語言包
77 TesseractEngine te_ocr = new TesseractEngine(@"tessdata", "chi_sim", EngineMode.TesseractAndLstm);
78 var img = Pix.LoadFromFile(imageFile);
79 var page = te_ocr.Process(img, PageSegMode.Auto);
80 string text = page.GetText().Trim().Replace("\\r", "").Replace("\\n", "");
81 _logger.Info("識別的原始數據:"+text);
82 page.Dispose();
83 // 只提取數字
84 number_str = System.Text.RegularExpressions.Regex.Replace(text, @"[^0-9]+", "");
85 _logger.Info("只提取數字結果:" + number_str);
86 return number_str;
87 }
88
89 /// <summary>
90 /// 灰度識別
91 /// /<summary>
92 /// <param>
93 /// <returns>
94 private string GrayDiscern(string imageFile)
95 {
96 string number_str = "";
97 using (Bitmap bmp = new Bitmap(imageFile))
98 {
99 // 灰度處理
100 var bmps = Utils.ToGray(bmp);
101 var tempFile = GetImageFileName(1);
102 bmps.Save(tempFile);
103 number_str = Discern(tempFile);
104 File.Delete(tempFile);
105 }
106 return number_str;
107 }

108 /// <summary>
109 /// 二值化識別
110 /// /<summary>
111 /// <param>
112 /// <returns>
113 private string BinaryzationDiscern(string imageFile)
114 {
115 string number_str = "";
116 using (Bitmap bmp = new Bitmap(imageFile))
117 {
118 // 灰度處理
119 var bmps = Utils.ToGray(bmp);
120 // 處理自動校正
121 gmseDeskew sk = new gmseDeskew(bmps);
122 double skewangle = sk.GetSkewAngle();
123 Bitmap bmpOut = Utils.RotateImage(bmps, -skewangle);
124 var tempFile = GetImageFileName(1);
125 // 將二值化後的圖像保存下
126 Utils.ToBinaryImage(bmpOut).Save(tempFile);
127 number_str = Discern(tempFile);
128 File.Delete(tempFile);
129 }
130 return number_str;
131 }
/<code>

具體詳細代碼,請看文章結尾github地址。

六、Linux上運行Tesseract

由於使用的是.NET Core開發的,所以最理想的運行環境自然是Linux了。但是當把項目直接發佈到Linux上,是會報錯的。所以在Linux上還是需要做一些安裝和配置的。

第一步我們需要在Linux上安裝Tesseract

首先Tesseract在github上有Linux平臺的安裝簡介 https://github.com/tesseract-ocr/tesseract/wiki

我是 Centos 安裝命令如下 :

yum-config-manager --add-repo https://download.opensuse.org/repositories/home:/Alexander_Pozdnyakov/ScientificLinux_7/

// 更新

yum update

// 安裝 tesseract

yum install tesseract

yum install tesseract-langpack-deu

// 如果提示Public key for leptonica-1.76.0-2.2.x86_64.rpm is not installed證明未通過gpg密鑰檢查所以在安裝時加上—nogpgcheck

yum install tesseract-langpack-deu –nogpgcheck

// 安裝完成 查看版本

tesseract --version

基於Tesseract的OCR識別小程序

這是最新版 4.1.0 。這個版本最好和自己項目中的版本匹配。

切換到項目發佈的目錄再進入 x64 (64 位系統選擇此目錄 ) 目錄然後做映射。

映射哪些文件主要是看我們發佈後 X64 裡面的 dll 文件叫什麼,比如我們發現是 libtesseract400.dll 和 libtesseract400.dll 。不同版本可能後面的數字不一樣。

然後我們找到剛剛安裝 Tesseract 的目錄然後搜索 libtesseract 和 libtesseract 開頭的 so 文件即可。最後我們會找到 libtesseract.so.4.0.1.so 、 liblept.so.5.0.3.so 文件。然後將這兩個文件做映射,映射到我們項目目錄中的名稱需要和本身項目中 dll 文件一致,只是後綴為 so ,不再是 dll 了。

映射命令如下

ln -s /usr/lib64/libtesseract.so.4.0.1 libtesseract400.so

ln -s /usr/lib64/liblept.so.5.0.3 liblept1760.so

注意這些 dll 或者 so 後面的版本號即數字不同版本不同時期可能都不一樣,以自己安裝的為準。

映射完畢後查看項目目錄 x64 目錄如下

基於Tesseract的OCR識別小程序

也可以通過 ftp 來查看目錄結構。下面箭頭狀的就是映射的,文件本身不在此目錄。有點類似如桌面快捷方式一樣。

基於Tesseract的OCR識別小程序

完成以上配置即可在 Linux 上完成 Tesseract 識別了。當然如果運行在 Windows 上,是不要安裝 Tesseract 即可識別的。

完整項目代碼

前端代碼 :

https://github.com/cfan1236/PhoneDiscern_wxapp

後端代碼

https://github.com/cfan1236/PhoneDiscern


分享到:


相關文章: