1) Import Modul
import os, sys
import cv2
import numpy as np
import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt- os, sys: manipulasi path & sys.path untuk impor relatif.
- cv2 (OpenCV): operasi citra—konversi warna, equalization, dan transformasi intensitas.
- numpy: representasi citra sebagai
ndarray. - tkinter: GUI native;
filedialoguntuk memilih berkas,messageboxuntuk dialog. - Pillow (
Image,ImageTk): konversi array ke objek gambar yang bisa ditampilkan di Tkinter. - matplotlib +
FigureCanvasTkAgg: menggambar histogram langsung di jendela Tkinter.
2) Setup Path & Impor Utilitas
_FILE_DIR = os.path.dirname(__file__)
_ROOT_DIR = os.path.abspath(os.path.join(_FILE_DIR, ".."))
if _ROOT_DIR not in sys.path:
sys.path.insert(0, _ROOT_DIR)
from common.io import read_imageMenjamin modul common.io dapat diimpor saat skrip dijalankan langsung. Fungsi read_image diasumsikan membaca gambar dan mengembalikannya sebagai array RGB numpy.
3) Fungsi adjust_bc(img, alpha=1.2, beta=20)
def adjust_bc(img, alpha=1.2, beta=20):
return cv2.convertScaleAbs(img, alpha=alpha, beta=beta)- Tujuan: Mengatur contrast (α) dan brightness (β) dengan transformasi linier
img' = α·img + βlalu diklip 0..255. - Parameter:
img(RGB uint8),alpha(float),beta(int). - Return: citra hasil dalam tipe
uint8.
4) Kelas BasicOpsGUI
4.1 __init__(self, root)
self.root = root
self.root.title("Basic Ops - Gray, HistEq, Brightness/Contrast")
# State
self.img_rgb: np.ndarray | None = None
self.photo: ImageTk.PhotoImage | None = None- State utama:
img_rgbmenyimpan citra saat ini (RGB);photoadalah objekImageTkuntukLabelTkinter.
# Bar atas: tombol buka, label status
# Frame kontrol: tombol operasi (asli, gray, equalization)
# Frame B/C: slider alpha (kontras) & beta (kecerahan) + tombol terapkan
# Area tampilan: label judul + label gambar
# Area histogram: Figure Matplotlib tertanam via FigureCanvasTkAgg4.2 open_image(self)
path = filedialog.askopenfilename(...)
self.img_rgb = read_image(path)
self.status.config(text=path)
self.show_original()- Membuka dialog pilih berkas, membaca citra (RGB), memperbarui status, dan merender citra asli.
- Penanganan error: kegagalan baca ditampilkan via
messagebox.showerror.
4.3 _ensure_image(self) → bool
if self.img_rgb is None:
messagebox.showinfo("Info", "Silakan buka gambar terlebih dahulu.")
return False
return TrueValidasi ketersediaan citra sebelum menjalankan operasi.
4.4 Method Operasi
show_original(self)
self._render(self.img_rgb, title="Citra Asli (RGB)")op_gray(self)
gray = cv2.cvtColor(self.img_rgb, cv2.COLOR_RGB2GRAY)
self._render(gray, title="Grayscale")op_hist_eq(self)
gray = cv2.cvtColor(self.img_rgb, cv2.COLOR_RGB2GRAY)
hist_eq = cv2.equalizeHist(gray)
self._render(hist_eq, title="Histogram Equalization (Gray)")op_bc_apply(self)
alpha = float(self.alpha_var.get())
beta = int(self.beta_var.get())
out = adjust_bc(self.img_rgb, alpha=alpha, beta=beta)
self._render(out, title=f"Brightness/Contrast (α={alpha:.2f}, β={beta})")Catatan: HistEq dilakukan di domain grayscale satu kanal, sedangkan B/C diterapkan pada citra RGB 3‑kanal.
4.5 Rendering & Histogram
_render(self, img, title="")
if img.ndim == 2:
pil = Image.fromarray(img.astype(np.uint8), mode="L")
else:
pil = Image.fromarray(img.astype(np.uint8))
pil = self._fit_image(pil, 960, 600)
self.photo = ImageTk.PhotoImage(pil)
self.img_label.configure(image=self.photo)
self.title_label.configure(text=title)
self._update_hist(img)- Konversi
ndarraykePIL.Image(mode L untuk grayscale). - Penskalaan proporsional agar muat di area tampilan (
_fit_image). - Perbarui label gambar & judul, lalu gambar histogram.
_update_hist(self, img)
self.ax.clear()
if img.ndim == 2:
data = img.ravel().astype(np.uint8)
self.ax.hist(data, bins=256, range=(0,255), color='black', alpha=0.8)
self.ax.set_title('Histogram (Grayscale)')
else:
for i, c in enumerate(['r','g','b']):
data = img[:,:,i].ravel().astype(np.uint8)
self.ax.hist(data, bins=256, range=(0,255), color=c, alpha=0.5, label=['R','G','B'][i])
self.ax.legend(loc='upper right', fontsize=8)
self.ax.set_xlim(0,255); self.ax.set_xlabel('Intensitas'); self.ax.set_ylabel('Frekuensi')
self.fig.tight_layout(); self.canvas.draw_idle()- Histogram 256 bin untuk intensitas 0–255.
- Grayscale: satu histogram; RGB: tiga histogram bertumpuk (R,G,B).
_fit_image(im, max_w, max_h) (static)
w, h = im.size
scale = min(max_w / w, max_h / h, 1.0)
new_size = (int(w * scale), int(h * scale))
return im.resize(new_size, Image.Resampling.LANCZOS)Menghitung skala agar gambar tidak melebihi area tampilan; menggunakan filter LANCZOS untuk kualitas tinggi.
5) Blok Main
if __name__ == "__main__":
root = tk.Tk()
app = BasicOpsGUI(root)
root.mainloop()Menjalankan aplikasi hanya saat file dieksekusi langsung; membuat root Tk, menginstansiasi GUI, lalu memulai mainloop().
6) Dependensi & Cara Menjalankan
| Paket | Peran |
|---|---|
| opencv-python | Operasi citra (konversi, equalization, skala intensitas) |
| numpy | Representasi data piksel |
| Pillow | Konversi ndarray → gambar Tkinter |
| matplotlib | Plot histogram |
Instal cepat (venv disarankan)
pip install opencv-python numpy pillow matplotlib- Simpan file sebagai
basic_ops.pydalam struktur yang berisi modulcommon/io.py. - Jalankan:
python basic_ops.py - Klik Buka Gambar... → pilih gambar → coba tombol operasi dan slider.
7) FAQ
Mengapa histogram untuk RGB dibuat per‑kanal?
Agar distribusi intensitas masing‑masing kanal terlihat jelas; penyesuaian B/C dapat memengaruhi kanal secara berbeda.
HistEq menghasilkan artefak, normal?
Pada citra berisik atau rentang dinamis sempit, equalization bisa menonjolkan noise; pertimbangkan CLAHE bila diperlukan.
Apa bedanya α (kontras) dan β (kecerahan)?
α mengatur kemiringan (sebaran kontras), β menggeser intensitas rata‑rata ke atas/bawah.