From 753b8f67d67415f062d53afa05971157bda0290a Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Sun, 22 Mar 2026 14:25:35 +0700 Subject: [PATCH 01/45] Rewrite --- CMakelist.txt | 27 +++ src/config.h | 0 src/image_processing.cpp | 397 +++++++++++++++++++++++++++++++++++ src/image_processing.h | 15 ++ src/image_processing.py | 435 --------------------------------------- src/main.cpp | 164 +++++++++++++++ src/main.py | 155 -------------- src/user_input.cpp | 121 +++++++++++ src/user_input.h | 0 src/user_input.py | 106 ---------- src/variable.py | 20 -- 11 files changed, 724 insertions(+), 716 deletions(-) create mode 100644 CMakelist.txt create mode 100644 src/config.h create mode 100644 src/image_processing.cpp create mode 100644 src/image_processing.h delete mode 100644 src/image_processing.py create mode 100644 src/main.cpp delete mode 100644 src/main.py create mode 100644 src/user_input.cpp create mode 100644 src/user_input.h delete mode 100644 src/user_input.py delete mode 100644 src/variable.py diff --git a/CMakelist.txt b/CMakelist.txt new file mode 100644 index 0000000..cfbb77a --- /dev/null +++ b/CMakelist.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.16) +project(CropSense CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# ---- OpenCV ---------------------------------------------------------------- +find_package(OpenCV REQUIRED) + +# ---- Sources --------------------------------------------------------------- +add_executable(CropSense + main.cpp + image_processing.cpp + user_input.cpp +) + +target_include_directories(CropSense PRIVATE ${OpenCV_INCLUDE_DIRS}) +target_link_libraries(CropSense PRIVATE ${OpenCV_LIBS}) + +# ---- Threads --------------------------------------------------------------- +find_package(Threads REQUIRED) +target_link_libraries(CropSense PRIVATE Threads::Threads) + +# ---- Windows shell shortcut (optional) ------------------------------------ +if(WIN32) + target_compile_definitions(CropSense PRIVATE _WIN32_WINNT=0x0601) +endif() diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..e69de29 diff --git a/src/image_processing.cpp b/src/image_processing.cpp new file mode 100644 index 0000000..d316a4e --- /dev/null +++ b/src/image_processing.cpp @@ -0,0 +1,397 @@ +#include "image_processing.h" +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +// --------------------------------------------------------------------------- +// Windows-only: create a .lnk shortcut pointing at imagePath inside errorFolder. +// On non-Windows platforms we just copy the file instead. +// --------------------------------------------------------------------------- +static void imagesError(const std::string& imagePath, const std::string& errorFolder) +{ + #ifdef _WIN32 + // Use the Windows Script Host COM object via system() call as a fallback — + // a proper COM implementation would require + IShellLink. + // For portability we write a small PowerShell one-liner. + std::string filename = fs::path(imagePath).filename().string(); + std::string lnkPath = errorFolder + "/" + filename + ".lnk"; + std::string absTarget = fs::absolute(imagePath).string(); + + std::string cmd = + "powershell -NoProfile -Command \"" + "$s=(New-Object -COM WScript.Shell).CreateShortcut('" + lnkPath + "');" + "$s.TargetPath='" + absTarget + "';" + "$s.Save()\""; + std::system(cmd.c_str()); + #else + // On Linux/macOS, create a symbolic link (or just copy). + std::string filename = fs::path(imagePath).filename().string(); + std::string linkPath = errorFolder + "/" + filename; + std::error_code ec; + fs::create_symlink(fs::absolute(imagePath), linkPath, ec); + // ignore error if link already exists + #endif +} + +// --------------------------------------------------------------------------- +// Supported image extensions (imghdr equivalent) +// --------------------------------------------------------------------------- +static bool isSupportedImage(const std::string& path) +{ + static const std::set supported = {".jpg",".jpeg",".png",".webp"}; + std::string ext = fs::path(path).extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + return supported.count(ext) > 0; +} + +// --------------------------------------------------------------------------- +// Forward declaration +// --------------------------------------------------------------------------- +static bool drawRectangle(int endX, int startX, int endY, int startY, + double topMarginValue, double bottomMarginValue, + const cv::Mat& image, + int outputRes, + const std::string& outputFolder, + const std::string& outputImagePath, + const std::string& filename, + const std::string& imagePath, + const std::string& debugOutput, + bool showPreview, + int previewOutputRes, + int previewDebugMaxRes, + bool isError, + int i, + float confidence, + const std::string& errorMsg); + +static void preview(const cv::Mat& debugImage, + const cv::Mat& resizedImage, + int previewOutputRes, + int previewDebugMaxRes, + bool isError); + +// --------------------------------------------------------------------------- +int processImage(const std::string& imagePath, + const std::string& errorFolder, + const std::string& outputFolder, + const std::string& debugOutput, + int outputRes, + int previewOutputRes, + int previewDebugMaxRes, + bool showPreview, + int cropType, + double topMarginValue, + double bottomMarginValue) +{ + int errorCount = 0; + + static cv::dnn::Net net = cv::dnn::readNetFromCaffe( + "deploy.prototxt.txt", "res10_300x300_ssd_iter_140000.caffemodel"); + + std::string stem = fs::path(imagePath).stem().string(); + std::string ext = fs::path(imagePath).extension().string(); + + if (!isSupportedImage(imagePath)) { + std::cout << "\rInvalid or unsupported image format, skipping " << stem << ext << "\n"; + ++errorCount; + return errorCount; + } + + cv::Mat image = cv::imread(imagePath); + if (image.empty()) { + std::cout << "\rCould not read image, skipping " << stem << ext << "\n"; + ++errorCount; + return errorCount; + } + + if (image.rows <= 300 || image.cols <= 300) { + std::cout << "\rThe resolution is too low for face detection, skipping " << stem << ext << "\n"; + imagesError(imagePath, errorFolder); + ++errorCount; + return errorCount; + } + + // Prepare blob and run forward pass + cv::Mat resized300; + cv::resize(image, resized300, {300, 300}); + cv::Mat blob = cv::dnn::blobFromImage(resized300, 1.0, {300, 300}, + {104.0, 177.0, 123.0}); + net.setInput(blob); + cv::Mat detections = net.forward(); // shape: 1×1×N×7 + + // detections.ptr(0,0) is a flat array; use reshape for convenience + // Shape after reshape: (N, 7) + cv::Mat det = detections.reshape(1, detections.size[2]); // N×7 + + int N = det.rows; + bool isError = false; + std::string outputImagePath; + std::string errorMsg; + + // ---- First pass: validate the first high-confidence detection ---------- + for (int i = 0; i < N; ++i) { + float conf = det.at(i, 2); + if (conf < CONFIDENCE_LEVEL) { + std::cout << "\rConfidence level too low (" << static_cast(conf * 100) + << "%), skipping face_" << i << " on " << stem << ext << "\n"; + errorMsg = "CONFIDENCE LEVEL TOO LOW"; + imagesError(imagePath, errorFolder); + isError = true; + ++errorCount; + + float x0 = det.at(i, 3), y0 = det.at(i, 4); + float x1 = det.at(i, 5), y1 = det.at(i, 6); + int sX = static_cast(x0 * image.cols); + int sY = static_cast(y0 * image.rows); + int eX = static_cast(x1 * image.cols); + int eY = static_cast(y1 * image.rows); + + drawRectangle(eX, sX, eY, sY, + topMarginValue, bottomMarginValue, + image, outputRes, outputFolder, outputImagePath, + stem, imagePath, debugOutput, + showPreview, previewOutputRes, previewDebugMaxRes, + isError, i, conf, errorMsg); + break; + } + + if (conf >= CONFIDENCE_LEVEL) { + float x0 = det.at(i, 3), y0 = det.at(i, 4); + float x1 = det.at(i, 5), y1 = det.at(i, 6); + int sX = static_cast(x0 * image.cols); + int sY = static_cast(y0 * image.rows); + int eX = static_cast(x1 * image.cols); + int eY = static_cast(y1 * image.rows); + int w = eX - sX, h = eY - sY; + + int minRes = 0; + if (cropType == 3) minRes = MIN_FULLBODY_RES; + else if (cropType == 2) minRes = MIN_FACE_RES; + else if (cropType == 1) minRes = MIN_UPPERBODY_RES; + + if (w < minRes || h < minRes) { + std::string typeLabel = (cropType == 3) ? "fullbody" : + (cropType == 2) ? "face" : "upperbody"; + std::cout << "\rFace resolution is too small for " << typeLabel + << " crop, skipping face_" << i << " on " << stem << ext << "\n"; + errorMsg = "FACE RESOLUTION IS TOO SMALL"; + imagesError(imagePath, errorFolder); + isError = true; + ++errorCount; + drawRectangle(eX, sX, eY, sY, + topMarginValue, bottomMarginValue, + image, outputRes, outputFolder, outputImagePath, + stem, imagePath, debugOutput, + showPreview, previewOutputRes, previewDebugMaxRes, + isError, i, conf, errorMsg); + break; + } + break; + } + } + + // ---- Second pass: process each high-confidence detection --------------- + for (int i = 0; i < N && !isError; ++i) { + float conf = det.at(i, 2); + if (conf <= CONFIDENCE_LEVEL) break; + + float x0 = det.at(i, 3), y0 = det.at(i, 4); + float x1 = det.at(i, 5), y1 = det.at(i, 6); + int sX = static_cast(x0 * image.cols); + int sY = static_cast(y0 * image.rows); + int eX = static_cast(x1 * image.cols); + int eY = static_cast(y1 * image.rows); + + isError = drawRectangle(eX, sX, eY, sY, + topMarginValue, bottomMarginValue, + image, outputRes, outputFolder, outputImagePath, + stem, imagePath, debugOutput, + showPreview, previewOutputRes, previewDebugMaxRes, + isError, i, conf, errorMsg); + } + + return errorCount; +} + +// --------------------------------------------------------------------------- +static bool drawRectangle(int endX, int startX, int endY, int startY, + double topMarginValue, double bottomMarginValue, + const cv::Mat& image, + int outputRes, + const std::string& outputFolder, + const std::string& /*outputImagePath*/, + const std::string& filename, + const std::string& imagePath, + const std::string& debugOutput, + bool showPreview, + int previewOutputRes, + int previewDebugMaxRes, + bool isError, + int i, + float confidence, + const std::string& errorMsg) +{ + int squareSize = std::min(endX - startX, endY - startY); + int sqCx = (endX + startX) / 2; + int sqCy = (endY + startY) / 2; + int sqULx = sqCx - squareSize / 2; + int sqULy = sqCy - squareSize / 2; + int sqLRx = sqULx + squareSize; + int sqLRy = sqULy + squareSize; + int widthSquare = sqLRx - sqULx; + + int topMargin = static_cast(squareSize * topMarginValue); + int bottomMargin = static_cast(squareSize * bottomMarginValue); + int leftMargin = static_cast(squareSize * bottomMarginValue); + int rightMargin = static_cast(squareSize * bottomMarginValue); + + int ulX = std::max(sqULx - leftMargin, 0); + int ulY = std::max(sqULy - topMargin, 0); + int lrX = std::min(sqLRx + rightMargin, image.cols); + int lrY = std::min(sqLRy + bottomMargin, image.rows); + + // Clamp width + if ((lrX - ulX) > image.cols) { + int shift = (lrX - ulX) - image.cols; + ulX = std::max(ulX - shift, 0); + lrX = image.cols; + } + // Clamp height + if ((lrY - ulY) > image.rows) { + int shift = (lrY - ulY) - image.rows; + ulY = std::max(ulY - shift, 0); + lrY = image.rows; + } + + int sqMarginSize = std::min(lrX - ulX, lrY - ulY); + int sqMCx = (lrX + ulX) / 2; + int sqMULx = sqMCx - sqMarginSize / 2; + int sqMULy = ulY; + int sqMLRx = sqMULx + sqMarginSize; + int sqMLRy = sqMULy + sqMarginSize; + + // Centre the crop horizontally on the detected face + int faceCx = (startX + endX) / 2; + int shift = faceCx - ((sqMULx + sqMLRx) / 2); + sqMULx += shift; + sqMLRx += shift; + + sqMULx = std::max(sqMULx, 0); + sqMLRx = std::min(sqMLRx, image.cols); + sqMarginSize = sqMLRx - sqMULx; + + cv::Mat squareRegion = image(cv::Rect(sqMULx, sqMULy, sqMarginSize, sqMarginSize)); + + std::string outPath = outputFolder + "/" + filename + "_face_" + std::to_string(i) + ".png"; + cv::Mat resizedImage; + + if (squareRegion.empty()) { + isError = true; + const_cast(errorMsg) = "NO FACE DETECTED"; + } else if (!isError) { + cv::resize(squareRegion, resizedImage, {outputRes, outputRes}); + cv::imwrite(outPath, resizedImage); + } + + // ---- Debug overlay ---- + int thickness = std::max(image.cols / 128, 5); + cv::Mat debugImage = image.clone(); + + cv::rectangle(debugImage, {startX, startY}, {endX, endY}, {0, 0, 255}, thickness); + cv::rectangle(debugImage, {sqULx, sqULy}, {sqLRx, sqLRy}, {0, 255, 0}, thickness); + cv::rectangle(debugImage, + {sqMULx, sqMULy}, + {sqMULx + sqMarginSize, sqMULy + sqMarginSize}, + {255, 165, 0}, thickness); + + double fontScale = std::min(image.cols, image.rows) / 1000.0; + int fontThick = std::max(1, std::min(image.cols, image.rows) / 500); + + // First label + std::string resText = std::to_string(image.cols) + "x" + std::to_string(image.rows) + + " face_" + std::to_string(i) + + "_" + std::to_string(widthSquare) + "px" + + " (" + std::to_string(static_cast(confidence * 100)) + "%)"; + + int baseline = 0; + cv::Size ts = cv::getTextSize(resText, cv::FONT_HERSHEY_SIMPLEX, fontScale, fontThick, &baseline); + int bgW = ts.width + 10, bgH = ts.height + 10; + bgW = std::min(bgW, debugImage.cols); + + cv::Mat bg1(bgH, bgW, CV_8UC3, cv::Scalar(255, 255, 0)); + cv::putText(bg1, resText, {10, ts.height + 5}, + cv::FONT_HERSHEY_SIMPLEX, fontScale, {0, 0, 0}, fontThick); + bg1.copyTo(debugImage(cv::Rect(0, 0, bgW, bgH))); + + // Second label + std::string statusText = isError ? ("ERROR " + errorMsg) : "OK PASSED"; + cv::Scalar statusColor = isError ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); + + cv::Size ts2 = cv::getTextSize(statusText, cv::FONT_HERSHEY_SIMPLEX, fontScale, fontThick, &baseline); + int bg2W = std::min(ts2.width + 10, debugImage.cols); + int bg2H = ts2.height + 10; + + int combinedH = bgH + bg2H; + if (combinedH > debugImage.rows) { + cv::resize(debugImage, debugImage, {debugImage.cols, combinedH}); + } + + cv::Mat bg2(bg2H, bg2W, CV_8UC3, statusColor); + cv::putText(bg2, statusText, {10, ts2.height + 5}, + cv::FONT_HERSHEY_SIMPLEX, fontScale, {0, 0, 0}, fontThick); + bg2.copyTo(debugImage(cv::Rect(0, bgH, bg2W, bg2H))); + + std::string debugPath = debugOutput + "/" + filename + "_face_" + std::to_string(i) + ".jpg"; + cv::imwrite(debugPath, debugImage); + + if (showPreview) { + preview(debugImage, resizedImage, previewOutputRes, previewDebugMaxRes, isError); + } + + return isError; +} + +// --------------------------------------------------------------------------- +static void preview(const cv::Mat& debugImage, + const cv::Mat& resizedImage, + int previewOutputRes, + int previewDebugMaxRes, + bool isError) +{ + double ww = debugImage.cols, wh = debugImage.rows; + double scale; + if (ww > previewDebugMaxRes || wh > previewDebugMaxRes) { + scale = std::min(previewDebugMaxRes / ww, previewDebugMaxRes / wh); + } else { + scale = std::max(previewOutputRes / ww, previewOutputRes / wh); + } + + cv::Mat dbPrev; + cv::resize(debugImage, dbPrev, + {static_cast(ww * scale), static_cast(wh * scale)}); + + cv::namedWindow("Debug Image", cv::WINDOW_AUTOSIZE); + cv::imshow("Debug Image", dbPrev); + cv::setWindowProperty("Debug Image", cv::WND_PROP_TOPMOST, 1); + cv::setWindowProperty("Debug Image", cv::WND_PROP_ASPECT_RATIO, cv::WINDOW_KEEPRATIO); + + if (!isError && !resizedImage.empty()) { + cv::Mat outPrev; + cv::resize(resizedImage, outPrev, {previewOutputRes, previewOutputRes}); + cv::namedWindow("Output Image", cv::WINDOW_AUTOSIZE); + cv::imshow("Output Image", outPrev); + cv::setWindowProperty("Output Image", cv::WND_PROP_TOPMOST, 1); + cv::setWindowProperty("Output Image", cv::WND_PROP_ASPECT_RATIO, cv::WINDOW_KEEPRATIO); + } + + cv::waitKey(250); +} diff --git a/src/image_processing.h b/src/image_processing.h new file mode 100644 index 0000000..8ca20fa --- /dev/null +++ b/src/image_processing.h @@ -0,0 +1,15 @@ +#pragma once +#include + +// Returns number of errors encountered while processing a single image file. +int processImage(const std::string& imagePath, + const std::string& errorFolder, + const std::string& outputFolder, + const std::string& debugOutput, + int outputRes, + int previewOutputRes, + int previewDebugMaxRes, + bool showPreview, + int cropType, + double topMarginValue, + double bottomMarginValue); diff --git a/src/image_processing.py b/src/image_processing.py deleted file mode 100644 index 97ed1f4..0000000 --- a/src/image_processing.py +++ /dev/null @@ -1,435 +0,0 @@ -import cv2 -import os -import imghdr -import win32com.client -from tqdm import tqdm -import numpy as np -import variable - -def images_error(image_path, error_folder): - shell = win32com.client.Dispatch("WScript.Shell") - filename_shortcut = os.path.basename(image_path) - shortcut_path = os.path.join(error_folder, filename_shortcut + ".lnk") - shortcut = shell.CreateShortcut(shortcut_path) - shortcut.TargetPath = os.path.abspath(image_path) - shortcut.Save() - - - -def process_image(image_path, - error_folder, - output_folder, - debug_output, - output_res, - preview_output_res, - preview_debug_max_res, - show_preview, - croptype, - top_margin_value, - bottom_margin_value,): - error_count = 0 - endX = 0 - endY = 0 - startX = 0 - startY = 0 - i = 0 - confidence = 0 - image = "" - output_image_path = "" - filename = "" - error_msg = "" - - net = cv2.dnn.readNetFromCaffe("deploy.prototxt.txt", "res10_300x300_ssd_iter_140000.caffemodel") - is_error = False - filename, extension = os.path.splitext(os.path.basename(image_path)) - image = cv2.imread(image_path) - image_format = imghdr.what(image_path) - supported_formats = ["jpg", "jpeg", "png", "webp"] - if image_format is None or image_format not in supported_formats: - print(f"\rInvalid image format or unsupported format, skipping {filename}{extension}") - error_count += 1 - else: - if image.shape[0] > 300 or image.shape[1] > 300: - blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0)) - net.setInput(blob) - detections = net.forward() - - for i in range(detections.shape[2]): - confidence = detections[0, 0, i, 2] - box = detections[0, 0, i, 3:7] * np.array([image.shape[1], image.shape[0], image.shape[1], image.shape[0]]) - (startX, startY, endX, endY) = box.astype(int) - - width = endX - startX - height = endY - startY - if confidence < variable.confidence_level: - print(f"\rConfidence level too low ({int(confidence * 100)}%), skipping face_{i} on {filename}{extension}") - error_msg = "CONFIDENCE LEVEL TOO LOW" - images_error(image_path, error_folder) - is_error = True - error_count += 1 - draw_rectangle(endX, - startX, - endY, - startY, - top_margin_value, - bottom_margin_value, - image, - output_res, - output_folder, - output_image_path, - filename, - image_path, - debug_output, - show_preview, - preview_output_res, - preview_debug_max_res, - is_error, - i, - confidence, - error_msg) - break - - if confidence > variable.confidence_level: - if croptype == 3: - if width < variable.min_fullbody_res or height < variable.min_fullbody_res: - print(f"\rFace resolution is too small for fullbody crop, skipping face_{i} on {filename}{extension}") - error_msg = "FACE RESOLUTION IS TOO SMALL" - images_error(image_path, error_folder) - is_error = True - error_count += 1 - draw_rectangle(endX, - startX, - endY, - startY, - top_margin_value, - bottom_margin_value, - image, - output_res, - output_folder, - output_image_path, - filename, - image_path, - debug_output, - show_preview, - preview_output_res, - preview_debug_max_res, - is_error, - i, - confidence, - error_msg) - break - elif croptype == 2: - if width < variable.min_face_res or height < variable.min_face_res: - print(f"\rFace resolution is too small for face crop, skipping face_{i} on {filename}{extension}") - error_msg = "FACE RESOLUTION IS TOO SMALL" - images_error(image_path, error_folder) - is_error = True - error_count += 1 - draw_rectangle(endX, - startX, - endY, - startY, - top_margin_value, - bottom_margin_value, - image, - output_res, - output_folder, - output_image_path, - filename, - image_path, - debug_output, - show_preview, - preview_output_res, - preview_debug_max_res, - is_error, - i, - confidence, - error_msg) - break - elif croptype == 1: - if width < variable.min_upperbody_res or height < variable.min_upperbody_res: - print(f"\rFace resolution is too small for upperbody crop, skipping face_{i} on {filename}{extension}") - error_msg = "FACE RESOLUTION IS TOO SMALL" - images_error(image_path, error_folder) - is_error = True - error_count += 1 - draw_rectangle(endX, - startX, - endY, - startY, - top_margin_value, - bottom_margin_value, - image, - output_res, - output_folder, - output_image_path, - filename, - image_path, - debug_output, - show_preview, - preview_output_res, - preview_debug_max_res, - is_error, - i, - confidence, - error_msg) - break - break - for i in range(detections.shape[2]): - if is_error == True: - break - confidence = detections[0, 0, i, 2] - - # Filter out weak detections - if confidence > variable.confidence_level: - box = detections[0, 0, i, 3:7] * np.array([image.shape[1], image.shape[0], image.shape[1], image.shape[0]]) - (startX, startY, endX, endY) = box.astype(int) - - width = endX - startX - height = endY - startY - is_error = draw_rectangle(endX, - startX, - endY, - startY, - top_margin_value, - bottom_margin_value, - image, - output_res, - output_folder, - output_image_path, - filename, - image_path, - debug_output, - show_preview, - preview_output_res, - preview_debug_max_res, - is_error, - i, - confidence, - error_msg) - else: - break - else: - print(f"\rThe resolution is too low for face detection, skipping {filename}{extension}") - images_error(image_path, error_folder) - error_count += 1 - return error_count - -def draw_rectangle(endX, - startX, - endY, - startY, - top_margin_value, - bottom_margin_value, - image, - output_res, - output_folder, - output_image_path, - filename, - image_path, - debug_output, - show_preview, - preview_output_res, - preview_debug_max_res, - is_error, - i, - confidence, - error_msg): - # Calculate the size of the square region to be cropped - square_size = min(endX - startX, endY - startY) - - # Calculate the coordinates for the square region - square_center_x = (endX + startX) // 2 - square_center_y = (endY + startY) // 2 - square_upper_left_x = square_center_x - square_size // 2 - square_upper_left_y = square_center_y - square_size // 2 - square_lower_right_x = square_upper_left_x + square_size - square_lower_right_y = square_upper_left_y + square_size - - width_square = square_lower_right_x - square_upper_left_x - height_square = square_lower_right_y - square_upper_left_y - - # Calculate the margin based on the height of the bounding box - top_margin_percent = int(square_size * top_margin_value) - bottom_margin_percent = int(square_size * bottom_margin_value) - left_margin_percent = int(square_size * bottom_margin_value) - right_margin_percent = int(square_size * bottom_margin_value) - - # Calculate the coordinates of the upper body region - upper_left_x = max(square_upper_left_x - left_margin_percent, 0) - upper_left_y = max(square_upper_left_y - top_margin_percent, 0) # Align to the top of the face - - # Calculate the lower right coordinates based on the maximum image dimensions - lower_right_x = min(square_lower_right_x + right_margin_percent, image.shape[1]) - lower_right_y = min(square_lower_right_y + bottom_margin_percent, image.shape[0]) - - # Adjust the upper_left_x and lower_right_x based on the width of the cropped region - width_square_margin = lower_right_x - upper_left_x - if width_square_margin > image.shape[1]: - shift_amount = width_square_margin - image.shape[1] - upper_left_x = max(upper_left_x - shift_amount, 0) - lower_right_x = image.shape[1] - - # Adjust the upper_left_y based on the height of the cropped region - height_square_margin = lower_right_y - upper_left_y - if height_square_margin > image.shape[0]: - shift_amount = height_square_margin - image.shape[0] - upper_left_y = max(upper_left_y - shift_amount, 0) - lower_right_y = image.shape[0] - - # Calculate the new square size based on the adjusted coordinates - square_margin_size = min(lower_right_x - upper_left_x, lower_right_y - upper_left_y) - - # Calculate the coordinates for the square region - square_margin_center_x = (lower_right_x + upper_left_x) // 2 - square_margin_upper_left_x = square_margin_center_x - square_margin_size // 2 - square_margin_upper_left_y = upper_left_y # Align to the top of the face - square_margin_lower_right_x = square_margin_upper_left_x + square_margin_size - square_margin_lower_right_y = square_margin_upper_left_y + square_margin_size - - # Calculate the center point of the second box - second_box_center_x = (startX + endX) // 2 - - # Calculate the shift amount for aligning the square_region horizontally - shift_amount = second_box_center_x - ((square_margin_upper_left_x + square_margin_lower_right_x) // 2) - - # Adjust the square_margin_upper_left_x and square_margin_lower_right_x based on the shift amount - square_margin_upper_left_x += shift_amount - square_margin_lower_right_x += shift_amount - - # Crop the image to the square region with margin - square_margin_upper_left_x = max(square_margin_upper_left_x, 0) - square_margin_lower_right_x = min(square_margin_lower_right_x, image.shape[1]) - - # Adjust square_margin_size based on the final coordinates - square_margin_size = square_margin_lower_right_x - square_margin_upper_left_x - - square_region = image[square_margin_upper_left_y:square_margin_upper_left_y + square_margin_size, square_margin_upper_left_x:square_margin_upper_left_x + square_margin_size] - - # Save the cropped and resized image - output_image_path = os.path.join(output_folder, f"{filename}_face_{i}.png") # type: ignore - resized_image = "" - - if square_region.size == 0: - is_error = True - error_msg = ("NO FACE DETECTED") - else: - if is_error == False: - resized_image = cv2.resize(square_region, (output_res, output_res)) - cv2.imwrite(output_image_path, resized_image) - - # Calculate the thickness of the rectangle based on the image resolution - resolution_thickness_ratio = image.shape[1] // 128 - thickness = max(resolution_thickness_ratio, 5) - - debug_image = image.copy() - - cv2.rectangle(debug_image, (startX, startY), (endX, endY), (0, 0, 255), thickness) #face rectangle - cv2.rectangle(debug_image, (square_upper_left_x, square_upper_left_y), (square_lower_right_x, square_lower_right_y), (0, 255, 0), thickness) #crop rectagle - cv2.rectangle(debug_image, (square_margin_upper_left_x, square_margin_upper_left_y), - (square_margin_upper_left_x + square_margin_size, square_margin_upper_left_y + square_margin_size), - (255,165,0), thickness) - font_scale = min(image.shape[1], image.shape[0]) / 1000 - font_thickness = max(1, int(min(image.shape[1], image.shape[0]) / 500)) - - # FIRST TEXT LABEL - resolution_text = f"{image.shape[1]}x{image.shape[0]} face_{i}_{width_square}px ({int(confidence * 100)}%)" - background_color = (255, 255, 0) - text_color = (0, 0, 0) - text_size, _ = cv2.getTextSize(resolution_text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, font_thickness) - - background_width = text_size[0] + 10 - background_height = text_size[1] + 10 - background = np.zeros((background_height, background_width, 3), dtype=np.uint8) - background[:, :] = background_color - - # Resize the background if its width is greater than the available width in debug_image - if background_width > debug_image.shape[1]: - ratio = debug_image.shape[1] / background_width - background_width = debug_image.shape[1] - background_height = int(background_height * ratio) - background = cv2.resize(background, (background_width, background_height)) - - cv2.putText(background, resolution_text, (10, text_size[1] + 5), cv2.FONT_HERSHEY_SIMPLEX, font_scale, text_color, font_thickness) - debug_image[0:background_height, 0:background_width] = background - - # SECOND TEXT LABEL - if is_error == True: - second_background_color = (0, 0, 255) - second_text_color = (0, 0, 0) - second_text = f"ERROR {error_msg}" - else: - second_background_color = (0, 255, 0) - second_text_color = (0, 0, 0) - second_text = "OK PASSED" - - second_font_scale = font_scale - second_font_thickness = font_thickness - second_text_size, _ = cv2.getTextSize(second_text, cv2.FONT_HERSHEY_SIMPLEX, second_font_scale, second_font_thickness) - - second_background_width = second_text_size[0] + 10 - second_background_height = second_text_size[1] + 10 - - second_background = np.zeros((second_background_height, second_background_width, 3), dtype=np.uint8) - second_background[:, :] = second_background_color - - if second_background_width > debug_image.shape[1]: - second_ratio = debug_image.shape[1] / second_background_width - second_background_width = debug_image.shape[1] - second_background_height = int(second_background_height * second_ratio) - second_background = cv2.resize(second_background, (second_background_width, second_background_height)) - - cv2.putText(second_background, second_text, (10, second_text_size[1] + 5), cv2.FONT_HERSHEY_SIMPLEX, second_font_scale, second_text_color, second_font_thickness) - - debug_image_height, debug_image_width, _ = debug_image.shape - combined_height = background_height + second_background_height - if combined_height > debug_image_height: - debug_image = cv2.resize(debug_image, (debug_image_width, combined_height)) - debug_image[background_height:combined_height, 0:second_background_width] = second_background - - filename = os.path.splitext(os.path.basename(image_path))[0] - debug_image_path = os.path.join(debug_output, f"{filename}_face_{i}.jpg") - cv2.imwrite(debug_image_path, debug_image) - - if show_preview == True: - preview(debug_image, resized_image, preview_output_res, preview_debug_max_res, is_error) - return is_error - -def preview(debug_image, - resized_image, - preview_output_res, - preview_debug_max_res, - is_error): - - # Resize the debug image to fit within the maximum window dimensions while maintaining the aspect ratio - window_width = debug_image.shape[1] - window_height = debug_image.shape[0] - - if window_width > preview_debug_max_res or window_height > preview_debug_max_res: - # Check if the width or height exceeds the maximum limits - width_scale_factor = preview_debug_max_res / window_width - height_scale_factor = preview_debug_max_res / window_height - scale_factor = min(width_scale_factor, height_scale_factor) - else: - # Check if the width or height is below the minimum limits - width_scale_factor = preview_output_res / window_width - height_scale_factor = preview_output_res / window_height - scale_factor = max(width_scale_factor, height_scale_factor) - - new_width = int(window_width * scale_factor) - new_height = int(window_height * scale_factor) - debug_preview_image = cv2.resize(debug_image, (new_width, new_height)) - - cv2.namedWindow("Debug Image", cv2.WINDOW_AUTOSIZE) - cv2.imshow("Debug Image", debug_preview_image) - cv2.setWindowProperty("Debug Image", cv2.WND_PROP_TOPMOST, 1) - cv2.setWindowProperty("Debug Image", cv2.WND_PROP_ASPECT_RATIO, cv2.WINDOW_KEEPRATIO) - - if is_error == False: - output_preview_image = cv2.resize(resized_image, (preview_output_res, preview_output_res)) - cv2.namedWindow("Output Image", cv2.WINDOW_AUTOSIZE) - cv2.imshow("Output Image", output_preview_image) - cv2.setWindowProperty("Output Image", cv2.WND_PROP_TOPMOST, 1) - cv2.setWindowProperty("Output Image", cv2.WND_PROP_ASPECT_RATIO, cv2.WINDOW_KEEPRATIO) - - cv2.waitKey(250) # Wait time \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..e357261 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,164 @@ +#include "config.h" +#include "user_input.h" +#include "image_processing.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +// --------------------------------------------------------------------------- +// Collect all regular files from a directory +// --------------------------------------------------------------------------- +static std::vector listFiles(const std::string& dir) +{ + std::vector files; + if (!fs::exists(dir)) return files; + for (auto& entry : fs::directory_iterator(dir)) { + if (fs::is_regular_file(entry.path())) { + files.push_back(entry.path().string()); + } + } + return files; +} + +// --------------------------------------------------------------------------- +// Print summary +// --------------------------------------------------------------------------- +static void printSummary(int total, int errors, const std::string& outputFolder) +{ + int outputCount = 0; + if (fs::exists(outputFolder)) { + for (auto& e : fs::directory_iterator(outputFolder)) + if (fs::is_regular_file(e)) ++outputCount; + } + std::cout << "\nTotal images: " << total << "\n"; + std::cout << "Processed images: " << total - errors << "\n"; + std::cout << "Total faces found: " << outputCount << "\n"; + std::cout << "Error images: " << errors << "\n"; +} + +// --------------------------------------------------------------------------- +// Single-thread processing +// --------------------------------------------------------------------------- +static void runSingleThread(const CropOptions& crop, + bool showPreview) +{ + auto files = listFiles(INPUT_FOLDER); + int errorCount = 0; + int done = 0; + + for (auto& path : files) { + int err = processImage(path, + ERROR_FOLDER, + crop.outputFolder, + crop.debugOutput, + OUTPUT_RES, + PREVIEW_OUTPUT_RES, + PREVIEW_DEBUG_MAX_RES, + showPreview, + crop.cropType, + crop.topMargin, + crop.bottomMargin); + errorCount += err; + ++done; + std::cout << "\r[SINGLETHREAD] Processed " << done << "/" << files.size() << std::flush; + } + std::cout << "\n"; + cv::destroyAllWindows(); + printSummary(static_cast(files.size()), errorCount, crop.outputFolder); +} + +// --------------------------------------------------------------------------- +// Multi-thread processing (uses std::async / thread-pool via hardware_concurrency) +// --------------------------------------------------------------------------- +static void runMultiThread(const CropOptions& crop, + bool showPreview) +{ + auto files = listFiles(INPUT_FOLDER); + std::atomic totalErrors{0}; + std::atomic done{0}; + int total = static_cast(files.size()); + + // Lambda for one image + auto worker = [&](const std::string& path) { + int err = processImage(path, + ERROR_FOLDER, + crop.outputFolder, + crop.debugOutput, + OUTPUT_RES, + PREVIEW_OUTPUT_RES, + PREVIEW_DEBUG_MAX_RES, + showPreview, + crop.cropType, + crop.topMargin, + crop.bottomMargin); + totalErrors += err; + int d = ++done; + std::cout << "\r[MULTITHREAD] Processed " << d << "/" << total << std::flush; + }; + + unsigned int nThreads = std::max(1u, std::thread::hardware_concurrency()); + std::vector> futures; + futures.reserve(files.size()); + + // Simple thread-pool using async with a semaphore-like approach + std::mutex queueMtx; + size_t nextIdx = 0; + + auto threadBody = [&]() { + while (true) { + std::string path; + { + std::lock_guard lk(queueMtx); + if (nextIdx >= files.size()) return; + path = files[nextIdx++]; + } + worker(path); + } + }; + + std::vector threads; + threads.reserve(nThreads); + for (unsigned i = 0; i < nThreads; ++i) + threads.emplace_back(threadBody); + for (auto& t : threads) t.join(); + + std::cout << "\n"; + cv::destroyAllWindows(); + printSummary(total, totalErrors.load(), crop.outputFolder); +} + +// --------------------------------------------------------------------------- +int main() +{ + CropOptions crop = selectOption(); + RunOptions run = previewWindow(); + + // Ensure folders exist + for (auto& dir : { INPUT_FOLDER, + crop.outputFolder, + crop.debugOutput, + ERROR_FOLDER }) + { + fs::create_directories(dir); + } + + cleanOutput(crop.outputFolder, crop.debugOutput, ERROR_FOLDER); + + if (run.parallel) { + runMultiThread(crop, run.showPreview); + } else { + runSingleThread(crop, run.showPreview); + } + + return 0; +} diff --git a/src/main.py b/src/main.py deleted file mode 100644 index 66df1c0..0000000 --- a/src/main.py +++ /dev/null @@ -1,155 +0,0 @@ -import os -import sys -import cv2 -import multiprocessing -from image_processing import process_image -from user_input import select_option, preview_window, clean_output -import variable -from tqdm import tqdm - -input_folder = variable.input_folder -output_res = variable.output_res -error_folder = variable.error_folder -preview_output_res = variable.preview_output_res -preview_debug_max_res = variable.preview_debug_max_res - -def process_image_worker(args): - input_file, \ - error_folder, \ - output_folder, \ - debug_output, \ - output_res, \ - preview_output_res, \ - preview_debug_max_res, \ - show_preview, \ - croptype, \ - top_margin_value, \ - bottom_margin_value \ - = args - - error_count = process_image(input_file, \ - error_folder, \ - output_folder, \ - debug_output, \ - output_res, \ - preview_output_res, \ - preview_debug_max_res, \ - show_preview, \ - croptype, \ - top_margin_value, \ - bottom_margin_value) - return error_count - - -if __name__ == '__main__': - def main(): - top_margin_value, \ - bottom_margin_value, \ - debug_output, \ - output_folder, \ - croptype = select_option() - - os.makedirs(input_folder, exist_ok=True) - os.makedirs(output_folder, exist_ok=True) - os.makedirs(debug_output, exist_ok=True) - os.makedirs(error_folder, exist_ok=True) - - show_preview, parallel = preview_window() - if parallel == True: - multithread(top_margin_value, \ - bottom_margin_value, \ - debug_output, \ - output_folder, \ - croptype,\ - show_preview) - return - clean_output(output_folder, debug_output, error_folder) - - input_files = [os.path.join(input_folder, file) for file in os.listdir(input_folder)] - progress_bar = tqdm(total=len(input_files), desc="[SINGLETHREAD] Processing images", dynamic_ncols=True) - - error_count = 0 - for image_path in input_files: - error_occurred = process_image(image_path, - error_folder, - output_folder, - debug_output, - output_res, - preview_output_res, - preview_debug_max_res, - show_preview, - croptype, - top_margin_value, - bottom_margin_value) - if error_occurred: - error_count += 1 - progress_bar.update(1) - - cv2.destroyAllWindows() - - total_images = len(input_files) - total_output_images = len(os.listdir(output_folder)) - print(f"Total images: {total_images}") - print(f"Processed images: {total_images - error_count}") - print(f"Total faces detected: {total_output_images}") - print(f"Error images: {error_count}") - - return parallel - - def multithread(top_margin_value, \ - bottom_margin_value, \ - debug_output, \ - output_folder, \ - croptype,\ - show_preview): - manager = multiprocessing.Manager() - error = manager.Value("i", 0) # Shared variable to track error and error count - - if sys.platform == "win32": - multiprocessing.freeze_support() - - clean_output(output_folder, debug_output, error_folder) - - # Get a list of input files - input_files = [os.path.join(input_folder, filename) for filename in os.listdir(input_folder) if - os.path.isfile(os.path.join(input_folder, filename))] - worker_args = [ - ( - input_file, - error_folder, - output_folder, - debug_output, - output_res, - preview_output_res, - preview_debug_max_res, - show_preview, - croptype, - top_margin_value, - bottom_margin_value, - ) - for input_file in input_files - ] - - # Create a multiprocessing Pool - pool = multiprocessing.Pool() - - # Apply the worker function to each input file using the multiprocessing Pool and tqdm - with tqdm(total=len(input_files), desc="[MULTITHREAD] Processing images", dynamic_ncols=True) as progress_bar: - for error_count in pool.imap_unordered(process_image_worker, worker_args): - error.value += error_count - progress_bar.update(1) - - cv2.destroyAllWindows() - - # Close the multiprocessing Pool - pool.close() - pool.join() - - total_images = len(input_files) - total_output_images = len(os.listdir(output_folder)) - print(f"Total images: {total_images}") - print(f"Processed images: {total_images - error.value}") - print(f"Total faces detected: {total_output_images}") - print(f"Error images: {error.value}") - - main() \ No newline at end of file diff --git a/src/user_input.cpp b/src/user_input.cpp new file mode 100644 index 0000000..369e075 --- /dev/null +++ b/src/user_input.cpp @@ -0,0 +1,121 @@ +#include "user_input.h" +#include "config.h" + +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +// --------------------------------------------------------------------------- +// Helper: delete every file inside a directory tree (non-recursive removal) +// --------------------------------------------------------------------------- +static void deleteFilesIn(const std::string& dir) +{ + if (!fs::exists(dir)) return; + for (auto& entry : fs::recursive_directory_iterator(dir)) { + if (fs::is_regular_file(entry.path())) { + fs::remove(entry.path()); + } + } +} + +// --------------------------------------------------------------------------- +CropOptions selectOption() +{ + std::string option; + while (true) { + std::cout << "Select a crop type:\n" + << " 1. Upper Body\n" + << " 2. Face\n" + << " 3. Full body (try to crop as much as possible)\n" + << "Select: "; + std::cin >> option; + if (option == "1" || option == "2" || option == "3") break; + std::cout << "Invalid option selected. Please try again.\n\n"; + } + + CropOptions opts{}; + if (option == "1") { + opts.topMargin = 1.0; + opts.bottomMargin = 3.0; + opts.debugOutput = DEBUG_UPPERBODY_FOLDER; + opts.outputFolder = OUTPUT_UPPERBODY_FOLDER; + opts.cropType = 1; + } else if (option == "2") { + opts.topMargin = 0.25; + opts.bottomMargin = 0.25; + opts.debugOutput = DEBUG_FACE_FOLDER; + opts.outputFolder = OUTPUT_FACE_FOLDER; + opts.cropType = 2; + } else { + opts.topMargin = 1.0; + opts.bottomMargin = 12.0; + opts.debugOutput = DEBUG_FULLBODY_FOLDER; + opts.outputFolder = OUTPUT_FULLBODY_FOLDER; + opts.cropType = 3; + } + return opts; +} + +// --------------------------------------------------------------------------- +RunOptions previewWindow() +{ + RunOptions ro{}; + std::string answer; + while (true) { + std::cout << "Show preview window? [Y]es/[N]o: "; + std::cin >> answer; + // lowercase + std::transform(answer.begin(), answer.end(), answer.begin(), ::tolower); + if (answer == "y") { + ro.showPreview = true; + ro.parallel = false; + break; + } else if (answer == "n") { + ro.showPreview = false; + ro.parallel = true; + break; + } else { + std::cout << "Invalid option. Please enter 'Y' or 'N'.\n\n"; + } + } + return ro; +} + +// --------------------------------------------------------------------------- +void cleanOutput(const std::string& outputFolder, + const std::string& debugOutput, + const std::string& errorFolder) +{ + // Only prompt when the output folder is non-empty + while (fs::exists(outputFolder) && !fs::is_empty(outputFolder)) { + std::string answer; + std::cout << "Output folders are not empty, clean it? [Y]es/[N]o: "; + std::cin >> answer; + std::transform(answer.begin(), answer.end(), answer.begin(), ::tolower); + + if (answer == "y") { + for (int i = 5; i > 0; --i) { + std::cout << "\rDeleting files in " << i << "... [PRESS CTRL+C TO CANCEL]" << std::flush; + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + std::cout << '\n'; + + deleteFilesIn(outputFolder); + deleteFilesIn(debugOutput); + deleteFilesIn(errorFolder); + + std::cout << "Done cleaning the output folder.\n\n"; + break; + } else if (answer == "n") { + std::cout << '\n'; + break; + } else { + std::cout << "Invalid option. Please enter 'Y' or 'N'.\n\n"; + } + } +} diff --git a/src/user_input.h b/src/user_input.h new file mode 100644 index 0000000..e69de29 diff --git a/src/user_input.py b/src/user_input.py deleted file mode 100644 index 8f44f8b..0000000 --- a/src/user_input.py +++ /dev/null @@ -1,106 +0,0 @@ -import os -import time -import variable -from tqdm import tqdm - -def select_option(): - output_upperbody_folder = variable.output_upperbody_folder - debug_upperbody_folder = variable.debug_upperbody_folder - output_face_folder = variable.output_face_folder - debug_face_folder = variable.debug_face_folder - output_fullbody_folder = variable.output_fullbody_folder - debug_fullbody_folder = variable.debug_fullbody_folder - - while True: - option = input("Select a crop type:\n1. Upper Body\n2. Face\n3. Full body (try to crop as much as possible)\nSelect: ") - if option not in ["1", "2", "3"]: - print("Invalid option selected. Please try again.") - print("") - else: - break - if option == "1": - top_margin_value = 1 - bottom_margin_value = 3 - debug_output = debug_upperbody_folder - output_folder = output_upperbody_folder - croptype = 1 - elif option == "2": - top_margin_value = 0.25 - bottom_margin_value = 0.25 - debug_output = debug_face_folder - output_folder = output_face_folder - croptype = 2 - elif option == "3": - top_margin_value = 1 - bottom_margin_value = 12 - debug_output = debug_fullbody_folder - output_folder = output_fullbody_folder - croptype = 3 - return [top_margin_value, #type: ignore - bottom_margin_value, #type: ignore - debug_output, #type: ignore - output_folder, #type: ignore - croptype] #type: ignore - -def preview_window(): - show_preview = False - multithread = False - while True: - show_preview = input("Show preview window? [Y]es/[N]o: ") - if show_preview.lower() == "y": - show_preview = True - parallel = False - break - elif show_preview.lower() == "n": - show_preview = False - parallel = True - break - else: - print("Invalid option selected. Please enter 'Y' or 'N'.") - print("") - return show_preview, parallel - -def clean_output(output_folder, debug_output, error_folder): - while len(os.listdir(output_folder)) > 0: - file_exist = input("Output folders are not empty, clean it? [Y]es/[N]o: ") - if file_exist.lower() == "y": - i = 5 - while i <= 5 and i > 0: - # Add a delay of 5 seconds before proceeding with the deletion - print(f"\rDeleting files in {i}... [PRESS CTRL+C TO CANCEL]") - time.sleep(1) - i -= 1 - - # Get the total number of files - total_files = sum([len(files) for _, _, files in os.walk(output_folder)]) - - # Deleting files in the output folder and subdirectories with a progress bar - with tqdm(total=total_files, unit='file') as pbar: - for root, dirs, files in os.walk(output_folder): - for file_name in files: - file_path = os.path.join(root, file_name) - os.remove(file_path) - pbar.update(1) - - with tqdm(total=total_files, unit='file') as pbar: - for root, dirs, files in os.walk(debug_output): - for file_name in files: - file_path = os.path.join(root, file_name) - os.remove(file_path) - pbar.update(1) - - with tqdm(total=total_files, unit='file') as pbar: - for root, dirs, files in os.walk(error_folder): - for file_name in files: - file_path = os.path.join(root, file_name) - os.remove(file_path) - pbar.update(1) - - print("Done cleaning the output folder.") - print("") - elif file_exist.lower() == "n": - print("") - break # Exit the while loop - else: - print("Invalid option selected. Please enter 'Y' or 'N'.") - print("") diff --git a/src/variable.py b/src/variable.py deleted file mode 100644 index fda484a..0000000 --- a/src/variable.py +++ /dev/null @@ -1,20 +0,0 @@ -confidence_level = 0.5 - -# Output cropped resolution (pixel) -output_res = 1080 -preview_output_res = 256 -preview_debug_max_res = 640 - -# Minimun face bounding box size (pixel) -min_face_res = 96 -min_upperbody_res = 64 -min_fullbody_res = 32 - -input_folder = "input" -output_upperbody_folder = "output/upperbody_cropped" -debug_upperbody_folder = "output/upperbody_debug" -output_face_folder = "output/face_cropped" -debug_face_folder = "output/face_debug" -output_fullbody_folder = "output/fullbody_cropped" -debug_fullbody_folder = "output/fullbody_debug" -error_folder = "output/error_images" \ No newline at end of file From 9c9c54c1f193654ba14132d49ea51f38c9273e4c Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Sun, 22 Mar 2026 14:26:46 +0700 Subject: [PATCH 02/45] Update --- src/config.h | 25 +++++++++++++++++++++++++ src/user_input.h | 22 ++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/config.h b/src/config.h index e69de29..e9b41fd 100644 --- a/src/config.h +++ b/src/config.h @@ -0,0 +1,25 @@ +#pragma once +#include + +// Detection +constexpr float CONFIDENCE_LEVEL = 0.5f; + +// Output resolutions +constexpr int OUTPUT_RES = 1080; +constexpr int PREVIEW_OUTPUT_RES = 256; +constexpr int PREVIEW_DEBUG_MAX_RES = 640; + +// Minimum face bounding-box sizes (px) +constexpr int MIN_FACE_RES = 96; +constexpr int MIN_UPPERBODY_RES = 64; +constexpr int MIN_FULLBODY_RES = 32; + +// Folders +const std::string INPUT_FOLDER = "input"; +const std::string OUTPUT_UPPERBODY_FOLDER = "output/upperbody_cropped"; +const std::string DEBUG_UPPERBODY_FOLDER = "output/upperbody_debug"; +const std::string OUTPUT_FACE_FOLDER = "output/face_cropped"; +const std::string DEBUG_FACE_FOLDER = "output/face_debug"; +const std::string OUTPUT_FULLBODY_FOLDER = "output/fullbody_cropped"; +const std::string DEBUG_FULLBODY_FOLDER = "output/fullbody_debug"; +const std::string ERROR_FOLDER = "output/error_images"; diff --git a/src/user_input.h b/src/user_input.h index e69de29..f315ef7 100644 --- a/src/user_input.h +++ b/src/user_input.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include + +struct CropOptions { + double topMargin; + double bottomMargin; + std::string debugOutput; + std::string outputFolder; + int cropType; // 1=upperbody, 2=face, 3=fullbody +}; + +struct RunOptions { + bool showPreview; + bool parallel; +}; + +CropOptions selectOption(); +RunOptions previewWindow(); +void cleanOutput(const std::string& outputFolder, + const std::string& debugOutput, + const std::string& errorFolder); From 61a4f6574baa6ba65b41ee8e32b0dfdb786543cf Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Sun, 22 Mar 2026 14:47:37 +0700 Subject: [PATCH 03/45] Rewrite --- CMakeLists.txt | 27 +++ CMakelist.txt | 12 +- src/config.h | 25 --- src/image_processing.cpp | 397 --------------------------------------- src/image_processing.h | 15 -- src/main.cpp | 161 +--------------- src/user_input.cpp | 121 ------------ src/user_input.h | 22 --- 8 files changed, 33 insertions(+), 747 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 src/config.h delete mode 100644 src/image_processing.cpp delete mode 100644 src/image_processing.h delete mode 100644 src/user_input.cpp delete mode 100644 src/user_input.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cfbb77a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.16) +project(CropSense CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# ---- OpenCV ---------------------------------------------------------------- +find_package(OpenCV REQUIRED) + +# ---- Sources --------------------------------------------------------------- +add_executable(CropSense + main.cpp + image_processing.cpp + user_input.cpp +) + +target_include_directories(CropSense PRIVATE ${OpenCV_INCLUDE_DIRS}) +target_link_libraries(CropSense PRIVATE ${OpenCV_LIBS}) + +# ---- Threads --------------------------------------------------------------- +find_package(Threads REQUIRED) +target_link_libraries(CropSense PRIVATE Threads::Threads) + +# ---- Windows shell shortcut (optional) ------------------------------------ +if(WIN32) + target_compile_definitions(CropSense PRIVATE _WIN32_WINNT=0x0601) +endif() diff --git a/CMakelist.txt b/CMakelist.txt index cfbb77a..cfa908f 100644 --- a/CMakelist.txt +++ b/CMakelist.txt @@ -4,24 +4,20 @@ project(CropSense CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# ---- OpenCV ---------------------------------------------------------------- find_package(OpenCV REQUIRED) -# ---- Sources --------------------------------------------------------------- add_executable(CropSense - main.cpp - image_processing.cpp - user_input.cpp + src/main.cpp + src/image_processing.cpp + src/user_input.cpp ) -target_include_directories(CropSense PRIVATE ${OpenCV_INCLUDE_DIRS}) +target_include_directories(CropSense PRIVATE ${OpenCV_INCLUDE_DIRS} src) target_link_libraries(CropSense PRIVATE ${OpenCV_LIBS}) -# ---- Threads --------------------------------------------------------------- find_package(Threads REQUIRED) target_link_libraries(CropSense PRIVATE Threads::Threads) -# ---- Windows shell shortcut (optional) ------------------------------------ if(WIN32) target_compile_definitions(CropSense PRIVATE _WIN32_WINNT=0x0601) endif() diff --git a/src/config.h b/src/config.h deleted file mode 100644 index e9b41fd..0000000 --- a/src/config.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include - -// Detection -constexpr float CONFIDENCE_LEVEL = 0.5f; - -// Output resolutions -constexpr int OUTPUT_RES = 1080; -constexpr int PREVIEW_OUTPUT_RES = 256; -constexpr int PREVIEW_DEBUG_MAX_RES = 640; - -// Minimum face bounding-box sizes (px) -constexpr int MIN_FACE_RES = 96; -constexpr int MIN_UPPERBODY_RES = 64; -constexpr int MIN_FULLBODY_RES = 32; - -// Folders -const std::string INPUT_FOLDER = "input"; -const std::string OUTPUT_UPPERBODY_FOLDER = "output/upperbody_cropped"; -const std::string DEBUG_UPPERBODY_FOLDER = "output/upperbody_debug"; -const std::string OUTPUT_FACE_FOLDER = "output/face_cropped"; -const std::string DEBUG_FACE_FOLDER = "output/face_debug"; -const std::string OUTPUT_FULLBODY_FOLDER = "output/fullbody_cropped"; -const std::string DEBUG_FULLBODY_FOLDER = "output/fullbody_debug"; -const std::string ERROR_FOLDER = "output/error_images"; diff --git a/src/image_processing.cpp b/src/image_processing.cpp deleted file mode 100644 index d316a4e..0000000 --- a/src/image_processing.cpp +++ /dev/null @@ -1,397 +0,0 @@ -#include "image_processing.h" -#include "config.h" - -#include -#include - -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -// --------------------------------------------------------------------------- -// Windows-only: create a .lnk shortcut pointing at imagePath inside errorFolder. -// On non-Windows platforms we just copy the file instead. -// --------------------------------------------------------------------------- -static void imagesError(const std::string& imagePath, const std::string& errorFolder) -{ - #ifdef _WIN32 - // Use the Windows Script Host COM object via system() call as a fallback — - // a proper COM implementation would require + IShellLink. - // For portability we write a small PowerShell one-liner. - std::string filename = fs::path(imagePath).filename().string(); - std::string lnkPath = errorFolder + "/" + filename + ".lnk"; - std::string absTarget = fs::absolute(imagePath).string(); - - std::string cmd = - "powershell -NoProfile -Command \"" - "$s=(New-Object -COM WScript.Shell).CreateShortcut('" + lnkPath + "');" - "$s.TargetPath='" + absTarget + "';" - "$s.Save()\""; - std::system(cmd.c_str()); - #else - // On Linux/macOS, create a symbolic link (or just copy). - std::string filename = fs::path(imagePath).filename().string(); - std::string linkPath = errorFolder + "/" + filename; - std::error_code ec; - fs::create_symlink(fs::absolute(imagePath), linkPath, ec); - // ignore error if link already exists - #endif -} - -// --------------------------------------------------------------------------- -// Supported image extensions (imghdr equivalent) -// --------------------------------------------------------------------------- -static bool isSupportedImage(const std::string& path) -{ - static const std::set supported = {".jpg",".jpeg",".png",".webp"}; - std::string ext = fs::path(path).extension().string(); - std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); - return supported.count(ext) > 0; -} - -// --------------------------------------------------------------------------- -// Forward declaration -// --------------------------------------------------------------------------- -static bool drawRectangle(int endX, int startX, int endY, int startY, - double topMarginValue, double bottomMarginValue, - const cv::Mat& image, - int outputRes, - const std::string& outputFolder, - const std::string& outputImagePath, - const std::string& filename, - const std::string& imagePath, - const std::string& debugOutput, - bool showPreview, - int previewOutputRes, - int previewDebugMaxRes, - bool isError, - int i, - float confidence, - const std::string& errorMsg); - -static void preview(const cv::Mat& debugImage, - const cv::Mat& resizedImage, - int previewOutputRes, - int previewDebugMaxRes, - bool isError); - -// --------------------------------------------------------------------------- -int processImage(const std::string& imagePath, - const std::string& errorFolder, - const std::string& outputFolder, - const std::string& debugOutput, - int outputRes, - int previewOutputRes, - int previewDebugMaxRes, - bool showPreview, - int cropType, - double topMarginValue, - double bottomMarginValue) -{ - int errorCount = 0; - - static cv::dnn::Net net = cv::dnn::readNetFromCaffe( - "deploy.prototxt.txt", "res10_300x300_ssd_iter_140000.caffemodel"); - - std::string stem = fs::path(imagePath).stem().string(); - std::string ext = fs::path(imagePath).extension().string(); - - if (!isSupportedImage(imagePath)) { - std::cout << "\rInvalid or unsupported image format, skipping " << stem << ext << "\n"; - ++errorCount; - return errorCount; - } - - cv::Mat image = cv::imread(imagePath); - if (image.empty()) { - std::cout << "\rCould not read image, skipping " << stem << ext << "\n"; - ++errorCount; - return errorCount; - } - - if (image.rows <= 300 || image.cols <= 300) { - std::cout << "\rThe resolution is too low for face detection, skipping " << stem << ext << "\n"; - imagesError(imagePath, errorFolder); - ++errorCount; - return errorCount; - } - - // Prepare blob and run forward pass - cv::Mat resized300; - cv::resize(image, resized300, {300, 300}); - cv::Mat blob = cv::dnn::blobFromImage(resized300, 1.0, {300, 300}, - {104.0, 177.0, 123.0}); - net.setInput(blob); - cv::Mat detections = net.forward(); // shape: 1×1×N×7 - - // detections.ptr(0,0) is a flat array; use reshape for convenience - // Shape after reshape: (N, 7) - cv::Mat det = detections.reshape(1, detections.size[2]); // N×7 - - int N = det.rows; - bool isError = false; - std::string outputImagePath; - std::string errorMsg; - - // ---- First pass: validate the first high-confidence detection ---------- - for (int i = 0; i < N; ++i) { - float conf = det.at(i, 2); - if (conf < CONFIDENCE_LEVEL) { - std::cout << "\rConfidence level too low (" << static_cast(conf * 100) - << "%), skipping face_" << i << " on " << stem << ext << "\n"; - errorMsg = "CONFIDENCE LEVEL TOO LOW"; - imagesError(imagePath, errorFolder); - isError = true; - ++errorCount; - - float x0 = det.at(i, 3), y0 = det.at(i, 4); - float x1 = det.at(i, 5), y1 = det.at(i, 6); - int sX = static_cast(x0 * image.cols); - int sY = static_cast(y0 * image.rows); - int eX = static_cast(x1 * image.cols); - int eY = static_cast(y1 * image.rows); - - drawRectangle(eX, sX, eY, sY, - topMarginValue, bottomMarginValue, - image, outputRes, outputFolder, outputImagePath, - stem, imagePath, debugOutput, - showPreview, previewOutputRes, previewDebugMaxRes, - isError, i, conf, errorMsg); - break; - } - - if (conf >= CONFIDENCE_LEVEL) { - float x0 = det.at(i, 3), y0 = det.at(i, 4); - float x1 = det.at(i, 5), y1 = det.at(i, 6); - int sX = static_cast(x0 * image.cols); - int sY = static_cast(y0 * image.rows); - int eX = static_cast(x1 * image.cols); - int eY = static_cast(y1 * image.rows); - int w = eX - sX, h = eY - sY; - - int minRes = 0; - if (cropType == 3) minRes = MIN_FULLBODY_RES; - else if (cropType == 2) minRes = MIN_FACE_RES; - else if (cropType == 1) minRes = MIN_UPPERBODY_RES; - - if (w < minRes || h < minRes) { - std::string typeLabel = (cropType == 3) ? "fullbody" : - (cropType == 2) ? "face" : "upperbody"; - std::cout << "\rFace resolution is too small for " << typeLabel - << " crop, skipping face_" << i << " on " << stem << ext << "\n"; - errorMsg = "FACE RESOLUTION IS TOO SMALL"; - imagesError(imagePath, errorFolder); - isError = true; - ++errorCount; - drawRectangle(eX, sX, eY, sY, - topMarginValue, bottomMarginValue, - image, outputRes, outputFolder, outputImagePath, - stem, imagePath, debugOutput, - showPreview, previewOutputRes, previewDebugMaxRes, - isError, i, conf, errorMsg); - break; - } - break; - } - } - - // ---- Second pass: process each high-confidence detection --------------- - for (int i = 0; i < N && !isError; ++i) { - float conf = det.at(i, 2); - if (conf <= CONFIDENCE_LEVEL) break; - - float x0 = det.at(i, 3), y0 = det.at(i, 4); - float x1 = det.at(i, 5), y1 = det.at(i, 6); - int sX = static_cast(x0 * image.cols); - int sY = static_cast(y0 * image.rows); - int eX = static_cast(x1 * image.cols); - int eY = static_cast(y1 * image.rows); - - isError = drawRectangle(eX, sX, eY, sY, - topMarginValue, bottomMarginValue, - image, outputRes, outputFolder, outputImagePath, - stem, imagePath, debugOutput, - showPreview, previewOutputRes, previewDebugMaxRes, - isError, i, conf, errorMsg); - } - - return errorCount; -} - -// --------------------------------------------------------------------------- -static bool drawRectangle(int endX, int startX, int endY, int startY, - double topMarginValue, double bottomMarginValue, - const cv::Mat& image, - int outputRes, - const std::string& outputFolder, - const std::string& /*outputImagePath*/, - const std::string& filename, - const std::string& imagePath, - const std::string& debugOutput, - bool showPreview, - int previewOutputRes, - int previewDebugMaxRes, - bool isError, - int i, - float confidence, - const std::string& errorMsg) -{ - int squareSize = std::min(endX - startX, endY - startY); - int sqCx = (endX + startX) / 2; - int sqCy = (endY + startY) / 2; - int sqULx = sqCx - squareSize / 2; - int sqULy = sqCy - squareSize / 2; - int sqLRx = sqULx + squareSize; - int sqLRy = sqULy + squareSize; - int widthSquare = sqLRx - sqULx; - - int topMargin = static_cast(squareSize * topMarginValue); - int bottomMargin = static_cast(squareSize * bottomMarginValue); - int leftMargin = static_cast(squareSize * bottomMarginValue); - int rightMargin = static_cast(squareSize * bottomMarginValue); - - int ulX = std::max(sqULx - leftMargin, 0); - int ulY = std::max(sqULy - topMargin, 0); - int lrX = std::min(sqLRx + rightMargin, image.cols); - int lrY = std::min(sqLRy + bottomMargin, image.rows); - - // Clamp width - if ((lrX - ulX) > image.cols) { - int shift = (lrX - ulX) - image.cols; - ulX = std::max(ulX - shift, 0); - lrX = image.cols; - } - // Clamp height - if ((lrY - ulY) > image.rows) { - int shift = (lrY - ulY) - image.rows; - ulY = std::max(ulY - shift, 0); - lrY = image.rows; - } - - int sqMarginSize = std::min(lrX - ulX, lrY - ulY); - int sqMCx = (lrX + ulX) / 2; - int sqMULx = sqMCx - sqMarginSize / 2; - int sqMULy = ulY; - int sqMLRx = sqMULx + sqMarginSize; - int sqMLRy = sqMULy + sqMarginSize; - - // Centre the crop horizontally on the detected face - int faceCx = (startX + endX) / 2; - int shift = faceCx - ((sqMULx + sqMLRx) / 2); - sqMULx += shift; - sqMLRx += shift; - - sqMULx = std::max(sqMULx, 0); - sqMLRx = std::min(sqMLRx, image.cols); - sqMarginSize = sqMLRx - sqMULx; - - cv::Mat squareRegion = image(cv::Rect(sqMULx, sqMULy, sqMarginSize, sqMarginSize)); - - std::string outPath = outputFolder + "/" + filename + "_face_" + std::to_string(i) + ".png"; - cv::Mat resizedImage; - - if (squareRegion.empty()) { - isError = true; - const_cast(errorMsg) = "NO FACE DETECTED"; - } else if (!isError) { - cv::resize(squareRegion, resizedImage, {outputRes, outputRes}); - cv::imwrite(outPath, resizedImage); - } - - // ---- Debug overlay ---- - int thickness = std::max(image.cols / 128, 5); - cv::Mat debugImage = image.clone(); - - cv::rectangle(debugImage, {startX, startY}, {endX, endY}, {0, 0, 255}, thickness); - cv::rectangle(debugImage, {sqULx, sqULy}, {sqLRx, sqLRy}, {0, 255, 0}, thickness); - cv::rectangle(debugImage, - {sqMULx, sqMULy}, - {sqMULx + sqMarginSize, sqMULy + sqMarginSize}, - {255, 165, 0}, thickness); - - double fontScale = std::min(image.cols, image.rows) / 1000.0; - int fontThick = std::max(1, std::min(image.cols, image.rows) / 500); - - // First label - std::string resText = std::to_string(image.cols) + "x" + std::to_string(image.rows) - + " face_" + std::to_string(i) - + "_" + std::to_string(widthSquare) + "px" - + " (" + std::to_string(static_cast(confidence * 100)) + "%)"; - - int baseline = 0; - cv::Size ts = cv::getTextSize(resText, cv::FONT_HERSHEY_SIMPLEX, fontScale, fontThick, &baseline); - int bgW = ts.width + 10, bgH = ts.height + 10; - bgW = std::min(bgW, debugImage.cols); - - cv::Mat bg1(bgH, bgW, CV_8UC3, cv::Scalar(255, 255, 0)); - cv::putText(bg1, resText, {10, ts.height + 5}, - cv::FONT_HERSHEY_SIMPLEX, fontScale, {0, 0, 0}, fontThick); - bg1.copyTo(debugImage(cv::Rect(0, 0, bgW, bgH))); - - // Second label - std::string statusText = isError ? ("ERROR " + errorMsg) : "OK PASSED"; - cv::Scalar statusColor = isError ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); - - cv::Size ts2 = cv::getTextSize(statusText, cv::FONT_HERSHEY_SIMPLEX, fontScale, fontThick, &baseline); - int bg2W = std::min(ts2.width + 10, debugImage.cols); - int bg2H = ts2.height + 10; - - int combinedH = bgH + bg2H; - if (combinedH > debugImage.rows) { - cv::resize(debugImage, debugImage, {debugImage.cols, combinedH}); - } - - cv::Mat bg2(bg2H, bg2W, CV_8UC3, statusColor); - cv::putText(bg2, statusText, {10, ts2.height + 5}, - cv::FONT_HERSHEY_SIMPLEX, fontScale, {0, 0, 0}, fontThick); - bg2.copyTo(debugImage(cv::Rect(0, bgH, bg2W, bg2H))); - - std::string debugPath = debugOutput + "/" + filename + "_face_" + std::to_string(i) + ".jpg"; - cv::imwrite(debugPath, debugImage); - - if (showPreview) { - preview(debugImage, resizedImage, previewOutputRes, previewDebugMaxRes, isError); - } - - return isError; -} - -// --------------------------------------------------------------------------- -static void preview(const cv::Mat& debugImage, - const cv::Mat& resizedImage, - int previewOutputRes, - int previewDebugMaxRes, - bool isError) -{ - double ww = debugImage.cols, wh = debugImage.rows; - double scale; - if (ww > previewDebugMaxRes || wh > previewDebugMaxRes) { - scale = std::min(previewDebugMaxRes / ww, previewDebugMaxRes / wh); - } else { - scale = std::max(previewOutputRes / ww, previewOutputRes / wh); - } - - cv::Mat dbPrev; - cv::resize(debugImage, dbPrev, - {static_cast(ww * scale), static_cast(wh * scale)}); - - cv::namedWindow("Debug Image", cv::WINDOW_AUTOSIZE); - cv::imshow("Debug Image", dbPrev); - cv::setWindowProperty("Debug Image", cv::WND_PROP_TOPMOST, 1); - cv::setWindowProperty("Debug Image", cv::WND_PROP_ASPECT_RATIO, cv::WINDOW_KEEPRATIO); - - if (!isError && !resizedImage.empty()) { - cv::Mat outPrev; - cv::resize(resizedImage, outPrev, {previewOutputRes, previewOutputRes}); - cv::namedWindow("Output Image", cv::WINDOW_AUTOSIZE); - cv::imshow("Output Image", outPrev); - cv::setWindowProperty("Output Image", cv::WND_PROP_TOPMOST, 1); - cv::setWindowProperty("Output Image", cv::WND_PROP_ASPECT_RATIO, cv::WINDOW_KEEPRATIO); - } - - cv::waitKey(250); -} diff --git a/src/image_processing.h b/src/image_processing.h deleted file mode 100644 index 8ca20fa..0000000 --- a/src/image_processing.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include - -// Returns number of errors encountered while processing a single image file. -int processImage(const std::string& imagePath, - const std::string& errorFolder, - const std::string& outputFolder, - const std::string& debugOutput, - int outputRes, - int previewOutputRes, - int previewDebugMaxRes, - bool showPreview, - int cropType, - double topMarginValue, - double bottomMarginValue); diff --git a/src/main.cpp b/src/main.cpp index e357261..293122f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,164 +1,7 @@ -#include "config.h" -#include "user_input.h" -#include "image_processing.h" +#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -// --------------------------------------------------------------------------- -// Collect all regular files from a directory -// --------------------------------------------------------------------------- -static std::vector listFiles(const std::string& dir) -{ - std::vector files; - if (!fs::exists(dir)) return files; - for (auto& entry : fs::directory_iterator(dir)) { - if (fs::is_regular_file(entry.path())) { - files.push_back(entry.path().string()); - } - } - return files; -} - -// --------------------------------------------------------------------------- -// Print summary -// --------------------------------------------------------------------------- -static void printSummary(int total, int errors, const std::string& outputFolder) -{ - int outputCount = 0; - if (fs::exists(outputFolder)) { - for (auto& e : fs::directory_iterator(outputFolder)) - if (fs::is_regular_file(e)) ++outputCount; - } - std::cout << "\nTotal images: " << total << "\n"; - std::cout << "Processed images: " << total - errors << "\n"; - std::cout << "Total faces found: " << outputCount << "\n"; - std::cout << "Error images: " << errors << "\n"; -} - -// --------------------------------------------------------------------------- -// Single-thread processing -// --------------------------------------------------------------------------- -static void runSingleThread(const CropOptions& crop, - bool showPreview) -{ - auto files = listFiles(INPUT_FOLDER); - int errorCount = 0; - int done = 0; - - for (auto& path : files) { - int err = processImage(path, - ERROR_FOLDER, - crop.outputFolder, - crop.debugOutput, - OUTPUT_RES, - PREVIEW_OUTPUT_RES, - PREVIEW_DEBUG_MAX_RES, - showPreview, - crop.cropType, - crop.topMargin, - crop.bottomMargin); - errorCount += err; - ++done; - std::cout << "\r[SINGLETHREAD] Processed " << done << "/" << files.size() << std::flush; - } - std::cout << "\n"; - cv::destroyAllWindows(); - printSummary(static_cast(files.size()), errorCount, crop.outputFolder); -} - -// --------------------------------------------------------------------------- -// Multi-thread processing (uses std::async / thread-pool via hardware_concurrency) -// --------------------------------------------------------------------------- -static void runMultiThread(const CropOptions& crop, - bool showPreview) -{ - auto files = listFiles(INPUT_FOLDER); - std::atomic totalErrors{0}; - std::atomic done{0}; - int total = static_cast(files.size()); - - // Lambda for one image - auto worker = [&](const std::string& path) { - int err = processImage(path, - ERROR_FOLDER, - crop.outputFolder, - crop.debugOutput, - OUTPUT_RES, - PREVIEW_OUTPUT_RES, - PREVIEW_DEBUG_MAX_RES, - showPreview, - crop.cropType, - crop.topMargin, - crop.bottomMargin); - totalErrors += err; - int d = ++done; - std::cout << "\r[MULTITHREAD] Processed " << d << "/" << total << std::flush; - }; - - unsigned int nThreads = std::max(1u, std::thread::hardware_concurrency()); - std::vector> futures; - futures.reserve(files.size()); - - // Simple thread-pool using async with a semaphore-like approach - std::mutex queueMtx; - size_t nextIdx = 0; - - auto threadBody = [&]() { - while (true) { - std::string path; - { - std::lock_guard lk(queueMtx); - if (nextIdx >= files.size()) return; - path = files[nextIdx++]; - } - worker(path); - } - }; - - std::vector threads; - threads.reserve(nThreads); - for (unsigned i = 0; i < nThreads; ++i) - threads.emplace_back(threadBody); - for (auto& t : threads) t.join(); - - std::cout << "\n"; - cv::destroyAllWindows(); - printSummary(total, totalErrors.load(), crop.outputFolder); -} - -// --------------------------------------------------------------------------- int main() { - CropOptions crop = selectOption(); - RunOptions run = previewWindow(); - - // Ensure folders exist - for (auto& dir : { INPUT_FOLDER, - crop.outputFolder, - crop.debugOutput, - ERROR_FOLDER }) - { - fs::create_directories(dir); - } - - cleanOutput(crop.outputFolder, crop.debugOutput, ERROR_FOLDER); - - if (run.parallel) { - runMultiThread(crop, run.showPreview); - } else { - runSingleThread(crop, run.showPreview); - } - + std::cout<<"Hello World"< -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -// --------------------------------------------------------------------------- -// Helper: delete every file inside a directory tree (non-recursive removal) -// --------------------------------------------------------------------------- -static void deleteFilesIn(const std::string& dir) -{ - if (!fs::exists(dir)) return; - for (auto& entry : fs::recursive_directory_iterator(dir)) { - if (fs::is_regular_file(entry.path())) { - fs::remove(entry.path()); - } - } -} - -// --------------------------------------------------------------------------- -CropOptions selectOption() -{ - std::string option; - while (true) { - std::cout << "Select a crop type:\n" - << " 1. Upper Body\n" - << " 2. Face\n" - << " 3. Full body (try to crop as much as possible)\n" - << "Select: "; - std::cin >> option; - if (option == "1" || option == "2" || option == "3") break; - std::cout << "Invalid option selected. Please try again.\n\n"; - } - - CropOptions opts{}; - if (option == "1") { - opts.topMargin = 1.0; - opts.bottomMargin = 3.0; - opts.debugOutput = DEBUG_UPPERBODY_FOLDER; - opts.outputFolder = OUTPUT_UPPERBODY_FOLDER; - opts.cropType = 1; - } else if (option == "2") { - opts.topMargin = 0.25; - opts.bottomMargin = 0.25; - opts.debugOutput = DEBUG_FACE_FOLDER; - opts.outputFolder = OUTPUT_FACE_FOLDER; - opts.cropType = 2; - } else { - opts.topMargin = 1.0; - opts.bottomMargin = 12.0; - opts.debugOutput = DEBUG_FULLBODY_FOLDER; - opts.outputFolder = OUTPUT_FULLBODY_FOLDER; - opts.cropType = 3; - } - return opts; -} - -// --------------------------------------------------------------------------- -RunOptions previewWindow() -{ - RunOptions ro{}; - std::string answer; - while (true) { - std::cout << "Show preview window? [Y]es/[N]o: "; - std::cin >> answer; - // lowercase - std::transform(answer.begin(), answer.end(), answer.begin(), ::tolower); - if (answer == "y") { - ro.showPreview = true; - ro.parallel = false; - break; - } else if (answer == "n") { - ro.showPreview = false; - ro.parallel = true; - break; - } else { - std::cout << "Invalid option. Please enter 'Y' or 'N'.\n\n"; - } - } - return ro; -} - -// --------------------------------------------------------------------------- -void cleanOutput(const std::string& outputFolder, - const std::string& debugOutput, - const std::string& errorFolder) -{ - // Only prompt when the output folder is non-empty - while (fs::exists(outputFolder) && !fs::is_empty(outputFolder)) { - std::string answer; - std::cout << "Output folders are not empty, clean it? [Y]es/[N]o: "; - std::cin >> answer; - std::transform(answer.begin(), answer.end(), answer.begin(), ::tolower); - - if (answer == "y") { - for (int i = 5; i > 0; --i) { - std::cout << "\rDeleting files in " << i << "... [PRESS CTRL+C TO CANCEL]" << std::flush; - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - std::cout << '\n'; - - deleteFilesIn(outputFolder); - deleteFilesIn(debugOutput); - deleteFilesIn(errorFolder); - - std::cout << "Done cleaning the output folder.\n\n"; - break; - } else if (answer == "n") { - std::cout << '\n'; - break; - } else { - std::cout << "Invalid option. Please enter 'Y' or 'N'.\n\n"; - } - } -} diff --git a/src/user_input.h b/src/user_input.h deleted file mode 100644 index f315ef7..0000000 --- a/src/user_input.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include -#include - -struct CropOptions { - double topMargin; - double bottomMargin; - std::string debugOutput; - std::string outputFolder; - int cropType; // 1=upperbody, 2=face, 3=fullbody -}; - -struct RunOptions { - bool showPreview; - bool parallel; -}; - -CropOptions selectOption(); -RunOptions previewWindow(); -void cleanOutput(const std::string& outputFolder, - const std::string& debugOutput, - const std::string& errorFolder); From 1002fad96e6703e30ca25023eacdb39bea92a3de Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Sun, 22 Mar 2026 14:49:29 +0700 Subject: [PATCH 04/45] Update --- CMakeLists.txt | 27 --------------------------- CMakelist.txt | 23 ----------------------- 2 files changed, 50 deletions(-) delete mode 100644 CMakelist.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index cfbb77a..e69de29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,27 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(CropSense CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# ---- OpenCV ---------------------------------------------------------------- -find_package(OpenCV REQUIRED) - -# ---- Sources --------------------------------------------------------------- -add_executable(CropSense - main.cpp - image_processing.cpp - user_input.cpp -) - -target_include_directories(CropSense PRIVATE ${OpenCV_INCLUDE_DIRS}) -target_link_libraries(CropSense PRIVATE ${OpenCV_LIBS}) - -# ---- Threads --------------------------------------------------------------- -find_package(Threads REQUIRED) -target_link_libraries(CropSense PRIVATE Threads::Threads) - -# ---- Windows shell shortcut (optional) ------------------------------------ -if(WIN32) - target_compile_definitions(CropSense PRIVATE _WIN32_WINNT=0x0601) -endif() diff --git a/CMakelist.txt b/CMakelist.txt deleted file mode 100644 index cfa908f..0000000 --- a/CMakelist.txt +++ /dev/null @@ -1,23 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(CropSense CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -find_package(OpenCV REQUIRED) - -add_executable(CropSense - src/main.cpp - src/image_processing.cpp - src/user_input.cpp -) - -target_include_directories(CropSense PRIVATE ${OpenCV_INCLUDE_DIRS} src) -target_link_libraries(CropSense PRIVATE ${OpenCV_LIBS}) - -find_package(Threads REQUIRED) -target_link_libraries(CropSense PRIVATE Threads::Threads) - -if(WIN32) - target_compile_definitions(CropSense PRIVATE _WIN32_WINNT=0x0601) -endif() From 60641fafcf10594b318cda828dd22d5dd29d9b8c Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 10:28:50 +0700 Subject: [PATCH 05/45] Test OpenCV --- .clangd | 4 ++++ src/main.cpp | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .clangd diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..228352b --- /dev/null +++ b/.clangd @@ -0,0 +1,4 @@ +CompileFlags: + Add: + - "-IC:\\libraries\\opencv\\build\\include" + - "-IC:\\libraries\\opencv\\build\\include\\opencv2" diff --git a/src/main.cpp b/src/main.cpp index 293122f..9b35934 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,13 @@ -#include +#include int main() { - std::cout<<"Hello World"< Date: Mon, 23 Mar 2026 10:35:10 +0700 Subject: [PATCH 06/45] Test CMakeList.txt --- CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e69de29..5f8231e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.10) +project(CropSense-Face-Detection) + +set(CMAKE_CXX_STANDARD 17) + +find_package(OpenCV REQUIRED) + +add_executable(main src/main.cpp) + +target_link_libraries(main ${OpenCV_LIBS}) From d22cf417647f09efd1222218704be0d373f0a2b0 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 10:46:38 +0700 Subject: [PATCH 07/45] Add OpenCV_DIR --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f8231e..dc9a94a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ project(CropSense-Face-Detection) set(CMAKE_CXX_STANDARD 17) +set(OpenCV_DIR "C:\\libraries\\opencv\\build") + find_package(OpenCV REQUIRED) add_executable(main src/main.cpp) From a1e3e8d2818791b5ed7844979fe9868cacc86c71 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 10:48:20 +0700 Subject: [PATCH 08/45] Update --- src/main.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 9b35934..b57207c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,12 +2,17 @@ int main() { - // Load an image - cv::Mat img = cv::imread("image.jpg"); + cv::VideoCapture cap(0); // 0 = default webcam + cv::Mat frame; - // Show it in a window - cv::imshow("Window", img); - cv::waitKey(0); + while(true) + { + cap >> frame; + cv::imshow("Webcam", frame); + + if(cv::waitKey(1) == 27) // press ESC to quit + break; + } return 0; } From 1340dd5b081cae06f721569ad91970117ddac562 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 10:53:39 +0700 Subject: [PATCH 09/45] Add .kateproject to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c0d6fd4..b2b4a75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode +.kateproject __pycache__/* input-* !input/example*.jpg From 3582a388aaf493dbb9dbbf094a705729a32ebdcd Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 11:12:12 +0700 Subject: [PATCH 10/45] Update workflow --- .github/workflows/cpp-build.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cpp-build.yml b/.github/workflows/cpp-build.yml index cf0d05d..fbf498f 100644 --- a/.github/workflows/cpp-build.yml +++ b/.github/workflows/cpp-build.yml @@ -1,24 +1,17 @@ name: C++ Build - on: push: branches: [ main ] pull_request: branches: [ main ] - jobs: build: runs-on: windows-latest - steps: - uses: actions/checkout@v3 - - name: Install OpenCV - run: | - choco install opencv - + run: choco install opencv - name: Configure with CMake - run: cmake -B build - + run: cmake -B build -DOpenCV_DIR="C:\tools\opencv\build" - name: Build run: cmake --build build From 266105123ca3e1eb4b3e6ab490a96f711d373206 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 11:15:44 +0700 Subject: [PATCH 11/45] Update workflow --- .github/workflows/cpp-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cpp-build.yml b/.github/workflows/cpp-build.yml index fbf498f..95f9508 100644 --- a/.github/workflows/cpp-build.yml +++ b/.github/workflows/cpp-build.yml @@ -11,6 +11,8 @@ jobs: - uses: actions/checkout@v3 - name: Install OpenCV run: choco install opencv + - name: Find OpenCV path + run: dir C:\tools\opencv /s /b | findstr "OpenCVConfig.cmake" - name: Configure with CMake run: cmake -B build -DOpenCV_DIR="C:\tools\opencv\build" - name: Build From cd40e0a8629ff30dd6bda7f96afa202430f74033 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 11:20:08 +0700 Subject: [PATCH 12/45] Add guarding --- CMakeLists.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dc9a94a..811c6e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,11 @@ cmake_minimum_required(VERSION 3.10) project(CropSense-Face-Detection) - set(CMAKE_CXX_STANDARD 17) -set(OpenCV_DIR "C:\\libraries\\opencv\\build") +if(NOT DEFINED OpenCV_DIR) + set(OpenCV_DIR "C:\\libraries\\opencv\\build") +endif() find_package(OpenCV REQUIRED) - add_executable(main src/main.cpp) - target_link_libraries(main ${OpenCV_LIBS}) From 1da35e21f0a7baf5f52b19f4805d2534a0fdc39c Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 11:21:13 +0700 Subject: [PATCH 13/45] Update workflow --- .github/workflows/cpp-build.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cpp-build.yml b/.github/workflows/cpp-build.yml index 95f9508..de1ef95 100644 --- a/.github/workflows/cpp-build.yml +++ b/.github/workflows/cpp-build.yml @@ -6,14 +6,12 @@ on: branches: [ main ] jobs: build: - runs-on: windows-latest + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install OpenCV - run: choco install opencv - - name: Find OpenCV path - run: dir C:\tools\opencv /s /b | findstr "OpenCVConfig.cmake" + run: sudo apt-get install -y libopencv-dev - name: Configure with CMake - run: cmake -B build -DOpenCV_DIR="C:\tools\opencv\build" + run: cmake -B build - name: Build run: cmake --build build From 95d305ff84ff64d4e76a1b4c91e12e8118b89838 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 11:23:20 +0700 Subject: [PATCH 14/45] Update workflow --- .github/workflows/cpp-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cpp-build.yml b/.github/workflows/cpp-build.yml index de1ef95..8670a5b 100644 --- a/.github/workflows/cpp-build.yml +++ b/.github/workflows/cpp-build.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install OpenCV - run: sudo apt-get install -y libopencv-dev + run: sudo apt-get install -y --fix-missing libopencv-dev - name: Configure with CMake run: cmake -B build - name: Build From a0f9f40b1ab290ec56e1820422d02dde81d990b8 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 11:26:30 +0700 Subject: [PATCH 15/45] Add guarding --- .github/workflows/cpp-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cpp-build.yml b/.github/workflows/cpp-build.yml index 8670a5b..a4f7fe7 100644 --- a/.github/workflows/cpp-build.yml +++ b/.github/workflows/cpp-build.yml @@ -10,6 +10,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install OpenCV + run: sudo apt-get update run: sudo apt-get install -y --fix-missing libopencv-dev - name: Configure with CMake run: cmake -B build From dc43b46e40ab10d77ed73bab4354af255aee2bc4 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 11:27:56 +0700 Subject: [PATCH 16/45] Update workflow --- .github/workflows/cpp-build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cpp-build.yml b/.github/workflows/cpp-build.yml index a4f7fe7..363a739 100644 --- a/.github/workflows/cpp-build.yml +++ b/.github/workflows/cpp-build.yml @@ -10,8 +10,9 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install OpenCV - run: sudo apt-get update - run: sudo apt-get install -y --fix-missing libopencv-dev + run: | + sudo apt-get update + sudo apt-get install -y --fix-missing libopencv-dev - name: Configure with CMake run: cmake -B build - name: Build From 9891c6c67a7a2df4c1d34a8bc4c4b6ae87132e97 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 14:35:07 +0700 Subject: [PATCH 17/45] Add configuration/json --- .clangd | 1 + CMakeLists.txt | 1 + src/config.json | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 src/config.json diff --git a/.clangd b/.clangd index 228352b..6371ec0 100644 --- a/.clangd +++ b/.clangd @@ -2,3 +2,4 @@ CompileFlags: Add: - "-IC:\\libraries\\opencv\\build\\include" - "-IC:\\libraries\\opencv\\build\\include\\opencv2" + - "-IC:\\libraries\\nlohmann" diff --git a/CMakeLists.txt b/CMakeLists.txt index 811c6e2..f980a37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.10) project(CropSense-Face-Detection) set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(NOT DEFINED OpenCV_DIR) set(OpenCV_DIR "C:\\libraries\\opencv\\build") diff --git a/src/config.json b/src/config.json new file mode 100644 index 0000000..ea10dec --- /dev/null +++ b/src/config.json @@ -0,0 +1,23 @@ +{ + "confidence_level" : 0.5, + + + "output_res" : 1080, + "preview_output_res" : 256, + "preview_debug_max_res" : 640, + + + "min_face_res" : 96, + "min_upperbody_res" : 64, + "min_fullbody_res" : 32, + + + "input_folder" : "input", + "output_upperbody_folder": "output/upperbody_cropped", + "debug_upperbody_folder": "output/upperbody_debug", + "output_face_folder": "output/face_cropped", + "debug_face_folder": "output/face_debug", + "output_fullbody_folder": "output/fullbody_cropped", + "debug_fullbody_folder": "output/fullbody_debug", + "error_folder": "output/error_images" +} From 2128a8dc85ddb520177fba32a4c0be6a1bc62853 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 20:30:47 +0700 Subject: [PATCH 18/45] Add crop_config.h --- src/crop_config.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/crop_config.h diff --git a/src/crop_config.h b/src/crop_config.h new file mode 100644 index 0000000..84c88f3 --- /dev/null +++ b/src/crop_config.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +struct CropConfig +{ + float top_margin; + float bottom_margin; + std::string debug_folder; + std::string output_folder; + int crop_type; +} From 52d93067cee3a578f827416da7d51a3d0e96742c Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 20:31:12 +0700 Subject: [PATCH 19/45] Add semicolon --- src/crop_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crop_config.h b/src/crop_config.h index 84c88f3..c71c6f0 100644 --- a/src/crop_config.h +++ b/src/crop_config.h @@ -9,4 +9,4 @@ struct CropConfig std::string debug_folder; std::string output_folder; int crop_type; -} +}; From dd3743838325a901a6b4deeb235afd6986d03644 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 21:12:51 +0700 Subject: [PATCH 20/45] Update CMakeLists.txt --- CMakeLists.txt | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f980a37..8b18159 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,24 @@ cmake_minimum_required(VERSION 3.10) project(CropSense-Face-Detection) set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - if(NOT DEFINED OpenCV_DIR) set(OpenCV_DIR "C:\\libraries\\opencv\\build") endif() - find_package(OpenCV REQUIRED) -add_executable(main src/main.cpp) -target_link_libraries(main ${OpenCV_LIBS}) +add_executable(main + src/main.cpp + src/user_input.cpp + src/image_processing.cpp +) +include(FetchContent) +FetchContent_Declare(json + URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz +) +FetchContent_MakeAvailable(json) +target_include_directories(main PRIVATE ${OpenCV_INCLUDE_DIRS}) +target_link_libraries(main + ${OpenCV_LIBS} + nlohmann_json::nlohmann_json +) From 8656a34decb706859eaa05319e6e9adc561d8d10 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 21:13:17 +0700 Subject: [PATCH 21/45] Update .clangd --- .clangd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.clangd b/.clangd index 6371ec0..f539fcf 100644 --- a/.clangd +++ b/.clangd @@ -1,5 +1,5 @@ CompileFlags: - Add: - - "-IC:\\libraries\\opencv\\build\\include" - - "-IC:\\libraries\\opencv\\build\\include\\opencv2" - - "-IC:\\libraries\\nlohmann" + Add: + - "-IC:\\libraries\\opencv\\build\\include" + - "-IC:\\libraries\\opencv\\build\\include\\opencv2" + - "-IC:\\libraries\\nlohmann" From 2903e36e07b4a467650a4802ce994f5772598d4e Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 21:51:16 +0700 Subject: [PATCH 22/45] Update crop_config.h --- .clangd | 1 + CMakeLists.txt | 1 + src/crop_config.h | 2 + src/image_processing.cpp | 0 src/image_processing.h | 24 ++++++++ src/user_input.cpp | 121 +++++++++++++++++++++++++++++++++++++++ src/user_input.h | 16 ++++++ 7 files changed, 165 insertions(+) create mode 100644 src/image_processing.cpp create mode 100644 src/image_processing.h create mode 100644 src/user_input.cpp create mode 100644 src/user_input.h diff --git a/.clangd b/.clangd index f539fcf..91d311b 100644 --- a/.clangd +++ b/.clangd @@ -1,5 +1,6 @@ CompileFlags: Add: + - "-std=c++17" - "-IC:\\libraries\\opencv\\build\\include" - "-IC:\\libraries\\opencv\\build\\include\\opencv2" - "-IC:\\libraries\\nlohmann" diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b18159..09c2cf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(NOT DEFINED OpenCV_DIR) + message(WARNING "OpenCV_DIR not set, trying a default path. Set -DOpenCV_DIR=... if this fails.") set(OpenCV_DIR "C:\\libraries\\opencv\\build") endif() find_package(OpenCV REQUIRED) diff --git a/src/crop_config.h b/src/crop_config.h index c71c6f0..64f9694 100644 --- a/src/crop_config.h +++ b/src/crop_config.h @@ -6,7 +6,9 @@ struct CropConfig { float top_margin; float bottom_margin; + std::string error_folder; std::string debug_folder; std::string output_folder; + // TODO: Use Enum type Instead of hard-coding crop_type int crop_type; }; diff --git a/src/image_processing.cpp b/src/image_processing.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/image_processing.h b/src/image_processing.h new file mode 100644 index 0000000..bb695f8 --- /dev/null +++ b/src/image_processing.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include "crop_config.h" + +struct ImageProcessConfig { + int output_res; + int preview_output_res; + int preview_debug_max_res; + float confidence_level; + int min_face_res; + int min_upperbody_res; + int min_fullbody_res; +}; + +void images_error( + const std::string& image_path, + const std::string& error_folder +); +int process_image( + const std::string& image_path, + const CropConfig& crop, + const ImageProcessConfig& config +); diff --git a/src/user_input.cpp b/src/user_input.cpp new file mode 100644 index 0000000..e205e01 --- /dev/null +++ b/src/user_input.cpp @@ -0,0 +1,121 @@ +// user_input.cpp +#include "user_input.h" +#include +#include +#include +#include + +namespace fs = std::filesystem; + +CropConfig select_option() { + int option; + + while (true) { + std::cout << "Select a crop type:\n"; + std::cout << "1. Upper Body\n"; + std::cout << "2. Face\n"; + std::cout << "3. Full Body\n"; + std::cout << "Select: "; + std::cin >> option; + + if (option < 1 || option > 3) { + std::cout << "Invalid option. Please try again.\n\n"; + } else { + break; + } + } + + CropConfig config; + config.crop_type = option; + + if (option == 1) { + config.top_margin = 1.0f; + config.bottom_margin = 3.0f; + config.output_folder = "output/upperbody_cropped"; + config.debug_folder = "output/upperbody_debug"; + } else if (option == 2) { + config.top_margin = 0.25f; + config.bottom_margin = 0.25f; + config.output_folder = "output/face_cropped"; + config.debug_folder = "output/face_debug"; + } else if (option == 3) { + config.top_margin = 1.0f; + config.bottom_margin = 12.0f; + config.output_folder = "output/fullbody_cropped"; + config.debug_folder = "output/fullbody_debug"; + } + + return config; +} + + +// Are you sure this is not some bs +PreviewConfig preview_window() { + std::string input; + + while (true) { + std::cout << "Show preview window? [Y]es/[N]o: "; + std::cin >> input; + + if (input == "y" || input == "Y") { + PreviewConfig config; + config.show_preview = true; + config.parallel = false; + return config; + } else if (input == "n" || input == "N") { + PreviewConfig config; + config.show_preview = false; + config.parallel = true; + return config; + } else { + std::cout << "Invalid option. Please enter 'Y' or 'N'.\n\n"; + } + } +} + +int count_files(const std::string& folder) { + int count = 0; + for (const auto& entry : fs::recursive_directory_iterator(folder)) { + if (fs::is_regular_file(entry)) count++; + } + return count; +} + +void delete_files(const std::string& folder) { + for (const auto& entry : fs::recursive_directory_iterator(folder)) { + if (fs::is_regular_file(entry)) { + fs::remove(entry.path()); + } + } +} + +void clean_output(const std::string& output_folder, + const std::string& debug_output, + const std::string& error_folder) { + + while (!fs::is_empty(output_folder)) { + char input; + std::cout << "Output folders are not empty, clean it? [Y]es/[N]o: "; + std::cin >> input; + + if (input == 'y' || input == 'Y') { + for (int i = 5; i > 0; i--) { + std::cout << "\rDeleting files in " << i << "... [PRESS CTRL+C TO CANCEL]" << std::flush; + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + std::cout << "\n"; + + delete_files(output_folder); + delete_files(debug_output); + delete_files(error_folder); + + std::cout << "Done cleaning the output folder.\n\n"; + + } else if (input == 'n' || input == 'N') { + std::cout << "\n"; + break; + } else { + std::cout << "Invalid option. Please enter 'Y' or 'N'.\n\n"; + } + } +} diff --git a/src/user_input.h b/src/user_input.h new file mode 100644 index 0000000..1010f9f --- /dev/null +++ b/src/user_input.h @@ -0,0 +1,16 @@ +#pragma once + +#include "crop_config.h" + +struct PreviewConfig { + bool show_preview; + bool parallel; +}; + +CropConfig select_option(); +PreviewConfig preview_window(); +void clean_output( + const std::string& output_folder, + const std::string& debug_output, + const std::string& error_folder +); From c601ab7020264b7ea2e673a286fc62d31056a269 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 21:52:39 +0700 Subject: [PATCH 23/45] Update crop_config.h --- src/crop_config.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/crop_config.h b/src/crop_config.h index 64f9694..c6d059b 100644 --- a/src/crop_config.h +++ b/src/crop_config.h @@ -2,6 +2,12 @@ #include +enum class CropType { + UpperBody = 1, + Face = 2, + FullBody = 3 +}; + struct CropConfig { float top_margin; @@ -9,6 +15,5 @@ struct CropConfig std::string error_folder; std::string debug_folder; std::string output_folder; - // TODO: Use Enum type Instead of hard-coding crop_type - int crop_type; + CropType crop_type; }; From e226a34f5429f681659866649b4406890080bf40 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 22:03:35 +0700 Subject: [PATCH 24/45] Type casting --- src/user_input.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user_input.cpp b/src/user_input.cpp index e205e01..c62e2e6 100644 --- a/src/user_input.cpp +++ b/src/user_input.cpp @@ -26,7 +26,7 @@ CropConfig select_option() { } CropConfig config; - config.crop_type = option; + config.crop_type = (CropType)option; if (option == 1) { config.top_margin = 1.0f; From d6d5a82cc8df97813142a2b7e09657c0e98b5bce Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 22:04:11 +0700 Subject: [PATCH 25/45] Update --- src/crop_config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crop_config.h b/src/crop_config.h index c6d059b..b50a920 100644 --- a/src/crop_config.h +++ b/src/crop_config.h @@ -10,10 +10,10 @@ enum class CropType { struct CropConfig { - float top_margin; - float bottom_margin; std::string error_folder; std::string debug_folder; std::string output_folder; + float top_margin; + float bottom_margin; CropType crop_type; }; From 9288583bd7d0a20e16524febfba9bf79cfd7a46d Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Mon, 23 Mar 2026 22:06:16 +0700 Subject: [PATCH 26/45] Update parameters --- src/image_processing.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/image_processing.h b/src/image_processing.h index bb695f8..39fc9b5 100644 --- a/src/image_processing.h +++ b/src/image_processing.h @@ -2,6 +2,7 @@ #include #include "crop_config.h" +#include "user_input.h" struct ImageProcessConfig { int output_res; @@ -20,5 +21,6 @@ void images_error( int process_image( const std::string& image_path, const CropConfig& crop, - const ImageProcessConfig& config + const ImageProcessConfig& process_config, + const PreviewConfig& preview_config ); From de5b36c823bfa24e84779f11b21a043e2250ea3b Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 00:03:37 +0700 Subject: [PATCH 27/45] Update CMakeLists.txt --- CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 09c2cf2..aafcc12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,12 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(NOT DEFINED OpenCV_DIR) - message(WARNING "OpenCV_DIR not set, trying a default path. Set -DOpenCV_DIR=... if this fails.") - set(OpenCV_DIR "C:\\libraries\\opencv\\build") + set(OpenCV_DIR "C:/libraries/opencv/build") + message(STATUS "Using default OpenCV_DIR: ${OpenCV_DIR}") +endif() + +if(NOT EXISTS "${OpenCV_DIR}/OpenCVConfig.cmake") + message(FATAL_ERROR "OpenCVConfig.cmake not found in ${OpenCV_DIR}") endif() find_package(OpenCV REQUIRED) add_executable(main From 05ba0e0d121715c1f2fcfd014cde8ccf4700900a Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 00:04:18 +0700 Subject: [PATCH 28/45] Update CMakeLists.txt --- CMakeLists.txt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aafcc12..1b82971 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,18 @@ endif() if(NOT EXISTS "${OpenCV_DIR}/OpenCVConfig.cmake") message(FATAL_ERROR "OpenCVConfig.cmake not found in ${OpenCV_DIR}") endif() -find_package(OpenCV REQUIRED) +find_package(OpenCV QUIET) + +if(NOT OpenCV_FOUND) + message(FATAL_ERROR + "OpenCV not found!\n" + "Check these:\n" + "1. OpenCV_DIR is correct\n" + "2. OpenCV is built (lib folder exists)\n" + "3. Architecture matches (x64 vs x86)\n" + ) +endif() + add_executable(main src/main.cpp src/user_input.cpp From e32a21a795490e6c0e8094e304af4dbb8f3187a3 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 00:04:57 +0700 Subject: [PATCH 29/45] Update CMakeLists.txt --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b82971..72722c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,3 +38,7 @@ target_link_libraries(main ${OpenCV_LIBS} nlohmann_json::nlohmann_json ) + +message(STATUS "OpenCV version: ${OpenCV_VERSION}") +message(STATUS "OpenCV include dirs: ${OpenCV_INCLUDE_DIRS}") +message(STATUS "OpenCV libraries: ${OpenCV_LIBS}") From 1d28d3d9ac9fb8d4dd94775fc9bf7f3f335928ee Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 00:49:28 +0700 Subject: [PATCH 30/45] Add window-build.yml workflow --- .github/workflows/window-build.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/window-build.yml diff --git a/.github/workflows/window-build.yml b/.github/workflows/window-build.yml new file mode 100644 index 0000000..67bcc0b --- /dev/null +++ b/.github/workflows/window-build.yml @@ -0,0 +1,26 @@ +name: C++ Build + +on: +push: +branches: [ main ] +pull_request: +branches: [ main ] + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup MSVC + uses: ilammy/msvc-dev-cmd@v1 + + - name: Install Ninja + run: choco install ninja + + - name: Configure with CMake (Ninja + MSVC) + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build From c353ed5456b0a6f514bdcbe0d09f6ffdf08b8a5d Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 00:50:06 +0700 Subject: [PATCH 31/45] Rename --- .github/workflows/ubuntu-build.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/ubuntu-build.yml diff --git a/.github/workflows/ubuntu-build.yml b/.github/workflows/ubuntu-build.yml new file mode 100644 index 0000000..df15b12 --- /dev/null +++ b/.github/workflows/ubuntu-build.yml @@ -0,0 +1,19 @@ +name: C++ Build +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install OpenCV + run: | + sudo apt-get update + sudo apt-get install -y --fix-missing libopencv-dev + - name: Configure with CMake + run: cmake -B build + - name: Build + run: cmake --build build From 7f01448b5dfe57f4913982f202efd2cdb5ca2e14 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 00:50:49 +0700 Subject: [PATCH 32/45] Remove cpp-build.yml --- .github/workflows/cpp-build.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .github/workflows/cpp-build.yml diff --git a/.github/workflows/cpp-build.yml b/.github/workflows/cpp-build.yml deleted file mode 100644 index 363a739..0000000 --- a/.github/workflows/cpp-build.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: C++ Build -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install OpenCV - run: | - sudo apt-get update - sudo apt-get install -y --fix-missing libopencv-dev - - name: Configure with CMake - run: cmake -B build - - name: Build - run: cmake --build build From 60e25786fad12c59863bb536f003b5c9c74fdf31 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 00:51:24 +0700 Subject: [PATCH 33/45] Update CMakeLists.txt --- CMakeLists.txt | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 72722c3..d88eb33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,11 @@ cmake_minimum_required(VERSION 3.10) project(CropSense-Face-Detection) + set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# ---- OpenCV setup ---- if(NOT DEFINED OpenCV_DIR) set(OpenCV_DIR "C:/libraries/opencv/build") message(STATUS "Using default OpenCV_DIR: ${OpenCV_DIR}") @@ -11,34 +14,40 @@ endif() if(NOT EXISTS "${OpenCV_DIR}/OpenCVConfig.cmake") message(FATAL_ERROR "OpenCVConfig.cmake not found in ${OpenCV_DIR}") endif() -find_package(OpenCV QUIET) + +find_package(OpenCV QUIET PATHS "${OpenCV_DIR}") if(NOT OpenCV_FOUND) message(FATAL_ERROR "OpenCV not found!\n" - "Check these:\n" - "1. OpenCV_DIR is correct\n" - "2. OpenCV is built (lib folder exists)\n" - "3. Architecture matches (x64 vs x86)\n" + "Check:\n" + "- OpenCV_DIR=${OpenCV_DIR}\n" + "- Make sure x64/vc16/lib exists\n" ) endif() +# ---- Executable ---- add_executable(main src/main.cpp src/user_input.cpp src/image_processing.cpp ) + +# ---- JSON (nlohmann) ---- include(FetchContent) FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz ) FetchContent_MakeAvailable(json) -target_include_directories(main PRIVATE ${OpenCV_INCLUDE_DIRS}) + +# ---- Linking ---- target_link_libraries(main + PRIVATE ${OpenCV_LIBS} nlohmann_json::nlohmann_json ) +# ---- Debug info ---- message(STATUS "OpenCV version: ${OpenCV_VERSION}") message(STATUS "OpenCV include dirs: ${OpenCV_INCLUDE_DIRS}") message(STATUS "OpenCV libraries: ${OpenCV_LIBS}") From 6f826b57a558702231ba7e6cb07ff5de51bdc422 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 00:51:45 +0700 Subject: [PATCH 34/45] Update --- .clangd | 10 +++++----- src/main.cpp | 13 +------------ 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/.clangd b/.clangd index 91d311b..b9bdedc 100644 --- a/.clangd +++ b/.clangd @@ -1,6 +1,6 @@ CompileFlags: - Add: - - "-std=c++17" - - "-IC:\\libraries\\opencv\\build\\include" - - "-IC:\\libraries\\opencv\\build\\include\\opencv2" - - "-IC:\\libraries\\nlohmann" + Compiler: g++ + Add: + - "-std=c++17" + - "-IC:/libraries/opencv/build/include" + - "-IC:/libraries/nlohmann" diff --git a/src/main.cpp b/src/main.cpp index b57207c..45b7133 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,18 +1,7 @@ #include +#include int main() { - cv::VideoCapture cap(0); // 0 = default webcam - cv::Mat frame; - - while(true) - { - cap >> frame; - cv::imshow("Webcam", frame); - - if(cv::waitKey(1) == 27) // press ESC to quit - break; - } - return 0; } From 2c5b90b371d0a61ca04e4c5f77a3d2cd83ba7f12 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 00:55:28 +0700 Subject: [PATCH 35/45] Fix Inden --- .github/workflows/ubuntu-build.yml | 38 +++++++++++++++++------------- .github/workflows/window-build.yml | 25 ++++++++++---------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ubuntu-build.yml b/.github/workflows/ubuntu-build.yml index df15b12..91abb0c 100644 --- a/.github/workflows/ubuntu-build.yml +++ b/.github/workflows/ubuntu-build.yml @@ -1,19 +1,25 @@ name: C++ Build + on: - push: - branches: [ main ] - pull_request: - branches: [ main ] +push: +branches: [ main ] +pull_request: +branches: [ main ] + jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install OpenCV - run: | - sudo apt-get update - sudo apt-get install -y --fix-missing libopencv-dev - - name: Configure with CMake - run: cmake -B build - - name: Build - run: cmake --build build +build: +runs-on: ubuntu-latest + +steps: + - uses: actions/checkout@v3 + + - name: Install OpenCV + run: | + sudo apt-get update + sudo apt-get install -y --fix-missing libopencv-dev ninja-build + + - name: Configure with CMake + run: cmake -B build -G Ninja + + - name: Build + run: cmake --build build diff --git a/.github/workflows/window-build.yml b/.github/workflows/window-build.yml index 67bcc0b..593cae8 100644 --- a/.github/workflows/window-build.yml +++ b/.github/workflows/window-build.yml @@ -7,20 +7,21 @@ pull_request: branches: [ main ] jobs: - build: - runs-on: windows-latest +build: +runs-on: windows-latest - steps: - - uses: actions/checkout@v3 - - name: Setup MSVC - uses: ilammy/msvc-dev-cmd@v1 +steps: + - uses: actions/checkout@v3 - - name: Install Ninja - run: choco install ninja + - name: Setup MSVC + uses: ilammy/msvc-dev-cmd@v1 - - name: Configure with CMake (Ninja + MSVC) - run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + - name: Install Ninja + run: choco install ninja - - name: Build - run: cmake --build build + - name: Configure with CMake (Ninja + MSVC) + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build From dab0a60bf30455e187c8fa7e508dd1e656a47895 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 06:44:06 +0700 Subject: [PATCH 36/45] Update --- .clangd | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.clangd b/.clangd index b9bdedc..41bfdb1 100644 --- a/.clangd +++ b/.clangd @@ -1,6 +1,5 @@ CompileFlags: - Compiler: g++ Add: - "-std=c++17" - - "-IC:/libraries/opencv/build/include" - - "-IC:/libraries/nlohmann" + - "-I C:/libraries/opencv/build/include" + - "-I C:/libraries/nlohmann" From 91e6c68f838c66db5e8486d419c9b54a179bdaa9 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 06:48:31 +0700 Subject: [PATCH 37/45] Fix Inden --- .github/workflows/ubuntu-build.yml | 39 +++++++++++++--------------- .github/workflows/window-build.yml | 41 +++++++++++++----------------- 2 files changed, 36 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ubuntu-build.yml b/.github/workflows/ubuntu-build.yml index 91abb0c..94a3c87 100644 --- a/.github/workflows/ubuntu-build.yml +++ b/.github/workflows/ubuntu-build.yml @@ -1,25 +1,22 @@ -name: C++ Build +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: Ubuntu C++ Build on: -push: -branches: [ main ] -pull_request: -branches: [ main ] + push: + branches: [ main ] + pull_request: + branches: [ main ] jobs: -build: -runs-on: ubuntu-latest - -steps: - - uses: actions/checkout@v3 - - - name: Install OpenCV - run: | - sudo apt-get update - sudo apt-get install -y --fix-missing libopencv-dev ninja-build - - - name: Configure with CMake - run: cmake -B build -G Ninja - - - name: Build - run: cmake --build build + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install OpenCV + run: | + sudo apt-get update + sudo apt-get install -y --fix-missing libopencv-dev ninja-build + - name: Configure with CMake + run: cmake -B build -G Ninja + - name: Build + run: cmake --build build diff --git a/.github/workflows/window-build.yml b/.github/workflows/window-build.yml index 593cae8..aa4862c 100644 --- a/.github/workflows/window-build.yml +++ b/.github/workflows/window-build.yml @@ -1,27 +1,22 @@ -name: C++ Build +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: Window C++ build on: -push: -branches: [ main ] -pull_request: -branches: [ main ] + push: + branches: [ main ] + pull_request: + branches: [ main ] jobs: -build: -runs-on: windows-latest - - -steps: - - uses: actions/checkout@v3 - - - name: Setup MSVC - uses: ilammy/msvc-dev-cmd@v1 - - - name: Install Ninja - run: choco install ninja - - - name: Configure with CMake (Ninja + MSVC) - run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release - - - name: Build - run: cmake --build build + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: Setup MSVC + uses: ilammy/msvc-dev-cmd@v1 + - name: Install Ninja + run: choco install ninja + - name: Configure with CMake (Ninja + MSVC) + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + - name: Build + run: cmake --build build From d87b60a42c38d75fc93f286b162468d5edfe5450 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 06:52:47 +0700 Subject: [PATCH 38/45] Update CMakeLists.txt --- CMakeLists.txt | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d88eb33..449c0f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,17 +6,19 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # ---- OpenCV setup ---- -if(NOT DEFINED OpenCV_DIR) - set(OpenCV_DIR "C:/libraries/opencv/build") - message(STATUS "Using default OpenCV_DIR: ${OpenCV_DIR}") +if(WIN32) + if(NOT DEFINED OpenCV_DIR) + set(OpenCV_DIR "C:/libraries/opencv/build") + message(STATUS "Using default OpenCV_DIR: ${OpenCV_DIR}") + endif() + if(NOT EXISTS "${OpenCV_DIR}/OpenCVConfig.cmake") + message(FATAL_ERROR "OpenCVConfig.cmake not found in ${OpenCV_DIR}") + endif() + find_package(OpenCV QUIET PATHS "${OpenCV_DIR}") +else() + find_package(OpenCV REQUIRED) endif() -if(NOT EXISTS "${OpenCV_DIR}/OpenCVConfig.cmake") - message(FATAL_ERROR "OpenCVConfig.cmake not found in ${OpenCV_DIR}") -endif() - -find_package(OpenCV QUIET PATHS "${OpenCV_DIR}") - if(NOT OpenCV_FOUND) message(FATAL_ERROR "OpenCV not found!\n" From 1d343debd8d8237aa51dd569e03498d06618aa7f Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 07:00:59 +0700 Subject: [PATCH 39/45] Add path flag for window workflow --- .github/workflows/window-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/window-build.yml b/.github/workflows/window-build.yml index aa4862c..1e773d7 100644 --- a/.github/workflows/window-build.yml +++ b/.github/workflows/window-build.yml @@ -17,6 +17,6 @@ jobs: - name: Install Ninja run: choco install ninja - name: Configure with CMake (Ninja + MSVC) - run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DOpenCV_DIR="C:/tools/opencv/build" - name: Build run: cmake --build build From 2dff7db691ea3f094f1112fc401e99660f44baf8 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 07:05:45 +0700 Subject: [PATCH 40/45] Add spacing for readable --- .github/workflows/ubuntu-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ubuntu-build.yml b/.github/workflows/ubuntu-build.yml index 94a3c87..125abd4 100644 --- a/.github/workflows/ubuntu-build.yml +++ b/.github/workflows/ubuntu-build.yml @@ -12,11 +12,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Install OpenCV run: | sudo apt-get update sudo apt-get install -y --fix-missing libopencv-dev ninja-build + - name: Configure with CMake run: cmake -B build -G Ninja + - name: Build run: cmake --build build From 9abb079a52b54f6a776a0323f30511df613923f4 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 07:05:56 +0700 Subject: [PATCH 41/45] Fix --- .github/workflows/window-build.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/window-build.yml b/.github/workflows/window-build.yml index 1e773d7..181003d 100644 --- a/.github/workflows/window-build.yml +++ b/.github/workflows/window-build.yml @@ -12,11 +12,21 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v3 + - name: Setup MSVC uses: ilammy/msvc-dev-cmd@v1 + - name: Install Ninja run: choco install ninja + + - name: Install OpenCV + run: choco install opencv -y + + - name: Find OpenCV path + run: dir C:\tools\opencv + - name: Configure with CMake (Ninja + MSVC) run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DOpenCV_DIR="C:/tools/opencv/build" + - name: Build run: cmake --build build From 48cf8b6a1dfae209e4189c19f32edebc685110c4 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 07:24:42 +0700 Subject: [PATCH 42/45] Add yaml-schema for better autocomplete --- .clangd | 2 ++ src/main.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.clangd b/.clangd index 41bfdb1..af6cd9f 100644 --- a/.clangd +++ b/.clangd @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://json.schemastore.org/clangd.json + CompileFlags: Add: - "-std=c++17" diff --git a/src/main.cpp b/src/main.cpp index 45b7133..273b8ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,9 @@ #include #include +#include int main() { + return 0; } From 98ecdd850e81bd6d8ce5b8a0ad9cf5305a3c8f20 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 09:44:26 +0700 Subject: [PATCH 43/45] Update --- .clangd | 6 ++++-- src/main.cpp | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.clangd b/.clangd index af6cd9f..21ddc37 100644 --- a/.clangd +++ b/.clangd @@ -1,7 +1,9 @@ # yaml-language-server: $schema=https://json.schemastore.org/clangd.json CompileFlags: + CompilationDatabase: "build/" + Add: - "-std=c++17" - - "-I C:/libraries/opencv/build/include" - - "-I C:/libraries/nlohmann" + - "-IC:/libraries/opencv/build/include" + - "-IC:/libraries/nlohmann" diff --git a/src/main.cpp b/src/main.cpp index 273b8ab..45b7133 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,7 @@ #include #include -#include int main() { - return 0; } From 9d977f9bfd1c30fea09e5a0701e2b159999f26ac Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 09:44:47 +0700 Subject: [PATCH 44/45] Update --- src/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 45b7133..e4e49e5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,9 @@ #include #include +#include int main() { + std::ifstream f("config.json"); return 0; } From 3959c6817b8db1c773c8152c8f09e4a6fdc7a905 Mon Sep 17 00:00:00 2001 From: Mawin CK Date: Tue, 24 Mar 2026 09:51:23 +0700 Subject: [PATCH 45/45] Final --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index e4e49e5..e1e513c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,6 @@ int main() { - std::ifstream f("config.json"); + std::ifstream config("config.json"); return 0; }