不使用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
相关产品推荐
相关产品推荐

