Thử làm phần mềm quét văn bản/scanner app bằng OpenCV

Xin chào anh em Mì AI, hôm nay chúng ta sẽ Thử giả lập máy quét văn bản/scanner bằng OpenCV xem nhé.

Chắc hẳn anh em còn nhớ ngày trước muốn scan các văn bản thì chỉ có một cách duy nhất đó là sử dụng những chiếc máy scann to bản, nặng trịch như này:

scanner máy quét
Nguồn: Tại đây

Tuy nhiên những năm gần đây thì anh em đã thấy rằng những chiếc máy quét đã dần vắng bóng và hầu như chỉ còn sử dụng trong các nghiệp vụ đặc thù, cần tốc độ nhanh và số lượng scan nhiều. Đa số chúng ta đã sử dụng điện thoại để làm máy scan với vô vàn các ứng dụng: Tiny Scan, Scan 2 PDF,….

Nguồn: Tại đây

Vậy đã bao giờ các bạn tự hỏi là cách thức hoạt động của các ứng dụng scan đó là như nào không? Vậy thì hôm nay chúng ta sẽ cùng nhau làm thử nhé. Let’s go!

Phần 1 – Phân tích bài toán quét văn bản

Phân tích đề bài

Bài toán của chúng ta đơn giản là chụp ảnh một văn bản nằm trên bề mặt phẳng như mặt bàn và xuất ra phiên bản scan của văn bản đó (như hình dưới)

Nguồn: Tại đây

Để đơn giản ta có thể giả sử bề mặt sạch, chỉ có nền và tờ văn bản cần scan nhé. Nếu bề mặt phức tạp thì sẽ cần nhiều phương pháp xử lý phức tạp hơn và thậm chí là Deep Learning.

Bài này chúng ta sẽ chỉ sử dụng OpenCV thuần.

Pipeline của bài toán

Bài này chúng ta sẽ qua các bước như sau:

Rồi, bây giờ chúng ta sẽ cùng nhau đi làm từng bước chi tiết nhé!

Link github cho các bạn tham khảo nha: https://github.com/thangnch/MiAI_Scanner_App

Phần 2 – Triển khai ứng dụng

Ứng dụng này để triển khai vào thực tế thì cần phải có app trên mobile thì mới tiện cho người sử dụng. Tuy nhiên ở đây mình chỉ demo cách thức triển khai nên xin phép được làm trên python thôi nhé. Sau khi đã biết cách triển khai các bạn có thể viết trên tất cả các nền tảng OS khác nhau vô tư.

Bước 1. Đọc ảnh đầu vào

Để đơn giản ta sẽ đọc ảnh từ một file có sẵn thay cho camera của điện thoại nhé. Phần đọc này thì hoàn toàn thuần OpenCV với lệnh imread thần thánh.

# import các thư viện
import cv2

# Tên file ảnh đầu vào
input_image  = 'mydoc.jpg'

# Đọc ảnh
image = cv2.imread(input_image)

# Hiển thị lên màn hình
cv2.imshow('Input', image)
cv2.waitKey()Code language: PHP (php)

Để tránh lỗi các bạn có thể cài đặt thư viện bằng lệnh:

pip install opencv-python imutils numpy

Okie! Nếu ảnh của bạn đã hiện lên màn hình là bạn đã thành công bước đầu rồi.

scan văn bản quét
Bước 2. Làm nổi bật phần cạnh của văn bản

Để phát hiện được cạnh văn bản thì ta phải làm cho nó show hàng lên cho dễ phát hiện.

May thay trong OpenCV có sẵn hàm Canny tìm cạnh bá đạo trên từng hạt gạo luôn. Chi tiết hàm này tại đây nhé các bạn!

Việc tìm cạnh khá đơn giản như sau:

# Chuyển ảnh mày thành ảnh xám
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Làm mờ ảnh xám để xóa noise
blur = cv2.blur(gray,(3,3))

# Tìm cạnh bằng Canny
edge = cv2.Canny(blur, 50, 300, 3)

