You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS下OpenCV Objective-C++鼻子缩放仅改裁剪区域而非直接调面部问题

iOS Objective-C++结合OpenCV实现自然鼻子缩放的解决方案

问题现状

我在iOS平台使用Objective-C++结合OpenCV开发面部鼻子缩放功能,目标是实现鼻子的自然拉长/缩短,但当前代码仅裁剪鼻子区域修改尺寸后叠加回原图,效果生硬(要么覆盖下方面部,要么留下空白),无法达到自然变形的效果。

当前代码片段:

+ (UIImage *)makeNoseLongerInImage:(UIImage *)image withFactor:(int)scaleFactor {
    cv::Mat mat;
    [image convertToMat:&mat :false];

    NSString *path = [[NSBundle mainBundle] pathForResource:@"haarcascade_frontalface_default" ofType:@"xml"];
    cv::CascadeClassifier face_cascade;
    if (!face_cascade.load([path UTF8String])) {
        NSLog(@"Failed to load face cascade from path: %@", path);
        return image;
    }

    std::vector<cv::Rect> faces;
    face_cascade.detectMultiScale(mat, faces, 1.1, 2, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30));

    for (const cv::Rect& face : faces) {
        cv::Rect noseArea(
            std::max(0, face.x + static_cast<int>(face.width * 0.25)),
            std::max(0, face.y + static_cast<int>(face.height * 0.60)),
            std::min(mat.cols - face.x, static_cast<int>(face.width * 0.50)),
            std::min(mat.rows - face.y, static_cast<int>(face.height * 0.12))
        );

        if (noseArea.width == 0 || noseArea.height == 0) {
            NSLog(@"nose area computed with zero width or height.");
            continue;
        }

        cv::Mat noseRegion = mat(noseArea);
        int newHeight = std::max(1, static_cast<int>(noseRegion.rows * scaleFactor / 100.0));
        cv::Mat resizednoseRegion;
        cv::resize(noseRegion, resizednoseRegion, cv::Size(noseRegion.cols, newHeight), 0, 0, cv::INTER_LINEAR);

        NSLog(@"Original nose region: (%d, %d), Resized: (%d, %d)", noseRegion.cols, noseRegion.rows, resizednoseRegion.cols, resizednoseRegion.rows);

        // Place the resized nose region back into the original mat
        if (!resizednoseRegion.empty() && resizednoseRegion.cols == noseRegion.cols && resizednoseRegion.rows == newHeight) {
            // Update the noseArea to match the resized dimensions
            cv::Rect newnoseArea = noseArea;
            newnoseArea.height = newHeight; // Update height to match resized region
            resizednoseRegion.copyTo(mat(newnoseArea));
        } else {
            NSLog(@"Mismatch in dimensions after resize or empty resized region.");
        }
    }

    return MatToUIImage(mat);
}

效果对比

  • 当前输出效果:
    当前输出效果
  • 期望实现的效果:
    期望实现的效果

解决方案

核心思路

放弃简单的裁剪替换,改用面部关键点驱动的局部图像变形,让鼻子及周围区域自然过渡,避免生硬的覆盖/空白。

具体步骤

  1. 替换面部检测方式:用OpenCV的Facemark模型(如LBF)替代Haar cascade,获取精准的面部关键点(尤其是鼻子区域的锚点:鼻尖、鼻翼、鼻底等)。
  2. 构建变形网格:基于鼻子关键点的原始位置和目标位置(拉长时下移鼻尖、调整鼻底;缩短时上移鼻尖),生成变形网格。
  3. 局部图像变形:使用Thin Plate Spline(TPS)或三角剖分仿射变换,对鼻子及周围小范围区域进行变形,保证面部整体连贯性。

修改后的代码示例(核心部分)

