PaddleOCR: Ekstraksi Teks dari Gambar dan Dokumen dengan Akurasi Tinggi

# PaddleOCR: Ekstraksi Teks dari Gambar dan Dokumen dengan Akurasi Tinggi Halo temen-temen, kali ini kita bahas salah satu library OCR open source terbaik yang pernah ada, namanya PaddleOCR. Kalau ka...

By Ruby Abdullah · · tutorial
paddleocrocrdocument-aicomputer-visionpython

PaddleOCR: Ekstraksi Teks dari Gambar dan Dokumen dengan Akurasi Tinggi

Halo temen-temen, kali ini kita bahas salah satu library OCR open source terbaik yang pernah ada, namanya PaddleOCR. Kalau kalian pernah harus mengekstrak teks dari foto KTP, struk belanja, faktur, formulir tulisan tangan, atau screenshot dokumen, kalian pasti tahu bahwa OCR itu terlihat sederhana tapi ternyata penuh jebakan. PaddleOCR adalah salah satu solusi yang paling matang untuk masalah ini, dan yang menyenangkan, dia gratis dan bisa jalan di CPU.

Yang bikin PaddleOCR menonjol dibanding Tesseract yang lebih tua adalah dia berbasis deep learning sepenuhnya, mendukung lebih dari 80 bahasa, punya model yang sangat ringan (versi mobile-nya cuma beberapa MB), dan yang paling penting, dia jago menangani teks miring, melengkung, dan gambar dengan latar belakang berantakan. Ini kondisi yang bikin Tesseract menyerah.

Di tutorial ini kita bahas arsitektur pipeline OCR, instalasi, ekstraksi teks pertama, memahami format outputnya, analisis struktur dokumen dan tabel, tips preprocessing untuk kasus sulit, sampai membangun pipeline ekstraksi data dari faktur.

Introduction

Bagaimana OCR Modern Bekerja

Aku mau kalian paham dulu pipeline-nya, karena ini membantu banget waktu debugging.

OCR modern itu bukan satu model, tapi tiga tahap yang berurutan.

Tahap pertama adalah deteksi teks. Model ini mencari di mana saja ada teks di gambar dan menggambar kotak (atau poligon) di sekelilingnya. Di sini belum ada yang dibaca, cuma ditandai lokasinya.

Tahap kedua adalah klasifikasi arah. Setiap potongan teks yang terdeteksi diperiksa orientasinya, apakah normal atau terbalik 180 derajat, lalu diputar kalau perlu. Tahap ini opsional tapi sangat membantu untuk foto yang diambil sembarangan.

Tahap ketiga adalah pengenalan teks. Setiap potongan gambar berisi satu baris teks dimasukkan ke model recognition yang mengubahnya jadi karakter.

Kenapa ini penting kalian tahu? Karena kalau hasil OCR kalian jelek, penyebabnya bisa di tahap mana saja. Kalau ada teks yang tidak terbaca sama sekali, kemungkinan besar masalahnya di deteksi, bukan di recognition. Kalau teks terbaca tapi hurufnya salah, masalahnya di recognition. Diagnosis yang tepat menghemat banyak waktu.

Instalasi

python -m venv venv

source venv/bin/activate # Linux / Mac

venv\Scripts\activate # Windows

CPU

pip install paddlepaddle

pip install paddleocr

Atau GPU (sesuaikan versi CUDA kalian)

pip install paddlepaddle-gpu

Untuk beberapa kasus kalian juga butuh dependensi gambar tambahan:

pip install opencv-python pillow

Model akan diunduh otomatis saat pertama kali dipakai dan disimpan di direktori cache, jadi panggilan berikutnya jauh lebih cepat.

Ekstraksi Teks Pertama

from paddleocr import PaddleOCR

lang="en" untuk Inggris, "id" untuk Indonesia (memakai model latin),

"ch" untuk Mandarin, "japan", "korean", "arabic", dan lainnya

ocr = PaddleOCR(useanglecls=True, lang="en")

hasil = ocr.ocr("struk.jpg", cls=True)

for baris in hasil[0]:

kotak, (teks, skor) = baris

print(f"{skor:.3f} {teks}")

Outputnya berupa list untuk setiap gambar, dan setiap elemen berisi dua hal: koordinat poligon (empat titik x,y) dan tuple berisi teks yang dibaca beserta skor kepercayaannya.

Mari kita lihat strukturnya lebih detail:

hasil = ocr.ocr("dokumen.jpg", cls=True)

for baris in hasil[0]:

