Guide to OCR with Tesseract, OpenCV and Python

Weerasak Thachai
8 min readSep 10, 2022

--

Optical Character Recognition (OCR) เป็นการสกัดข้อความจากภาพ ซึ่งมีกระบวนการย่อยๆ เพื่อให้มีความแม่นยำดังนี้

  • การเตรียมรูปภาพ
  • การหาตำแหน่งข้อความ (Text localization)
  • การแบ่งอักขระ (Character Segmentation)
  • การรู้จำอักขระ (Character Recognition)
  • Post processing

กระบวนการย่อยเหล่านี้อาจแตกต่างกันไปขึ้นอยู่กับลักษณะงาน หลายปีที่ผ่านมามีการประยุกต์ใช้ OCR กันอย่างแพร่หลายอีกทั้งนิยมใช้แปลงข้อมูลในรูปของแบบฟอร์ม ใบเสร็จ ต่างๆ เพื่อนำเข้าระบบคอมพิวเตอร์เพื่อให้สามารถประมวลผลได้

OCR นับเป็นปัญหาที่ท้าทายในเมื่อรูปภาพมีความแตกต่างกัน เช่น รูปพื้นหลังที่ซับซ้อน ความคมชัด ฟอนต์ แสง สี ซึ่งความแม่นยำอาจต้องใช้ deep learning เข้ามาช่วย

ในบทความนี้เราจะมาอธิบายเทคโนโลยีที่อยู่เบื้องหลังซึ่งส่วนใหญ่จะใช้ Tesseract engine โดยเราจะอธิบายตามหัวข้อดังต่อไปนี้

  • ฟีเจอร์ใน Tesseract OCR
  • การเตรียมประมวลผลสำหรับ OCR โดยใช้ OpenCV
  • รัน Tesseract ด้วย CLI และ Python
  • ข้อจำกัดของ Tesseract engine

Tesseract OCR

Tesseract เป็น open source text recognition (OCR) ภายใต้ Apache 2.0 license สามารถใช้งานโดยตรง หรือใช้งานผ่าน API รองรับหลายภาษา แต่ Tesseract ไม่มี built-in GUI

Install Tesseract

หลังจากติดตั้ง Tesseract แล้วลองใช้คำสั่ง

$ tesseract --version

ควรจะได้

tesseract 4.0.0 
leptonica-1.76.0
libjpeg 9c : libpng 1.6.34 : libtiff 4.0.9 : zlib 1.2.8
Found AVX2
Found AVX
Found SSE

จากนั้นให้ติดตั้ง python wrapper สำหรับ tesseract

$ pip instll pytesseract

Running Tesseract with CLI

ก่อนอื่นให้กำหนดค่าให้ image_path ก่อนจากนั้น

$ tesseract image_path stdout

หากต้องการเขียนไปยัง text ไฟล์

$ tesseract image_path text_result.txt

สามารถกำหนดโมเดลภาษาที่ต้องการได้

$ tesseract image_path text_result.txt -l eng

เราสามารถตั้งค่า psm คือวิธีที่ให้โมเดลจะปฏิบัติต่อรูปภาพ ความแม่นยำขึ้นโหมดการแบ่งส่วนหน้าจะเป็น 3 (Auto)

$ tesseract image_path text_result.txt -l eng --psm 6

มีอีกตัวแปรที่สำคัญคือ OCR Engine Mode (oem) ใน tesseract 4 มี 2 OCR engine คือ Legacy Tesseract engine และ LSTM engine มี 4 โมเดลให้เลือกใช้ผ่าน — oem (option)

  • 0: legacy engine only
  • 1: neural nets LSTM engine only
  • 2: legacy + LSTM engines
  • 3: Default, ขึ้นอยู่กับว่าต้องการแบบไหน

OCR with Pytesseract and OpenCV

pytesseract เป็น wrapper ของ tesseract-OCR engine สามารถอ่านภาพโดยใช้ Pillow และ Leptonica ภาพชนิด jpeg, png, gif, bmp, tiff และอื่นๆ

