"""Membuat gambar uji koin_100.png dari koin.png. Soal mengandaikan gambar berisi 100 koin berbagai ukuran yang tidak saling bersentuhan di atas latar belakang seragam. koin.png hanya berisi satu koin, jadi koin tersebut diperkecil ke beberapa ukuran lalu ditempel 100 kali pada posisi acak di atas latar seragam. Setiap koin diputar acak dan seluruh gambar diberi sedikit noise agar koin sejenis tidak identik piksel per piksel, seperti pada foto asli. Jumlah koin per ukuran diketahui, sehingga hasil pengolahan_citra.py dapat diperiksa. Cara menjalankan: python buat_gambar_koin.py """ import os import cv2 import numpy as np BASE_DIR = os.path.dirname(os.path.abspath(__file__)) SOURCE_PATH = os.path.join(BASE_DIR, "koin.png") OUTPUT_PATH = os.path.join(BASE_DIR, "koin_100.png") CANVAS_SIZE = 3000 BACKGROUND_BGR = (40, 40, 40) # latar abu-abu gelap yang seragam MIN_GAP = 10 # jarak minimum antar-tepi koin (piksel), agar tidak bersentuhan # (diameter koin dalam piksel, jumlah koin); total 100 koin COIN_SIZES = [(140, 30), (170, 27), (205, 25), (245, 18)] NOISE_STD = 3.0 # simpangan baku noise Gaussian (tingkat keabuan) SEED = 0 def load_coin() -> np.ndarray: coin = cv2.imread(SOURCE_PATH, cv2.IMREAD_UNCHANGED) if coin is None or coin.shape[2] != 4: raise FileNotFoundError(f"{SOURCE_PATH} harus berupa PNG dengan kanal alpha.") # Potong ke area koin saja (alpha > 0) rows, cols = np.nonzero(coin[:, :, 3]) return coin[rows.min():rows.max() + 1, cols.min():cols.max() + 1] def main() -> None: rng = np.random.default_rng(SEED) coin = load_coin() canvas = np.full((CANVAS_SIZE, CANVAS_SIZE, 3), BACKGROUND_BGR, np.float64) diameters = [d for d, n in COIN_SIZES for _ in range(n)] rng.shuffle(diameters) # Tempatkan koin besar lebih dulu agar mudah mendapat tempat placed = [] # (baris pusat, kolom pusat, jari-jari) for d in sorted(diameters, reverse=True): r = d / 2 for _ in range(100000): y, x = rng.uniform(r + MIN_GAP, CANVAS_SIZE - r - MIN_GAP, size=2) if all(np.hypot(y - py, x - px) >= r + pr + MIN_GAP for py, px, pr in placed): placed.append((y, x, r)) break else: raise RuntimeError("Tidak cukup ruang untuk menempatkan semua koin.") for y, x, r in placed: d = round(2 * r) small = cv2.resize(coin, (d, d), interpolation=cv2.INTER_AREA) # Putar koin dengan sudut acak; area di luar koin tetap transparan rotation = cv2.getRotationMatrix2D(((d - 1) / 2, (d - 1) / 2), rng.uniform(0, 360), 1.0) small = cv2.warpAffine(small, rotation, (d, d), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(0, 0, 0, 0)) small = small.astype(np.float64) top, left = round(y - r), round(x - r) region = canvas[top:top + d, left:left + d] # Alpha blending: tepi koin menyatu halus dengan latar alpha = small[:, :, 3:4] / 255 region[:] = alpha * small[:, :, :3] + (1 - alpha) * region canvas += rng.normal(0, NOISE_STD, canvas.shape) cv2.imwrite(OUTPUT_PATH, np.clip(np.round(canvas), 0, 255).astype(np.uint8)) print(f"Gambar uji disimpan di: {OUTPUT_PATH}") for d, n in COIN_SIZES: print(f"Diameter {d} piksel: {n} koin") if __name__ == "__main__": main()