commit 58d45e99be514a6181fb65b9726aa4f5f51d17b4 Author: MelvynFaith Date: Sun Sep 20 21:35:29 2026 +0700 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..224e900 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Build +build/ +cmake-build-*/ +CMakeCache.txt +CMakeFiles/ +*.exe +*.dll +*.o +*.obj + +# Hasil program +output_results/ + +# LaTeX (laporan) +report/*.aux +report/*.log +report/*.out +report/*.toc +report/*.lof +report/*.lot +report/*.fls +report/*.fdb_latexmk +report/*.synctex.gz +report/*.pdf + +# Editor / OS +.vscode/ +.idea/ +.vs/ +.DS_Store +Thumbs.db diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..eb9db29 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.10) +project(ColorProcessor CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(OpenCV REQUIRED) + +add_executable(color_processor color_processor.cpp) +target_link_libraries(color_processor ${OpenCV_LIBS}) + +# Haar cascade dan DLL OpenCV disalin ke folder exe +set(CASCADE_XML "${OpenCV_DIR}/../../../etc/haarcascades/haarcascade_frontalface_default.xml") +add_custom_command(TARGET color_processor POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CASCADE_XML}" "$" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${OpenCV_DIR}/../bin" "$" +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..4de894e --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# Color Processor: Pemrosesan Citra Warna dan Pengubahan Warna Wajah + +Tugas mata kuliah **Pengolahan Citra Digital** (Gonzalez & Woods, Bab 6: *Color Image Processing*), ditulis dengan C++17 dan OpenCV. + +Program menerapkan enam operasi pemrosesan citra warna, lalu mendeteksi wajah dan mengubah warna kulitnya. Masukan dapat berupa berkas gambar atau kamera (*webcam*). + +- **Nama:** Efraim Melvyn Rafelino Lahai +- **NRP:** 5803024009 +- **Program Studi:** Informatika +- **Dosen Pengajar:** Ir. Drs. Peter Rhatodirdjo Angka, M.Kom., IPM., ASEAN Eng. + +## Fitur + +| No | Operasi | Fungsi | +|----|---------|--------| +| 1 | Komplemen warna | `computeComplement` | +| 2 | Kurva S (penyesuaian nada) | `adjustToneSCurve` | +| 3 | Ekualisasi histogram kanal *Value* (HSV) | `equalizeIntensityHSI` | +| 4 | Pengirisan warna berbentuk bola 3D | `sliceColorSphere` | +| 5 | Penajaman Laplacian | `sharpenLaplacian` | +| 6 | Pseudowarna (JET) | `generatePseudocolor` | +| 7 | Deteksi wajah (Haar cascade + CLAHE) | `detectFaces` | +| 8 | Ubah warna kulit wajah (rotasi *Hue* di HSV, masker kulit YCrCb) | `recolorFace` | + +Pengubahan warna wajah memutar *Hue* sebesar 60 dan mengalikan *Saturation* dengan 1,3, sehingga kulit menjadi hijau. Nilai ini dapat diubah lewat `kHueShift` dan `kSatScale` di `color_processor.cpp`. + +## Kebutuhan + +- Kompilator C++17 (diuji dengan MinGW-w64 GCC 16.1) +- CMake 3.10 atau lebih baru +- OpenCV 4.x dengan modul `core`, `imgproc`, `imgcodecs`, `videoio`, `highgui`, `objdetect` (diuji dengan 4.10.0) +- Berkas `haarcascade_frontalface_default.xml` (ada di folder `etc/haarcascades` atau `data/haarcascades` pada instalasi OpenCV) + +> Catatan: pada MinGW, OpenCV harus dikompilasi dengan kompilator yang sama. Pada pengujian, MSMF dimatikan (`-DWITH_MSMF=OFF`) karena gagal dikompilasi dengan MinGW, sehingga kamera memakai DirectShow. + +## Kompilasi + +``` +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DOpenCV_DIR= +cmake --build build +``` + +Contoh `OpenCV_DIR` pada Windows dengan MinGW: `D:/opencv/install/x64/mingw/lib`. + +Setelah *build*, CMake menyalin `haarcascade_frontalface_default.xml` dan DLL OpenCV ke folder `build/`. Jika berkas XML tidak ditemukan, program menampilkan pesan kesalahan; gunakan opsi `--cascade`. + +## Penggunaan + +Jalankan dari dalam folder `build`, karena program mencari berkas XML di folder kerja. + +``` +# Mode berkas: jalankan semua operasi pada satu gambar +.\color_processor.exe path\ke\foto.jpg + +# Mode kamera +.\color_processor.exe --cam + +# Tentukan lokasi berkas cascade sendiri +.\color_processor.exe foto.jpg --cascade path\ke\haarcascade_frontalface_default.xml +``` + +Tanpa argumen, program membuka kamera. + +**Mode berkas** menyimpan hasil ke `output_results/` dengan format PNG: + +``` +_complement.png +_tone_scurve.png +_hsi_equalized.png +_color_sliced.png +_sharpened.png +_pseudocolor.png +_face_recolor.png +``` + +**Mode kamera:** tekan `s` untuk menyimpan bingkai (`output_results/webcam_N.png`), `q` atau `Esc` untuk keluar. + +## Struktur Proyek + +``` +. +├── color_processor.cpp # kode program +├── CMakeLists.txt +├── report/ # laporan LaTeX dan gambar hasil +└── README.md +``` + +## Keterbatasan + +- Haar cascade bergantung pada pencahayaan. Pada ruangan redup, wajah dapat gagal terdeteksi. +- Wajah yang menunduk, miring, atau tertutup (misalnya topi) sulit dideteksi. +- Menurunkan ambang deteksi menambah kemungkinan positif palsu. +- Rentang warna kulit di YCrCb bersifat tetap, sehingga objek berwarna serupa di dalam kotak wajah (misalnya tepi topi krem) dapat ikut berubah. +- Pengirisan warna memakai satu warna prototipe tetap, sehingga hanya cocok untuk satu nada kulit. +- Mode kamera belum diuji ulang pada kondisi redup setelah penggantian ke CLAHE. + +## Referensi + +- R. C. Gonzalez dan R. E. Woods, *Digital Image Processing*, Bab 6. +- OpenCV: diff --git a/color_processor.cpp b/color_processor.cpp new file mode 100644 index 0000000..1e4132d --- /dev/null +++ b/color_processor.cpp @@ -0,0 +1,293 @@ +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace ColorProcessing { + +// 1. Color Complement / Negative (Section 6.5) +cv::Mat computeComplement(const cv::Mat& input) { + cv::Mat output; + // Operasi vektor element-wise: s_i = 255 - r_i + cv::bitwise_not(input, output); + return output; +} + +// 2. Tonal Contrast Adjustment menggunakan kurva S (Section 6.5, Fig 6.33) +cv::Mat adjustToneSCurve(const cv::Mat& input) { + cv::Mat lut(1, 256, CV_8U); + uchar* ptr = lut.ptr(); + + // Membentuk kurva S halus: f(x) = 255 / (1 + exp(-k * (x - 128))) + // Dinormalisasi agar f(0) = 0 dan f(255) = 255 + const double k = 0.035; + auto sigmoid = [k](double x) { return 1.0 / (1.0 + std::exp(-k * (x - 128.0))); }; + const double lo = sigmoid(0.0); + const double hi = sigmoid(255.0); + for (int i = 0; i < 256; ++i) { + double val = 255.0 * (sigmoid(i) - lo) / (hi - lo); + ptr[i] = cv::saturate_cast(val); + } + + cv::Mat output; + cv::LUT(input, lut, output); + return output; +} + +// 3. Histogram Equalization pada kanal Value di ruang HSV (pendekatan kanal intensitas HSI) (Section 6.5, Fig 6.35) +cv::Mat equalizeIntensityHSI(const cv::Mat& input) { + cv::Mat hsv; + cv::cvtColor(input, hsv, cv::COLOR_BGR2HSV); + + std::vector channels; + cv::split(hsv, channels); + + // Hanya ratakan kanal Value/Intensity (index 2) + // agar hue dan saturation tidak terdistorsi menjadi warna palsu + cv::equalizeHist(channels[2], channels[2]); + + // Sedikit optimasi saturasi seperti di Example 6.11 + channels[1] = channels[1] * 1.15; + + cv::Mat merged; + cv::merge(channels, merged); + + cv::Mat output; + cv::cvtColor(merged, output, cv::COLOR_HSV2BGR); + return output; +} + +// 4. Color Slicing berbasis bola Euclidean 3D (Section 6.5, Eq. 6-44) +// Mengisolasi warna target (default: warna kulit/hangat) dan mengubah sisanya ke abu-abu netral +cv::Mat sliceColorSphere(const cv::Mat& input, const cv::Vec3b& prototypeColor, double radius) { + cv::Mat output = input.clone(); + const double radiusSq = radius * radius; + const cv::Vec3b neutralGray(128, 128, 128); + + // Akses data berurutan untuk memaksimalkan cache locality + for (int r = 0; r < input.rows; ++r) { + const cv::Vec3b* inRow = input.ptr(r); + cv::Vec3b* outRow = output.ptr(r); + + for (int c = 0; c < input.cols; ++c) { + double db = static_cast(inRow[c][0]) - prototypeColor[0]; + double dg = static_cast(inRow[c][1]) - prototypeColor[1]; + double dr = static_cast(inRow[c][2]) - prototypeColor[2]; + + double distSq = (dr * dr) + (dg * dg) + (db * db); + + if (distSq > radiusSq) { + outRow[c] = neutralGray; + } + } + } + return output; +} + +// 5. Sharpening Vektor Warna via Laplacian Operator (Section 6.6, Eq. 6-47) +cv::Mat sharpenLaplacian(const cv::Mat& input) { + cv::Mat laplacian, output; + + // Matriks kernel Laplacian 3x3 standar + cv::Mat kernel = (cv::Mat_(3, 3) << + 0, -1, 0, + -1, 5, -1, + 0, -1, 0); + + cv::filter2D(input, output, input.depth(), kernel); + return output; +} + +// 6. Pseudocolor Processing (Section 6.3) +cv::Mat generatePseudocolor(const cv::Mat& input) { + cv::Mat gray, output; + cv::cvtColor(input, gray, cv::COLOR_BGR2GRAY); + cv::applyColorMap(gray, output, cv::COLORMAP_JET); + return output; +} + +// 7. Deteksi wajah dengan Haar cascade +std::vector detectFaces(const cv::Mat& input, cv::CascadeClassifier& cascade) { + cv::Mat gray; + cv::cvtColor(input, gray, cv::COLOR_BGR2GRAY); + // CLAHE (equalisasi lokal) lebih tahan terhadap pencahayaan tidak merata daripada equalizeHist global + cv::createCLAHE(3.0, cv::Size(8, 8))->apply(gray, gray); + + std::vector faces; + cascade.detectMultiScale(gray, faces, 1.1, 5, 0, cv::Size(40, 40)); + return faces; +} + +// 8. Ubah warna kulit wajah: rotasi Hue + skala Saturation pada piksel kulit di dalam wajah (Section 6.2, 6.5) +// Mask = rentang kulit di YCrCb AND elips di dalam kotak wajah, tepi di-blur agar transisi halus +cv::Mat recolorFace(const cv::Mat& input, const std::vector& faces, int hueShift, double satScale) { + cv::Mat output = input.clone(); + + // Hue OpenCV 0..179, rotasi melingkar + cv::Mat hueLut(1, 256, CV_8U); + for (int i = 0; i < 256; ++i) { + hueLut.at(i) = static_cast(((i + hueShift) % 180 + 180) % 180); + } + + for (const cv::Rect& face : faces) { + cv::Rect box = face & cv::Rect(0, 0, input.cols, input.rows); + if (box.empty()) continue; + cv::Mat roi = output(box); + + cv::Mat ycrcb, skinMask; + cv::cvtColor(roi, ycrcb, cv::COLOR_BGR2YCrCb); + cv::inRange(ycrcb, cv::Scalar(0, 135, 80), cv::Scalar(255, 195, 135), skinMask); + + cv::Mat ellipse = cv::Mat::zeros(roi.size(), CV_8U); + cv::ellipse(ellipse, cv::Point(roi.cols / 2, roi.rows / 2), + cv::Size(roi.cols / 2, roi.rows / 2), 0, 0, 360, cv::Scalar(255), cv::FILLED); + cv::bitwise_and(skinMask, ellipse, skinMask); + + cv::Mat alpha; + skinMask.convertTo(alpha, CV_32F, 1.0 / 255.0); + cv::GaussianBlur(alpha, alpha, cv::Size(15, 15), 0); + + cv::Mat hsv; + cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV); + std::vector ch; + cv::split(hsv, ch); + cv::LUT(ch[0], hueLut, ch[0]); + ch[1] = ch[1] * satScale; + cv::merge(ch, hsv); + + cv::Mat recolored; + cv::cvtColor(hsv, recolored, cv::COLOR_HSV2BGR); + + cv::Mat alpha3, roiF, recF, blended; + cv::merge(std::vector{alpha, alpha, alpha}, alpha3); + roi.convertTo(roiF, CV_32F); + recolored.convertTo(recF, CV_32F); + blended = recF.mul(alpha3) + roiF.mul(cv::Scalar::all(1.0) - alpha3); + blended.convertTo(roi, CV_8U); + } + return output; +} + +} // namespace ColorProcessing + +namespace { + +const int kHueShift = 60; +const double kSatScale = 1.3; + +bool loadCascade(cv::CascadeClassifier& cascade, const std::string& userPath) { + if (!userPath.empty() && cascade.load(userPath)) return true; + if (cascade.load("haarcascade_frontalface_default.xml")) return true; + + std::string found = cv::samples::findFile("haarcascades/haarcascade_frontalface_default.xml", false, false); + return !found.empty() && cascade.load(found); +} + +int runWebcam(cv::CascadeClassifier& cascade, const std::string& outDir) { + cv::VideoCapture cap(0); + if (!cap.isOpened()) { + std::cerr << "Kesalahan: Webcam tidak dapat dibuka\n"; + return 1; + } + + std::cout << "[INFO] Webcam aktif. Tekan 's' simpan frame, 'q' atau Esc keluar.\n"; + cv::Mat frame; + int saved = 0; + while (cap.read(frame)) { + auto faces = ColorProcessing::detectFaces(frame, cascade); + cv::Mat view = ColorProcessing::recolorFace(frame, faces, kHueShift, kSatScale); + cv::imshow("Face Recolor", view); + + int key = cv::waitKey(1) & 0xFF; + if (key == 'q' || key == 27) break; + if (key == 's') { + std::string name = outDir + "/webcam_" + std::to_string(saved++) + ".png"; + cv::imwrite(name, view); + std::cout << "[+] Tersimpan: " << name << "\n"; + } + } + return 0; +} + +} // namespace + +int main(int argc, char** argv) { + std::string inputPath, cascadePath; + bool useCam = (argc < 2); + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "--cam") useCam = true; + else if (arg == "--cascade" && i + 1 < argc) cascadePath = argv[++i]; + else inputPath = arg; + } + if (inputPath.empty()) useCam = true; + + std::string outDir = "output_results"; + fs::create_directories(outDir); + + cv::CascadeClassifier cascade; + if (!loadCascade(cascade, cascadePath)) { + std::cerr << "Kesalahan: haarcascade_frontalface_default.xml tidak ditemukan. " + << "Gunakan --cascade \n"; + return 1; + } + + if (useCam) return runWebcam(cascade, outDir); + + cv::Mat src = cv::imread(inputPath, cv::IMREAD_COLOR); + + if (src.empty()) { + std::cerr << "Kesalahan: Tidak dapat memuat gambar dari " << inputPath << std::endl; + return 1; + } + + fs::path p(inputPath); + std::string stem = p.stem().string(); + + std::cout << "[INFO] Memproses citra: " << inputPath << " (" << src.cols << "x" << src.rows << " px)\n"; + + // 1. Eksekusi Complement + cv::Mat imgComp = ColorProcessing::computeComplement(src); + cv::imwrite(outDir + "/" + stem + "_complement.png", imgComp); + std::cout << "[+] Selesai: Color Complement\n"; + + // 2. Eksekusi Tone S-Curve + cv::Mat imgTone = ColorProcessing::adjustToneSCurve(src); + cv::imwrite(outDir + "/" + stem + "_tone_scurve.png", imgTone); + std::cout << "[+] Selesai: Tonal S-Curve Enhancement\n"; + + // 3. Eksekusi HSI Equalization + cv::Mat imgHist = ColorProcessing::equalizeIntensityHSI(src); + cv::imwrite(outDir + "/" + stem + "_hsi_equalized.png", imgHist); + std::cout << "[+] Selesai: HSI Intensity Histogram Equalization\n"; + + // 4. Eksekusi Color Slicing (BGR: B=110, G=140, R=190 -> Estimasi Nada Kulit / Warm Tone) + cv::Vec3b targetSkinTone(110, 140, 190); + double thresholdRadius = 65.0; + cv::Mat imgSliced = ColorProcessing::sliceColorSphere(src, targetSkinTone, thresholdRadius); + cv::imwrite(outDir + "/" + stem + "_color_sliced.png", imgSliced); + std::cout << "[+] Selesai: Color Slicing (Euclidean Sphere)\n"; + + // 5. Eksekusi Laplacian Sharpening + cv::Mat imgSharp = ColorProcessing::sharpenLaplacian(src); + cv::imwrite(outDir + "/" + stem + "_sharpened.png", imgSharp); + std::cout << "[+] Selesai: Laplacian Sharpening\n"; + + // 6. Eksekusi Pseudocolor + cv::Mat imgPseudo = ColorProcessing::generatePseudocolor(src); + cv::imwrite(outDir + "/" + stem + "_pseudocolor.png", imgPseudo); + std::cout << "[+] Selesai: Pseudocolor Transformation\n"; + + // 7. Deteksi wajah + ubah warna kulit + auto faces = ColorProcessing::detectFaces(src, cascade); + cv::Mat imgFace = ColorProcessing::recolorFace(src, faces, kHueShift, kSatScale); + cv::imwrite(outDir + "/" + stem + "_face_recolor.png", imgFace); + std::cout << "[+] Selesai: Face Recolor (" << faces.size() << " wajah terdeteksi)\n"; + + std::cout << "\nSemua proses selesai. Hasil tersimpan di folder: ./" << outDir << "/" << std::endl; + + return 0; +} \ No newline at end of file diff --git a/report/images/foto_asli.jpeg b/report/images/foto_asli.jpeg new file mode 100644 index 0000000..ed031b1 Binary files /dev/null and b/report/images/foto_asli.jpeg differ diff --git a/report/images/foto_color_sliced.png b/report/images/foto_color_sliced.png new file mode 100644 index 0000000..dcb279d Binary files /dev/null and b/report/images/foto_color_sliced.png differ diff --git a/report/images/foto_complement.png b/report/images/foto_complement.png new file mode 100644 index 0000000..300f682 Binary files /dev/null and b/report/images/foto_complement.png differ diff --git a/report/images/foto_face_recolor.png b/report/images/foto_face_recolor.png new file mode 100644 index 0000000..d2f308d Binary files /dev/null and b/report/images/foto_face_recolor.png differ diff --git a/report/images/foto_hsi_equalized.png b/report/images/foto_hsi_equalized.png new file mode 100644 index 0000000..158b7a4 Binary files /dev/null and b/report/images/foto_hsi_equalized.png differ diff --git a/report/images/foto_pseudocolor.png b/report/images/foto_pseudocolor.png new file mode 100644 index 0000000..6c26660 Binary files /dev/null and b/report/images/foto_pseudocolor.png differ diff --git a/report/images/foto_sharpened.png b/report/images/foto_sharpened.png new file mode 100644 index 0000000..52ea07d Binary files /dev/null and b/report/images/foto_sharpened.png differ diff --git a/report/images/foto_tone_scurve.png b/report/images/foto_tone_scurve.png new file mode 100644 index 0000000..c99396b Binary files /dev/null and b/report/images/foto_tone_scurve.png differ diff --git a/report/images/lena_face_recolor.png b/report/images/lena_face_recolor.png new file mode 100644 index 0000000..50542e7 Binary files /dev/null and b/report/images/lena_face_recolor.png differ diff --git a/report/laporan.tex b/report/laporan.tex new file mode 100644 index 0000000..4b2fd64 --- /dev/null +++ b/report/laporan.tex @@ -0,0 +1,385 @@ +\documentclass[12pt,a4paper]{article} + +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage[indonesian]{babel} +\usepackage{lmodern} +\usepackage[margin=2.5cm]{geometry} +\usepackage{graphicx} +\usepackage{caption} +\usepackage{subcaption} +\usepackage{amsmath} +\usepackage{listings} +\usepackage{xcolor} +\usepackage{booktabs} +\usepackage{float} +\usepackage{setspace} +\usepackage[hidelinks]{hyperref} + +\graphicspath{{images/}} +\setstretch{1.15} + +\lstset{ + language=C++, + basicstyle=\ttfamily\footnotesize, + keywordstyle=\color{blue!60!black}, + commentstyle=\color{green!40!black}, + stringstyle=\color{red!60!black}, + numbers=left, + numberstyle=\tiny\color{gray}, + breaklines=true, + frame=single, + tabsize=4, + showstringspaces=false +} + +\begin{document} + +% ---------------- Halaman judul ---------------- +\begin{titlepage} + \centering + \vspace*{2cm} + {\Large \textbf{LAPORAN TUGAS}}\\[0.6cm] + {\Large \textbf{PENGOLAHAN CITRA DIGITAL}}\\[1.5cm] + {\LARGE \textbf{Pemrosesan Citra Warna dan Pengubahan Warna Wajah Menggunakan C++ dan OpenCV}}\\[0.6cm] + {\large (Gonzalez \& Woods, Bab 6: Color Image Processing)}\\[3cm] + + \begin{tabular}{ll} + \textbf{Nama} & : Efraim Melvyn Rafelino Lahai \\ + \textbf{NRP} & : 5803024009 \\ + \textbf{Program Studi} & : Informatika \\ + \textbf{Mata Kuliah} & : Pengolahan Citra Digital \\ + \textbf{Dosen Pengajar} & : Ir. Drs. Peter Rhatodirdjo Angka, M.Kom., IPM., ASEAN Eng. + \end{tabular} + + \vfill + {\large \today} +\end{titlepage} + +\tableofcontents +\newpage + +% ---------------- Bab 1 ---------------- +\section{Pendahuluan} + +\subsection{Latar Belakang} +Citra berwarna membawa informasi yang jauh lebih kaya dibandingkan citra +keabuan. Bab 6 buku \textit{Digital Image Processing} (Gonzalez \& Woods) +membahas model warna, transformasi warna, penghalusan dan penajaman citra +berwarna, serta segmentasi berbasis warna. Tugas ini menerapkan konsep-konsep +tersebut dalam sebuah program C++ yang memproses citra wajah, baik dari berkas +gambar maupun dari kamera (\textit{webcam}) secara langsung. + +\subsection{Tujuan} +\begin{enumerate} + \item Mengimplementasikan beberapa operasi pemrosesan citra warna dari Bab 6. + \item Mendeteksi wajah pada citra atau video, lalu mengubah warna kulit wajah + secara otomatis. + \item Menganalisis hasil dan keterbatasan program. +\end{enumerate} + +\subsection{Batasan} +Program menggunakan pustaka OpenCV untuk pembacaan citra, konversi ruang warna, +dan deteksi wajah. Beberapa operasi dasar (misalnya pengirisan warna) ditulis +manual per piksel, sedangkan operasi lain memakai fungsi bawaan OpenCV. + +% ---------------- Bab 2 ---------------- +\section{Landasan Teori} + +\subsection{Komplemen Warna} +Komplemen warna pada citra 8-bit (\(L = 256\)) dihitung per komponen: +\begin{equation} + s_i = (L - 1) - r_i, \qquad i = B, G, R. +\end{equation} +Operasi ini setara dengan negatif citra dan diterapkan pada setiap kanal. + +\subsection{Penyesuaian Nada dengan Kurva S} +Kurva S meningkatkan kontras dengan menggelapkan nada gelap dan menerangkan +nada terang. Program memakai fungsi sigmoid yang dinormalisasi agar +\(f(0)=0\) dan \(f(255)=255\): +\begin{equation} + \sigma(r) = \frac{1}{1 + e^{-k(r-128)}}, \qquad + f(r) = 255\,\frac{\sigma(r) - \sigma(0)}{\sigma(255) - \sigma(0)}, \qquad k = 0{,}035. +\end{equation} +Fungsi ini dihitung sekali dalam bentuk tabel pencarian (\textit{look-up table}) +berisi 256 nilai, lalu diterapkan ke seluruh piksel. + +\subsection{Ekualisasi Histogram pada Kanal Intensitas} +Ekualisasi histogram langsung pada kanal R, G, dan B akan menggeser rona +sehingga muncul warna palsu. Cara yang lebih aman adalah mengubah citra ke ruang +warna yang memisahkan informasi warna dari kecerahan, lalu meratakan histogram +hanya pada komponen kecerahan. Program memakai ruang HSV dan meratakan kanal +\textit{Value}, sebagai pendekatan terhadap kanal intensitas pada model HSI. +Kanal \textit{Saturation} dinaikkan 15\%. + +\subsection{Pengirisan Warna (\textit{Color Slicing}) Berbentuk Bola} +Pengirisan warna mengisolasi warna tertentu. Untuk daerah berbentuk bola +dengan pusat \(\mathbf{a} = (a_B, a_G, a_R)\) dan jari-jari \(R_0\), piksel +\(\mathbf{z}\) dipertahankan jika jaraknya ke pusat tidak melebihi \(R_0\), dan +diganti dengan warna netral jika sebaliknya: +\begin{equation} + s_i = + \begin{cases} + 128 & \text{jika } \sum_{j}(z_j - a_j)^2 > R_0^2, \\ + r_i & \text{lainnya.} + \end{cases} +\end{equation} +Nilai 128 adalah padanan 8-bit dari abu-abu netral \(0{,}5\). + +\subsection{Penajaman Citra Warna dengan Laplacian} +Laplacian vektor pada citra warna sama dengan Laplacian yang dihitung terpisah +pada tiap komponen warna. Penajaman dilakukan dengan mengurangkan Laplacian +dari citra asli, \(g = f - \nabla^2 f\). Untuk Laplacian 4-tetangga, hal ini +menghasilkan kernel +\begin{equation} + \begin{bmatrix} + 0 & -1 & 0 \\ + -1 & 5 & -1 \\ + 0 & -1 & 0 + \end{bmatrix}. +\end{equation} + +\subsection{Pemrosesan Pseudowarna} +Pseudowarna menugaskan warna pada tiap tingkat keabuan sehingga perbedaan +intensitas yang halus lebih mudah dilihat. Program mengubah citra ke skala +keabuan lalu menerapkan peta warna JET. + +\subsection{Deteksi Wajah dengan Haar Cascade} +Deteksi wajah memakai \textit{Haar cascade classifier} (metode Viola--Jones) +yang tersedia di OpenCV, berkas \texttt{haarcascade\_frontalface\_default.xml}. +Pengklasifikasi ini memindai citra keabuan pada beberapa skala dan mengembalikan +kotak pembatas untuk wajah menghadap depan. + +\subsection{Segmentasi Kulit di Ruang YCrCb} +Warna kulit manusia mengelompok pada rentang sempit di komponen krominansi +\(C_r\) dan \(C_b\) dan relatif tidak bergantung pada kecerahan (\(Y\)). +Karena itu piksel kulit dapat dipisahkan dengan ambang sederhana pada +\(C_r\) dan \(C_b\). + +% ---------------- Bab 3 ---------------- +\section{Perancangan dan Implementasi} + +\subsection{Lingkungan Pengembangan} +\begin{itemize} + \item Bahasa: C++17 (kompilator MinGW-w64 GCC 16.1, UCRT). + \item Pustaka: OpenCV 4.10.0, dikompilasi dari kode sumber dengan modul + \texttt{core}, \texttt{imgproc}, \texttt{imgcodecs}, \texttt{videoio}, + \texttt{highgui}, dan \texttt{objdetect}. + \item Sistem \textit{build}: CMake dan Ninja. + \item Sistem operasi: Windows 11. +\end{itemize} + +\subsection{Alur Program} +\begin{enumerate} + \item Program dijalankan dengan jalur berkas gambar, atau dengan + \texttt{--cam} (atau tanpa argumen) untuk mode kamera. + \item Berkas \textit{Haar cascade} dimuat. + \item \textbf{Mode berkas:} enam operasi Bab 6 dijalankan pada gambar + masukan, lalu wajah dideteksi dan warnanya diubah. Semua hasil disimpan + dalam format PNG ke folder \texttt{output\_results}. + \item \textbf{Mode kamera:} setiap bingkai dideteksi wajahnya dan diubah + warnanya, lalu ditampilkan. Tombol \texttt{s} menyimpan bingkai, + tombol \texttt{q} atau \texttt{Esc} keluar. +\end{enumerate} + +\subsection{Fungsi Utama} +\begin{table}[H] + \centering + \caption{Fungsi pada namespace \texttt{ColorProcessing}.} + \begin{tabular}{@{}lp{9cm}@{}} + \toprule + Fungsi & Keterangan \\ + \midrule + \texttt{computeComplement} & Komplemen warna dengan \texttt{bitwise\_not}. \\ + \texttt{adjustToneSCurve} & Kurva S berbasis tabel pencarian. \\ + \texttt{equalizeIntensityHSI} & Ekualisasi kanal \textit{Value} di HSV. \\ + \texttt{sliceColorSphere} & Pengirisan warna bola 3D, ditulis manual per piksel. \\ + \texttt{sharpenLaplacian} & Penajaman dengan kernel Laplacian 3$\times$3. \\ + \texttt{generatePseudocolor} & Keabuan lalu peta warna JET. \\ + \texttt{detectFaces} & Deteksi wajah dengan Haar cascade. \\ + \texttt{recolorFace} & Pengubahan warna kulit di dalam wajah. \\ + \bottomrule + \end{tabular} +\end{table} + +\subsection{Pengubahan Warna Wajah} +Fungsi \texttt{recolorFace} bekerja pada setiap kotak wajah hasil deteksi: +\begin{enumerate} + \item Bagian wajah diubah ke ruang YCrCb, lalu piksel kulit dipilih dengan + ambang \(C_r \in [135, 195]\) dan \(C_b \in [80, 135]\). + \item Hasilnya dikalikan (operasi AND) dengan elips yang mengisi kotak wajah, + sehingga latar di sudut kotak tidak ikut berubah. + \item Masker dihaluskan dengan \textit{Gaussian blur} 15$\times$15 agar tepi + transisinya lembut. + \item Bagian wajah diubah ke HSV. Kanal \textit{Hue} diputar sebesar 60 + (skala OpenCV 0--179, sehingga kulit oranye menjadi hijau) dan + \textit{Saturation} dikalikan 1{,}3. + \item Hasil putaran warna dicampur dengan citra asli menurut masker: + \(\text{keluaran} = \alpha \cdot \text{berubah} + (1-\alpha) \cdot \text{asli}\). +\end{enumerate} +Karena hanya piksel kulit di dalam wajah yang bobotnya besar, rambut, mata, +dan latar tetap berwarna asli. + +\subsection{Peningkatan Ketahanan Deteksi terhadap Cahaya} +Pengujian awal menunjukkan deteksi wajah gagal pada citra dengan pencahayaan +kurang merata. Ekualisasi histogram global (\texttt{equalizeHist}) diganti +dengan CLAHE (\textit{Contrast Limited Adaptive Histogram Equalization}, batas +kontras 3,0, petak 8$\times$8) sebelum deteksi. Setelah perubahan ini, foto uji +yang sebelumnya menghasilkan 0 wajah kini menghasilkan 1 wajah. + +% ---------------- Bab 4 ---------------- +\section{Hasil dan Pembahasan} + +Pengujian memakai foto penulis (Gambar~\ref{fig:asli}) dan citra uji standar +\textit{Lena} yang disertakan dalam kode sumber OpenCV. + +\begin{figure}[H] + \centering + \includegraphics[width=0.4\textwidth]{foto_asli.jpeg} + \caption{Citra masukan (foto penulis).} + \label{fig:asli} +\end{figure} + +\subsection{Hasil Operasi Bab 6} + +\begin{figure}[H] + \centering + \begin{subfigure}[b]{0.32\textwidth} + \includegraphics[width=\textwidth]{foto_complement.png} + \caption{Komplemen warna} + \end{subfigure} + \hfill + \begin{subfigure}[b]{0.32\textwidth} + \includegraphics[width=\textwidth]{foto_tone_scurve.png} + \caption{Kurva S} + \end{subfigure} + \hfill + \begin{subfigure}[b]{0.32\textwidth} + \includegraphics[width=\textwidth]{foto_hsi_equalized.png} + \caption{Ekualisasi kanal $V$} + \end{subfigure} + \caption{Transformasi warna dan nada.} + \label{fig:transformasi} +\end{figure} + +\begin{figure}[H] + \centering + \begin{subfigure}[b]{0.32\textwidth} + \includegraphics[width=\textwidth]{foto_color_sliced.png} + \caption{Pengirisan warna} + \end{subfigure} + \hfill + \begin{subfigure}[b]{0.32\textwidth} + \includegraphics[width=\textwidth]{foto_sharpened.png} + \caption{Penajaman Laplacian} + \end{subfigure} + \hfill + \begin{subfigure}[b]{0.32\textwidth} + \includegraphics[width=\textwidth]{foto_pseudocolor.png} + \caption{Pseudowarna JET} + \end{subfigure} + \caption{Pengirisan warna, penajaman, dan pseudowarna.} + \label{fig:lanjutan} +\end{figure} + +\textbf{Komplemen} membalik seluruh warna: kulit menjadi kebiruan dan putih +menjadi gelap. \textbf{Kurva S} menambah kontras nada tengah. \textbf{Ekualisasi +kanal \textit{Value}} meratakan sebaran kecerahan tanpa menggeser rona. +\textbf{Penajaman Laplacian} mempertegas tepi dan tekstur. \textbf{Pseudowarna} +mengubah tingkat keabuan menjadi warna sehingga daerah terang dan gelap lebih +mudah dibedakan. + +Pada \textbf{pengirisan warna}, hanya sebagian kulit wajah yang dipertahankan, +sedangkan sisanya menjadi abu-abu. Hal ini terjadi karena pusat bola +(BGR = 110, 140, 190) dan jari-jari 65 bersifat tetap, sehingga bagian wajah +yang lebih terang atau lebih gelap karena bayangan jatuh di luar bola. +Beberapa objek berwarna hangat di latar (misalnya dinding krem) juga ikut +lolos. Ini menunjukkan bahwa pengirisan warna dengan prototipe tetap peka +terhadap pencahayaan dan warna kulit. + +\subsection{Hasil Pengubahan Warna Wajah} + +\begin{figure}[H] + \centering + \begin{subfigure}[b]{0.4\textwidth} + \includegraphics[width=\textwidth]{foto_asli.jpeg} + \caption{Sebelum} + \end{subfigure} + \hspace{0.05\textwidth} + \begin{subfigure}[b]{0.4\textwidth} + \includegraphics[width=\textwidth]{foto_face_recolor.png} + \caption{Sesudah} + \end{subfigure} + \caption{Pengubahan warna wajah pada foto penulis (1 wajah terdeteksi).} + \label{fig:recolor} +\end{figure} + +Wajah terdeteksi dan kulit berubah menjadi hijau, sedangkan baju, kursi, dan +latar tidak berubah. Pada tepi topi terlihat sedikit rona hijau karena +warna topi krem masuk ke dalam rentang kulit dan berada di dalam kotak wajah. + +\begin{figure}[H] + \centering + \includegraphics[width=0.45\textwidth]{lena_face_recolor.png} + \caption{Pengubahan warna wajah pada citra uji \textit{Lena}.} + \label{fig:lena} +\end{figure} + +Pada citra \textit{Lena}, wajah utama berubah warna dengan baik. Dengan +parameter deteksi yang sekarang, program mengembalikan 2 kotak wajah pada +citra ini, sehingga ada satu deteksi tambahan (positif palsu). + +\subsection{Pengujian Kamera} +Mode kamera bekerja dan menampilkan wajah dengan warna yang sudah diubah secara +langsung. Namun pada ruangan yang kurang terang, wajah awalnya tidak +terdeteksi; deteksi baru berhasil setelah wajah diberi cahaya tambahan dari +senter ponsel. Pengujian kamera setelah penggantian ke CLAHE belum +dilakukan pada kondisi redup yang sama. + +\subsection{Keterbatasan} +\begin{enumerate} + \item \textbf{Bergantung pada pencahayaan.} Haar cascade dilatih pada wajah + menghadap depan dengan pencahayaan cukup. Cahaya redup, bayangan kuat, + wajah menunduk atau miring, serta penutup seperti topi dapat + menggagalkan deteksi. + \item \textbf{Positif palsu.} Menurunkan ambang deteksi meningkatkan + kemungkinan objek non-wajah terdeteksi sebagai wajah. + \item \textbf{Ambang kulit tetap.} Rentang \(C_r/C_b\) yang tetap tidak + cocok untuk semua warna kulit dan pencahayaan, dan dapat mengenai + objek berwarna serupa (topi, kayu, dinding). + \item \textbf{Pengirisan warna} memakai prototipe tetap, sehingga hasilnya + hanya baik untuk satu nada kulit. + \item \textbf{Bukan pelacakan.} Setiap bingkai dideteksi ulang tanpa + pelacakan, sehingga kotak dapat berkedip antarbingkai. +\end{enumerate} + +\subsection{Saran Pengembangan} +Detektor wajah berbasis jaringan saraf (misalnya model DNN pada OpenCV) lebih +tahan terhadap cahaya dan sudut pandang dibandingkan Haar cascade. Ambang kulit +juga dapat dibuat adaptif, misalnya dengan mengambil statistik warna dari +bagian tengah wajah yang terdeteksi. + +% ---------------- Bab 5 ---------------- +\section{Kesimpulan} +Program berhasil menerapkan enam operasi pemrosesan citra warna dari Bab 6 +(komplemen, kurva S, ekualisasi kanal intensitas, pengirisan warna, +penajaman Laplacian, dan pseudowarna) serta mendeteksi wajah dan mengubah warna +kulitnya pada gambar dan pada kamera. Pengubahan warna paling baik pada wajah +menghadap depan dengan pencahayaan cukup. Penggantian ekualisasi global dengan +CLAHE meningkatkan deteksi pada foto uji, tetapi deteksi pada cahaya redup +dan positif palsu masih menjadi keterbatasan. + +% ---------------- Lampiran ---------------- +\appendix +\section{Cara Kompilasi dan Menjalankan} +\begin{lstlisting}[language={},numbers=none] +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release ^ + -DOpenCV_DIR=D:/opencv/install/x64/mingw/lib +cmake --build build + +cd build +.\color_processor.exe path\ke\foto.jpg # mode berkas +.\color_processor.exe --cam # mode kamera +\end{lstlisting} + +\end{document}