# Hiển thị lên màn hình cho dễ xem
cv2.imshow("gray", gray)
cv2.imshow("blur", blur)
cv2.imshow("edge", edge)
cv2.waitKey()Code language: PHP (php)

Và kết quả của bước này là một tờ văn bản với các cạnh đã show ra khá rõ ràng rồi:

quét văn bản

Chúng ta sang bước tiếp theo!

Bước 3. “Tóm” lấy các cạnh của văn bản

Hàng đã show ra, giờ ta dùng contours finding để tóm lấy các cạnh đó. Nhìn qua thì ta dự kiến sẽ làm như sau:

  • Tìm tất cả các contour trong ảnh chứa cạnh
  • Sắp xếp các contour theo diện tích giảm dần
  • Lấy contour to nhất chính là văn bản của chúng ta

Ok! Triển luôn!

# Tìm contours
cnts = cv2.findContours(edge, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
import imutils
cnts = imutils.grab_contours(cnts)

# Sắp xếp theo diện tích giảm dần
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)


# Lấy contour đầu tiên - to nhất
cnts = cnts[:1]

# Vẽ contour lên ảnh gốc cho trực quan
p = cv2.arcLength(cnts[0], True)
r = cv2.approxPolyDP(cnts[0], 0.02*p, True)
cv2.drawContours(image, [r], -1, (0,0,255), 3)

# Show ảnh
cv2.imshow("Draw", image)
cv2.waitKey()
Code language: PHP (php)

Và đây rồi, đường viền đẹp đẽ đã được show ra:

scannner app quét văn bản

Chuẩn vãi rồi anh em.

Bước 4. Bây giờ là xoay nó thẳng thắn ra thôi

Giờ có khung rồi, biết văn bản ở đâu rồi thì xoay phát cho nó thẳng ra nào. Đoạn này ta sẽ tính chiều rộng, chiều dài biên văn bản:

# Đầu tiên reshape cái ROI của chúng ta về (4,2) - 4 tọa độ, mỗi tọa độ gồm x,y
r = r.reshape(4,2)

# Tính toán 04 góc theo thứ tự trên trái, trên phải, dưới phải, dưới trái
rect = np.zeros((4,2), dtype='float32')

# Ta tính tổng các tọa độ theo cột
# Điểm trên trái sẽ có tổng nhỏ nhất
# Điểm dưới phải sẽ có tổng lớn nhất
s = np.sum(r, axis=1)
rect[0] = r[np.argmin(s)] # Trên trái
rect[2] = r[np.argmax(s)] # Dưới phải

# Ta tính sự khác nhau giữa các tọa độ theo cột
# Trên phải sẽ ít khác biệt nhất
# dưới trái là khác biệt nhất
diff = np.diff(r, axis=1)
rect[1] = r[np.argmin(diff)]
rect[3] = r[np.argmax(diff)]

# Tính toán chiều rộng và chiều cao của văn bản
(tl, tr, br, bl) = rect

width1 = np.sqrt((tl[0] - tr[0])**2 + (tl[1] - tr[1])**2 )
width2 = np.sqrt((bl[0] - br[0])**2 + (bl[1] - br[1])**2 )
Width = max(int(width1), int(width2))

height1 = np.sqrt((tl[0] - bl[0])**2 + (tl[1] - bl[1])**2 )
height2 = np.sqrt((tr[0] - br[0])**2 + (tr[1] - br[1])**2 )
Height = max(int(height1), int(height2))Code language: PHP (php)

Sau đó là xoay:

# Đầu tiên reshape cái ROI của chúng ta về (4,2) - 4 tọa độ, mỗi tọa độ gồm x,y
r = r.reshape(4,2)

# Tính toán 04 góc theo thứ tự trên trái, trên phải, dưới phải, dưới trái
import numpy as np
rect = np.zeros((4,2), dtype='float32')