import cv2
import pytesseract
img = cv2.imread('image.jpg')# Adding custom options
custom_config = r'--oem 3 --psm 6'
pytesseract.image_to_string(img, config=custom_config)

Preprocessing for Tesseract

เพื่อให้ผลลัพธ์แม่นยำเราต้องแน่ใจว่าเราทำ pre-processed ที่ดีพอ รวมทั้งการทำ rescaling, binarization, noise removal, deskewing และอื่นๆ

การ preprocess image สำหรับ OCR ใน python มีฟังก์ชันต่างๆ ใน OpenCV ให้แล้ว

import cv2
import numpy as np
img = cv2.imread('image.jpg')# get grayscale image
def get_grayscale(image):
return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# noise removal
def remove_noise(image):
return cv2.medianBlur(image, 5)
# thresholding
def thresholding(image):
return cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# dilation
def dilate(image):
kernel = np.ones((5,5),np.uint8)
return cv2.dilate(image, kernel, iterations = 1)
#erosion
def erode(image):
kernel = np.ones((5,5),np.uint8)
return cv2.erode(image, kernel, iterations = 1)
#opening - erosion followed by dilation
def opening(image):
kernel = np.ones((5,5),np.uint8)
return cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
#canny edge detection
def canny(image):
return cv2.Canny(image, 100, 200)
#skew correction
def deskew(image):
coords = np.column_stack(np.where(image > 0))
angle = cv2.minAreaRect(coords)[-1]
if angle < -45:
angle = -(90 + angle)
else:
angle = -angle
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
return rotated
#template matching
def match_template(image, template):
return cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)

เพื่อให้เห็นภาพการทำงานเราจะใช้ภาพนี้เป็นตัวอย่าง

หลังการ preprocessing ด้วยโค้ด

image = cv2.imread('aurebesh.jpg')
grey = get_grayscale(image)
thresh = thresholding(grey)
opening = opening(grey)
canny = canny(grey)

ผลลัพธ์จากการทำ OCR ด้วยภาพต้นฉบับจะได้

GALACTIC BASIC 
(AUREBESH)
RE HFVMEVEIiZwoyv
Ze ABC DE F GH I JK LM
N—0- PQ RST Uv WX
2 | Ff 8 G& Pf fF § 5 op 7
ee
5, jf FF Ty ee ee
=
334 477 OED

ผลลัพธ์จากการทำ OCR ด้วยภาพที่ preprocessed ด้วย canny edge (ไม่ค่อยดี)