kotak = baris[0] # [[x1,y1],[x2,y2],[x3,y3],[x4,y4]]

teks = baris[1][0] # string hasil pembacaan

skor = baris[1][1] # 0.0 sampai 1.0

xkiri = min(p[0] for p in kotak)

yatas = min(p[1] for p in kotak)

print(f"posisi ({xkiri:.0f}, {yatas:.0f}) skor {skor:.2f} '{teks}'")

Koordinat itu bukan sekadar informasi tambahan, dia adalah kunci untuk banyak hal berguna. Dengan koordinat kalian bisa mengurutkan teks sesuai urutan baca, mengelompokkan teks yang berada di kolom yang sama, atau mencari nilai yang berada di sebelah kanan sebuah label.

Mengurutkan Hasil Sesuai Urutan Baca

Ini masalah praktis yang sering muncul. PaddleOCR mengembalikan hasil dalam urutan deteksi, yang tidak selalu urutan baca manusia.

def urutkanbaca(hasilocr, toleransiy=10):

"""Urutkan hasil OCR dari atas ke bawah, lalu kiri ke kanan."""

item = []

for baris in hasilocr:

kotak, (teks, skor) = baris

y = sum(p[1] for p in kotak) / 4

x = min(p[0] for p in kotak)

item.append({"x": x, "y": y, "teks": teks, "skor": skor})

item.sort(key=lambda i: i["y"])

barisbaris = []

barissekarang = []

yacuan = None

for it in item:

if yacuan is None or abs(it["y"] - yacuan) <= toleransiy:

barissekarang.append(it)

yacuan = it["y"] if yacuan is None else yacuan

else:

barisbaris.append(sorted(barissekarang, key=lambda i: i["x"]))

barissekarang = [it]

yacuan = it["y"]

if barissekarang:

barisbaris.append(sorted(barissekarang, key=lambda i: i["x"]))

return barisbaris

hasil = ocr.ocr("faktur.jpg", cls=True)

for baris in urutkanbaca(hasil[0]):

print(" | ".join(i["teks"] for i in baris))

Fungsi ini mengelompokkan teks yang tinggi vertikalnya berdekatan menjadi satu baris, lalu mengurutkannya dari kiri ke kanan. Sesuaikan toleransiy dengan resolusi gambar kalian. Untuk gambar besar, nilainya perlu lebih besar.

Analisis Struktur Dokumen dan Tabel

Ini fitur yang membedakan PaddleOCR dari OCR biasa. Modul PP-Structure bisa mengenali layout dokumen: mana judul, mana paragraf, mana tabel, mana gambar. Dan yang paling berguna, dia bisa merekonstruksi tabel menjadi HTML.

from paddleocr import PPStructure, savestructureres

import cv2

engine = PPStructure(table=True, ocr=True, showlog=False, lang="en")

img = cv2.imread("laporankeuangan.jpg")

hasil = engine(img)

for blok in hasil:

print("tipe:", blok["type"]) # text, title, table, figure, list

print("posisi:", blok["bbox"])

if blok["type"] == "table":

print("HTML tabel:")

print(blok["res"]["html"])

savestructureres(hasil, "output", "laporan")

Untuk mengubah tabel HTML itu jadi DataFrame pandas:

import pandas as pd

for blok in hasil:

if blok["type"] == "table":

tabel = pd.readhtml(blok["res"]["html"])[0]

print(tabel)

tabel.toexcel("tabelhasil.xlsx", index=False)

Ini sangat berguna untuk memproses laporan keuangan, hasil lab, atau dokumen apa pun yang isinya tabel. Aku sering pakai ini untuk klien yang punya ribuan dokumen PDF hasil scan dan ingin datanya masuk ke database.

Menangani PDF

PaddleOCR bekerja dengan gambar, jadi PDF perlu dikonversi dulu.

import fitz  # pip install pymupdf

from paddleocr import PaddleOCR

import numpy as np

ocr = PaddleOCR(useanglecls=True, lang="en")

doc = fitz.open("dokumen.pdf")

semuateks = []

for nomorhalaman, halaman in enumerate(doc, start=1):

# Render dengan resolusi tinggi, penting untuk akurasi OCR

pix = halaman.getpixmap(dpi=300)

img = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.height, pix.width, pix.n)

img = img[:, :, :3] # buang alpha kalau ada

hasil = ocr.ocr(img, cls=True)

if hasil and hasil[0]:

tekshalaman = "\n".join(b[1][0] for b in hasil[0])

