从Halcon算子到线性代数:手把手教你用矩阵玩转图像平移旋转(附完整代码)
从Halcon算子到线性代数手把手教你用矩阵玩转图像平移旋转附完整代码在计算机视觉领域图像变换是最基础却至关重要的操作之一。当我们使用Halcon这样的专业工具时往往会直接调用hom_mat2d_translate或hom_mat2d_rotate等封装好的算子这确实能快速实现功能但也让我们错过了理解背后数学原理的绝佳机会。本文将带你从零开始通过线性代数的视角一步步拆解图像变换的本质并用Halcon代码实现完整的矩阵运算过程。1. 图像变换的数学本质图像变换的核心在于坐标映射。当我们对图像进行平移、旋转等操作时实际上是在对图像中每个像素的坐标进行数学变换。这种变换可以用矩阵乘法优雅地表示[x] [a b c] [x] [y] [d e f] * [y] [1 ] [0 0 1] [1]其中(x,y)是原图像素坐标(x,y)是变换后的坐标3x3矩阵就是所谓的齐次变换矩阵。这种表示方法的优势在于统一性平移、旋转、缩放等不同变换可以用相同形式的矩阵表示可组合性多个变换可以通过矩阵相乘合并为一个复合变换计算效率现代GPU对矩阵运算有专门优化为什么使用齐次坐标在二维空间中引入第三个维度通常设为1是为了用矩阵乘法统一表示平移需要加法和线性变换只需乘法这两种操作。2. 基础变换矩阵构建2.1 平移变换平移是最简单的变换其矩阵形式为[1 0 tx] [0 1 ty] [0 0 1 ]Halcon代码实现* 创建平移矩阵 (tx64, ty64) create_matrix(3, 3, [1,0,64, 0,1,64, 0,0,1], MatrixTranslate)2.2 旋转变换旋转矩阵稍微复杂涉及三角函数[cosθ -sinθ 0] [sinθ cosθ 0] [ 0 0 1]Halcon实现45度旋转* 创建旋转矩阵 (45度) angle : rad(45) create_matrix(3, 3, [cos(angle),-sin(angle),0, sin(angle),cos(angle),0, 0,0,1], MatrixRotate)2.3 缩放变换缩放矩阵的对角线元素表示各轴缩放比例[sx 0 0] [ 0 sy 0] [ 0 0 1]Halcon实现2倍缩放* 创建缩放矩阵 (2倍) create_matrix(3, 3, [2,0,0, 0,2,0, 0,0,1], MatrixScale)3. 矩阵变换的工程实现理解了数学原理后我们需要解决实际的工程问题如何高效地将这些矩阵运算应用到图像处理中。3.1 正向映射与逆向映射正向映射是直接遍历原图像素计算其在结果图中的位置for x : 0 to Height-1 by 1 for y : 0 to Width-1 by 1 get_grayval(Image, x, y, Grayval) * 坐标变换 create_matrix(3, 1, [x,y,1], CoordMatrix) mult_matrix(TransformMatrix, CoordMatrix, AB, ResultMatrix) get_full_matrix(ResultMatrix, NewCoord) * 边界检查 if (NewCoord[0]0 and NewCoord[0]Height and NewCoord[1]0 and NewCoord[1]Width) set_grayval(ImageOut, NewCoord[0], NewCoord[1], Grayval) endif endfor endfor但这种方法会导致空洞问题某些结果像素未被赋值。更优的解决方案是逆向映射遍历结果图像的每个像素计算其在原图中的对应位置* 使用逆矩阵 invert_matrix(TransformMatrix, general, 0, InverseMatrix) for x : 0 to Height-1 by 1 for y : 0 to Width-1 by 1 create_matrix(3, 1, [x,y,1], CoordMatrix) mult_matrix(InverseMatrix, CoordMatrix, AB, SourceCoordMatrix) get_full_matrix(SourceCoordMatrix, SourceCoord) * 双线性插值获取像素值 double_biline(Image, SourceCoord[0], SourceCoord[1], Grayval) set_grayval(ImageOut, x, y, Grayval) endfor endfor3.2 双线性插值实现逆向映射常会遇到非整数坐标这时需要插值计算像素值。双线性插值综合考虑四个最近邻像素* 双线性插值函数 procedure double_biline(Image, x, y, Grayval) * 边界检查 get_image_size(Image, Width, Height) if (x0 or y0 or xHeight or yWidth) Grayval : -1 return endif * 找到四个相邻像素 x1 : int(floor(x)); x2 : x11 y1 : int(floor(y)); y2 : y11 * 边界处理 if (x2 Height) x2 : Height-1 endif if (y2 Width) y2 : Width-1 endif * 获取四个点的像素值 get_grayval(Image, x1, y1, p11) get_grayval(Image, x2, y1, p21) get_grayval(Image, x1, y2, p12) get_grayval(Image, x2, y2, p22) * 计算权重 dx : x - x1; dy : y - y1 * 插值计算 Grayval : (1-dx)*(1-dy)*p11 dx*(1-dy)*p21 (1-dx)*dy*p12 dx*dy*p22 endprocedure4. 复合变换与性能优化实际项目中我们常需要组合多个变换。矩阵乘法的结合律让我们可以预先计算复合矩阵* 创建各基础矩阵 create_matrix(3,3,[1,0,100, 0,1,50, 0,0,1], TranslateMatrix) * 平移(100,50) create_matrix(3,3,[cos(0.5),-sin(0.5),0, sin(0.5),cos(0.5),0, 0,0,1], RotateMatrix) * 旋转30度 create_matrix(3,3,[1.5,0,0, 0,1.5,0, 0,0,1], ScaleMatrix) * 缩放1.5倍 * 计算复合矩阵: 先缩放再旋转最后平移 mult_matrix(TranslateMatrix, RotateMatrix, AB, TempMatrix) mult_matrix(TempMatrix, ScaleMatrix, AB, FinalMatrix)对于大型图像逐像素计算效率低下。Halcon提供了优化方案向量化运算使用affine_trans_pixel等批量处理函数并行计算利用多核CPU或GPU加速区域裁剪只处理感兴趣区域(ROI)* 高效实现方式 affine_trans_image(Image, TransImage, FinalMatrix, constant, false)5. 完整代码示例以下是整合了所有功能的完整实现* 读取图像 read_image(Image, example.png) get_image_size(Image, Width, Height) * 1. 创建各种变换矩阵 * 平移矩阵 create_matrix(3,3,[1,0,50, 0,1,30, 0,0,1], MatrixTranslate) * 旋转矩阵(30度) angle : rad(30) create_matrix(3,3,[cos(angle),-sin(angle),0, sin(angle),cos(angle),0, 0,0,1], MatrixRotate) * 缩放矩阵(1.5倍) create_matrix(3,3,[1.5,0,0, 0,1.5,0, 0,0,1], MatrixScale) * 2. 计算复合矩阵: 缩放-旋转-平移 mult_matrix(MatrixRotate, MatrixScale, AB, TempMatrix) mult_matrix(MatrixTranslate, TempMatrix, AB, FinalMatrix) * 3. 准备输出图像 gen_image_const(ImageOut, byte, Width, Height) * 4. 逆向映射实现 invert_matrix(FinalMatrix, general, 0, InverseMatrix) for Row : 0 to Height-1 by 1 for Col : 0 to Width-1 by 1 * 计算原图对应坐标 create_matrix(3,1,[Row,Col,1], CoordMatrix) mult_matrix(InverseMatrix, CoordMatrix, AB, SourceCoordMatrix) get_full_matrix(SourceCoordMatrix, SourceCoord) * 双线性插值 double_biline(Image, SourceCoord[0], SourceCoord[1], Grayval) if (Grayval ! -1) set_grayval(ImageOut, Row, Col, Grayval) endif endfor endfor * 显示结果 dev_display(ImageOut)6. 验证与调试技巧为确保自定义实现的正确性建议与Halcon内置算子结果对比* 使用Halcon内置算子 hom_mat2d_identity(HomMat) hom_mat2d_scale(HomMat, 1.5, 1.5, 0, 0, HomMatScaled) hom_mat2d_rotate(HomMatScaled, 0.5, 0, 0, HomMatRotated) hom_mat2d_translate(HomMatRotated, 50, 30, HomMatFinal) affine_trans_image(Image, ImageHalcon, HomMatFinal, constant, false) * 比较结果 sub_image(ImageOut, ImageHalcon, ImageDiff, 1, 0) min_max_gray(ImageDiff, ImageDiff, 0, Min, Max, Range) if (Max 1) * 存在显著差异 dev_display(ImageDiff) endif常见问题排查黑边问题检查边界处理逻辑确认插值函数是否正确处理边缘图像错位验证矩阵乘法顺序是否符合变换顺序要求性能瓶颈使用Halcon的count_seconds测量关键代码段耗时在实际项目中我通常会先在小尺寸图像上测试基本功能验证无误后再应用到全尺寸图像。对于特别大的图像可以考虑分块处理或使用Halcon的并行计算功能。