从Halcon算子到线性代数:手把手教你用3x3矩阵搞定图像平移旋转(附完整代码)

在计算机视觉领域,图像变换是最基础也最核心的操作之一。无论是工业检测中的零件定位,还是自动驾驶中的场景理解,都离不开对图像进行平移、旋转等几何变换。Halcon作为业界领先的机器视觉软件,提供了丰富的算子来实现这些功能。但知其然更要知其所以然,本文将带你深入理解这些算子背后的数学原理,并用线性代数中的3x3矩阵手动实现相同的功能。

1. 图像变换的数学基础

1.1 齐次坐标:让平移也能用矩阵表示

在二维空间中,一个点的坐标通常表示为(x,y)。但如果只用这样的二维向量,平移操作就无法用矩阵乘法来表示。这就是引入齐次坐标的原因。

齐次坐标通过在二维坐标后添加一个额外的维度(通常设为1),将点表示为(x,y,1)。这样,所有常见的几何变换都可以统一用3x3矩阵来表示:

  • 平移矩阵

    [1, 0, tx]
    [0, 1, ty]
    [0, 0, 1 ]
    

    其中tx和ty分别表示x和y方向的平移量

  • 旋转矩阵

    [cosθ, -sinθ, 0]
    [sinθ,  cosθ, 0]
    [   0,     0, 1]
    

    θ表示旋转角度,逆时针方向为正

1.2 变换矩阵的构成原理

理解变换矩阵的关键在于分析矩阵的每一部分如何影响坐标:

  • 左上2x2子矩阵控制线性变换(旋转、缩放、剪切)
  • 第三列的前两行控制平移
  • 最后一行保持为[0,0,1]以保证齐次坐标性质

这种表示方法的优势在于可以将多个变换通过矩阵乘法组合起来。例如,先旋转再平移的操作可以表示为:

T = 平移矩阵 × 旋转矩阵

2. Halcon中的矩阵操作函数

2.1 创建和操作矩阵

Halcon提供了一系列矩阵操作函数,让我们能够直接使用这些数学原理:

* 创建3x3单位矩阵
create_matrix(3, 3, [1,0,0, 0,1,0, 0,0,1], MatrixID)

* 矩阵相乘
mult_matrix(MatrixA, MatrixB, 'AB', ResultMatrix)

* 矩阵求逆
invert_matrix(MatrixID, 'general', 0, InverseMatrix)

2.2 与标准算子的对应关系

Halcon的几何变换算子实际上都是在内部构建并应用了相应的变换矩阵:

算子 等效矩阵操作
hom_mat2d_translate 创建平移矩阵
hom_mat2d_rotate 创建旋转矩阵
hom_mat2d_scale 创建缩放矩阵
affine_trans_image 应用矩阵变换

3. 手动实现图像平移

3.1 构建平移矩阵

让我们从最简单的平移开始,手动构建变换矩阵:

* 定义平移量
tx := 64
ty := 64

* 创建平移矩阵
create_matrix(3, 3, [1,0,tx, 0,1,ty, 0,0,1], TranslateMatrix)

3.2 应用变换的两种方法

方法一:正向映射(遍历原图)
* 遍历原图像素
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], CoordVector)
        
        * 应用变换
        mult_matrix(TranslateMatrix, CoordVector, 'AB', TransformedCoord)
        
        * 获取变换后坐标
        get_full_matrix(TransformedCoord, NewXY)
        
        * 在新图像中设置像素值
        if (NewXY[0] >= 0 and NewXY[0] < Height and 
            NewXY[1] >= 0 and NewXY[1] < Width)
            set_grayval(ImageTranslated, NewXY[0], NewXY[1], Grayval)
        endif
    endfor
endfor
方法二:逆向映射(遍历目标图)

逆向映射可以避免出现空洞,是更推荐的方法:

* 创建目标图像
gen_image_const(ImageTranslated, 'byte', Width, Height)

* 获取平移矩阵的逆矩阵
invert_matrix(TranslateMatrix, 'general', 0, InverseTranslateMatrix)

* 遍历目标图像
for newX := 0 to Height-1 by 1
    for newY := 0 to Width-1 by 1
        * 构建目标坐标向量
        create_matrix(3, 1, [newX,newY,1], TargetCoord)
        
        * 计算对应的原图坐标
        mult_matrix(InverseTranslateMatrix, TargetCoord, 'AB', SourceCoord)
        get_full_matrix(SourceCoord, OrigXY)
        
        * 边界检查
        if (OrigXY[0] >= 0 and OrigXY[0] < Height and 
            OrigXY[1] >= 0 and OrigXY[1] < Width)
            * 获取原图像素值
            get_grayval(Image, OrigXY[0], OrigXY[1], Grayval)
            set_grayval(ImageTranslated, newX, newY, Grayval)
        endif
    endfor