semuateks.append(f"=== Halaman {nomorhalaman} ===\n{tekshalaman}")

with open("hasil.txt", "w", encoding="utf-8") as f:

f.write("\n\n".join(semuateks))

Perhatikan dpi=300. Ini salah satu pengaturan paling berpengaruh. Merender PDF di 72 dpi menghasilkan gambar kecil dan buram yang membuat OCR kesulitan. 300 dpi adalah titik manis untuk kebanyakan dokumen. Untuk teks yang sangat kecil, coba 400.

Preprocessing untuk Kasus Sulit

Kalau hasil OCR kalian jelek, sering kali perbaikan terbesar datang dari memperbaiki gambarnya, bukan dari mengganti model.

import cv2

import numpy as np

def preprocess(path):

img = cv2.imread(path)

# 1. Perbesar kalau resolusinya rendah

h, w = img.shape[:2]

if max(h, w) < 1000:

skala = 1500 / max(h, w)

img = cv2.resize(img, None, fx=skala, fy=skala, interpolation=cv2.INTERCUBIC)

# 2. Ubah ke grayscale

abu = cv2.cvtColor(img, cv2.COLORBGR2GRAY)

# 3. Kurangi noise sambil menjaga tepi huruf

abu = cv2.bilateralFilter(abu, 9, 75, 75)

# 4. Perbaiki kontras dengan CLAHE

clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))

abu = clahe.apply(abu)

return cv2.cvtColor(abu, cv2.COLORGRAY2BGR)

imgbersih = preprocess("fotostrukgelap.jpg")

hasil = ocr.ocr(imgbersih, cls=True)

Untuk foto dokumen yang miring, koreksi kemiringan sangat membantu:

def luruskan(img):

abu = cv2.cvtColor(img, cv2.COLORBGR2GRAY)

tepi = cv2.Canny(abu, 50, 150, apertureSize=3)

garis = cv2.HoughLinesP(tepi, 1, np.pi / 180, 100, minLineLength=100, maxLineGap=10)

if garis is None:

return img

sudut = []

for x1, y1, x2, y2 in garis[:, 0]:

a = np.degrees(np.arctan2(y2 - y1, x2 - x1))

if abs(a) < 45:

sudut.append(a)

if not sudut:

return img

median = np.median(sudut)

h, w = img.shape[:2]

