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

不使用OpenCV内置函数实现带线性/三次插值的图像旋转

手动实现带多插值的图像旋转方案

现有输出异常修复

  • 核心错误点1:坐标映射逻辑错误,当前使用正向旋转矩阵,且混淆了OpenCV行/列与x/y的对应关系(OpenCV中像素访问格式为mat.at<Vec3b>(y, x),x对应列索引、y对应行索引),同时未使用反向映射(需从目标图像像素坐标反推原图对应位置,避免出现像素孔洞)
  • 核心错误点2:未做坐标系偏移,默认以左上角为旋转原点,导致像素位置整体错位
  • 边界填充修复:修正坐标映射后,超出原图坐标范围的位置直接赋值为黑像素即可,原有边界判断逻辑无需大改

中心旋转实现逻辑

旋转计算前先将目标像素坐标平移到以图像中心为原点的坐标系,旋转完成后再平移回原图左上角为原点的坐标系,平移参数:

double src_cx = src.cols / 2.0, src_cy = src.rows / 2.0;
double dst_cx = dst.cols / 2.0, dst_cy = dst.rows / 2.0;

插值实现逻辑

最近邻插值

直接对浮点坐标做四舍五入取整数坐标,对应像素值直接赋值

双线性插值

取浮点坐标周围4个相邻整数像素,按照像素与浮点坐标的距离占比做加权求和,得到最终像素值

双三次插值

取浮点坐标周围16个相邻整数像素,使用BiCubic基函数计算每个像素的权重,加权求和得到最终像素值,基函数参数a通常取-0.5。

完整修正代码

#include <iostream>
#include <math.h>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

enum interpolation_type{
    INTERPOLATION_CUBIC,
    INTERPOLATION_LINEAR,
    INTERPOLATION_NEAREST_NEIGHBOR
};

// 双三次基函数
float cubic_transform(float x) {
    float a = -0.5f;
    float abs_x = fabs(x);
    if (abs_x < 1) {
        return (a + 2) * pow(abs_x, 3) - (a + 3) * pow(abs_x, 2) + 1;
    } else if (abs_x < 2) {
        return a * pow(abs_x, 3) - 5 * a * pow(abs_x, 2) + 8 * a * abs_x - 4 * a;
    }
    return 0;
}

Vec3b Interpolation_Calculator(const Mat& src, Point2f src_pixel, interpolation_type type) {
    // 边界判断
    if (src_pixel.x < 0 || src_pixel.x >= src.cols || src_pixel.y <0 || src_pixel.y >= src.rows) {
        return Vec3b(0,0,0);
    }
    if (type == INTERPOLATION_NEAREST_NEIGHBOR) {
        int x = clamp((int)round(src_pixel.x), 0, src.cols-1);
        int y = clamp((int)round(src_pixel.y), 0, src.rows-1);
        return src.at<Vec3b>(y, x);
    } else if (type == INTERPOLATION_LINEAR) {
        float x = src_pixel.x, y = src_pixel.y;
        int x1 = floor(x), x2 = min(x1 + 1, src.cols-1);
        int y1 = floor(y), y2 = min(y1 + 1, src.rows-1);
        float dx = x - x1, dy = y - y1;
        Vec3b p1 = src.at<Vec3b>(y1, x1), p2 = src.at<Vec3b>(y1, x2);
        Vec3b p3 = src.at<Vec3b>(y2, x1), p4 = src.at<Vec3b>(y2, x2);
        Vec3b res;
        for (int i=0; i<3; i++) {
            res[i] = saturate_cast<uchar>(
                p1[i]*(1-dx)*(1-dy) + p2[i]*dx*(1-dy) + p3[i]*(1-dx)*dy + p4[i]*dx*dy
            );
        }
        return res;
    } else if (type == INTERPOLATION_CUBIC) {
        float x = src_pixel.x, y = src_pixel.y;
        int floor_x = floor(x), floor_y = floor(y);
        float dx = x - floor_x, dy = y - floor_y;
        float res[3] = {0,0,0};
        for (int i = -1; i <=2; i++) {
            for (int j = -1; j <=2; j++) {
                int cur_x = clamp(floor_x + j, 0, src.cols-1);
                int cur_y = clamp(floor_y + i, 0, src.rows-1);
                float w_x = cubic_transform(dx - j);
                float w_y = cubic_transform(dy - i);
                Vec3b pixel = src.at<Vec3b>(cur_y, cur_x);
                for (int c=0; c<3; c++) {
                    res[c] += pixel[c] * w_x * w_y;
                }
            }
        }
        Vec3b out;
        for (int c=0; c<3; c++) {
            out[c] = saturate_cast<uchar>(clamp(res[c], 0.0f, 255.0f));
        }
        return out;
    }
    return Vec3b(0,0,0);
}

void RotationFunction(const Mat& src, Mat& dst, int angle, interpolation_type type, bool center_rotate = true) {
    double rad = angle * CV_PI / 180.0;
    double cos_val = cos(rad), sin_val = sin(rad);
    double src_cx = src.cols / 2.0, src_cy = src.rows / 2.0;
    double dst_cx = dst.cols / 2.0, dst_cy = dst.rows / 2.0;

    for (int y = 0; y < dst.rows; y++) {
        for (int x = 0; x < dst.cols; x++) {
            double tx, ty;
            if (center_rotate) {
                // 先平移到目标中心为原点,反向旋转,再平移到原图坐标系
                tx = (x - dst_cx) * cos_val + (y - dst_cy) * sin_val + src_cx;
                ty = -(x - dst_cx) * sin_val + (y - dst_cy) * cos_val + src_cy;
            } else {
                // 左上角为原点的反向旋转
                tx = x * cos_val + y * sin_val;
                ty = -x * sin_val + y * cos_val;
            }
            Point2f src_p(tx, ty);
            dst.at<Vec3b>(y, x) = Interpolation_Calculator(src, src_p, type);
        }
    }
}

int main() {
    Mat img = imread("../lion.jpeg");
    if (img.empty()) {
        cout << "读取图像失败" << endl;
        return -1;
    }
    // 可调整dst尺寸适配旋转后完整图像,这里保持和原图一致
    Mat rotatedImage(img.rows, img.cols, CV_8UC3, Scalar(0));
    // 参数可修改:角度、插值类型、是否中心旋转
    RotationFunction(img, rotatedImage, 25, INTERPOLATION_NEAREST_NEIGHBOR, true);

    imshow("原图", img);
    imshow("旋转后", rotatedImage);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 11:54:03