endfor

4. 实现图像旋转

4.1 旋转矩阵的构建

旋转矩阵需要考虑角度与弧度的转换:

* 定义旋转角度(45度)
angle_deg := 45
angle_rad := rad(angle_deg)

* 构建旋转矩阵
create_matrix(3, 3, 
    [cos(angle_rad), -sin(angle_rad), 0,
     sin(angle_rad),  cos(angle_rad), 0,
     0,              0,              1], 
    RotateMatrix)

4.2 处理旋转中的插值问题

旋转会导致像素位置的非整数映射,需要使用插值:

* 双线性插值函数
procedure double_biline (Image, Value_x, Value_y, pout)
    * 边界处理
    get_image_size(Image, Width, Height)
    if (Value_x < 0 or Value_y < 0 or 
        Value_x > Height-1 or Value_y > Width-1)
        pout := -1
        return ()
    endif
    
    * 获取相邻整数坐标
    tuple_floor(Value_x, x1) 
    x1 := int(x1)
    tuple_ceil(Value_x, x2)
    x2 := int(x2)
    
    tuple_floor(Value_y, y1)
    y1 := int(y1)
    tuple_ceil(Value_y, y2)
    y2 := int(y2)
    
    * 获取四个相邻点的像素值
    get_grayval(Image, x1, y1, p1)
    get_grayval(Image, x2, y1, p2)
    get_grayval(Image, x1, y2, p3)
    get_grayval(Image, x2, y2, p4)
    
    * 执行双线性插值
    if (x1 == x2)
        px1 := p1
        px2 := p3
    else
        px1 := p1 + (p2 - p1) * (Value_x - x1) / (x2 - x1)
        px2 := p3 + (p4 - p3) * (Value_x - x1) / (x2 - x1)
    endif
    
    if (y1 == y2)
        pout := px1
    else
        pout := px1 + (px2 - px1) * (Value_y - y1) / (y2 - y1)
    endif
endprocedure

4.3 应用旋转矩阵

* 获取旋转矩阵的逆矩阵
invert_matrix(RotateMatrix, 'general', 0, InverseRotateMatrix)

* 创建目标图像
gen_image_const(ImageRotated, 'byte', Width, Height)

* 遍历目标图像
for x := 0 to Height-1 by 1
    for y := 0 to Width-1 by 1
        * 构建目标坐标
        create_matrix(3, 1, [x,y,1], TargetCoord)
        
        * 计算对应的原图坐标
        mult_matrix(InverseRotateMatrix, TargetCoord, 'AB', SourceCoord)
        get_full_matrix(SourceCoord, OrigXY)
        
        * 使用双线性插值获取像素值
        double_biline(Image, OrigXY[0], OrigXY[1], Grayval)
        
        if (Grayval != -1)
            set_grayval(ImageRotated, x, y, Grayval)
        endif
    endfor
endfor

5. 组合变换与性能优化

5.1 变换矩阵的组合

通过矩阵乘法,我们可以将多个变换组合成一个复合变换:

* 先旋转再平移
create_matrix(3, 3, [1,0,50, 0,1,50, 0,0,1], TranslateMatrix)
create_matrix(3, 3, 
    [cos(rad(30)), -sin(rad(30)), 0,
     sin(rad(30)),  cos(rad(30)), 0,
     0,              0,              1], 
    RotateMatrix)

* 组合变换:T = 平移 × 旋转
mult_matrix(TranslateMatrix, RotateMatrix, 'AB', CombinedMatrix)

5.2 性能优化技巧

  1. 矩阵预计算:提前计算好所有变换矩阵,避免在循环中重复计算
  2. 批量操作:使用Halcon的向量化操作替代逐像素处理
  3. 内存管理:及时释放不再使用的矩阵句柄
* 优化后的旋转实现
procedure optimized_rotate (Image, angle_deg, ImageRotated)
    * 预计算矩阵
    angle_rad := rad(angle_deg)
    create_matrix(3, 3, 
        [cos(angle_rad), -sin(angle_rad), 0,
         sin(angle_rad),  cos(angle_rad), 0,
         0,              0,              1], 
        RotateMatrix)
    invert_matrix(RotateMatrix, 'general', 0, InverseRotateMatrix)
    
    * 获取图像尺寸
    get_image_size(Image, Width, Height)
    gen_image_const(ImageRotated, 'byte', Width, Height)
    
    * 批量处理坐标
    create_matrix(3, Height*Width, [...], AllCoords)  // 实际代码需填充所有坐标
    mult_matrix(InverseRotateMatrix, AllCoords, 'AB', TransformedCoords)
    
    * 批量插值和赋值
    // 实际实现会更复杂,这里展示思路
endprocedure
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