Jawaban UTS Pengolahan Citra Digital Kelompok I
Soal 1: selisih Laplacian of Gaussian (sigma 0,1 dan 0,2). Soal 2: memecah teks menjadi karakter individual dengan regionprops. Soal 3: menghitung jumlah koin per ukuran dengan regionprops.
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 741 B |
|
After Width: | Height: | Size: 545 B |
|
After Width: | Height: | Size: 616 B |
|
After Width: | Height: | Size: 811 B |
|
After Width: | Height: | Size: 993 B |
|
After Width: | Height: | Size: 943 B |
|
After Width: | Height: | Size: 998 B |
|
After Width: | Height: | Size: 871 B |
|
After Width: | Height: | Size: 991 B |
|
After Width: | Height: | Size: 400 B |
|
After Width: | Height: | Size: 1022 B |
|
After Width: | Height: | Size: 550 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 872 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 986 B |
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
UTS INF301 Pengolahan Citra Digital - Soal 2
|
||||
Memecah teks menjadi karakter individual menggunakan regionprops,
|
||||
lalu menyimpan setiap karakter sebagai gambar terpisah.
|
||||
"""
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
from skimage import io, color, filters, measure, morphology, transform, util
|
||||
|
||||
# Input dan output relatif terhadap folder program ini, sehingga program
|
||||
# bisa dijalankan dari folder mana pun.
|
||||
FOLDER = os.path.dirname(os.path.abspath(__file__))
|
||||
INPUT = os.path.join(FOLDER, "Problem 2 Image.png")
|
||||
OUTPUT_DIR = os.path.join(FOLDER, "karakter")
|
||||
MIN_AREA = 20 # region lebih kecil dari ini dianggap noise
|
||||
# Radius dilasi untuk menggabungkan huruf menjadi kata. Pada citra ini celah
|
||||
# antar-huruf dalam satu kata <= 87 piksel dan celah antar-kata >= 104 piksel,
|
||||
# sehingga disk radius 48 (menutup celah ~96 piksel) memisahkan keduanya.
|
||||
WORD_RADIUS = 48
|
||||
# Huruf satu goresan (seperti I) sangat memanjang. Untuk huruf ini sumbu
|
||||
# utamanya sendiri lebih tepat dipakai daripada sudut lengkung kata. Pada
|
||||
# citra ini eccentricity I = 0.97, huruf lain <= 0.88.
|
||||
ELONGATED_ECC = 0.95
|
||||
|
||||
# 1. Baca citra lalu ubah ke grayscale (citra asli RGBA)
|
||||
img = io.imread(INPUT)
|
||||
if img.ndim == 3 and img.shape[2] == 4:
|
||||
img = color.rgba2rgb(img)
|
||||
gray = color.rgb2gray(img) if img.ndim == 3 else util.img_as_float(img)
|
||||
|
||||
# 2. Segmentasi: threshold Otsu memisahkan teks putih dari latar hitam
|
||||
thresh = filters.threshold_otsu(gray)
|
||||
binary = gray > thresh
|
||||
binary = morphology.remove_small_objects(binary, max_size=MIN_AREA)
|
||||
|
||||
# 3. Labeling: setiap komponen terhubung (8-connectivity) = satu karakter
|
||||
labels = measure.label(binary, connectivity=2)
|
||||
|
||||
# 4. regionprops: ambil bounding box setiap karakter
|
||||
regions = measure.regionprops(labels, intensity_image=gray)
|
||||
print(f"Jumlah karakter terdeteksi: {len(regions)}")
|
||||
|
||||
# 5. Kelompokkan karakter menjadi kata: dilasi menyatukan huruf yang
|
||||
# berdekatan, lalu labeling memberi satu label per kata.
|
||||
words = measure.label(morphology.dilation(binary, morphology.disk(WORD_RADIUS)))
|
||||
word_of = [words[tuple(region.coords[0])] for region in regions]
|
||||
|
||||
# 6. Sudut kemiringan setiap huruf. Baseline kata bisa melengkung
|
||||
# (contoh: TWINPEAK), jadi sudut dihitung per huruf dari garis singgung
|
||||
# lengkung yang melalui centroid huruf-huruf dalam katanya.
|
||||
char_angle = [0.0] * len(regions)
|
||||
for w in set(word_of):
|
||||
idx = [i for i, k in enumerate(word_of) if k == w]
|
||||
pts = np.array([regions[i].centroid for i in idx])
|
||||
pts = pts - pts.mean(axis=0)
|
||||
# Sumbu utama (PCA) kumpulan centroid = arah umum kata
|
||||
_, _, vt = np.linalg.svd(pts)
|
||||
u_dir, v_dir = vt[0], vt[1] # vektor (baris, kolom)
|
||||
if u_dir[1] < 0: # teks dibaca dari kiri ke kanan
|
||||
u_dir = -u_dir
|
||||
u, v = pts @ u_dir, pts @ v_dir
|
||||
# Lengkung kuadratik v = a u^2 + b u + c, kemiringan lokal dv/du = 2au + b
|
||||
a, b, _ = np.polyfit(u, v, 2)
|
||||
for i, ui in zip(idx, u):
|
||||
dr, dc = u_dir + (2 * a * ui + b) * v_dir
|
||||
# baris citra bertambah ke bawah, maka -dr agar sudut berlawanan jarum jam positif
|
||||
char_angle[i] = np.degrees(np.arctan2(-dr, dc))
|
||||
|
||||
# 7. Crop, luruskan, dan simpan setiap karakter.
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
for i, (region, w) in enumerate(zip(regions, word_of), start=1):
|
||||
angle = char_angle[i - 1]
|
||||
if region.eccentricity > ELONGATED_ECC:
|
||||
# orientation = sudut sumbu utama terhadap sumbu vertikal (baris);
|
||||
# putar sebesar itu agar goresan menjadi vertikal
|
||||
angle = np.degrees(region.orientation)
|
||||
# image_intensity = piksel karakter ini saja; karakter tetangga yang
|
||||
# masuk ke dalam kotak tidak ikut tersimpan
|
||||
# Putar balik sebesar sudut kata agar huruf tegak; resize=True agar
|
||||
# ujung huruf tidak terpotong.
|
||||
upright = transform.rotate(region.image_intensity, -angle, resize=True)
|
||||
# Kotak baru mengikuti huruf yang sudah tegak
|
||||
rows, cols = np.nonzero(upright > thresh)
|
||||
upright = upright[rows.min():rows.max() + 1, cols.min():cols.max() + 1]
|
||||
char_img = util.img_as_ubyte(np.clip(upright, 0, 1))
|
||||
filename = os.path.join(OUTPUT_DIR, f"karakter_{i:02d}.png")
|
||||
io.imsave(filename, char_img)
|
||||
print(f"{filename}: kata={w}, sudut={angle:.1f} derajat, area={region.area}")
|
||||
@@ -0,0 +1,215 @@
|
||||
# Soal 2 — Memecah Teks Menjadi Karakter Individual dengan `regionprops`
|
||||
|
||||
Program: `soal2.py` · Input: `Problem 2 Image.png` · Output: folder `karakter/` (18 file PNG)
|
||||
|
||||
Cara menjalankan: `python soal2.py` (library: `numpy`, `scikit-image`)
|
||||
|
||||
Referensi teori: Gonzalez & Woods, *Digital Image Processing*, edisi ke-4 (Pearson, 2018).
|
||||
|
||||
## 1. Tujuan
|
||||
|
||||
Citra input berisi tiga kata putih di atas latar hitam: **ZEUS** (horizontal), **TWINPEAK** (miring naik), dan **ACOSTA** (miring tajam). Program harus memisahkan setiap huruf dan menyimpannya sebagai gambar tersendiri. Huruf pada hasil harus tegak, yaitu kotak potongannya mengikuti kemiringan tulisan, lalu huruf diputar balik hingga lurus.
|
||||
|
||||

|
||||
|
||||
## 2. Alur Program
|
||||
|
||||
1. Baca citra, ubah ke grayscale.
|
||||
2. Segmentasi dengan threshold Otsu menjadi citra biner.
|
||||
3. Buang noise kecil.
|
||||
4. Labeling komponen terhubung: satu komponen = satu karakter.
|
||||
5. `regionprops`: ambil properti setiap karakter.
|
||||
6. Dilasi untuk mengelompokkan karakter menjadi kata.
|
||||
7. Hitung sudut kemiringan setiap huruf dari lengkung yang melalui centroid huruf-huruf dalam katanya. Huruf satu goresan (I) memakai sumbu utamanya sendiri.
|
||||
8. Putar setiap karakter sebesar sudutnya, potong ulang, simpan.
|
||||
|
||||
## 3. Penjelasan Setiap Langkah
|
||||
|
||||
### 3.1 Representasi citra dan konversi grayscale (Bab 2 dan Bab 7)
|
||||
|
||||
Citra digital adalah fungsi dua dimensi f(x, y) yang disimpan sebagai matriks piksel. File input berformat RGBA berukuran 762 × 860 × 4. Kanal alpha dibuang (`rgba2rgb`), lalu citra RGB diubah menjadi satu kanal intensitas (`rgb2gray`) dengan kombinasi linear:
|
||||
|
||||
```
|
||||
Y = 0.2125 R + 0.7154 G + 0.0721 B
|
||||
```
|
||||
|
||||
Hasilnya matriks bernilai [0, 1]. Tulisan bernilai mendekati 1 (putih), latar mendekati 0 (hitam).
|
||||
|
||||
### 3.2 Segmentasi dengan threshold Otsu
|
||||
|
||||
Thresholding mengubah citra grayscale menjadi citra biner:
|
||||
|
||||
```
|
||||
g(x, y) = 1 jika f(x, y) > T
|
||||
g(x, y) = 0 jika f(x, y) ≤ T
|
||||
```
|
||||
|
||||
Nilai T dipilih otomatis dengan metode Otsu. Otsu mencoba setiap nilai k dan memilih k yang memaksimalkan varians antar-kelas:
|
||||
|
||||
```
|
||||
σ²_B(k) = [m_G · P₁(k) − m(k)]² / [P₁(k) · (1 − P₁(k))]
|
||||
```
|
||||
|
||||
dengan P₁(k) = proporsi piksel ≤ k, m(k) = rata-rata kumulatif intensitas sampai k, dan m_G = rata-rata intensitas global. Histogram citra ini bimodal (tulisan dan latar), jadi Otsu memisahkan keduanya dengan baik. Pada citra ini T = 0,342 (skala 0–1).
|
||||
|
||||
Catatan: metode Otsu dibahas di Bab 10 (Segmentasi), setelah materi UTS. Konsep dasarnya, yaitu fungsi thresholding sebagai transformasi intensitas, sudah dibahas di Bab 3.
|
||||
|
||||
### 3.3 Pembuangan noise
|
||||
|
||||
`remove_small_objects` menghapus komponen dengan luas ≤ 20 piksel. Komponen sekecil itu bukan huruf, melainkan noise sisa kompresi citra. Pada citra ini tidak ada komponen yang terbuang, tetapi langkah ini menjaga agar noise tidak tersimpan sebagai "karakter".
|
||||
|
||||
### 3.4 Labeling komponen terhubung (Bab 2 dan Bab 9)
|
||||
|
||||
**Ketetanggaan (Bab 2).** Piksel p di (x, y) memiliki:
|
||||
|
||||
- 4-tetangga N₄(p): (x±1, y) dan (x, y±1).
|
||||
- 8-tetangga N₈(p): N₄(p) ditambah 4 tetangga diagonal.
|
||||
|
||||
Dua piksel foreground termasuk satu komponen jika ada jalur piksel foreground di antara keduanya. Program memakai **8-connectivity** (`connectivity=2`), sehingga goresan diagonal huruf seperti A, K, dan W tetap tersambung sebagai satu komponen.
|
||||
|
||||
**Ekstraksi komponen terhubung (Bab 9).** Secara morfologi, komponen yang memuat piksel awal X₀ diperoleh dengan iterasi:
|
||||
|
||||
```
|
||||
X_k = (X_{k−1} ⊕ B) ∩ I, k = 1, 2, 3, ...
|
||||
```
|
||||
|
||||
dengan I = citra biner dan B = elemen penstruktur 3 × 3 berisi 1 (untuk 8-connectivity). Iterasi berhenti saat X_k = X_{k−1}. `measure.label` menjalankan proses yang setara untuk setiap komponen dan memberi label 1, 2, 3, dan seterusnya. Hasilnya **18 label**, sesuai jumlah huruf: ZEUS (4) + TWINPEAK (8) + ACOSTA (6).
|
||||
|
||||
Pemisahan ini berhasil karena setiap huruf tidak bersentuhan dengan huruf lain. Huruf "I" juga tidak bermasalah karena kapital dan tidak memiliki titik.
|
||||
|
||||
### 3.5 Cara `regionprops` digunakan
|
||||
|
||||
`measure.regionprops(labels, intensity_image=gray)` menghasilkan satu objek per label. Properti yang dipakai program:
|
||||
|
||||
| Properti | Arti | Kegunaan di program |
|
||||
|---|---|---|
|
||||
| `coords` | Daftar koordinat (baris, kolom) piksel region | Mencari label kata dari region (langkah 3.6) |
|
||||
| `centroid` | Titik pusat region: rata-rata koordinat piksel | Menghitung lengkung kata dan sudut setiap huruf (langkah 3.7) |
|
||||
| `eccentricity` | Seberapa memanjang region: 0 = lingkaran, mendekati 1 = garis | Mengenali huruf satu goresan seperti I (langkah 3.7) |
|
||||
| `orientation` | Sudut sumbu utama region terhadap sumbu vertikal | Sudut putar huruf satu goresan (langkah 3.7) |
|
||||
| `image_intensity` | Potongan citra grayscale seukuran bounding box; piksel di luar region bernilai 0 | Gambar karakter yang diputar dan disimpan (langkah 3.8) |
|
||||
| `area` | Jumlah piksel region | Informasi pada output |
|
||||
|
||||
`image_intensity` dipakai alih-alih memotong citra langsung. Pada huruf yang miring, bounding box satu huruf bisa memuat sebagian huruf tetangga. Dengan `image_intensity`, hanya piksel milik huruf itu sendiri yang ikut tersimpan.
|
||||
|
||||
Centroid dihitung dari momen orde nol dan orde satu region R:
|
||||
|
||||
```
|
||||
area = M₀₀ = Σ 1
|
||||
r̄ = M₁₀ / M₀₀ = (1/area) Σ r
|
||||
c̄ = M₀₁ / M₀₀ = (1/area) Σ c
|
||||
```
|
||||
|
||||
Catatan: deskriptor region seperti area, centroid, momen, dan komponen utama dibahas lebih lanjut di Bab 11. Program memakainya lewat `regionprops`, sesuai petunjuk soal.
|
||||
|
||||
### 3.6 Pengelompokan karakter menjadi kata dengan dilasi (Bab 9)
|
||||
|
||||
Huruf harus diputar sesuai kemiringan **katanya**, jadi program perlu tahu setiap huruf milik kata yang mana. Dilasi A oleh elemen penstruktur B didefinisikan:
|
||||
|
||||
```
|
||||
A ⊕ B = { z | (B̂)_z ∩ A ≠ ∅ }
|
||||
```
|
||||
|
||||
Dilasi menebalkan setiap huruf. Dengan B berupa disk radius r, dua huruf menyatu jika celah di antaranya kurang dari sekitar 2r. Hasil pengukuran jarak terdekat antar-piksel huruf pada citra ini:
|
||||
|
||||
- Celah antar-huruf dalam satu kata: paling besar **87 piksel** (pada ZEUS).
|
||||
- Celah antar-kata: paling kecil **104 piksel** (antara E pada TWINPEAK dan A pada ACOSTA).
|
||||
|
||||
Radius r = 48 (menutup celah sekitar 96 piksel) berada di antara kedua nilai itu. Dilasi menyatukan huruf dalam satu kata tetapi tidak menyatukan kata yang berbeda. Labeling ulang pada citra hasil dilasi menghasilkan **3 label**, satu per kata. Label kata sebuah huruf dibaca dari citra label kata pada salah satu koordinat (`coords`) huruf tersebut.
|
||||
|
||||
### 3.7 Sudut kemiringan setiap huruf
|
||||
|
||||
Huruf-huruf dalam satu kata tersusun di sepanjang garis dasar (baseline). Baseline tidak selalu lurus. Pada TWINPEAK, kemiringan antar-centroid naik dari sekitar 2° (T–W) menjadi sekitar 27° (A–K), jadi baseline-nya melengkung. Satu sudut rata-rata per kata akan membuat huruf di ujung kata diputar terlalu jauh atau kurang jauh. Karena itu sudut dihitung **per huruf**, dari garis singgung lengkung baseline di posisi huruf tersebut.
|
||||
|
||||
**Langkah 1: arah umum kata (PCA).** Arah umum sebaran centroid dicari dengan analisis komponen utama:
|
||||
|
||||
1. Kurangi setiap centroid dengan rata-ratanya: **p**ᵢ − **p̄**.
|
||||
2. Bentuk matriks kovarians C = (1/n) Σ (**p**ᵢ − **p̄**)(**p**ᵢ − **p̄**)ᵀ.
|
||||
3. Eigenvektor C dengan eigenvalue terbesar = arah umum kata **u**. Eigenvektor kedua **v** tegak lurus terhadap **u**.
|
||||
|
||||
Program menghitungnya dengan SVD, yang memberi vektor yang sama. Arah **u** dipilih agar komponen kolomnya positif (teks dibaca dari kiri ke kanan).
|
||||
|
||||
**Langkah 2: lengkung baseline.** Setiap centroid diproyeksikan ke sumbu **u** dan **v**, lalu dicocokkan lengkung kuadratik dengan metode kuadrat terkecil:
|
||||
|
||||
```
|
||||
v = a·u² + b·u + c
|
||||
```
|
||||
|
||||
Kemiringan lengkung di posisi huruf ke-i adalah turunannya, dv/du = 2a·uᵢ + b. Arah garis singgung dalam koordinat citra:
|
||||
|
||||
```
|
||||
(Δr, Δc) = u + (2a·uᵢ + b) · v
|
||||
```
|
||||
|
||||
**Langkah 3: sudut.** Sudut terhadap sumbu horizontal:
|
||||
|
||||
```
|
||||
θ = arctan2(−Δr, Δc)
|
||||
```
|
||||
|
||||
Tanda minus diperlukan karena indeks baris citra bertambah ke bawah. Dengan begitu, θ positif berarti tulisan naik (berlawanan arah jarum jam). Untuk kata yang lurus seperti ZEUS, a ≈ 0 dan semua huruf mendapat sudut ≈ 0°.
|
||||
|
||||
**Pengecualian: huruf satu goresan.** Huruf I pada TWINPEAK di citra asli condong sekitar 10,7°. Nilai ini hampir sama dengan kemiringan italic font (sekitar 9,5°, diukur dari batang huruf E pada ZEUS). Artinya, I di citra asli praktis tidak diputar mengikuti lengkung, padahal lengkung memberi sudut 6,2°. Jika diputar 6,2°, I justru menjadi semakin condong (sekitar 17°).
|
||||
|
||||
Untuk huruf yang berupa satu goresan, arah huruf bisa dibaca langsung dari bentuknya. `regionprops` menghitung elips yang memiliki momen pusat orde dua sama dengan region:
|
||||
|
||||
```
|
||||
μ_pq = Σ (r − r̄)^p (c − c̄)^q
|
||||
```
|
||||
|
||||
Dari matriks kovarians [μ₂₀ μ₁₁; μ₁₁ μ₀₂] / M₀₀ diperoleh eigenvalue λ₁ ≥ λ₂. Panjang sumbu elips sebanding dengan √λ, sehingga:
|
||||
|
||||
- `eccentricity` = √(1 − λ₂/λ₁). Nilainya 0 untuk lingkaran dan mendekati 1 untuk garis.
|
||||
- `orientation` = sudut eigenvektor λ₁ (sumbu utama) terhadap sumbu vertikal.
|
||||
|
||||
Eccentricity huruf I = 0,97, sedangkan huruf lain paling tinggi 0,88. Program memakai batas 0,95: huruf dengan eccentricity di atas batas itu diputar sebesar `orientation`-nya sendiri sehingga goresannya tepat vertikal. Huruf lain memakai sudut lengkung.
|
||||
|
||||
### 3.8 Rotasi dan pemotongan ulang (Bab 2)
|
||||
|
||||
Rotasi adalah transformasi geometri affine. Dalam koordinat homogen:
|
||||
|
||||
```
|
||||
[x'] [cos θ −sin θ 0] [x]
|
||||
[y'] = [sin θ cos θ 0] [y]
|
||||
[1 ] [ 0 0 1] [1]
|
||||
```
|
||||
|
||||
Setiap huruf diputar sebesar **−θ** (searah jarum jam sebesar θ) agar baseline-nya kembali horizontal. `transform.rotate` memakai *inverse mapping*: setiap piksel output (x', y') dipetakan balik ke posisi asal (x, y) dengan matriks invers. Posisi asal umumnya bukan bilangan bulat, sehingga nilainya dihitung dengan **interpolasi bilinear** dari 4 piksel terdekat:
|
||||
|
||||
```
|
||||
v(x, y) = ax + by + cxy + d
|
||||
```
|
||||
|
||||
Opsi `resize=True` memperbesar kanvas agar ujung huruf tidak terpotong saat diputar. Setelah rotasi, kotak dipotong ulang ke baris dan kolom yang masih memuat piksel huruf (intensitas > T Otsu). Hasilnya kotak rapat di sekitar huruf yang sudah tegak, lalu disimpan sebagai PNG 8-bit.
|
||||
|
||||
## 4. Hasil
|
||||
|
||||
Penamaan file mengikuti urutan label dari `regionprops`, yaitu urutan scan dari atas ke bawah. Karena itu urutan file tidak sama dengan urutan baca untuk TWINPEAK dan ACOSTA.
|
||||
|
||||
| File | Hasil | Huruf | Kata | Sudut | Sumber sudut |
|
||||
|---|---|---|---|---|---|
|
||||
| karakter_01.png |  | Z | ZEUS | −0,3° | lengkung kata |
|
||||
| karakter_02.png |  | E | ZEUS | −0,1° | lengkung kata |
|
||||
| karakter_03.png |  | U | ZEUS | 0,1° | lengkung kata |
|
||||
| karakter_04.png |  | S | ZEUS | 0,3° | lengkung kata |
|
||||
| karakter_05.png |  | K | TWINPEAK | 26,9° | lengkung kata |
|
||||
| karakter_06.png |  | A | TWINPEAK | 22,9° | lengkung kata |
|
||||
| karakter_07.png |  | E | TWINPEAK | 18,6° | lengkung kata |
|
||||
| karakter_08.png |  | P | TWINPEAK | 14,4° | lengkung kata |
|
||||
| karakter_09.png |  | N | TWINPEAK | 10,1° | lengkung kata |
|
||||
| karakter_10.png |  | I | TWINPEAK | −10,7° | sumbu utama huruf |
|
||||
| karakter_11.png |  | W | TWINPEAK | 2,1° | lengkung kata |
|
||||
| karakter_12.png |  | T | TWINPEAK | −2,1° | lengkung kata |
|
||||
| karakter_13.png |  | A | ACOSTA | 55,5° | lengkung kata |
|
||||
| karakter_14.png |  | T | ACOSTA | 56,1° | lengkung kata |
|
||||
| karakter_15.png |  | S | ACOSTA | 56,8° | lengkung kata |
|
||||
| karakter_16.png |  | O | ACOSTA | 57,4° | lengkung kata |
|
||||
| karakter_17.png |  | C | ACOSTA | 58,0° | lengkung kata |
|
||||
| karakter_18.png |  | A | ACOSTA | 58,6° | lengkung kata |
|
||||
|
||||
## 5. Batasan
|
||||
|
||||
- **Font italic.** Setelah diluruskan, huruf masih tampak sedikit condong (sekitar 8–10°) karena fontnya italic. Ini bentuk huruf, bukan rotasi: ZEUS (sudut 0°) juga italic pada citra asli. Pengecualiannya huruf I: karena diluruskan dengan sumbu utamanya sendiri, I menjadi tepat vertikal.
|
||||
- **Parameter disesuaikan dengan citra ini.** Radius dilasi 48 dan batas eccentricity 0,95 dipilih dari nilai yang diukur pada citra ini. Citra lain bisa membutuhkan nilai lain.
|
||||
- **Kata minimal tiga huruf.** Lengkung kuadratik membutuhkan minimal tiga centroid.
|
||||
- **Huruf tidak boleh bersentuhan.** Jika dua huruf bersentuhan, keduanya menjadi satu komponen dan tersimpan sebagai satu gambar.
|
||||