Web Dev

Auto Capture Hasil X-Ray dengan Barcode Queue dan Deteksi Garis Tengah OpenCV

admin
admin

14 Okt 2026 • 7 min baca

Auto Capture Hasil X-Ray dengan Barcode Queue dan Deteksi Garis Tengah OpenCV

Saya pernah membangun sistem stasiun X-ray untuk kargo yang meneruskan foto hasil pemeriksaan ke dokumen airwaybill secara otomatis. Bukan petugas yang memotret layar, bukan juga saya menekan tombol capture. Aliran video dari PC X-ray diambil lewat HDMI capture, dibuka di aplikasi web, lalu OpenCV mendeteksi paket yang bergerak melewati satu garis deteksi di tengah frame. Begitu objeknya melewati garis, frame tersimpan otomatis dan langsung menempel ke AWB yang saat itu giliran antreiannya. Di artikel ini saya ceritakan konsepnya dari nol, kenapa jalur OCR yang tadinya saya bangun akhirnya saya ganti dengan barcode reader, dan bagaimana seluruh sistem ini bekerja dari backend NestJS sampai aplikasi web dan mobile.

Alur kerja sistemnya dari awal sampai akhir

Mulai dari barang yang masuk. Petugas memindai barcode di dokumen AWB paket yang akan diperiksa, dan hasil scan itu langsung masuk aplikasi membentuk antrian. Urutan antrian mengikuti urutan scan, jadi paket pertama yang discan adalah paket pertama yang hasil X-ray-nya tersimpan. Antrian ini penting karena di ujung lain sistem harus tahu foto X-ray yang barusan tercapture itu milik paket siapa.

Di ruangan pemeriksaan, paket melewati mesin X-ray seperti biasa dan petugas X-ray bekerja di PC-nya sendiri. Layar PC X-ray itu kemudian dibagi lewat HDMI splitter ke sebuah capture card, dan aplikasi web di stasien kerja terpisah membuka video source tersebut. Di layar stasiun inilah OpenCV menjalankan object tracking. Ada satu garis deteksi virtual tepat di tengah frame. Ketika objek terlacak bergerak dan melewati garis itu, aplikasi otomatis menyimpan frame saat itu sebagai foto hasil X-ray, lalu mengirimkannya ke backend untuk disimpan ke AWB yang sedang berada di urutan terdepan antrian. Dari sisi mobile, orang gudang bisa langsung melihat paketnya sudah lengkap dengan foto X-ray.

Alur sistem stasiun X-ray otomatis: barcode queue, HDMI capture, OpenCV garis deteksi tengah, auto capture ke antrian AWB
Alur sistem: scan barcode membentuk antrian, video X-ray via HDMI splitter, auto capture saat objek melewati garis tengah, foto menempel ke AWB sesuai antrian

Kenapa saya ganti jalur OCR dengan barcode reader

Ceritanya tidak langsung mulai dari desain final. Versi pertama yang saya bangun memakai jalur OCR: foto dokumen AWB diunggah, masuk queue BullMQ, lalu model vision berbasis Ollama membaca isinya dan mengekstrak nomor AWB, HAWB, kurir, sampai tanggal ke bentuk JSON. Jalur itu berfungsi, dan arsitektur queue-nya tetap saya pakai sebagai pelajaran berharga: kerja berat memang tidak boleh menempel di request HTTP.

Tapi di praktiknya, membaca dokumen hasil foto selalu punya gesekan. Pencahayaan tidak selalu ideal, hasil baca kadang harus dikoreksi, dan yang paling mahal: langkahnya menambah waktu di titik paling sibuk, yaitu saat barang menumpuk. Petugas jauh lebih cepat mengarahkan barcode dokumen ke barcode reader daripada memotret lalu menunggu OCR. Jadi saya ambil keputusan memensiunkan jalur OCR dan mengganti input antrian dengan barcode reader camera. Satu langkah hilang, antrian jalan lebih cepat, dan error baca dokumen hilang sama sekali. Pelajarannya sederhana: otomasi yang paling bagus bukan yang paling pintar, tapi yang paling ringan untuk orang yang menjalankannya.

Stasiun terpisah dan HDMI capture

Keputusan arsitektur lain yang tidak kalah penting: sistem deteksi ini saya taruh di stasiun yang benar-benar terpisah dari PC X-ray. PC X-ray punya pekerjaannya sendiri dan tidak boleh saya sentuh dengan aplikasi tambahan apa pun. Jadi saya memakai HDMI splitter pasif: satu output tetap ke monitor petugas X-ray, satu output lagi ke capture card yang menempel di PC stasiun saya. Aplikasi web tinggal membuka video source dari capture card itu.

Pendekatan ini membuat integrasinya sangat aman. Tidak ada software yang dipasang di PC X-ray, tidak ada protokol khusus, tidak ada risiko mengganggu mesin pemeriksaan. PC X-ray hanya melihat ada monitor kedua yang menyala. Dari sisi maintenance juga enak: kalau stasiun deteksi saya bermasalah, mesin X-ray sama sekali tidak terdampak.

Deteksi objek dan garis tengah untuk auto capture

Inti dari semuanya ada di deteksi. Saya memakai kombinasi OpenCV dan model deteksi berbasis ONNX yang berjalan di backend lewat onnxruntime-node, dengan sharp untuk pra-pemrosesan gambar. Setiap frame diubah ukurannya ke 640 kali 640 piksel, dinormalkan, lalu diinferensi. Hasilnya bukan sekadar kotak deteksi, tapi sudah dengan identitas tracking per objek dan satu penanda kunci: apakah objek itu sudah melewati garis deteksi. Bentuk keluarannya seperti ini, sudah saya sederhanakan dari kode aslinya:

