first commit
31
.gitignore
vendored
Normal file
@ -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
|
||||||
17
CMakeLists.txt
Normal file
@ -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}" "$<TARGET_FILE_DIR:color_processor>"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory "${OpenCV_DIR}/../bin" "$<TARGET_FILE_DIR:color_processor>"
|
||||||
|
)
|
||||||
100
README.md
Normal file
@ -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=<folder OpenCVConfig.cmake>
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
<nama>_complement.png
|
||||||
|
<nama>_tone_scurve.png
|
||||||
|
<nama>_hsi_equalized.png
|
||||||
|
<nama>_color_sliced.png
|
||||||
|
<nama>_sharpened.png
|
||||||
|
<nama>_pseudocolor.png
|
||||||
|
<nama>_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: <https://opencv.org>
|
||||||
293
color_processor.cpp
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <cmath>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <opencv2/opencv.hpp>
|
||||||
|
|
||||||
|
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<uchar>(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<cv::Mat> 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<cv::Vec3b>(r);
|
||||||
|
cv::Vec3b* outRow = output.ptr<cv::Vec3b>(r);
|
||||||
|
|
||||||
|
for (int c = 0; c < input.cols; ++c) {
|
||||||
|
double db = static_cast<double>(inRow[c][0]) - prototypeColor[0];
|
||||||
|
double dg = static_cast<double>(inRow[c][1]) - prototypeColor[1];
|
||||||
|
double dr = static_cast<double>(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_<float>(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<cv::Rect> 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<cv::Rect> 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<cv::Rect>& 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<uchar>(i) = static_cast<uchar>(((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<cv::Mat> 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<cv::Mat>{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 <path_xml>\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;
|
||||||
|
}
|
||||||
BIN
report/images/foto_asli.jpeg
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
report/images/foto_color_sliced.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
report/images/foto_complement.png
Normal file
|
After Width: | Height: | Size: 668 KiB |
BIN
report/images/foto_face_recolor.png
Normal file
|
After Width: | Height: | Size: 672 KiB |
BIN
report/images/foto_hsi_equalized.png
Normal file
|
After Width: | Height: | Size: 783 KiB |
BIN
report/images/foto_pseudocolor.png
Normal file
|
After Width: | Height: | Size: 479 KiB |
BIN
report/images/foto_sharpened.png
Normal file
|
After Width: | Height: | Size: 979 KiB |
BIN
report/images/foto_tone_scurve.png
Normal file
|
After Width: | Height: | Size: 642 KiB |
BIN
report/images/lena_face_recolor.png
Normal file
|
After Width: | Height: | Size: 486 KiB |
385
report/laporan.tex
Normal file
@ -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}
|
||||||