+ (UIImage *)makeNoseLongerInImage:(UIImage *)image withFactor:(float)scaleFactor {
    cv::Mat mat;
    [image convertToMat:&mat :false];
    cv::Mat gray;
    cv::cvtColor(mat, gray, cv::COLOR_BGR2GRAY);

    // 加载面部检测器和关键点模型
    NSString *faceCascadePath = [[NSBundle mainBundle] pathForResource:@"haarcascade_frontalface_default" ofType:@"xml"];
    cv::CascadeClassifier faceCascade;
    if (!faceCascade.load([faceCascadePath UTF8String])) {
        NSLog(@"Failed to load face cascade");
        return image;
    }

    NSString *facemarkPath = [[NSBundle mainBundle] pathForResource:@"lbfmodel.yaml" ofType:@"yaml"];
    cv::Ptr<cv::face::Facemark> facemark = cv::face::FacemarkLBF::create();
    if (!facemark->loadModel([facemarkPath UTF8String])) {
        NSLog(@"Failed to load facemark model");
        return image;
    }

    // 检测面部
    std::vector<cv::Rect> faces;
    faceCascade.detectMultiScale(gray, faces, 1.1, 2, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30));
    std::vector<std::vector<cv::Point2f>> landmarks;
    if (!facemark->fit(mat, faces, landmarks)) {
        NSLog(@"Failed to detect landmarks");
        return image;
    }

    for (size_t i = 0; i < faces.size(); i++) {
        std::vector<cv::Point2f> noseLandmarks;
        // 提取鼻子区域关键点(OpenCV Facemark LBF中27-35为鼻子关键点)
        for (int j = 27; j <= 35; j++) {
            noseLandmarks.push_back(landmarks[i][j]);
        }

        // 计算鼻子中心和变形偏移
        cv::Point2f tip = noseLandmarks[6]; // 鼻尖点
        cv::Point2f base = noseLandmarks[0]; // 鼻底中点
        float noseLength = cv::norm(tip - base);
        float newLength = noseLength * scaleFactor;
        float offsetY = newLength - noseLength;

        // 构建目标关键点:鼻尖下移,鼻底保持,中间点线性过渡
        std::vector<cv::Point2f> targetNoseLandmarks = noseLandmarks;
        targetNoseLandmarks[6].y += offsetY; // 移动鼻尖
        // 中间点(28-34)按比例偏移
        for (int j = 1; j < 6; j++) {
            float ratio = (float)j / 6.0;
            targetNoseLandmarks[j].y += offsetY * ratio;
        }

        // 准备全局关键点(加入面部其他锚点避免整体变形)
        std::vector<cv::Point2f> srcPoints, dstPoints;
        srcPoints.insert(srcPoints.end(), noseLandmarks.begin(), noseLandmarks.end());
        dstPoints.insert(dstPoints.end(), targetNoseLandmarks.begin(), targetNoseLandmarks.end());
        // 加入面部轮廓点增强稳定性(如下巴、眼角)
        srcPoints.push_back(landmarks[i][0]); // 左眼角
        srcPoints.push_back(landmarks[i][16]); // 右眼角
        srcPoints.push_back(landmarks[i][8]); // 下巴尖
        dstPoints.push_back(landmarks[i][0]);
        dstPoints.push_back(landmarks[i][16]);
        dstPoints.push_back(landmarks[i][8]);

        // 生成TPS变形矩阵并应用
        cv::Mat warpMat = cv::createThinPlateSplineShapeTransformer()->estimateTransformation(dstPoints, srcPoints);
        cv::Mat warpedMat;
        cv::warpPerspective(mat, warpedMat, warpMat, mat.size());

        // 融合变形区域和原图(可选,增强自然度)
        cv::Mat mask = cv::Mat::zeros(mat.size(), CV_8UC1);
        cv::fillConvexPoly(mask, cv::Mat(noseLandmarks), cv::Scalar(255));
        cv::dilate(mask, mask, cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(15,15)));
        warpedMat.copyTo(mat, mask);
    }

    return MatToUIImage(mat);
}

注意事项

  • 需要提前下载OpenCV的LBF关键点模型(lbfmodel.yaml)放入项目资源。
  • 调整scaleFactor参数时,建议限制在0.7-1.5之间,避免过度变形导致面部失真。
  • 可优化融合步骤,用渐变mask让变形区域和原图过渡更自然。

内容的提问来源于stack exchange,提问作者Diana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 08:36:20