From 54d5deb83258c6f6cff37166064e3686a48ee86f Mon Sep 17 00:00:00 2001 From: guanyuankai Date: Mon, 17 Nov 2025 16:39:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=A8=A1=E7=B3=8A=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=A2=9E=E5=BC=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/face_pipeline.cpp | 250 ++++++++++++++++-------------------------- src/face_pipeline.h | 2 + 2 files changed, 99 insertions(+), 153 deletions(-) diff --git a/src/face_pipeline.cpp b/src/face_pipeline.cpp index b927bb0..fd2b18f 100644 --- a/src/face_pipeline.cpp +++ b/src/face_pipeline.cpp @@ -2,12 +2,11 @@ #include #include - FacePipeline::FacePipeline(const std::string &model_dir) : m_env(ORT_LOGGING_LEVEL_WARNING, "FaceSDK"), m_memory_info( Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault)) { - m_session_options.SetIntraOpNumThreads(4); + m_session_options.SetIntraOpNumThreads(4); m_session_options.SetGraphOptimizationLevel( GraphOptimizationLevel::ORT_ENABLE_ALL); @@ -22,7 +21,6 @@ FacePipeline::FacePipeline(const std::string &model_dir) FacePipeline::~FacePipeline() {} - bool FacePipeline::LoadModels(const std::string &model_dir) { auto load_session = [&](std::unique_ptr &session, const std::string &model_name) { @@ -57,9 +55,8 @@ bool FacePipeline::LoadModels(const std::string &model_dir) { return true; } - void FacePipeline::InitMemoryAllocators() { - + auto get_io_names = [&](Ort::Session *session, std::vector &input_names, std::vector &output_names, @@ -99,7 +96,6 @@ void FacePipeline::InitMemoryAllocators() { throw std::runtime_error("Model input shape is empty"); } - std::string shape_str = "["; for (long long dim : input_shape) shape_str += std::to_string(dim) + ", "; @@ -107,13 +103,12 @@ void FacePipeline::InitMemoryAllocators() { LOGI("Model %s input shape: %s", model_name, shape_str.c_str()); if (input_shape[0] < 1) - input_shape[0] = 1; + input_shape[0] = 1; } else { LOGE("Model %s has no inputs!", model_name); } }; - get_io_names(m_session_rotator.get(), m_rot_input_names, m_rot_output_names, m_rot_input_shape, "Rotator"); get_io_names(m_session_detector.get(), m_det_input_names, m_det_output_names, @@ -129,41 +124,38 @@ void FacePipeline::InitMemoryAllocators() { get_io_names(m_session_recognizer.get(), m_rec_input_names, m_rec_output_names, m_rec_input_shape, "Recognizer"); - if (m_det_input_shape.size() < 4) { LOGE("Detector input shape has < 4 dimensions! Cannot generate anchors."); throw std::runtime_error("Detector input shape invalid"); } - + if (m_det_input_shape[2] < 0 || m_det_input_shape[3] < 0) { LOGE("Detector input shape is dynamic (H/W is -1). This is not supported " "by the Python logic."); - + LOGI("Forcing detector H/W to 640x640."); m_det_input_shape[2] = 640; m_det_input_shape[3] = 640; } generate_anchors_faceboxes(m_det_input_shape[2], m_det_input_shape[3]); - size_t max_blob_size = 0; - auto update_max = [&](const std::vector &shape, const char *model_name) { if (shape.size() <= 1) { - return; + return; } size_t s = 1; - + for (size_t i = 1; i < shape.size(); ++i) { if (shape[i] < 0) { - + LOGE("Model %s has dynamic dimension at index %zu. Skipping for " "max_blob_size calculation.", model_name, i); - return; + return; } s *= static_cast(shape[i]); } @@ -178,7 +170,6 @@ void FacePipeline::InitMemoryAllocators() { update_max(m_pose_var_input_shape, "PoseVar"); update_max(m_lm1_input_shape, "Landmarker1"); update_max(m_rec_input_shape, "Recognizer"); - if (max_blob_size == 0) { LOGE( @@ -191,7 +182,6 @@ void FacePipeline::InitMemoryAllocators() { LOGI("m_blob_buffer resized successfully."); } - void FacePipeline::image_to_blob(const cv::Mat &img, std::vector &blob, const float *mean, const float *std) { int channels = img.channels(); @@ -221,8 +211,6 @@ FacePipeline::create_tensor(const std::vector &blob_data, input_shape.data(), input_shape.size()); } - - bool FacePipeline::Extract(const cv::Mat &image, std::vector &feature) { if (!m_initialized) { LOGE("Extract failed: Pipeline is not initialized."); @@ -233,8 +221,6 @@ bool FacePipeline::Extract(const cv::Mat &image, std::vector &feature) { return false; } - - int rot_angle_code = RunRotation(image); cv::Mat upright_image; if (rot_angle_code >= 0) { @@ -243,8 +229,6 @@ bool FacePipeline::Extract(const cv::Mat &image, std::vector &feature) { upright_image = image; } - - std::vector boxes; if (!RunDetection(upright_image, boxes)) { LOGI("Extract failed: No face detected."); @@ -252,9 +236,6 @@ bool FacePipeline::Extract(const cv::Mat &image, std::vector &feature) { } FaceBox best_box = boxes[0]; - - - cv::Rect face_rect_raw(best_box.x1, best_box.y1, best_box.x2 - best_box.x1, best_box.y2 - best_box.y1); int pad_top = std::max(0, -face_rect_raw.y); @@ -273,7 +254,6 @@ bool FacePipeline::Extract(const cv::Mat &image, std::vector &feature) { face_rect_raw.y + pad_top, face_rect_raw.width, face_rect_raw.height); - if (face_rect_padded.width <= 0 || face_rect_padded.height <= 0 || face_rect_padded.x < 0 || face_rect_padded.y < 0 || face_rect_padded.x + face_rect_padded.width > face_crop_padded.cols || @@ -288,33 +268,24 @@ bool FacePipeline::Extract(const cv::Mat &image, std::vector &feature) { return false; } - - FaceLandmark landmark; if (!RunLandmark(upright_image, best_box, landmark)) { LOGI("Extract failed: Landmark detection failed."); return false; } - - cv::Mat aligned_face = RunAlignment(upright_image, landmark); if (aligned_face.empty()) { LOGI("Extract failed: Alignment produced an empty image."); return false; } - - FacePose pose; - if (!RunPose(aligned_face, pose)) - { + if (!RunPose(aligned_face, pose)) { LOGI("Extract failed: Pose estimation failed."); return false; } - - if (std::abs(pose.yaw) > m_pose_yaw_threshold || std::abs(pose.pitch) > m_pose_pitch_threshold) { LOGI("Extract failed: Face pose (Y:%.1f, P:%.1f) exceeds threshold " @@ -322,45 +293,36 @@ bool FacePipeline::Extract(const cv::Mat &image, std::vector &feature) { pose.yaw, pose.pitch, m_pose_yaw_threshold, m_pose_pitch_threshold); return false; } + cv::Mat enhanced_face_region = PreprocessSmallFace(face_region); - - - if (!CheckResolution(face_region)) { + if (!CheckResolution(enhanced_face_region)) { LOGI("Extract failed: Resolution (H:%d, W:%d) below threshold (%d, %d)", - face_region.rows, face_region.cols, m_quality_min_resolution.height, - m_quality_min_resolution.width); + enhanced_face_region.rows, enhanced_face_region.cols, + m_quality_min_resolution.height, m_quality_min_resolution.width); return false; } - - - if (!CheckBrightness(face_region)) { + if (!CheckBrightness(enhanced_face_region)) { LOGI("Extract failed: Brightness check failed (thresholds [%.1f, %.1f]).", m_quality_bright_v1, m_quality_bright_v2); return false; } - - - if (!CheckClarity(face_region)) { + if (!CheckClarity(enhanced_face_region)) { LOGI("Extract failed: Clarity check failed (threshold [%.2f]).", m_quality_clarity_low_thresh); return false; } - - if (!RunRecognition(aligned_face, feature)) { LOGI("Extract failed: Feature recognition failed."); return false; } - LOGI("Extract success."); return true; } - void FacePipeline::preprocess_rotation(const cv::Mat &image, std::vector &blob_data) { cv::Mat gray_img, resized, cropped, gray_3d; @@ -369,12 +331,10 @@ void FacePipeline::preprocess_rotation(const cv::Mat &image, int start = (256 - 224) / 2; cv::Rect crop_rect(start, start, 224, 224); cropped = resized(crop_rect); - cv::cvtColor(cropped, gray_3d, cv::COLOR_GRAY2BGR); + cv::cvtColor(cropped, gray_3d, cv::COLOR_GRAY2BGR); - const float mean[3] = {0.0f, 0.0f, 0.0f}; - const float std[3] = {1.0f / 255.0f, 1.0f / 255.0f, - 1.0f / 255.0f}; + const float std[3] = {1.0f / 255.0f, 1.0f / 255.0f, 1.0f / 255.0f}; image_to_blob(gray_3d, blob_data, mean, std); } @@ -390,7 +350,6 @@ int FacePipeline::RunRotation(const cv::Mat &image) { int max_index = std::distance(output_data, std::max_element(output_data, output_data + 4)); - if (max_index == 1) return cv::ROTATE_90_CLOCKWISE; if (max_index == 2) @@ -400,15 +359,13 @@ int FacePipeline::RunRotation(const cv::Mat &image) { return -1; } - void FacePipeline::preprocess_detection(const cv::Mat &img, std::vector &blob_data) { cv::Mat resized; cv::resize(img, resized, - cv::Size(m_det_input_shape[3], m_det_input_shape[2])); + cv::Size(m_det_input_shape[3], m_det_input_shape[2])); - - const float mean[3] = {104.0f, 117.0f, 123.0f}; + const float mean[3] = {104.0f, 117.0f, 123.0f}; const float std[3] = {1.0f, 1.0f, 1.0f}; image_to_blob(resized, blob_data, mean, std); } @@ -423,12 +380,10 @@ bool FacePipeline::RunDetection(const cv::Mat &image, auto output_tensors = m_session_detector->Run( Ort::RunOptions{nullptr}, m_det_input_names.data(), &input_tensor, 1, - m_det_output_names.data(), 2); + m_det_output_names.data(), 2); - const float *bboxes_data = - output_tensors[0].GetTensorData(); - const float *probs_data = - output_tensors[1].GetTensorData(); + const float *bboxes_data = output_tensors[0].GetTensorData(); + const float *probs_data = output_tensors[1].GetTensorData(); long num_anchors = output_tensors[0].GetTensorTypeAndShapeInfo().GetShape()[1]; @@ -439,10 +394,10 @@ bool FacePipeline::RunDetection(const cv::Mat &image, } std::vector bbox_collection; - const float variance[2] = {0.1f, 0.2f}; + const float variance[2] = {0.1f, 0.2f}; for (long i = 0; i < num_anchors; ++i) { - float conf = probs_data[i * 2 + 1]; + float conf = probs_data[i * 2 + 1]; if (conf < m_det_threshold) continue; @@ -452,24 +407,23 @@ bool FacePipeline::RunDetection(const cv::Mat &image, float dw = bboxes_data[i * 4 + 2]; float dh = bboxes_data[i * 4 + 3]; - float cx = anchor.cx + dx * variance[0] * anchor.s_kx; - float cy = anchor.cy + dy * variance[0] * anchor.s_ky; - float w = anchor.s_kx * std::exp(dw * variance[1]); - float h = anchor.s_ky * std::exp(dh * variance[1]); + float cx = anchor.cx + dx * variance[0] * anchor.s_kx; + float cy = anchor.cy + dy * variance[0] * anchor.s_ky; + float w = anchor.s_kx * std::exp(dw * variance[1]); + float h = anchor.s_ky * std::exp(dh * variance[1]); bbox_collection.push_back( {(cx - w / 2.0f) * img_width, (cy - h / 2.0f) * img_height, (cx + w / 2.0f) * img_width, (cy + h / 2.0f) * img_height, conf}); } - boxes = hard_nms(bbox_collection, m_det_iou_threshold, - m_det_topk); + boxes = hard_nms(bbox_collection, m_det_iou_threshold, m_det_topk); return !boxes.empty(); } void FacePipeline::generate_anchors_faceboxes(int target_height, int target_width) { - + m_anchors.clear(); std::vector steps = {32, 64, 128}; std::vector> min_sizes = {{32, 64, 128}, {256}, {512}}; @@ -516,13 +470,9 @@ void FacePipeline::generate_anchors_faceboxes(int target_height, } } - void FacePipeline::preprocess_pose(const cv::Mat &img, std::vector &blob_data) { - - - - + float pad = 0.3f; int h = img.rows; int w = img.cols; @@ -535,27 +485,23 @@ void FacePipeline::preprocess_pose(const cv::Mat &img, img.copyTo(canvas(cv::Rect(nx1, ny1, w, h))); cv::Mat resized; - cv::resize( - canvas, resized, - cv::Size(m_pose_var_input_shape[3], m_pose_var_input_shape[2])); + cv::resize(canvas, resized, + cv::Size(m_pose_var_input_shape[3], m_pose_var_input_shape[2])); - const float mean[3] = {127.5f, 127.5f, 127.5f}; const float std[3] = {1.0f / 127.5f, 1.0f / 127.5f, 1.0f / 127.5f}; image_to_blob(resized, blob_data, mean, std); } bool FacePipeline::RunPose(const cv::Mat &face_input, FacePose &pose) { - + preprocess_pose(face_input, m_blob_buffer); - auto input_tensor_var = create_tensor(m_blob_buffer, m_pose_var_input_shape); auto output_var = m_session_pose_var->Run( Ort::RunOptions{nullptr}, m_pose_var_input_names.data(), &input_tensor_var, 1, m_pose_var_output_names.data(), 1); - auto input_tensor_conv = create_tensor(m_blob_buffer, m_pose_conv_input_shape); auto output_conv = m_session_pose_conv->Run( @@ -565,28 +511,24 @@ bool FacePipeline::RunPose(const cv::Mat &face_input, FacePose &pose) { const float *data_var = output_var[0].GetTensorData(); const float *data_conv = output_conv[0].GetTensorData(); - pose.yaw = (data_var[0] + data_conv[0]) / 2.0f; pose.pitch = (data_var[1] + data_conv[1]) / 2.0f; pose.roll = (data_var[2] + data_conv[2]) / 2.0f; return true; } - void FacePipeline::preprocess_landmark_net1(const cv::Mat &img, std::vector &blob_data) { cv::Mat resized, gray_img; cv::resize(img, resized, - cv::Size(m_lm1_input_shape[3], m_lm1_input_shape[2])); - cv::cvtColor(resized, gray_img, cv::COLOR_BGR2GRAY); + cv::Size(m_lm1_input_shape[3], m_lm1_input_shape[2])); + cv::cvtColor(resized, gray_img, cv::COLOR_BGR2GRAY); - const float mean[1] = {0.0f}; const float std[1] = {1.0f}; image_to_blob(gray_img, blob_data, mean, std); } - std::vector FacePipeline::shape_index_process(const Ort::Value &feat_val, const Ort::Value &pos_val) { @@ -595,13 +537,13 @@ FacePipeline::shape_index_process(const Ort::Value &feat_val, const float *feat_data = feat_val.GetTensorData(); const float *pos_data = pos_val.GetTensorData(); - long feat_n = feat_shape[0]; + long feat_n = feat_shape[0]; long feat_c = feat_shape[1]; long feat_h = feat_shape[2]; long feat_w = feat_shape[3]; - long pos_n = pos_shape[0]; - long landmark_x2 = pos_shape[1]; - int landmark_num = landmark_x2 / 2; + long pos_n = pos_shape[0]; + long landmark_x2 = pos_shape[1]; + int landmark_num = landmark_x2 / 2; float m_origin[] = {112.0f, 112.0f}; float m_origin_patch[] = {15.0f, 15.0f}; @@ -655,7 +597,7 @@ FacePipeline::shape_index_process(const Ort::Value &feat_val, bool FacePipeline::RunLandmark(const cv::Mat &image, const FaceBox &box, FaceLandmark &landmark) { - + cv::Rect face_rect_raw(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1); int pad_top = std::max(0, -face_rect_raw.y); int pad_bottom = @@ -671,41 +613,34 @@ bool FacePipeline::RunLandmark(const cv::Mat &image, const FaceBox &box, face_rect_raw.height); cv::Mat face_crop = face_crop_padded(face_rect_padded); - preprocess_landmark_net1(face_crop, m_blob_buffer); auto input_tensor_net1 = create_tensor(m_blob_buffer, m_lm1_input_shape); - auto output_net1 = m_session_landmarker1->Run( Ort::RunOptions{nullptr}, m_lm1_input_names.data(), &input_tensor_net1, 1, - m_lm1_output_names.data(), 2); + m_lm1_output_names.data(), 2); - std::vector shape_index_blob = shape_index_process(output_net1[0], output_net1[1]); - auto input_tensor_net2 = Ort::Value::CreateTensor( m_memory_info, shape_index_blob.data(), shape_index_blob.size(), m_lm2_input_shape.data(), m_lm2_input_shape.size()); - auto output_net2 = m_session_landmarker2->Run( Ort::RunOptions{nullptr}, m_lm2_input_names.data(), &input_tensor_net2, 1, m_lm2_output_names.data(), 1); - const float *data_net1_pos = output_net1[1].GetTensorData(); const float *data_net2 = output_net2[0].GetTensorData(); - auto shape_net1_pos = - output_net1[1].GetTensorTypeAndShapeInfo().GetShape(); + auto shape_net1_pos = output_net1[1].GetTensorTypeAndShapeInfo().GetShape(); int landmark_x2 = shape_net1_pos[1]; float scale_x = (box.x2 - box.x1) / 112.0f; float scale_y = (box.y2 - box.y1) / 112.0f; for (int i = 0; i < 5; ++i) { - + float x_norm = (data_net2[i * 2 + 0] + data_net1_pos[i * 2 + 0]) * 112.0f; float y_norm = (data_net2[i * 2 + 1] + data_net1_pos[i * 2 + 1]) * 112.0f; @@ -719,10 +654,9 @@ bool FacePipeline::RunLandmark(const cv::Mat &image, const FaceBox &box, return true; } - cv::Mat FacePipeline::RunAlignment(const cv::Mat &image, const FaceLandmark &landmark) { - + std::vector src_points; std::vector dst_points; @@ -732,49 +666,40 @@ cv::Mat FacePipeline::RunAlignment(const cv::Mat &image, m_landmark_template.at(i, 1))); } - - - cv::Mat transform_matrix = cv::estimateAffinePartial2D(src_points, dst_points); cv::Mat aligned_face; - - + cv::warpAffine(image, aligned_face, transform_matrix, m_align_output_size, cv::INTER_LINEAR); return aligned_face; } - void FacePipeline::preprocess_recognition(const cv::Mat &img, std::vector &blob_data) { cv::Mat resized, rgb_img; const cv::Size target_size(248, 248); - cv::resize(img, resized, target_size); - cv::cvtColor(resized, rgb_img, cv::COLOR_BGR2RGB); - const float mean[3] = {0.0f, 0.0f, 0.0f}; const float std[3] = {1.0f, 1.0f, 1.0f}; image_to_blob(rgb_img, blob_data, mean, std); } void FacePipeline::normalize_sqrt_l2(std::vector &v) { - + double norm = 0.0; for (float &val : v) { - val = std::sqrt(std::max(0.0f, val)); + val = std::sqrt(std::max(0.0f, val)); norm += val * val; } - if (norm > 1e-6) { norm = std::sqrt(norm); for (float &val : v) { @@ -785,19 +710,13 @@ void FacePipeline::normalize_sqrt_l2(std::vector &v) { bool FacePipeline::RunRecognition(const cv::Mat &aligned_face, std::vector &feature) { - - preprocess_recognition(aligned_face, m_blob_buffer); - - - const std::vector hardcoded_shape = {1, 3, 248, 248}; + const std::vector hardcoded_shape = {1, 3, 248, 248}; - auto input_tensor = create_tensor(m_blob_buffer, hardcoded_shape); - auto output_tensors = m_session_recognizer->Run( Ort::RunOptions{nullptr}, m_rec_input_names.data(), &input_tensor, 1, m_rec_output_names.data(), 1); @@ -809,15 +728,11 @@ bool FacePipeline::RunRecognition(const cv::Mat &aligned_face, feature.resize(feature_dim); memcpy(feature.data(), output_data, feature_dim * sizeof(float)); - normalize_sqrt_l2(feature); return true; } - - - bool FacePipeline::CheckResolution(const cv::Mat &face_region) { if (face_region.rows < m_quality_min_resolution.height || face_region.cols < m_quality_min_resolution.width) { @@ -826,7 +741,6 @@ bool FacePipeline::CheckResolution(const cv::Mat &face_region) { return true; } - bool FacePipeline::CheckBrightness(const cv::Mat &face_region) { cv::Mat gray; if (face_region.channels() == 3) @@ -836,17 +750,14 @@ bool FacePipeline::CheckBrightness(const cv::Mat &face_region) { float bright_value = grid_max_bright(gray, 3, 3); - return (bright_value >= m_quality_bright_v1 && bright_value <= m_quality_bright_v2); } - float FacePipeline::grid_max_bright(const cv::Mat &gray_img, int rows, int cols) { float max_bright = 0.0f; - if (rows == 0 || cols == 0) return 0.0f; int row_height = gray_img.rows / rows; @@ -867,14 +778,12 @@ float FacePipeline::grid_max_bright(const cv::Mat &gray_img, int rows, return max_bright; } - bool FacePipeline::CheckClarity(const cv::Mat &face_region) { float clarity = clarity_estimate(face_region); - + return (clarity >= m_quality_clarity_low_thresh); } - float FacePipeline::clarity_estimate(const cv::Mat &image) { cv::Mat gray; if (image.channels() == 3) @@ -885,21 +794,19 @@ float FacePipeline::clarity_estimate(const cv::Mat &image) { float blur_val = grid_max_reblur(gray, 2, 2); float clarity = 1.0f - blur_val; - return std::max(0.0f, std::min(1.0f, clarity)); } - float FacePipeline::grid_max_reblur(const cv::Mat &img, int rows, int cols) { - + int row_height = img.rows / rows; int col_width = img.cols / cols; if (row_height == 0 || col_width == 0) - return 1.0f; + return 1.0f; float max_blur_val = -FLT_MAX; cv::Mat data_float; - img.convertTo(data_float, CV_32F); + img.convertTo(data_float, CV_32F); for (int y = 0; y < rows; ++y) { for (int x = 0; x < cols; ++x) { @@ -916,14 +823,13 @@ float FacePipeline::grid_max_reblur(const cv::Mat &img, int rows, int cols) { return std::max(max_blur_val, 0.0f); } - float FacePipeline::reblur(const cv::Mat &data) { - - if (data.rows <= 1 || data.cols <= 1) - return 1.0f; - cv::Mat kernel_v = cv::Mat::ones(9, 1, CV_32F) / 9.0f; - cv::Mat kernel_h = cv::Mat::ones(1, 9, CV_32F) / 9.0f; + if (data.rows <= 1 || data.cols <= 1) + return 1.0f; + + cv::Mat kernel_v = cv::Mat::ones(9, 1, CV_32F) / 9.0f; + cv::Mat kernel_h = cv::Mat::ones(1, 9, CV_32F) / 9.0f; cv::Mat BVer, BHor; cv::filter2D(data, BVer, CV_32F, kernel_v, cv::Point(-1, -1), 0, @@ -952,4 +858,42 @@ float FacePipeline::reblur(const cv::Mat &data) { (s_FHor > 1e-6) ? static_cast((s_FHor - s_VHor) / s_FHor) : 0.0f; return std::max(b_FVer, b_FHor); +} + +cv::Mat FacePipeline::PreprocessSmallFace(const cv::Mat &face_region) { + int h = face_region.rows; + int w = face_region.cols; + + if (h >= m_quality_min_resolution.height && + w >= m_quality_min_resolution.width) { + return face_region; + } + + LOGI("PreprocessSmallFace: Input (H:%d, W:%d) is small. Enhancing...", h, w); + + float scale_w = (w < m_quality_min_resolution.width) + ? (float)m_quality_min_resolution.width / w + : 1.0f; + float scale_h = (h < m_quality_min_resolution.height) + ? (float)m_quality_min_resolution.height / h + : 1.0f; + float scale = std::max(scale_w, scale_h); + + int new_width = static_cast(w * scale); + int new_height = static_cast(h * scale); + + cv::Mat resized; + + cv::resize(face_region, resized, cv::Size(new_width, new_height), 0, 0, + cv::INTER_CUBIC); + + cv::Mat blurred; + + cv::GaussianBlur(resized, blurred, cv::Size(0, 0), 2.0); + + cv::Mat sharpened; + + cv::addWeighted(resized, 1.5, blurred, -0.5, 0, sharpened); + + return sharpened; } \ No newline at end of file diff --git a/src/face_pipeline.h b/src/face_pipeline.h index 87ef5df..c6db0fc 100644 --- a/src/face_pipeline.h +++ b/src/face_pipeline.h @@ -197,4 +197,6 @@ private: const float m_quality_bright_v2 = 230.0f; const float m_quality_clarity_low_thresh = 0.10f; + + cv::Mat PreprocessSmallFace(const cv::Mat &face_region); }; \ No newline at end of file