Tools

Membangun Image Preprocessing untuk OCR: Pengalaman Menyiapkan Gambar Sebelum Diproses AI

admin
admin

2 Sep 202610 min baca

Membangun Image Preprocessing untuk OCR: Pengalaman Menyiapkan Gambar Sebelum Diproses AI

Ketika pertama kali membangun pipeline OCR untuk dokumen, saya sempat berpikir bahwa tantangan utamanya adalah memilih model OCR yang tepat.

Saya menggunakan Qwen3-VL 8b yang dijalankan secara self-hosted melalui Ollama di server dengan 2× NVIDIA Tesla T4. Namun setelah memproses image dokumen dari kondisi nyata, saya menemukan bahwa masalahnya tidak selalu ada pada model.

Dokumen bisa miring, orientasinya salah, background terlalu luas, kualitas gambar berbeda-beda, bahkan satu foto bisa berisi lebih dari satu dokumen.

Dari situ saya menyadari satu hal:

Sebelum meminta AI membaca dokumen, image yang diberikan harus dipastikan layak untuk dibaca.

Karena itu, saya mulai membangun image preprocessing pipeline khusus untuk OCR.


Dari Raw Image ke Data

Kalau dilihat dari luar, proses OCR mungkin terlihat sederhana:

Image
  ↓
OCR
  ↓
Text

Tetapi pipeline yang saya bangun akhirnya menjadi jauh lebih panjang:

Raw Image
    ↓
Input Validation
    ↓
Document Detection
    ↓
Crop Document
    ↓
Orientation Detection
    ↓
Rotate
    ↓
Image Enhancement
    ↓
OCR / Vision Model
    ↓
Structured JSON
    ↓
Mapping
    ↓
Database

Jadi image preprocessing bukan tujuan akhir.

Preprocessing adalah tahap untuk menyiapkan image sebelum image tersebut diproses oleh OCR atau vision model.

Output akhirnya bukan sekadar gambar yang sudah diperbaiki, tetapi data terstruktur yang nantinya bisa digunakan oleh sistem.


Tantangan Pertama: Tidak Semua Image Siap Diproses

Image yang masuk ke sistem bisa berasal dari berbagai kondisi.

Misalnya:

  • foto dokumen dari kamera
  • scan dokumen
  • dokumen dengan background
  • dokumen yang sedikit miring
  • dokumen dengan orientasi 90° atau 180°
  • beberapa dokumen dalam satu image

Kalau seluruh image langsung diberikan ke vision model, model harus melakukan banyak pekerjaan tambahan.

Model bukan hanya harus membaca teks, tetapi juga harus memahami:

“Dokumennya ada di mana?”

“Orientasinya bagaimana?”

“Bagian mana yang sebenarnya perlu dibaca?”

Saya kemudian memindahkan sebagian pekerjaan tersebut ke tahap preprocessing.


Pipeline Preprocessing

Secara keseluruhan, service preprocessing yang saya bangun kurang lebih seperti ini:

                 POST /preprocess
                       │
                       ▼
                HTTP + Guard
                       │
                       ▼
              Image Information
                  ImageMagick
                       │
                       ▼
              YOLO Document Detection
                       │
              ┌────────┴────────┐
              │                 │
       Dokumen ditemukan    Tidak ditemukan
              │                 │
              ▼                 ▼
       Sort Detection       Text Detection
              │              PP-OCRv5
              │                 │
              │                 ▼
              │             Text Focus
              └────────┬────────┘
                       ▼
                Crop + Padding
                       │
                       ▼
              Orientation Detection
                   PP-LCNet
                       │
                       ▼
                    Rotate
                       │
                       ▼
                  Enhancement
                  ImageMagick
                       │
                       ▼
                 Processed Image

Ada beberapa keputusan yang saya buat di setiap tahap.


1. Memeriksa Image Sebelum Diproses

Request masuk melalui:

POST /preprocess

Service menerima image melalui multipart/form-data.

Sebelum masuk ke model, saya terlebih dahulu mengambil informasi dasar image menggunakan ImageMagick identify.

Tujuannya untuk mengetahui ukuran image dan memastikan input yang diterima memang bisa diproses.

Selain itu, service juga memiliki guard untuk membatasi concurrency.

Saya menggunakan batas concurrency 6 request.

Kalau semua request langsung menjalankan inference dan ImageMagick secara bersamaan tanpa pembatasan, penggunaan CPU dan proses subprocess bisa meningkat cukup cepat.

Jika slot penuh, service dapat mengembalikan:

503 Server is busy
Retry-After: 5

Dengan cara ini, service tidak membiarkan request masuk tanpa batas.


2. YOLO untuk Mencari Dokumen

Tahap utama pertama adalah document detection menggunakan:

