Deddy's picture
Upload 8 files
6ac308a verified
"""
Croper Utility
--------------
Kelas utilitas untuk melakukan crop area tertentu (berbasis mask) pada gambar, lalu menyiapkan square crop image dan square mask.
Juga menyediakan fungsi untuk mengembalikan (restore) hasil modifikasi ke gambar asli.
Created by _drat | 2025
"""
import PIL
import numpy as np
from PIL import Image
class Croper:
def __init__(
self,
input_image: PIL.Image, # Gambar input (format PIL Image)
target_mask: np.ndarray, # Mask biner area target (array numpy)
mask_size: int = 256, # Ukuran akhir mask & image yang dihasilkan (default 256x256)
mask_expansion: int = 20, # Ekspansi area mask (agar crop lebih lebar)
):
self.input_image = input_image
self.target_mask = target_mask
self.mask_size = mask_size
self.mask_expansion = mask_expansion
# Fungsi utama untuk crop area sesuai mask dan membentuk square crop & mask
def corp_mask_image(self):
target_mask = self.target_mask
input_image = self.input_image
mask_expansion = self.mask_expansion
original_width, original_height = input_image.size
# Cari koordinat bounding box area mask (area bertanda True/1)
mask_indices = np.where(target_mask)
start_y = np.min(mask_indices[0])
end_y = np.max(mask_indices[0])
start_x = np.min(mask_indices[1])
end_x = np.max(mask_indices[1])
mask_height = end_y - start_y
mask_width = end_x - start_x
# Ambil sisi terpanjang untuk square crop
max_side_length = max(mask_height, mask_width)
# Ekspansi area mask agar crop tidak terlalu sempit
height_diff = (max_side_length - mask_height) // 2
width_diff = (max_side_length - mask_width) // 2
start_y = start_y - mask_expansion - height_diff
if start_y < 0:
start_y = 0
end_y = end_y + mask_expansion + height_diff
if end_y > original_height:
end_y = original_height
start_x = start_x - mask_expansion - width_diff
if start_x < 0:
start_x = 0
end_x = end_x + mask_expansion + width_diff
if end_x > original_width:
end_x = original_width
expanded_height = end_y - start_y
expanded_width = end_x - start_x
expanded_max_side_length = max(expanded_height, expanded_width)
# Crop mask dari area yang sudah diekspansi
crop_mask = target_mask[start_y:end_y, start_x:end_x]
# Hitung posisi crop mask pada square mask
crop_mask_start_y = (expanded_max_side_length - expanded_height) // 2
crop_mask_end_y = crop_mask_start_y + expanded_height
crop_mask_start_x = (expanded_max_side_length - expanded_width) // 2
crop_mask_end_x = crop_mask_start_x + expanded_width
# Buat square mask dengan area crop mask di tengah
square_mask = np.zeros((expanded_max_side_length, expanded_max_side_length), dtype=target_mask.dtype)
square_mask[crop_mask_start_y:crop_mask_end_y, crop_mask_start_x:crop_mask_end_x] = crop_mask
square_mask_image = Image.fromarray((square_mask * 255).astype(np.uint8))
# Crop image asli sesuai area ekspansi
crop_image = input_image.crop((start_x, start_y, end_x, end_y))
# Buat square image, lalu paste crop image di tengah
square_image = Image.new("RGB", (expanded_max_side_length, expanded_max_side_length))
square_image.paste(crop_image, (crop_mask_start_x, crop_mask_start_y))
# Simpan koordinat/ukuran untuk keperluan restore nanti
self.origin_start_x = start_x
self.origin_start_y = start_y
self.origin_end_x = end_x
self.origin_end_y = end_y
self.square_start_x = crop_mask_start_x
self.square_start_y = crop_mask_start_y
self.square_end_x = crop_mask_end_x
self.square_end_y = crop_mask_end_y
self.square_length = expanded_max_side_length
self.square_mask_image = square_mask_image
self.square_image = square_image
self.corp_mask = crop_mask
# Resize hasil square mask & image ke mask_size (misal: 256x256) untuk kebutuhan pipeline AI
mask_size = self.mask_size
self.resized_square_mask_image = square_mask_image.resize((mask_size, mask_size))
self.resized_square_image = square_image.resize((mask_size, mask_size))
return self.resized_square_mask_image # Kembalikan mask hasil resize
# Fungsi untuk restore hasil generate ke gambar asli dengan bantuan mask transparan
def restore_result(self, generated_image):
square_length = self.square_length
# Resize hasil generate ke ukuran square original
generated_image = generated_image.resize((square_length, square_length))
square_mask_image = self.square_mask_image
# Crop area yang sudah di-generate sesuai posisi awal
cropped_generated_image = generated_image.crop((self.square_start_x, self.square_start_y, self.square_end_x, self.square_end_y))
cropped_square_mask_image = square_mask_image.crop((self.square_start_x, self.square_start_y, self.square_end_x, self.square_end_y))
# Tempel hasil generate ke gambar asli dengan mask transparan
restored_image = self.input_image.copy()
restored_image.paste(cropped_generated_image, (self.origin_start_x, self.origin_start_y), cropped_square_mask_image)
return restored_image
# Versi restore tanpa mask (area hasil tempelkan langsung tanpa transparansi)
def restore_result_v2(self, generated_image):
square_length = self.square_length
generated_image = generated_image.resize((square_length, square_length))
cropped_generated_image = generated_image.crop((self.square_start_x, self.square_start_y, self.square_end_x, self.square_end_y))
restored_image = self.input_image.copy()
restored_image.paste(cropped_generated_image, (self.origin_start_x, self.origin_start_y))
return restored_image