顺时针旋转90°需按行优先布局映射为newx = oldy,OpenCV用cv::rotate(src, dst, cv::ROTATE_90_CLOCKWISE)最安全,手动实现须转有符号整型防溢出。

旋转前必须明确图像数据的内存布局
图像顺时针旋转90度不是简单调换行列索引,关键取决于你手上的数据是按「行优先」存储(如 OpenCV 的 cv::Mat、普通二维数组)还是「列优先」。绝大多数 C++ 图像处理场景(包括 std::vector<:vector>></:vector> 或一维 std::vector<uchar></uchar>)默认行优先:第 0 行从左到右连续存放,接着是第 1 行……
如果原始图像是 height × width,顺时针转 90° 后尺寸变为 width × height,新图像第 i 行第 j 列对应原图第 height - 1 - j 行第 i 列 —— 这个映射关系必须严格验证,否则会出现镜像或错位。
- 常见错误:直接用
new_img[i][j] = old_img[j][i]→ 这是转置,不是顺时针90° - 正确映射:假设原图
old[y][x](y 行、x 列),则新图new[x][height - 1 - y] - 若用一维数组存图(如
std::vector<uchar> data(height * width)</uchar>),原图坐标(y, x)对应索引y * width + x;新图坐标(x, height - 1 - y)对应索引x * height + (height - 1 - y)
用 OpenCV 的 cv::rotate 最快且安全
如果你项目已依赖 OpenCV,别自己手写循环。OpenCV 的 cv::rotate 内部做了内存对齐、SIMD 加速和边界处理,比裸循环稳定得多。
注意:cv::rotate 的旋转类型参数是枚举值,顺时针90° 必须传 cv::ROTATE_90_CLOCKWISE,不是 cv::ROTATE_90_COUNTERCLOCKWISE 或数字 1/2。
立即学习“C++免费学习笔记(深入)”;
- 输入
cv::Mat必须是连续内存(mat.isContinuous()返回 true),否则先mat.clone() - 输出
cv::Mat会自动分配新内存,尺寸变为width × height,通道数不变 - 示例:
cv::Mat src = cv::imread("in.jpg");<br>cv::Mat dst;<br>cv::rotate(src, dst, cv::ROTATE_90_CLOCKWISE);
纯 C++ 手动实现时避免越界和类型溢出
手动实现适合学习或嵌入式等无 OpenCV 环境。核心是两层 for 循环,但容易在索引计算时越界或整数溢出,尤其当 height 或 width 是 unsigned int 类型时,height - 1 - y 在 y == 0 时可能因无符号回绕变成极大值。
- 务必把
height和width转为有符号整型(如int)再参与减法运算 - 新图分配:用
std::vector<:vector>> new_img(width, std::vector<uchar>(height))</uchar></:vector> - 循环顺序建议外层遍历原图行(
y),内层遍历原图列(x),这样缓存友好 - 典型错误代码:
new_img[x][y] = old_img[y][x]→ 缺少翻转,结果是转置后上下颠倒 - 正确赋值:
new_img[x][height_int - 1 - y] = old_img[y][x]
灰度图和多通道图的处理逻辑一致,但别忽略通道维度
无论单通道灰度图还是三通道 BGR 图,旋转逻辑完全一样:每个像素作为一个整体搬移位置。区别只在内存步长和拷贝单位。
- 灰度图:每个像素 1 字节,一维数组中相邻像素地址差 1
- BGR 图:每个像素 3 字节,一维数组中相邻像素地址差 3;复制时需按像素粒度操作(
memcpy(&new_data[...], &old_data[...], 3)),不能只拷 1 字节 - 错误示范:对 BGR 图用
new_data[i] = old_data[j]→ 只复制了 R 或 B 分量,图像变色 - 安全做法:用
std::array<uchar></uchar>或结构体封装像素,或直接用cv::Vec3b(如果用了 OpenCV)
真正容易被忽略的是:旋转后图像宽高互换,所有后续操作(比如 ROI 提取、坐标变换)都必须同步更新尺寸认知,否则访问 new_img[y][x] 时 y 超过原 width 就崩了。


