document-yolo.onnx

Image di-resize secara paksa menjadi:

640 × 640

kemudian diubah menjadi tensor RGB yang sudah dinormalisasi.

Tujuan YOLO di sini bukan membaca teks.

YOLO hanya menjawab pertanyaan:

“Di mana dokumennya?”

Misalnya image berisi:

┌──────────────────────────────┐
│                              │
│        Background            │
│                              │
│      ┌───────────────┐       │
│      │   DOCUMENT    │       │
│      │               │       │
│      └───────────────┘       │
│                              │
└──────────────────────────────┘

Yang saya butuhkan adalah bounding box dokumennya.

Output detection kemudian difilter menggunakan beberapa aturan:

  • confidence ≥ 0.35
  • NMS IoU 0.45
  • bounding box minimal sekitar 8% dari ukuran image
  • maksimal 12 dokumen

Detection juga diurutkan dari posisi atas ke bawah dan kiri ke kanan.

Dengan begitu, jika terdapat beberapa dokumen dalam satu foto, hasilnya memiliki urutan yang konsisten.


3. Tidak Menemukan Dokumen? Gunakan Text Detection

Saya tidak ingin pipeline langsung berhenti ketika YOLO tidak menemukan dokumen.

Karena itu saya menambahkan fallback menggunakan:

pp-ocrv5-mobile-det.onnx

Model ini digunakan untuk mencari area yang mengandung teks.

Berbeda dengan YOLO, model ini tidak mencari “dokumen”.

Ia mencari area teks yang bisa digunakan sebagai indikasi bahwa di area tersebut terdapat informasi yang kemungkinan perlu dibaca.

Image di-resize menjadi:

960 × 960

Kemudian bounding box teks digunakan sebagai textFocus.

Jika area teks yang ditemukan terlalu kecil, misalnya kurang dari:

  • 5% lebar image
  • 3% tinggi image

maka hasilnya dianggap tidak valid.

Jadi fallback ini hanya digunakan ketika:

YOLO
  ↓
Tidak menemukan dokumen
  ↓
PP-OCRv5 Text Detection
  ↓
Text Focus

Ini penting karena PP-OCRv5 bukan selalu menjadi tahap utama.

Ia hanya menjadi fallback ketika document detection tidak menemukan target.


4. Crop dan Padding

Setelah mendapatkan bounding box, area dokumen kemudian di-crop.

Saya juga menambahkan padding sekitar 2.5%.

Tujuannya supaya crop tidak terlalu mepet dengan batas bounding box.

Secara sederhana:

Bounding Box
┌──────────────┐
│   DOCUMENT   │
└──────────────┘

menjadi:

Padding
┌────────────────┐
│  ┌──────────┐  │
│  │ DOCUMENT │  │
│  └──────────┘  │
└────────────────┘

Hal kecil seperti ini cukup penting karena bounding box hasil detection tidak selalu tepat sampai ke batas sebenarnya dari dokumen.


5. Menentukan Orientasi Dokumen

Setelah crop, image belum tentu berada dalam posisi tegak.

Untuk itu saya menggunakan:

pp-lcnet-doc-ori.onnx

Crop di-resize menjadi:

224 × 224

Model kemudian melakukan klasifikasi ke empat kelas:

0°
90°
180°
270°

Misalnya hasilnya:

orientation = 90°

maka image akan diputar hingga kembali ke posisi normal.

Jadi model ini tidak bertugas membaca teks.

Tugasnya hanya menentukan:

Image ini harus diputar berapa derajat?


6. Image Enhancement Menggunakan ImageMagick

Setelah posisi dokumen benar, image masuk ke tahap enhancement.

Di sini saya tidak menggunakan machine learning.

Saya menggunakan ImageMagick.

Pipeline enhancement yang digunakan antara lain:

deskew 40%
        ↓
resize 1800x1800>
        ↓
contrast-stretch 0.25%x0.1%
        ↓
sigmoidal-contrast 4.5x45%
        ↓
unsharp 0x1.25+1.6+0.008
        ↓
sharpen 0x0.65

Tujuannya adalah membuat image lebih siap untuk tahap OCR.

Deskew

Membantu meluruskan image yang sedikit miring.

Resize

Image yang terlalu besar diperkecil agar tidak diproses dalam ukuran yang berlebihan.

Contrast

Membantu memperjelas perbedaan antara teks dan background.

Unsharp dan Sharpen

Digunakan untuk membantu mempertajam detail teks.

Tetapi saya juga menemukan bahwa enhancement tidak boleh dilakukan secara berlebihan.

Misalnya terlalu banyak sharpening atau contrast enhancement justru dapat menghasilkan artefak.

Jadi prinsip yang saya gunakan bukan:

Semakin banyak preprocessing, semakin bagus.

Tetapi:

Lakukan preprocessing secukupnya untuk membuat image lebih mudah dibaca.

Perbandingan nyatanya kurang lebih seperti ini. Yang pertama contoh hasil OCR sebelum preprocessing, di mana dokumen masih mengandung banyak noise dan miring:

Contoh hasil OCR sebelum image preprocessing, dokumen masih mengandung noise dan miring

Dan yang kedua hasil setelah melewati pipeline image preprocessing: dokumen sudah lurus, bersih, dan jauh lebih mudah dibaca:

Contoh hasil OCR setelah image preprocessing, dokumen lurus bersih dan mudah dibaca

7. Kalau Ada Banyak Dokumen

Ada kemungkinan satu image berisi lebih dari satu dokumen.

Misalnya:

Document 1
Document 2
Document 3

Daripada mengembalikan banyak image secara terpisah, hasil crop kemudian digabungkan secara vertikal.

┌──────────────────┐
│    Document 1    │
└──────────────────┘
┌──────────────────┐
│    Document 2    │
└──────────────────┘
┌──────────────────┐
│    Document 3    │
└──────────────────┘

Background yang digunakan adalah putih.

Untuk satu dokumen, image langsung dikembalikan.

Untuk lebih dari satu dokumen, ImageMagick melakukan proses append secara vertikal.


8. Output Tidak Hanya Image

Service preprocessing mengembalikan PNG sebagai output utama.

Tetapi saya juga mengirimkan metadata melalui response header.

Contohnya:

X-Cropped
X-Orientation-Angle
X-Detector
X-Document-Count
X-Deskew-Angle
X-Detection-Status

Misalnya:

X-Cropped: true
X-Orientation-Angle: 90
X-Detector: yolo
X-Document-Count: 1

Metadata ini sangat membantu ketika debugging.

Kalau hasil OCR ternyata salah, saya bisa melihat apakah image sebelumnya berhasil di-crop, diputar, dan diproses sesuai yang diharapkan.


9. Dari Preprocessed Image ke Structured JSON

Setelah preprocessing selesai, image tersebut kemudian diteruskan ke tahap OCR atau vision model.

Di project saya, preprocessing bukan tahap terakhir.

Flow yang lebih lengkap adalah:

Raw Image
    ↓
Preprocessing
    ↓
OCR / Vision Model
    ↓
Extract Information
    ↓
Structured JSON

Misalnya dari sebuah dokumen, model perlu mengambil beberapa field:

{
  "reference_number": "POD-20260831-001",
  "container_number": "MSCU1234567",
  "driver_name": "Budi Santoso",
  "document_date": "2026-08-31",
  "destination": "Jakarta"
}

Format seperti ini jauh lebih berguna dibandingkan hanya mendapatkan hasil OCR berupa text panjang.

Karena data tersebut sudah memiliki struktur yang bisa langsung diproses oleh sistem.


10. JSON Kemudian Di-mapping ke Database

Pipeline saya dijalankan menggunakan n8n sebagai orchestrator.

Kurang lebih alurnya:

Database
    ↓
n8n
    ↓
Ambil Document
    ↓
Preprocessing API
    ↓
OCR / Vision
    ↓
Structured JSON
    ↓
Mapping
    ↓
Database

Contohnya:

{
  "reference_number": "POD-20260831-001",
  "container_number": "MSCU1234567",
  "driver_name": "Budi Santoso",
  "document_date": "2026-08-31"
}

Kemudian n8n melakukan mapping:

reference_number → reference_number
container_number → container_number
driver_name      → driver_name
document_date    → document_date

dan hasil akhirnya disimpan ke database.

Di sinilah preprocessing menjadi bagian dari sistem yang lebih besar.

Tujuan akhirnya bukan sekadar:

Image → Image

tetapi:

Image
  ↓
Better Image
  ↓
AI
  ↓
Structured JSON
  ↓
Database

11. Kenapa n8n dan Preprocessing Saya Pisahkan?

Saya menggunakan n8n untuk menjalankan workflow, bukan untuk menangani seluruh logic computer vision.

Preprocessing saya deploy sebagai API service tersendiri.

Pembagiannya kira-kira seperti ini:

                 n8n
                  │
                  │ HTTP
                  ▼
        ┌───────────────────┐
        │ Preprocessing API │
        ├───────────────────┤
        │ YOLO              │
        │ PP-OCRv5          │
        │ PP-LCNet          │
        │ ImageMagick       │
        └───────────────────┘
                  │
                  ▼
             Processed Image
                  │
                  ▼
             OCR / Vision
                  │
                  ▼
            Structured JSON
                  │
                  ▼
               Database

Dengan cara ini, workflow n8n tetap relatif sederhana.