# Ta tính tổng các tọa độ theo cột
# Điểm trên trái sẽ có tổng nhỏ nhất
# Điểm dưới phải sẽ có tổng lớn nhất
s = np.sum(r, axis=1)
rect[0] = r[np.argmin(s)] # Trên trái
rect[2] = r[np.argmax(s)] # Dưới phải

# Ta tính sự khác nhau giữa các tọa độ theo cột
# Trên phải sẽ ít khác biệt nhất
# dưới trái là khác biệt nhất
diff = np.diff(r, axis=1)
rect[1] = r[np.argmin(diff)]
rect[3] = r[np.argmax(diff)]

# Tính toán chiều rộng và chiều cao của văn bản
(tl, tr, br, bl) = rect

width1 = np.sqrt((tl[0] - tr[0])**2 + (tl[1] - tr[1])**2 )
width2 = np.sqrt((bl[0] - br[0])**2 + (bl[1] - br[1])**2 )
Width = max(int(width1), int(width2))

height1 = np.sqrt((tl[0] - bl[0])**2 + (tl[1] - bl[1])**2 )
height2 = np.sqrt((tr[0] - br[0])**2 + (tr[1] - br[1])**2 )
Height = max(int(height1), int(height2))


# Tọa độ mới của văn bản
new_rect = np.array([
    [0,0],
    [Width-1, 0],
    [Width-1, Height-1],
    [0, Height-1]], dtype="float32")

# Tinh toán ma trận transform
M = cv2.getPerspectiveTransform(rect, new_rect)

# Thực hiện xoay và crop
output = cv2.warpPerspective(image, M, (Width, Height))

# Show ảnh
cv2.imshow("Output",output)
cv2.waitKey()Code language: PHP (php)

Hàng về hàng về đây rồi:

quét văn bản scan document
Bước 5. Trau chuốt kết quả quét văn bản

Anh em nhìn vào kết quả trên sẽ thấy nó sao sao ấy nhỉ:

  • Đầu tiên là có cái viền đỏ ngứa mắt vãi
  • Thứ hai là nó có vẻ không giống ảnh scan thường thấy.

Chuẩn đới, bây giờ ta thực hiện như sau:Để bỏ viền đỏ, ta bỏ đi dòng “cv2.drawContours(image, [r], -1, (0,0,255), 3)” tại bước 3.

Để trông cho nó có vẻ giống ảnh scan thì đơn giản lắm, ta áp thêm cho nó cái threshold nữa là ổn


# Chuyển thành xám
gray = cv2.cvtColor(output, cv2.COLOR_BGR2GRAY)

# Áp threshold

_, output_final = cv2.threshold(gray,200,255,cv2.THRESH_BINARY)

# Show hàng
cv2.imshow("Ouput", output_final)
cv2.waitKey()Code language: PHP (php)

Rồi bây giờ thì ngon lành cành đào nhé anh em!

quét văn bản

Ok, như vậy hôm nay mình đã chỉ cho các bạn cách làm phần mềm quét văn bản/scan văn bản. Các bạn thấy đó OpenCV có thể làm được khác nhiều thứ giúp ích cho cuộc sống chứ đâu phải cứ phải là Deep Learning đâu.

Mình xin dừng bài này tại đây và hẹn gặp lại các bạn trong các bài tiếp theo về OpenCV thuần nhé!

Hãy join cùng cộng đồng Mì AI nhé!

Fanpage: http://facebook.com/miaiblog
Group trao đổi, chia sẻ: https://www.facebook.com/groups/miaigroup
Website: https://miai.vn
Youtube: http://bit.ly/miaiyoutube

Cảm ơn bài tham khảo tại đây.

Related Post

2 Replies to “Thử làm phần mềm quét văn bản/scanner app bằng OpenCV”

  1. code trên có thể chạy được với tất cả các file ảnh k? vì mình thử chạy file ảnh của bạn thì ok, nhưng với file ảnh khác thì báo lỗi? giải pháp khắc phục?

Leave a Reply

Your email address will not be published. Required fields are marked *