Files
UTS-Pengolahan-Citra-Digita…/Problem 2/soal2.py
T
MelvynFaith 003b2c544c 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.
2026-10-22 23:43:56 +07:00

90 lines
4.1 KiB
Python

"""
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}")