当前位置: 首页 > news >正文

OpenCV: cv::warpAffine()逆仿射变换详解

一、函数原型(常用)

void cv::warpAffine(InputArray src,OutputArray dst,InputArray M,           // 2x3 仿射矩阵Size dsize,             // 输出图像尺寸 (width, height)int flags = INTER_LINEAR | WARP_INVERSE_MAP,int borderMode = BORDER_CONSTANT,const Scalar& borderValue = Scalar()
);

二、基本概念与数学公式

warpAffine 执行的是二维仿射变换(平移/缩放/旋转/剪切/组合)。仿射矩阵 M 是一个 2×3 矩阵:

标准坐标变换(M源坐标 映射到 目标坐标):

[x_dst]   [m00 m01 m02] [x_src]
[y_dst] = [m10 m11 m12] [y_src]
[ 1   ]   [  0   0   1 ] [  1  ]

warpAffine 在内部采用的是 逆映射(inverse mapping) 的方式来进行插值:对每个目标像素 (x_dst,y_dst),计算对应的源坐标 (x_src,y_src),然后对源图像做插值取值。
注意 flags 的 WARP_INVERSE_MAP

  • 如果设置了 WARP_INVERSE_MAP,则传入的 M 被认为是从目标坐标到源坐标的矩阵(dst→src),即直接用 M 映射 (x_dst,y_dst)(x_src,y_src)

  • 如果没有设置该标志,M 被视为 src→dst,你应传 src→dst 的矩阵;OpenCV 会在内部对 M 做逆运算(或等价处理)以得到 dst→src 的映射。

通俗:如果你手里已经有了把目标映射到源的矩阵(dst→src),在调用时加上 WARP_INVERSE_MAP,避免内部再求逆,节省一点开销并避免数值误差。

三、参数解释

  • src:输入图像(任意通道数、通常为 CV_8U、CV_16U、CV_32F 等)。

  • dst:输出图像(大小为 dsize,类型与 src 一致;若 dsize 为 (0,0) 通常不允许,需显式给定)。

  • M:2×3 仿射矩阵(CV_32FCV_64F),用 cv::Mat 构造,例如 Mat M = (Mat_<double>(2,3) << ...);

  • dsize:目标图像尺寸 Size(width, height)。注意:若你旋转图像并想保留完整内容,需计算合适的 dsize

  • flags:插值方式 + 可选 WARP_INVERSE_MAP。常见插值:

    • INTER_NEAREST:最近邻(最快,但质量最差)

    • INTER_LINEAR:双线性(默认,速度/质量平衡)

    • INTER_CUBIC:双三次(更慢更平滑)

    • INTER_AREA:像素区域重采样(通常用于缩小)

    • INTER_LANCZOS4:高质量(上采样时)
      可将插值与 WARP_INVERSE_MAP 用按位或组合:INTER_LINEAR | WARP_INVERSE_MAP

  • borderMode:当源坐标落在图像范围外时如何取值:

    • BORDER_CONSTANT:常数填充(使用 borderValue

    • BORDER_REPLICATE:复制边界最近像素

    • BORDER_REFLECTBORDER_REFLECT_101:反射

    • BORDER_WRAP:环绕

    • BORDER_TRANSPARENT:不绘制(保留 dst 原值)

    • BORDER_ISOLATED:不考虑外部像素(与复制不同)

  • borderValue:当 BORDER_CONSTANT 时使用的常量(例如 Scalar(0,0,0))。

四、插值方法选取建议

  • 缩小(downsample):优先使用 INTER_AREA(抗混叠好)。

  • 放大(upsample):使用 INTER_CUBICINTER_LANCZOS4 获得更好视觉效果。

  • 实时/速度优先:使用 INTER_LINEAR(速度快、效果合理)。

  • 二值图或掩码:使用 INTER_NEAREST(避免插值造成非 0/1 值)。

详细说明

  1. 当尺寸不一致(缩放)时

    • 目标像素对应的源坐标几乎总是非整数位置,所以需要插值。

    • 例如:把 480×480 缩放到 48×48,目标像素会映射到源图的 10×10 区域,需要插值来估算值。

  2. 当尺寸一致,但有旋转 / 平移 / 剪切时

    • 即使输出尺寸和输入尺寸一样,目标像素也可能映射到源图像的浮点坐标,仍然要插值。

    • 例如:平移 0.5 个像素,旋转 30°,这些变换都会让采样点落在“像素之间”,需要插值。

  3. 当变换刚好是整数平移且不缩放、不旋转时

    • 每个目标像素会对应到源图像的整数坐标,不需要插值。

    • 在这种情况下,INTER_NEAREST 实际上就是直接复制像素,效果和插值无差别。

如果你旋转 45°,目标图的某个像素可能对应到源图的 (x_src=1.3, y_src=2.7),那么:

  • INTER_NEAREST:直接取离它最近的整数像素,比如 (1,3)

  • INTER_LINEAR:取周围 4 个像素,按距离双线性加权

  • INTER_CUBIC:取周围 16 个像素,做更平滑的插值

五、常见用例与代码示例

1) 旋转(绕图像中心)