type YoloDetection = {
  trackId: number;
  x: number; y: number;
  width: number; height: number;
  confidence: number;
  crossed: boolean;
};

Garis deteksinya sendiri satu saja, di tengah frame, pada posisi setengah lebar gambar. Setiap objek yang terlacak punya trackId, jadi saya bisa tahu objek yang sama saat masuk frame di kiri dan saat keluar di kanan. Saat posisi objek dengan trackId tertentu melewati garis tengah, penanda crossed menyala dan frame pada momen itulah yang diambil sebagai foto hasil X-ray. Satu garis, satu kondisi, tidak ada tombol. Penyederhanaan ini disengaja: semakin sedikit kondisi yang harus dinilai sistem, semakin sedikit pula peluang salah capture.

Menyimpan capture ke AWB sesuai antrian

Setelah auto capture terjadi, foto dikirim ke backend NestJS dan disimpan sebagai detail dari record AWB. Relasinya satu AWB bisa punya banyak foto X-ray, dan satu tabel detail menyimpan path foto beserta waktu pembuatannya. Yang mencocokkan foto dengan paket adalah urutan antrian hasil scan barcode tadi. Karena urutan scan sama dengan urutan pemeriksaan, foto hasil capture berikutnya jatuh ke AWB di urutan terdepan yang belum punya foto. Struktur entity-nya seperti ini, juga sudah saya ringkas:

@Entity('airwaybill_xrays')
@ObjectType()
export class AirwaybillXray {
  @PrimaryGeneratedColumn()
  @Field(() => ID)
  id!: number;

  @Column({ type: 'varchar', length: 500 })
  @Field()
  airwaybill_number!: string;

  @OneToMany(() => AirwaybillXrayDetail, (d) => d.airwaybill)
  @Field(() => [AirwaybillXrayDetail])
  airwaybillXrayDetails?: AirwaybillXrayDetail[];
}

Backend GraphQL dan satu aplikasi Expo untuk semua platform

Backend-nya NestJS dengan GraphQL sebagai interface utama, code first, jadi setiap entity TypeORM sekaligus jadi tipe GraphQL. Modulnya terbagi per domain: auth, awb, upload, ocr, dan vision. Untuk database saya menjaga disiplin migration dari hari pertama: semua perubahan skema lewat migration CLI dengan skrip generate, run, dan revert. Lima migration yang terbentuk selama pengembangan semuanya tercatat rapi, dan kebiasaan ini berkali-kali menyelamatkan saya saat ubah skema sambil data sudah terisi.

Untuk query dari aplikasi, resolver-nya mendukung pagination bergaya relay dengan orderBy fleksibel, pencarian bebas, sampai pencarian langsung by nomor AWB karena kasus paling sering di lapangan memang mencari satu nomor tertentu:

@Query(() => AirwaybillXrayPage, { name: 'airwaybillxrays' })
listAirwaybillXrays(
  @Args('first', { type: () => Int, nullable: true }) first?: number,
  @Args('page', { type: () => Int, nullable: true }) page?: number,
  @Args('orderBy', { type: () => [QueryOrderByClause], nullable: true })
  orderBy?: QueryOrderByClause[],
) {
  return this.awbService.list(first, page, orderBy);
}

Sisi client saya tulis dengan Expo satu kode untuk Android, iOS, dan web. Stasiun deteksi memakai versi web karena harus membuka source HDMI capture di browser, sementara versi mobile dipakai petugas dan gudang untuk memantau antrian dan hasil capture dari mana saja. Beberapa layar saya buat dua versi, index.tsx untuk native dan index.web.tsx untuk web, supaya layout stasiun yang bekerja dengan mouse dan layar besar tidak mengorbankan pengalaman mobile. State server diurus Apollo Client, state lokal pakai zustand, dan UI memakai gluestack-ui yang konsisten di semua platform.

Gesekan build native yang membekas

Bagian yang paling menyita waktu secara mengejutkan adalah build native. Pindah dari Expo Go ke development build lalu build Android dan iOS selalu ada gesekan, dan di README proyek saya sampai mencatat sendiri versi CMake dan kombinasi reanimated dan worklets yang aman, karena kombinasi yang salah membuat build Android gagal dengan pesan yang tidak jelas penyebabnya. Kalau kamu baru mulai dengan Expo dan build nativemu tiba-tiba merah, periksa dulu kombinasi versi library animasi dan toolchain-nya sebelum menyalahkan kode.

Testing dan pelajaran yang saya bawa

Backend saya uji dengan vitest dari unit sampai konfigurasi e2e terpisah, dan di mobile ada skenario e2e login dengan jest yang menguji jalur kritikal dari tampilan awal sampai masuk dashboard. Jumlahnya tidak banyak, tapi jalur yang paling penting tercover. Dari seluruh perjalanan proyek ini, empat pelajaran yang paling melekat: pertama, input paling cepat di titik sibuk tetap barcode, bukan OCR, karena otomasi terbaik adalah yang paling ringan untuk orang yang menjalankannya. Kedua, integrasi lewat sinyal video pasif seperti HDMI splitter membuat sistem tidak pernah mengganggu mesin utama. Ketiga, satu garis deteksi dengan object tracking lebih dapat diandalkan daripada logika capture yang rumit. Keempat, satu kode Expo untuk web dan mobile terbukti cukup untuk dua jenis pengguna yang sangat berbeda. Semua kode di artikel ini sudah saya generalisasi dari proyek aslinya, dan pola arsitekturnya bisa diterapkan ke sistem antrian dengan auto capture apa pun, bukan hanya X-ray kargo.

Artikel Terkait

(3)

Komentar

(0)

Komentar Anda akan dimoderasi.

Memuat komentar...