opencv如何在仿射变换后保留完整图像内容并自动裁剪

背景

我需要对一张图像应用仿射变换,并确保变换后的图像内容不发生信息丢失。为此,需要根据变换后图像的四个角点,计算其最小外接矩形,并以此矩形为区域裁剪变换后的图像。目标是获得一个完整、有效保留原始图像信息的最小包围图像区域。

代码如下

cpp 复制代码
int affineTransform(
    const std::string& srcImagePath,
    const std::string& dstImagePath,
    const Point* srcPoints,
    const Point* dstPoints,
    double* affineParams,
    int debugFlag
) {
    // 1. 加载原始图像
    cv::Mat srcImage = cv::imread(srcImagePath, cv::IMREAD_UNCHANGED);
    if (srcImage.empty()) {
        std::cerr << "Failed to load image: " << srcImagePath << std::endl;
        return -1;
    }

    // 2. 输入点校验
    if (!srcPoints || !dstPoints) return -2;
    constexpr int REQUIRED_POINTS = 3;
    std::vector<cv::Point2f> srcPts, dstPts;
    for (int i = 0; i < REQUIRED_POINTS; ++i) {
        srcPts.emplace_back(srcPoints[i].x, srcPoints[i].y);
        dstPts.emplace_back(dstPoints[i].x, dstPoints[i].y);
    }

    // 3. 获取仿射变换矩阵并提取参数
    cv::Mat affineMat = cv::getAffineTransform(srcPts, dstPts);
    extractAffineParameters(affineMat, affineParams);

    // 4. 原图四角及变换后的边界框
    std::vector<cv::Point2f> corners = {
        {0, 0},
        {static_cast<float>(srcImage.cols), 0},
        {static_cast<float>(srcImage.cols), static_cast<float>(srcImage.rows)},
        {0, static_cast<float>(srcImage.rows)}
    };
    std::vector<cv::Point2f> transformedCorners;
    cv::transform(corners, transformedCorners, affineMat);
    cv::Rect boundingBox = cv::boundingRect(transformedCorners);

    // 5. 计算需要填充的边界
    int padLeft = boundingBox.x < 0 ? -boundingBox.x : 0;
    int padTop  = boundingBox.y < 0 ? -boundingBox.y : 0;

    // 6. 根据通道数设置白色边界
    cv::Scalar borderColor;
    switch (srcImage.channels()) {
        case 1: borderColor = cv::Scalar(255); break;
        case 3: borderColor = cv::Scalar(255, 255, 255); break;
        case 4: borderColor = cv::Scalar(255, 255, 255, 255); break;
        default: throw std::runtime_error("Unsupported number of channels.");
    }

    // 7. 执行图像扩展
    cv::Mat paddedImage;
    cv::copyMakeBorder(srcImage, paddedImage, padTop, 0, padLeft, 0, cv::BORDER_CONSTANT, borderColor);

    // 8. 更新坐标后重新计算变换矩阵
    cv::Point2f offset(padLeft, padTop);
    for (auto& pt : srcPts) pt += offset;
    for (auto& pt : dstPts) pt += offset;
    cv::Mat adjustedAffineMat = cv::getAffineTransform(srcPts, dstPts);

    // 9. 仿射变换到目标图像
    cv::Size dstSize(boundingBox.width + padLeft, boundingBox.height + padTop);
    cv::Mat dstImage;
    cv::warpAffine(paddedImage, dstImage, adjustedAffineMat, dstSize, cv::INTER_LINEAR, cv::BORDER_CONSTANT, borderColor);

    // 10. 如果变换后图像偏移,进行裁剪
    if (boundingBox.x > 0 || boundingBox.y > 0) {
    	boundingBox.x = std::max(boundingBox.x,0);
    	boundingBox.y = std::max(boundingBox.y,0);
        int cropX = std::min(boundingBox.x, dstImage.cols);
        int cropY = std::min(boundingBox.y, dstImage.rows);
        int cropWidth  = std::max(0, dstImage.cols - cropX);
        int cropHeight = std::max(0, dstImage.rows - cropY);
        cv::Rect validRoi(cropX, cropY, cropWidth, cropHeight);
        dstImage = dstImage(validRoi).clone();
    }

    // 11. 调试信息
    if (debugFlag) {
        std::cout << "[AffineTransform Debug Info]\n";
        std::cout << "Original Corners: "; printPoints(corners);
        std::cout << "Transformed Corners: "; printPoints(transformedCorners);
        std::cout << "Bounding Box: x=" << boundingBox.x << ", y=" << boundingBox.y
                  << ", w=" << boundingBox.width << ", h=" << boundingBox.height << "\n";
        std::cout << "Padding: left=" << padLeft << ", top=" << padTop << "\n";
        std::cout << "Affine Matrix:\n" << affineMat << "\n";
        std::cout << "Adjusted Affine Matrix:\n" << adjustedAffineMat << "\n";
        std::cout << "Output Size: " << dstImage.cols << " x " << dstImage.rows << "\n";
    }

    // 12. 保存图像
    return cv::imwrite(dstImagePath, dstImage) ? 0 : -3;
}

解释

随便在网上找一下放射变换的教程,可以知道,变换后的图像,经常是缩小且周围白边很大。因为一不小心就会过界导致图像信息丢失。一般的教程也不处理最小外接矩形。

思路如下:

首先是将原图的四个角进行放射变换。由变换后的四个角得到最小包围盒:cv::Rect boundingBox = cv::boundingRect(transformedCorners);

如果boundingBox 左上角顶点有负数,则说明变换后的图像到了第234象限,我们需要向上,向左扩展图像。这是第七步做的事情。得到扩充后的图像

将图像扩充白边以后,我们原图的坐标系其实被改变了,即向左上方移动了。所以我们要把原来计算仿射变换矩阵的三个点平移,重新计算仿射变换。

我们使用扩充后的图像和第二次计算的放射变换进行变换。得到目标图像。

第十步,如果变换后的图像包围盒的左上点并不是原点,我们还要进行剪裁

如此,才能得到最小矩形包围的图像

相关推荐
Ai多利4 小时前
深度学习登上Nature子刊!特征选择创新思路
人工智能·算法·计算机视觉·多模态·特征选择
T.D.C4 小时前
【OpenCV】使用opencv找哈士奇的脸
人工智能·opencv·计算机视觉
sponge'6 小时前
opencv学习笔记2:卷积、均值滤波、中值滤波
笔记·python·opencv·学习
春末的南方城市7 小时前
中山大学&美团&港科大提出首个音频驱动多人对话视频生成MultiTalk,输入一个音频和提示,即可生成对应唇部、音频交互视频。
人工智能·python·深度学习·计算机视觉·transformer
春末的南方城市7 小时前
Ctrl-Crash 助力交通安全:可控生成逼真车祸视频,防患于未然
人工智能·计算机视觉·自然语言处理·aigc·音视频
只有左边一个小酒窝9 小时前
(六)卷积神经网络:深度学习在计算机视觉中的应用
深度学习·计算机视觉·cnn
whoarethenext10 小时前
使用 C/C++的OpenCV 实时播放火柴人爱心舞蹈动画
c语言·c++·opencv
carpell11 小时前
【语义分割专栏】3:Segnet实战篇(附上完整可运行的代码pytorch)
人工智能·python·深度学习·计算机视觉·语义分割
whoarethenext11 小时前
C++ OpenCV 学习路线图
c++·opencv·学习