OpencvSharp 算子学习教案之 - Cv2.Remap
OpencvSharp 算子学习教案之 - Cv2.Remap大家好Opencv在很多工程项目中都会用到而OpencvSharp则是以C#开发与实现的Opencv操作库对.NET开发人员友好但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案供大家参考学习。Cv2.Remap教案版本V1.0面向对象OpenCvSharp 初学者所属模块imgproc源码位置OpenCvSharp/Cv2/Cv2_imgproc.cs:1135摘要Cv2.Remap 会按照 map1 和 map2 提供的坐标表逐像素重采样图像。本文用“每个输出像素都知道自己该去哪里取值”的方式解释 Remap并配合 WPF 场景帮助初学者理解 mapX、mapY 的含义。1. 函数名称带参数签名publicstaticvoidRemap(InputArraysrc,OutputArraydst,InputArraymap1,InputArraymap2,InterpolationFlagsinterpolationInterpolationFlags.Linear,BorderTypesborderModeBorderTypes.Constant,Scalar?borderValuenull)2. 函数用途Cv2.Remap的作用是按照预先准备好的坐标映射表重新采样图像。它最常见的用途有做镜头畸变校正。做波浪扭曲、鱼眼展开和自定义坐标变形。把图像投影到新的坐标系里。和ConvertMaps配套使用优化重映射性能。这个函数和Resize的区别很大Resize只是在规则缩放而Remap可以让每个像素去任意位置取样。3. 函数公式Remap 可以理解为d s t ( x , y ) s r c ( m a p X ( x , y ) , m a p Y ( x , y ) ) dst(x, y) src(mapX(x, y), mapY(x, y))dst(x,y)src(mapX(x,y),mapY(x,y))如果采样坐标不是整数OpenCV 会根据插值方式计算最终像素值。因此map1和map2本质上就是一张“坐标地图”。4. 函数原理说明Remap的本质是“输出图像上的每个点都知道自己应该从源图像哪里取值”。读取map1和map2。对输出图像中的每个像素查表。从源图像对应位置取样。把结果写入目标图像。初学者最容易出错的地方是把map1和map2的方向看反。忘记它不是“移动图像”而是“重新定义采样坐标”。用了不合适的插值方式导致变形边缘锯齿明显。没有理解边界模式结果边缘像素出现不想要的颜色。本页的 WPF 场景会把 mapX、mapY 和重映射结果放在一起对照。5. 参数含义解析参数名类型必填含义srcInputArray是输入图像dstOutputArray是输出图像map1InputArray是第一张坐标映射表map2InputArray是第二张坐标映射表interpolationInterpolationFlags否插值方式borderModeBorderTypes否边界模式borderValueScalar?否常量边界值补充说明map1和map2常常是同尺寸矩阵。map1/map2可以来自几何标定、计算公式或手写坐标表。INTER_AREA不适用于Remap。如果你有复杂坐标需求Remap往往比Resize更合适。6. 应用场景列表场景名场景说明典型用途场景A镜头校正把畸变图像重新映射标定、视觉前处理场景B波浪扭曲让坐标表出现周期变化教学、特效场景C局部拉伸对图像某些区域做重采样视觉增强场景D坐标投影直接控制像素来源算法研究7. 函数使用示例下面的 Console 程序用一个很简单的“向右平移一列”的坐标表演示Remap。这样更容易先看懂坐标表的作用再去理解更复杂的波浪形映射。usingSystem;usingSystem.Text;usingOpenCvSharp;internalstaticclassProgram{/// summary/// 程序入口。/// /summaryprivatestaticvoidMain(){// 控制台输出切到 UTF-8避免中文说明乱码。Console.OutputEncodingEncoding.UTF8;usingvarsourceCreateSourceImage();usingvarmapXnewMat(source.Rows,source.Cols,MatType.CV_32FC1);usingvarmapYnewMat(source.Rows,source.Cols,MatType.CV_32FC1);// 这里构造一个最简单的坐标表输出图像中的每个像素都去源图像中左边一列取值。for(varrow0;rowsource.Rows;row){for(varcol0;colsource.Cols;col){mapX.Atfloat(row,col)col-1;mapY.Atfloat(row,col)row;}}usingvardestinationnewMat();Cv2.Remap(source,destination,mapX,mapY,InterpolationFlags.Nearest,BorderTypes.Constant,Scalar.All(0));Console.WriteLine(源图);PrintByteMat(source);Console.WriteLine(mapX);PrintFloatMat(mapX);Console.WriteLine(mapY);PrintFloatMat(mapY);Console.WriteLine(重映射结果);PrintByteMat(destination);}/// summary/// 创建一个单通道测试图。/// /summary/// returns4x4 灰度图。/returnsprivatestaticMatCreateSourceImage(){// 用递增数字做测试图方便观察坐标表如何改变像素来源。varsourcenewMat(4,4,MatType.CV_8UC1);bytevalue1;for(varrow0;rowsource.Rows;row){for(varcol0;colsource.Cols;col){source.Atbyte(row,col)value;}}returnsource;}/// summary/// 打印 byte 矩阵。/// /summaryprivatestaticvoidPrintByteMat(Matmat){for(varrow0;rowmat.Rows;row){for(varcol0;colmat.Cols;col){// 输出每个像素值便于理解 remap 前后的变化。Console.Write(${mat.Atbyte(row,col),4});}Console.WriteLine();}Console.WriteLine();}/// summary/// 打印 float 矩阵。/// /summaryprivatestaticvoidPrintFloatMat(Matmat){for(varrow0;rowmat.Rows;row){for(varcol0;colmat.Cols;col){// mapX / mapY 用浮点数表达坐标所以按 float 打印更直观。Console.Write(${mat.Atfloat(row,col),8:F1});}Console.WriteLine();}Console.WriteLine();}}8. 注意事项map1和map2的数据类型必须符合 Remap 的要求。INTER_AREA不支持Remap。如果坐标表超出源图范围边界模式会决定如何补值。Remap的思路是查表不是简单平移。9. 调优建议先从简单平移表开始理解再看复杂波浪映射。如果映射表比较固定可以配合ConvertMaps提升速度。初学者适合先用INTER_NEAREST看清楚坐标关系再切到INTER_LINEAR。在教学场景里把mapX、mapY和结果图放在一起最容易讲清楚。10. 运行说明如果你在控制台工程里运行本文示例直接把代码放进Program.cs即可。如果你在本仓库里学习请打开 WPF 控件 Cv2RemapControl.xaml.cs 对应的页面。点击“运行场景A”可以看到 mapX、mapY 和输出结果的对应关系。11. 常见错误排查把mapX和mapY反过来使用。误以为Remap只能处理规则缩放。忘记设置边界模式导致边缘像素看起来不自然。把坐标表做错尺寸结果和源图无法对齐。