M = cv2.getRotationMatrix2D((w // 2, h // 2), median, 1.0)

return cv2.warpAffine(img, M, (w, h), flags=cv2.INTERCUBIC,

borderMode=cv2.BORDERREPLICATE)

Satu tips penting: jangan langsung melakukan binarisasi (mengubah ke hitam putih murni) seperti yang biasa disarankan untuk Tesseract. Model deep learning PaddleOCR justru bekerja lebih baik dengan gambar grayscale atau warna yang masih punya gradasi. Binarisasi yang agresif malah membuang informasi yang dia butuhkan.

Studi Kasus: Ekstraksi Data Faktur

Ayo kita gabungkan semuanya jadi sesuatu yang berguna. Aku akan buat ekstraktor sederhana yang mencari field tertentu di faktur.

import re

from paddleocr import PaddleOCR

ocr = PaddleOCR(useanglecls=True, lang="en")

def ekstrakfaktur(path):

hasil = ocr.ocr(path, cls=True)

if not hasil or not hasil[0]:

return {}

item = []

for baris in hasil[0]:

kotak, (teks, skor) = baris

item.append({

"teks": teks,

"skor": skor,

"x": min(p[0] for p in kotak),

"y": sum(p[1] for p in kotak) / 4,

})

teksgabungan = " ".join(i["teks"] for i in item)

data = {}

# Nomor faktur

m = re.search(r"(?:invoice|faktur)[\s#:]*([A-Z0-9\-/]{4,})", teksgabungan, re.I)

if m:

data["nomorfaktur"] = m.group(1)

# Tanggal

m = re.search(r"(\d{1,2}[/\-]\d{1,2}[/\-]\d{2,4})", teksgabungan)

if m:

data["tanggal"] = m.group(1)

# Total: cari nilai numerik terbesar di dekat kata total

kandidat = []

for i, it in enumerate(item):

if re.search(r"total|jumlah|grand", it["teks"], re.I):

# cari angka di baris yang sama, di sebelah kanan

for lain in item:

if abs(lain["y"] - it["y"]) < 15 and lain["x"] > it["x"]:

angka = re.sub(r"[^\d]", "", lain["teks"])

if angka:

kandidat.append(int(angka))

if kandidat:

data["total"] = max(kandidat)

# Skor kepercayaan rata-rata, untuk quality gate

data["skorrata2"] = sum(i["skor"] for i in item) / len(item)

return data

print(ekstrakfaktur("faktur.jpg"))

Perhatikan bagian pencarian total. Aku tidak hanya mencari pola regex di teks gabungan, tapi memakai koordinat untuk mencari angka yang berada di baris yang sama dengan kata "total". Ini jauh lebih akurat daripada regex murni, karena di faktur nyata sering ada beberapa angka yang mirip.

Dan perhatikan juga skorrata2. Di produksi, kalian harus punya quality gate. Kalau skor rata-rata di bawah ambang tertentu, misalnya 0.85, kirim dokumen itu ke antrean review manusia alih-alih memasukkan data yang mungkin salah ke sistem.

Tips dan Best Practice

Resolusi adalah raja. Sebagian besar kegagalan OCR yang aku temui disebabkan gambar input yang terlalu kecil. Pastikan tinggi karakter minimal 20 sampai 30 piksel.

Jangan lakukan binarisasi agresif. Model deep learning berbeda dari OCR klasik dalam hal ini.

Aktifkan useangle_cls=True untuk foto yang diambil pengguna, karena banyak yang terbalik atau miring.

Simpan skor kepercayaan dan koordinat ke database kalian, bukan cuma teksnya. Data ini emas untuk debugging dan untuk membangun quality gate.

Untuk dokumen dengan format tetap seperti KTP atau formulir standar, pertimbangkan pendekatan berbasis template: tentukan area (region of interest) untuk tiap field, lalu jalankan OCR hanya di area itu. Akurasinya jauh lebih tinggi daripada mencari pola di seluruh halaman.

Kalau kalian punya banyak dokumen dengan font atau gaya khusus, PaddleOCR memungkinkan fine-tuning model recognition dengan data sendiri. Ini pekerjaan yang tidak sepele tapi bisa menaikkan akurasi secara dramatis untuk domain sempit.

Untuk deployment, muat objek PaddleOCR sekali saja di level modul, jangan per request, karena inisialisasi model memakan waktu beberapa detik.

Kesimpulan

PaddleOCR adalah pilihan yang sangat kuat untuk hampir semua kebutuhan OCR, dan yang menyenangkan, dia berjalan baik bahkan di CPU. Ringkasan pentingnya:

Pipeline OCR terdiri dari deteksi, klasifikasi arah, dan pengenalan, dan mengetahui tahap mana yang gagal sangat mempercepat debugging.

Output berisi koordinat dan skor kepercayaan, dan keduanya sama berharganya dengan teksnya sendiri.

Modul PP-Structure bisa merekonstruksi tabel jadi HTML yang langsung bisa diubah ke DataFrame, sangat berguna untuk dokumen laporan.

Kualitas gambar input jauh lebih menentukan daripada pemilihan model. Render PDF di 300 dpi, perbesar gambar kecil, perbaiki kontras, dan luruskan dokumen miring.

Untuk ekstraksi field, gabungkan regex dengan penalaran spasial berbasis koordinat, dan selalu pasang quality gate berbasis skor kepercayaan.

Coba ambil sepuluh foto struk atau faktur yang kalian punya, jalankan pipeline di atas, lalu lihat berapa persen yang berhasil diekstrak dengan benar. Angka itu akan memberi tahu kalian dengan jujur berapa banyak preprocessing yang perlu kalian tambahkan sebelum sistemnya layak produksi. Selamat mencoba.

Artikel Terkait

Tutorial Surya OCR: Document AI untuk Text Detection, OCR, dan Layout Analysis

Tutorial Surya OCR: Document AI untuk Text Detection, OCR, dan Layout Analysis Surya adalah library Document AI open-sou...

timm: Gudang 1000+ Model Computer Vision Siap Pakai di PyTorch

timm: Gudang 1000+ Model Computer Vision Siap Pakai di PyTorch Halo temen-temen, di tutorial kali ini aku mau ngajak kal...

DSPy: Berhenti Ngoprek Prompt Manual, Biarkan Compiler yang Optimasi

DSPy: Berhenti Ngoprek Prompt Manual, Biarkan Compiler yang Optimasi Halo temen-temen, kali ini aku mau ngenalin satu li...

Depth Anything V2: Prediksi Kedalaman dari Satu Gambar dengan Python

Depth Anything V2: Prediksi Kedalaman dari Satu Gambar dengan Python Halo temen-temen, di tutorial kali ini aku mau ngaj...