利用OpenCV实现关键点的仿射变换

1. 基本思路

本文主要用一个函数实现图像的放缩、旋转,并最终得到仿射变换矩阵tm 和逆仿射变换的矩阵 tm_inv。仿射变换矩阵 tm 可以将原图中坐标为 \((x, y)\) 的像素值映射到仿射变换后的图中坐标为 \((x^{\prime}, y^{\prime})\) 的位置。

2. 具体步骤

实现该变换的基本逻辑是找到不共线的 3 个点,然后将

3. C++ 代码实现

具体实现如下:

#include <algorithm>
#include <cmath>
#include <iostream>
#include <opencv2/opencv.hpp>

/**
 * @brief 逆时针旋转
 * @param x 原坐标 x
 * @param y 原坐标 y
 * @param rx 旋转后的坐标 x
 * @param ry 旋转后的坐标 y
 * @param angle 逆时针旋转角度,单位: 度
 */
void getCounterclockwiseRotatePoint(float x, float y, float &rx, float &ry, float angle) {
    float sn = sin(angle);
    float cs = cos(angle);
    rx = x * cs - y * sn;
    ry = x * sn + y * cs;
}

/**
 * @brief 获取与前两个点不共线的第三个点
 */
void getNotCollinear3rdPoint(float x1, float y1, float x2, float y2, float &x3, float &y3) {
    float dx = x1 - x2;
    float dy = y1 - y2;
    x3 = x2 - dy;
    y3 = y2 + dx;
}

/**
 * @brief 图像执行缩放、顺时针旋转操作
 * @param src_img 原图
 * @param dst_img 目标图
 * @param dst_w 目标图的宽度
 * @param dst_h 目标图的高度
 * @param angle 顺时针旋转角度
 * @param padVal 背景值
 * @param tm 变换矩阵
 * @param tm_inv 反向变换矩阵
 */
void rotateAffineTransformByOpenCV(cv::Mat &src_img, cv::Mat &dst_img, float dst_w, float dst_h, float angle, int padVal, cv::Mat& tm, cv::Mat& tm_inv) {
    float src_w = src_img.cols, src_h = src_img.rows;
    float r = dst_w / dst_h;
    float sw, sh;  // scaled width, height
    // 缩放0.8x 能够完成在目标图像大小内的任意角度旋转
    if (src_w > r * src_h) {
        sw = src_w * 1.25f;
        sh = src_w / r * 1.25f;
    } else {
        sw = src_h * r * 1.25f;
        sh = src_h * 1.25f;
    }

    cv::Point2f src_pts[3];
    cv::Point2f dst_pts[3];

    // 1st point: Pick a center point
    src_pts[0].x = src_w / 2;
    src_pts[0].y = src_h / 2;
    dst_pts[0].x = dst_w / 2;
    dst_pts[0].y = dst_h / 2;

    // 2nd point: Pick a point at random and rotate it counterclockwise
    angle = angle / 180.f * M_PI;
    float rx, ry;  // rotated x, y coordinate
    getCounterclockwiseRotatePoint(0, -0.5 * sw, rx, ry, angle);
    src_pts[1].x = src_pts[0].x + rx;
    src_pts[1].y = src_pts[0].y + ry;
    dst_pts[1].x = dst_pts[0].x;
    dst_pts[1].y = dst_pts[0].y - 0.5 * dst_w;

    // 3rd point
    getNotCollinear3rdPoint(src_pts[0].x, src_pts[0].y, src_pts[1].x, src_pts[1].y, src_pts[2].x, src_pts[2].y);
    getNotCollinear3rdPoint(dst_pts[0].x, dst_pts[0].y, dst_pts[1].x, dst_pts[1].y, dst_pts[2].x, dst_pts[2].y);

    tm = cv::getAffineTransform(src_pts, dst_pts);
    cv::invertAffineTransform(tm, tm_inv);
    cv::warpAffine(src_img, dst_img, tm, cv::Size((int)dst_w, (int)dst_h), 1, 0, cv::Scalar(padVal));
}

int main() {
    cv::Mat src_img = cv::imread("input.jpg");
    if (src_img.empty()) {
        std::cout << "Failed to load image" << std::endl;
        return -1;
    }
    int dst_w = 320, dst_h = 320;
    float angle = 45;
    int padVal = 0;

    cv::Mat dst_img(dst_w, dst_h, CV_8UC3);
    cv::Mat tm, tm_inv;  // 注意:输出的数据类型为 double
    rotateAffineTransformByOpenCV(src_img, dst_img, dst_w, dst_h, angle, padVal, tm, tm_inv);
    cv::imwrite("output.jpg", dst_img);

    return 0;
}
posted @ 2024-06-11 14:06  RenjieW  阅读(184)  评论(0)    收藏  举报