CAE Cn Cae AS 
(AUREBESE)
EA Na
oe SS
(Ne CI (ENE
a, ee oe ea
2
a a A: rc
|, |
a
Sear eo/e
ecm emclomt Cia cuoomct mi im

ผลลัพธ์จากการทำ OCR ด้วยภาพที่ preprocessed ด้วย threshold

GALACTIC BASIC 
(AVREBESH)
RS 7FVMeEVEi1iFf o£
A B C D EF GH IJ K LM
AOoder7Nnvroroava
N O P Q@R S$ TU VW XK Y¥ Z
7 ee For 8 Ro Pf F Boao om #
0 12 3 4 5 6 7 8 9 , . !
>» 1kr7 @ by FEN
2? S$ ( Por Foy of ee
ASGSANDIE
CH AE EO KH NG OO SH TH

ผลลัพธ์จากการทำ OCR ด้วยภาพที่ preprocessed ด้วย opening

GALACTIC BASIC 
(AUREZEBELSH)
KEE VTMEUOU EB iw oN es
A BC D EF F @ H | J K LT Ww
AOGdrcrT7WTt HYOAVa4
WO P Q R BS T U VW WK y Z
i J
Oo 1 2 3 46 8 7 SC Ps,
VY ir- -rp,ptUuY?
a a a
AGoOAnNnoOID
CH AE BO KH ®@ OO SH TH

Getting boxes around text

เราสามารถทำ bounding box เพื่อตีกรอบในผลลัพธ์ OCR โค้ดล่างนี้จะทำ bounding box เพื่อตีกรอบแต่ละอักขระที่หาเจอโดย tesseract ระหว่างการทำ OCR

import cv2
import pytesseract
img = cv2.imread('image.jpg')h, w, c = img.shape
boxes = pytesseract.image_to_boxes(img)
for b in boxes.splitlines():
b = b.spit(' ')
img = cv2.rectangle(img, (int(b[1]), h - int(b[2]), (int(b[3]), h - int(b[4])), (0, 255, 0), 2)
cv2.imshow('img', img)
cv2.waitKey(0)

หากต้องการตีกรอบคำแทนที่อักขระ มีฟังก์ชัน image_to_data ซึ่งต้องกำหนด Output type ของ pytesseract เราจะใช้ตัวอย่างใบ invoice เพื่อทดสอบผลลัพธ์ที่ได้จาก tesseract

import cv2
import pytesseract
from pytesseract import Output
img = cv2.imread('invoice-sample.jpg')
d = pytesseract.image_to_data(img, output_type=Output.DICT)
print(d.keys())

ควรจะแสดงผลออกมาดังนี้

dict_keys(['level', 'page_num', 'block_num', 'par_num', 'line_num', 'word_num', 'left', 'top', 'width', 'height', 'conf', 'text'])

การใช้ dictionary เราจะได้แต่ละคำที่เจอและตีกรอบรอบข้อมูล ข้อความที่เจอและค่าความเชื่อมั่น (confidence) ของแต่ละกรอบ

เราจะตีกรอบโดยใช้โค้ดต่อไปนี้

n_boxes = len(d['text'])
for i in range(n_boxes):
if int(d['conf'][i]) > 60:
(x, y, w, h) = (d['left'][i], d['top'][i], d['width'][i], d['height'][i])
img = cv2.rectangle(img, (x, y), (x + w, y+h), (0, 255, 0), 2)
cv2.imshow('img', img)
cv2.waitKey(0)

สิ่งที่ได้คือผลลัพธ์ดังต่อไปนี้

Text template matching

ลองหาตำแหน่งของวันที่ในภาพ เราจะใช้ template ซึ่งเป็น regular expression pattern เพื่อตรวจสอบ (match) กับผลของ OCR เพื่อหาตำแหน่งกรอบที่เป็นไปได้ เราจะใช้ regex module และ image_to_data

import re
import cv2
import pytesseract
from pytesseract import Output
img = cv2.imread('invoice-sample.jpg')
d = pytesseract.image_to_data(img, output_type=Output.DICT)
keys = list(d.keys())
date_pattern = '^(0[1-9]|[12][0-9]|3[01])/(0[1-9]|1[012])/(19|20)\d\d$'n_boxes = len(d['text'])
for i in range(n_boxes):
if int(d['conf'][i]) > 60:
if re.match(date_pattern, d['text'][i]):
(x, y, w, h) = (d['left'][i], d['top'][i], d['width'][i], d['height'][i])

เราจะได้กรอบวันที่ในภาพ

Page segmentation modes

มีสองสามวิธีที่จะวิเคราะห์หน้าของข้อความ tesseract api มีโหมดการแยกหน้าอยู่ หากต้องการรัน OCR ในพื้นที่ (region) หรือการวางแนว (orientation) ที่ต่างกัน และอื่นๆ

นี่เป็นรายการสำหรับโหมดการแบ่งหน้าใน tesseract

  • 0: OSD_ONLY ตรวจจับการวางแนวและสคริปต์เท่านั้น
  • 1: AUTO_OSD แบ่งส่วนหน้าอัตโนมัติพร้อมการวางแนวและการตรวจจับสคริปต์ (OSD)
  • 2: AUTO_ONLY แบ่งส่วนหน้าอัตโนมัติ แต่ไม่มี OSD หรือ OCR
  • 3: AUTO แบ่งส่วนหน้าอัตโนมัติ แต่ไม่มี OSD (default สำหรับ tesseract)
  • 4: SINGLE_COLUMN สมมติข้อความคอลัมน์เดียวที่มีขนาดตัวแปร
  • 5: SINGLE_BLOCK_VERT_TEXT สมมติว่าเป็นบล็อกเดียวของข้อความที่จัดแนวในแนวตั้ง
  • 6: SINGLE_BLOCK สมมติว่ามีข้อความชุดเดียว
  • 7: SINGLE_LINE — Treat ภาพเป็นบรรทัดข้อความเดียว
  • 8: SINGLE_WORD — Treat ภาพเป็นคำเดียว
  • 9: CIRCLE_WORD ใช้รูปเป็นคำเดียวในวงกลม
  • 10: SINGLE_CHAR ใช้รูปเป็นอักขระเดี่ยว
  • 11: SPARSE_TEXT ค้นหาข้อความให้ได้มากที่สุดโดยไม่เรียงลำดับ
  • 12: SPARSE_TEXT_OSD — Sparse ข้อความพร้อมการวางแนวและการตรวจจับสคริปต์
  • 13: RAW_LINE ใช้ภาพเป็นบรรทัดข้อความเดียวโดยข้ามการแฮ็คที่เฉพาะเจาะจงของ Tesseract

การเปลี่ยน page segmentation mode ใช้ — psm

Detect orientation and script

เราสามารถหาการวางแนวของข้อความในภาพ และสคริปต์ที่ใช้เขียนตามภาพ

หลังจากรันโค้ดต่อไปนี้

osd = pytesseract.image_to_osd(img)
angle = re.search('(?<=Rotate: )\d+', osd).group(0)
script = re.search('(?<=Script: )\d+', osd).group(1)
print(f"angle : {angle}")
print(f"script : {script}")

ผลที่ได้คือ

angle: 90
script: Latin

Detect only digits

ยกตัวอย่างภาพ

ลอง extract ข้อความจากภาพจะได้

‘Customer name Hallium Energy services 
Project NEHINS-HIB-HSA
lavoice no 43876324
Dated 17%h Nov2018
Pono 76496234

เราสามารถให้มันรู้จำเฉพาะตัวเลขเท่านั้นโดยการเปลี่ยน config

custom_config = r'--oem 3 --psm 6 outputbase digits'
print(pytesseract.image_to_string(img, config=custom_config))

ผลที่ได้

--. 43876324
172018
0 7646234

Whitelisting characters

หากเราต้องการหาเฉพาะอักขระจากภาพโดยไม่สนใจส่วนอื่น เราสามารถกำหนด whitelist ของอักขระ (กรณีนี้เราใช้ lowercase character เฉพาะ a-z เท่านั้น) โดยใช้ config ต่อไปนี้

custom_config = r'-c tessedit_char_whitelist=abcdefghijklmnopqrstuvwxyz --psm 6'
print(pytesseract.image_to_string(img, config=custom_config))

ผลที่ได้

customername 
roject
tnvoleeno
ated
alliumenergyservices
e
thovo

Blacklisting characters

หากต้องการให้แน่ใจว่าอักขระที่เราไม่ต้องการนั้นจะไม่รวมอยู่ในผลลัพธ์ ต้องกำหนด blacklist characters โดยใช้ config ต่อไปนี้

custom_config = r'-c tessedit_char_blacklist=0123456789 --psm 6'
pytesseract.image_to_string(img, config=custom_config)

ผลที่ได้

Customer name Hallium Energy services 
Project NEHINS-HIB-HSA
lavoice no
Dated %h Nov%
Pono

Detect in multiple languages

เราสามารถตรวจสอบภาษาที่รองรับได้โดย

$ tesseract --list-langs

ดาวน์โหลดภาษาเพิ่มได้โดย

$ sudo apt-get install tesseract-ocr-LANG

โดย LANG ใช้รหัส 3 ตัวสำหรับ language ที่เราต้องการหาได้จากที่นี่

หรือเราจะดาวน์โหลดไฟล์ .traindata ของภาษาที่เราต้องการจากที่นี่ วางไว้ที่ไดเรกทอรี่ $TESSDATA_PREFIX

เรากำหนดภาษาผ่าน -l LANG โดย LANG เป็นรหัส 3 ตัวที่เราต้องการ

custom_config = r'-l eng --psm 6'
pytesseract.image_to_string(img, config=custom_config)

ลองใช้กับตัวอย่าง

custom_config = r'-l grc+tha_eng --psm 6'
pytesseract.image_to_string(img, config=custom_config)

ผลที่ได้

Here’s some Greek:  Οδιο διστα ιμπεδιτ φιμ ει, αδ φελ αβχορρεανθ ελωκυενθιαμ, εξ εσε 
εξερσι γυ-
βεργρεν ηας. Ατ μει σολετ σριπτορεμ. ἴυς αλια λαβωρε θε. Σιθ κυωτ
νυσκυαμ
τρασυνδια αν, ὠμνιυμ ελιγενδι τιν πρι. Παρτεμ φερθερεμ συσιπιαντὺυρ
εξ ιυς,ναμ
%0790 แ ร เง ๑ ๕ 80 ๕ 6 ๑ อ 06 ส 0 เง น อ ๓ , πρω πρωπριαε
σαεφολα ιδ. Ατ πρι δολορ νυ-
σκυαμ.
6 Thai Here’s some Thai: ν᾿ ค อ ร ั ป ซั น จ ุ ้ ย โป ร ด ิ ว เซ อ ร ์ ส ถา ป ั ต ย ์ จ ๊ า บ
แจ ็ ก พ ็ อ ต ม ้ า ห ิ น อ ่ อ น ซา ก ุ ร ะ ค ั น ถ ธ ุ ร ะ ฟิ ด
ส ต า ร ์ ท ง ี ้ บ อ ย
ค อ ต อ ื ่ ม แป ร ั ส ั ง โฆ ค ํ า ส า ป แฟ น ซี ศิ ล ป ว ั ฒ น
ธร ร ม ไฟ ล ท ์ จ ิ ๊ ก โก ๋ ก ั บ ด ั ก เจ ล พ ล ็ อ ต ม า ม ่ า
ซา ก ุ ร ะ ด ี ล เล อ
ร ์ ซี น ด ั ม พ ์ แฮ ป ป ี ้ เอ ๊ ้ า ะ อ ุ ร ั ง ค ธา ต ุ ซิ ม ฟิ น ิ
ก ซ์ เท ร ล เล ่ อ ร ์ อ ว อ ร ์ ด แค น ย อ น ส ม า พ ั น ธ์ ค ร ั
ว ซอ ง ฮั ม อ า
ข่ า เอ ็ ก ซ์ เพ ร ส

ภาษาที่กำหนดตัวแรกผ่าน -l จะเป็นภาษาหลัก

tesseract ไม่มีฟีเจอร์สำหรับการค้นหาภาษาอัตโนมัติให้ แต่มีวิธีโดยใช้ langdetect ของ python ติดตั้งผ่าน

$ pip install langdetect

module นี้ไม่ได้ค้นหาข้อความในภาพแต่ต้องสกัดเป็น string ก่อน ตัวอย่างเราจะทำ OCR ภาษาอังกฤษและโปรตุเกส

custom_config = r'-l eng+por --psm 6'
txt = pytesseract.image_to_string(img, config=custom_config)
from langdetect import detect_langs
detect_langs(txt)

ผลที่ได้จะเป็นภาษาและค่าความน่าจะเป็น

[en:0.714282468983554, es:0.2857145605644145]

รหัสภาษาที่ใช้กับ langdetect ต้องอยู่ใน ISO 639–1 ตัวอย่างโค้ดค้นหาข้อความเป็นภาษาอังกฤษและสเปน

custom_config = r'-l eng+spa --psm 6'
txt = pytesseract.image_to_string(img, config=custom_config)

tesseract จะให้ผลที่ไม่ดีหากภาพนั้นมีหลายภาษา เมื่อภาษาที่เรากำหนดไว้ใน config นั้นผิดหรือไม่ได้ระบุไว้ ดังนั้นหากใช้ langdetect จะทำให้ผลนั้นดีขึ้นได้

Using tessdata_fast

หากความเร็วเป็นสิ่งที่เรากังวล เราสามารถใช้ tessdata_fast model แทน tessdata language ด้วย 8-bit integer version ของ tessdata model

tessdata_fast github ใน repository จะระบุ fast integer version ของ trained model สำหรับ Tesseract Open Source OCR Engine

model เหล่านี้ใช้ได้สำหรับ LSTM OCR engine ของ Tesseract 4 เท่านั้น

  • ต้องแลกระหว่าง ความเร็ว/ความแม่นยำ ว่าอะไรคือ “ความคุ้มค่า” ที่ดีที่สุด
  • โมเดลบางภาษามีความแม่นยำ แต่ส่วนใหญ่ยังแย่อยู่
  • คนส่วนใหญ่จะใช้ไฟล์ traineddata เพื่อทำ OCR และ ship ไปเป็นส่วนหนึ่งของ Linux distribution เช่น Ubuntu 18.04
  • Fine tunning/ incremental training จะใช้ไม่ได้สำหรับ fast model
  • เมื่อใช้ model จาก repository นี้จะใช้ LSTM-based OCR engine ที่รองรับอันใหม่เท่านั้น แต่ tesseract engine อันเก่าจะไม่รองรับไฟล์เหล่านี้ และ tesseract oem model ‘0’ และ ‘2’ ก็ใช้ไม่ได้เหมือนกัน

การใช้ tessdata_fast model แทน tessdata สิ่งที่ต้องทำคือดาวน์โหลดไฟล์ภาษาจากที่นี่ และนำไปวางที่ $TESSDATA_PREFIX

Training Tesseract on custom data

tesseract 4.00 มี neural network-base recognition engine ตัวใหม่ที่มีความแม่นยำสูงกว่า

สามารถเทรนข้อมูลใหม่โดยอาจใช้เวลาไม่กี่วันหรือไม่กี่สัปดาห์โดยมี option สำหรับการเทรนดังนี้:

  • Fine-tune — เริ่มจาก trained language เดิมที่มีอยู่ เอามาเทรนข้อมูลเพิ่มยกตัวอย่างเทรน handwritten dataset และเพิ่ม font ใหม่
  • Cut off the top layer — บน network ลองตัด top layer ออกแล้ว fine-tune ด้วยข้อมูลใหม่แล้ว fine-tune อาจได้ model ที่ดีขึ้น
  • Retrain from scratch — เป็นวิธีที่ช้าที่สุดเพราะต้อง train ด้วยข้อมูลจำนวนมากแต่ก็ทำได้ถ้าว่างพอตามนี้

Limitations of Tesseract

tesseract จะ work เมื่อภาพถูก clean ข้อความ foreground ให้แยกจาก background แต่ก็มีหลายปัจจัยที่ทำให้ผลลัพธ์ออกมาไม่ดีพอ เช่น มี noise บน background การทำให้มีคุณภาพที่ดีกว่า (size, contrast, lightning) จะทำให้ผลการรู้จำดีกว่า ภาพจำเป็นต้องมี scale ที่เหมาะสม

Tesseract limitations summed in the list

  • OCR ไม่แม่นยำเท่ากับ solution เชิงพาณิชย์
  • ผลลัพธ์จะไม่ดีหากภาพถูกบดบังในบางส่วน มุมมองที่ผิดเพี้ยน และพื้นหลังที่ซับซ้อน
  • ไม่สามารถรู้จำลายมือได้
  • หากไม่ระบุภาษาใน -l LANG ผลลัพธ์อาจไม่ดี
  • มันไม่สามารถวิเคราะห์ลำดับการอ่านได้ เช่น มันไม่รู้ว่ามี 2 คอลัมน์มันจึงรวมข้อความทั้งสองคอลัมน์เข้าด้วยกัน
  • ภาพที่ได้จากการสแกนที่แย่ ย่อมทำได้ผล OCR แย่ด้วย
  • เราไม่รู้ว่าสกัดข้อความจาก font อะไร

--

--