cv::Mat src = cv::imread("img.jpg");
cv::Point2f center(src.cols/2.0f, src.rows/2.0f);
double angle = 30.0, scale = 1.0;
cv::Mat M = cv::getRotationMatrix2D(center, angle, scale);
cv::Mat dst;
cv::warpAffine(src, dst, M, src.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0,0,0));

2) 由 3 个点构造仿射(例如把三角形 A映射到三角形 B)

std::vector<cv::Point2f> srcTri = { {0,0}, {100,0}, {0,100} };
std::vector<cv::Point2f> dstTri = { {10,20}, {120,30}, {20,200} };
cv::Mat M = cv::getAffineTransform(srcTri, dstTri);
cv::warpAffine(src, dst, M, dstSize);

3) 平移 + 缩放 + 剪切(手写矩阵)

平移 (tx,ty) 与缩放 (sx,sy)

cv::Mat M = (cv::Mat_<double>(2,3) << sx, 0, tx,0, sy, ty);
cv::warpAffine(src, dst, M, cv::Size(newW,newH));

剪切(水平 shear):

double shx = 0.3; // x' = x + shx*y
cv::Mat M = (cv::Mat_<double>(2,3) << 1, shx, 0,0,   1, 0);

4) 保证旋转后完整图像不被裁剪(compute bounding)

cv::Mat M = cv::getRotationMatrix2D(center, angle, 1.0);// 计算旋转后四角的新坐标,求 bounding box
std::vector<cv::Point2f> corners = { {0,0}, {w,0}, {w,h}, {0,h} };
std::vector<cv::Point2f> cornersTrans(4);
cv::transform(corners, cornersTrans, M);
cv::Rect bbox = cv::boundingRect(cornersTrans);// 为避免裁剪,平移矩阵 M 的平移分量
M.at<double>(0,2) += -bbox.x;
M.at<double>(1,2) += -bbox.y;
cv::warpAffine(src, dst, M, bbox.size());

六、注意细节与陷阱

  1. M 的类型:用 floatdouble 都可以,但使用时需匹配 Mat 类型,通常 CV_64F 更稳妥(精度高)。

  2. WARP_INVERSE_MAP:如果你已经有映射 dst→src(例如来自某些算法),设置该 flag 可以避免内部求逆。

  3. 像素坐标原点/像素中心:OpenCV 用像素中心坐标,即像素 (i,j) 对应坐标 (x=j, y=i)。若非常讲究数值精度要注意这一点。

  4. 数据类型与插值:如果 input 为 CV_16UCV_32F,warpAffine 在内部会进行浮点插值并再转换为目标类型(可能会有截断)。

  5. 掩码与透明区域:若想把变换后的透明区域保持原样可用 BORDER_TRANSPARENT,或者自己先用掩码处理再合并。

  6. ROI 与 非连续内存:若 src 是子矩阵(ROI),它可能不是连续的,warpAffine 仍然能工作但性能可能略差。

  7. 仿射 vs 透视:若需要做透视变换(非线性),请用 warpPerspective()(3×3 矩阵)

七、性能与优化建议

  • 使用合适的插值,INTER_LINEAR 在大多数场景性价比最好;缩小图像使用 INTER_AREA

  • 如果对速度敏感(GPU 可用),用 CUDA 版本:cv::cuda::warpAffine(需要 OpenCV 的 CUDA 模块)。

  • 若需要对大量小块重复调用,尽量复用(预分配)目标 Mat,并预计算矩阵 M

  • M 已经是 dst→src,设置 WARP_INVERSE_MAP 可节省一次矩阵求逆。

  • 如果做大量整数平移,考虑用 copyTo + ROI 操作替代通用 warpAffine

八、完整示例集(可直接拷贝运行)

#include <opencv2/opencv.hpp>
using namespace cv;// 旋转并保持不裁剪
Mat rotateNoCrop(const Mat& src, double angle) {Point2f center(src.cols/2.0f, src.rows/2.0f);Mat M = getRotationMatrix2D(center, angle, 1.0);// 计算新边界std::vector<Point2f> corners = { {0,0}, { (float)src.cols, 0 }, { (float)src.cols, (float)src.rows }, {0, (float)src.rows} };std::vector<Point2f> cornersT(4);transform(corners, cornersT, M);Rect bbox = boundingRect(cornersT);// 平移M.at<double>(0,2) -= bbox.x;M.at<double>(1,2) -= bbox.y;Mat dst;warpAffine(src, dst, M, bbox.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0,0));return dst;
}// affine from three points
Mat affineFrom3(const Mat& src, const std::vector<Point2f>& srcTri, const std::vector<Point2f>& dstTri) {Mat M = getAffineTransform(srcTri, dstTri);Mat dst;warpAffine(src, dst, M, Size(src.cols, src.rows), INTER_LINEAR);return dst;
}int main() {Mat img = imread("lena.png");Mat r = rotateNoCrop(img, 37.0);imshow("rot", r);// example: shearMat M = (Mat_<double>(2,3) << 1, 0.5, 0, 0, 1, 0);Mat shear;warpAffine(img, shear, M, Size(img.cols + 100, img.rows + 50), INTER_CUBIC, BORDER_REPLICATE);imshow("shear", shear);waitKey();
}

