OpenCV C++学习笔记(5): 原位做4点投影变换

OpenCV官方的warpPerspective示例用一个窗口进行选区, 用另一个窗口进行显示:

trans

但我觉得这样有点麻烦, 不太适合移动端、小屏幕的操作. 于是做了一点改进, 使我可以在一个窗口内直接操作图像.

2020-06-09 13-14-58.2020-06-09 13_17_31

下面的过程说起来有点绕. 反正变换的控制在于找到两组对应点, 在原始图上的o_corner, 和在变形图上的d_corner.

vector<Point2f> d_corner(4);
vector<Point2f> o_corner(4);

在使用两个窗口进行控制+显示时, 变形图上的d_corner就是4个角, 是不变的. 但如果在一个窗口内控制, 就不能是不变的点了.

先假定已经有一个映射矩阵H, 和它的逆矩阵H_inv, 一开始的时候, 原始图像和变形图像是相等的, 那么这两个矩阵就都是单位矩阵.

现在用鼠标点中变形图中的位置(x,y), 那么就可以找到在原始图中这个点对应的位置(ox, oy), 当鼠标按住拖动到(xt, yt)时, 含义就是将原始图中的(ox,oy)映射到了(xt,yt).

比如, 鼠标控制的是vector<Point2f> d_corner;中的第3点, 那么d_corner[3]=Point2f(x,y);, 我写了一个函数来求对应的o_corner[3]

void point_warp(
    Point2f &p_target, 
    const Point2f &p, 
    const Mat &H){
//    映射矩阵H是一个3x3的矩阵,
//    s*[p_target.x, p_target.y, 1]= H * [p.x, p.y, 1]
//    乘出来以后要对第三项归一化
    Mat p_vector = (Mat_ <double>(3, 1) << p.x,p.y, 1);
    p_vector = H * p_vector;
    p_target.x=(float)(p_vector.at<double>(0)/
                       p_vector.at<double>(2));
    p_target.y=(float)(p_vector.at<double>(1)/
                       p_vector.at<double>(2));
}

这里用到的公式就是 s[d_x, d_y, 1] = H * [o_x, o_y, 1]

其中s是一个归一化系数, 就是第三项. 这是要反过来求的, 要用上H的逆矩阵H_inv, 所以实际上是: s[o_x, o_y, 1] = H_inv * [d_x, d_y, 1]

其中Mat类里, 要访问单个元素(像素)的数值, 可以使用.at<类型>(行, 列)的方式来访问. 因为之前使用findHomography求出来的H矩阵似乎是一个double类型的, 所以这里其他的向量最好也先使用double类型. 如果使用float会出错.

所以流程就是:

  • 鼠标在变形图上(x,y)点按下, d_corner[selected_corner_i]=Point2f(x,y);

  • 计算出原始图上的对应点位置

point_warp(
    o_corner[selected_corner_i],
    d_corner[selected_corner_i],
    H_inv);
  • 鼠标开始拖动, 移动到新的(x,y)位置, 此时新的: d_corner[selected_corner_i]=Point2f(x,y);

  • 根据新的o_corner和d_corner分别计算映射矩阵H和逆矩阵H_inv:

H = findHomography(o_corner, d_corner);
H_inv= findHomography(d_corner, o_corner);
  • 在根据映射矩阵H, 将原始图映射到变形图上
Mat warped_image;
warpPerspective(img, warped_image, H, Size(width,height));

完整的代码, 放在gist中