Kalau saya ingin mengubah algoritma crop, menambahkan deskew, mengganti model orientation, atau mengubah enhancement, saya cukup mengubah preprocessing service.

Workflow utamanya tidak perlu ikut berubah.


12. Performa: Ternyata ImageMagick Cukup Berpengaruh

Satu hal menarik yang saya temukan setelah mengukur pipeline adalah bahwa inference model bukan satu-satunya sumber waktu.

Ketiga model ONNX:

document-yolo.onnx
pp-ocrv5-mobile-det.onnx
pp-lcnet-doc-ori.onnx

berjalan menggunakan CPU melalui onnxruntime-node.

Selain itu, pipeline cukup sering menjalankan ImageMagick melalui subprocess.

Beberapa operasi bahkan membutuhkan pola:

create temporary directory
        ↓
write image
        ↓
run magick
        ↓
read result
        ↓
delete temporary files

Untuk satu dokumen, berdasarkan pengukuran pipeline, proporsi waktunya kira-kira:

YOLO              28%
Orientation       14%
ImageMagick       42%
HTTP + temp I/O   16%

Yang cukup mengejutkan adalah ImageMagick justru mengambil porsi terbesar.

Artinya, ketika melakukan optimasi, saya tidak bisa hanya melihat waktu inference model.

Saya juga harus melihat:

  • jumlah subprocess
  • temporary file
  • disk I/O
  • jumlah transformasi image
  • concurrency
  • ukuran image

Ini menjadi salah satu pelajaran penting dari project ini.


13. Concurrency Juga Berpengaruh

Karena sebagian besar preprocessing berjalan di CPU, menjalankan beberapa request secara paralel dapat membantu meningkatkan throughput.

Saya menggunakan guard concurrency dengan batas sekitar:

MAX_CONCURRENT_PREPROCESS = 6

Dengan demikian service tidak menerima pekerjaan tanpa batas.

Jika semua slot sedang digunakan, request berikutnya dapat menerima:

503 Server is busy
Retry-After: 5

Ini lebih aman daripada membiarkan terlalu banyak request masuk dan akhirnya membuat server kehabisan resource.


Apa yang Sebenarnya Saya Pelajari dari Project Ini?

Project ini awalnya saya mulai karena ingin mendapatkan hasil OCR yang lebih baik.

Tetapi akhirnya saya justru belajar bahwa OCR bukan hanya masalah model.

Ada banyak hal yang terjadi sebelum model membaca teks.

Image Quality
      ↓
Document Detection
      ↓
Cropping
      ↓
Orientation
      ↓
Deskew
      ↓
Enhancement
      ↓
OCR / Vision
      ↓
Extraction
      ↓
JSON
      ↓
Database

Setiap tahap mempunyai tanggung jawab sendiri.

Dan tidak semuanya harus menggunakan AI.

YOLO digunakan untuk mengetahui posisi dokumen.

PP-LCNet digunakan untuk mengetahui orientasi.

PP-OCRv5 digunakan sebagai fallback ketika dokumen tidak ditemukan.

Sementara crop, rotate, deskew, contrast, dan sharpening dilakukan menggunakan image processing.

Menurut saya, kombinasi ini justru membuat pipeline menjadi lebih fleksibel.


Kesimpulan

Hal yang paling saya ubah dari cara berpikir saya adalah:

Jangan langsung menyalahkan model OCR ketika hasilnya buruk.

Periksa dulu image yang diberikan kepadanya.

Apakah dokumennya sudah ditemukan?

Apakah crop-nya benar?

Apakah orientasinya sudah benar?

Apakah image terlalu besar?

Apakah dokumennya miring?

Apakah contrast dan ketajaman cukup?

Setelah image dipersiapkan dengan baik, barulah OCR atau vision model melakukan tugasnya.

Pada akhirnya pipeline yang saya bangun bukan sekadar:

Image → OCR

tetapi:

                 ┌─ Document Detection
                 │
Raw Image ────────┼─ Orientation
                 │
                 ├─ Image Enhancement
                 │
                 ▼
           Preprocessed Image
                 │
                 ▼
             OCR / AI
                 │
                 ▼
          Structured JSON
                 │
                 ▼
              Mapping
                 │
                 ▼
             Database

Dan mungkin itu pelajaran paling penting dari project ini:

Model yang bagus membutuhkan input yang baik.

Image preprocessing bukan sekadar tahap tambahan sebelum OCR.

Dalam pipeline yang saya bangun, preprocessing menjadi bagian penting untuk menjembatani raw image dengan data terstruktur yang akhirnya disimpan ke database.

Artikel Terkait

(3)

Komentar

(0)

Komentar Anda akan dimoderasi.

Memuat komentar...