九、什么时候用 remap

若你的变换不是仿射或透视(例如光学畸变校正、复杂的非线性变形),remap() 更灵活:你可直接给出 map_xmap_y(每个 dst 像素对应 src 坐标),然后选择插值与边界策略。warpAffineremap 的特例(矩阵产生的规则映射)。


十、总结速查表

  • 用途:对图像做仿射变换(旋转/缩放/剪切/平移)

  • 关键矩阵:2×3 (CV_32F/CV_64F);用 getRotationMatrix2DgetAffineTransform 或自构造

  • 常用 flagsINTER_LINEAR(多数场景),INTER_AREA(缩小),INTER_CUBIC(放大)

  • 边界BORDER_CONSTANT + borderValueBORDER_REPLICATEBORDER_REFLECT

  • 提示:若 M 已经是 dst→src,使用 WARP_INVERSE_MAP;想保留整个旋转图像需调整输出尺寸与平移分量。


文章转载自:

http://yZ3lRxRu.cLpdm.cn
http://aVERzOJE.cLpdm.cn
http://Soe4S5KB.cLpdm.cn
http://Yubuo2Cj.cLpdm.cn
http://s9GHoO0f.cLpdm.cn
http://BfcoOyaa.cLpdm.cn
http://asVbHU4J.cLpdm.cn
http://LNwN8qU4.cLpdm.cn
http://vZnOuMcr.cLpdm.cn
http://SgR5iEut.cLpdm.cn
http://jt0VUYY9.cLpdm.cn
http://93bCGbQq.cLpdm.cn
http://sMsf8VS3.cLpdm.cn
http://wIb4uhaI.cLpdm.cn
http://LM7uUIvo.cLpdm.cn
http://HehLj8pL.cLpdm.cn
http://lcRPGCBn.cLpdm.cn
http://6FTDheJN.cLpdm.cn
http://M2Oxf7eE.cLpdm.cn
http://rV2jiReH.cLpdm.cn
http://ykAtXFL8.cLpdm.cn
http://MxDuEZL4.cLpdm.cn
http://WZ8Eczxl.cLpdm.cn
http://foHRNwVY.cLpdm.cn
http://QXRPYelX.cLpdm.cn
http://oh5kDr8k.cLpdm.cn
http://JjCTiNhA.cLpdm.cn
http://3ylEzJWj.cLpdm.cn
http://bJA0028b.cLpdm.cn
http://lIcp65kA.cLpdm.cn
http://www.dtcms.com/a/369356.html

相关文章:

  • 复合机器人能否更换末端执行器?
  • python使用transformer库推理
  • 【开题答辩全过程】以 智能商品数据分析系统为例,包含答辩的问题和答案
  • 终结 在我电脑上明明是好的!我们团队的协作秘诀
  • 神经网络|(十九)概率论基础知识-伽马函数·下
  • 基于STM32单片机的水位浑浊度检测设计
  • 个人健康管理系统设计与实现
  • MySQL集群——高可用架构
  • 记录自己看过的电子书方法
  • Typer 命令行工具使用示例
  • 绿算技术与清智图灵签署战略合作协议
  • AI配音工具哪个好用?7款热门配音软件推荐指南!
  • 【关系型数据库SQL】MySql数据库基础学习(一)
  • 软考刷题真题app,软考真题题库推荐
  • 25高教社杯数模国赛【B题高质量成品论文+无盲点解析】第一弹
  • CAD【xplode】和【explode】功能的区别
  • MOSFET SOA曲线评估
  • 《计算机网络安全》实验报告一 现代网络安全挑战 拒绝服务与分布式拒绝服务攻击的演变与防御策略(3)
  • c++ 压缩与解压缩
  • 代码改变生活:我用Python+LLM给自己写了个健身私教
  • python创建并写入excel文件
  • Anaconda下载安装及详细配置的保姆级教程【Windows系统】
  • 【休闲娱乐】“无用”之大用——会玩,是成年人的顶级能力
  • 【leetcode】77.组合
  • 【算法--链表】82.删除排序链表中的重复元素 II--通俗讲解
  • 【CMake】变量作用域1——块作用域
  • 你的提问方式错了!让AI生成高质量实验方案的秘诀
  • Java第十四幕集合啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦
  • VS2022运行openCV报错:应用程序无法正常启动(0xc000279)
  • 敏捷开发-Scrum(下)