图像几何变换是图像处理中最基础的一类操作,本文不讲任何公式和理论,只用 OpenCV C++ 演示如何实现常见几何变换,包括:缩放、平移、旋转、仿射变换、透视变换。
欧式变换
欧式变换是一类特殊的变换,包括旋转与平移,该变换保持了物体间的距离,角度等物理量,因此也被称作刚体变换(即变换不发生形变)。OpenCV提供cv::getRotationMatrix2D生成欧式变换的变换矩阵,定义如下:
Mat getRotationMatrix2D(Point2f center, double angle, double scale);
- center:旋转中心点,我们可以绕图像中心旋转,也可以绕原点旋转
- angle:旋转角度,使用角度值表示(非弧度),表示绕旋转点逆时针旋转
- scale:缩放因子,一般情况下该因子设置为1,大于1表示放大图像,小于1表示缩小图像
以下给出旋转矩阵获取示例:
// 绕图像中心旋转45度,执行如下步骤:
// 1 将图像中心center平移到原点(-cx,-cy)
// 2 施加旋转
// 3 平移回原坐标(+cx,+cy)
// 以上构成了绕中心旋转矩阵
cv::Point2f center(src.cols / 2.0f, src.rows / 2.0f);
// 参数scale表示缩放系数,大于1时放大图像,小于1时缩小图像
// 这里使用scale=1保持图像尺寸(即仅欧式变换)
cv::Mat M = cv::getRotationMatrix2D(center, 45, 1.);
std::cout << "rotate 45 around image center:\n" << M << std::endl;
以上代码中我们备注了平移相关内容,注意此处的平移仅仅是为了实现绕图像中心点旋转功能。我们使用了两次相反方向的平移抵消了平移效果,最终实现了绕图像中心点旋转的变换。
为了实现平移,我们可以构造平移矩阵。所谓平移矩阵,即为旋转量为单位矩阵,平移量为的矩阵,如
。
给定一个变换:如对图像平移并绕图像中心旋转
,可以使用矩阵乘法实现,如下:
- 使用cv::getRotationMatrix2D获取绕图像中心旋转
的旋转矩阵
- 由于该矩阵为2*3维度矩阵,我们添加第三行数据
构成齐次矩阵
- 构造平移
的齐次矩阵为
- 将两个3*3的矩阵相乘即可得到平移+旋转的效果
有了平移+旋转矩阵后,使用cv::warpAffine函数可实现图像变换,具体如下:
void warpAffine(InputArray src, OutputArray dst, InputArray M,
Size dsize, int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT,
const Scalar& borderValue = Scalar());
- src,dst:输入输出图像
- M:2×3 仿射变换矩阵
- dsize:输出图像大小,通过修改该参数可以避免变换后越界问题
- flags:插值方式
,在深入理解图像插值:从原理到应用
中有系统讲解,一般选择默认参数即可 - borderMode:边界处理,如
BORDER_CONSTANT
,BORDER_REFLECT,函数根据不同选择策略进行边界填充
- borderValue:当边界模式为
BORDER_CONSTANT时,该参数给定填充常量值
以下实现了平移+旋转的效果:
void shiftAndRotate()
{
cv::Mat src = cv::imread("1_src.bmp", cv::IMREAD_COLOR);
if (src.empty()) return; // 检查是否成功读取图像
cv::imwrite("1_src.png", src);
// 绕图像中心旋转45度,执行如下步骤:
// 1 将图像中心center平移到原点(-cx,-cy)
// 2 施加旋转
// 3 平移回原坐标(+cx,+cy)
// 以上构成了绕中心旋转矩阵
cv::Point2f center(src.cols / 2.0f, src.rows / 2.0f);
// 参数scale表示缩放系数,大于1时放大图像,小于1时缩小图像
// 这里使用scale=1保持图像尺寸(即仅欧式变换)
cv::Mat M = cv::getRotationMatrix2D(center, 45, 1.);
std::cout << "rotate 45 around image center:\n" << M << std::endl;
// 构造一个3x3单位矩阵
cv::Mat _M = cv::Mat::eye(3, 3, CV_32F);
// 将2x3前两行复制到3x3前两行
M.row(0).copyTo(_M.row(0).colRange(0, 3));
M.row(1).copyTo(_M.row(1).colRange(0, 3));
// 平移(20,30)
cv::Mat M2 = (cv::Mat_<float>(3, 3) << 1, 0, 50, 0, 1, 50, 0,0,1);
// 获取平移+旋转矩阵
cv::Mat M3 = M2 * _M;
// 裁剪到2*3矩阵
cv::Mat M4 = cv::Mat::zeros(2, 3, CV_32F);
M3.row(0).copyTo(M4.row(0).colRange(0, 3));
M3.row(1).copyTo(M4.row(1).colRange(0, 3));
cv::Mat dst;
warpAffine(src, dst, M4, src.size());
cv::imwrite("2_rotate_shift.png", dst);
}


仿射变换与透视变换
对于一般的仿射变换与透视变换,在OpenCV中,他们的变换矩阵维度不一样。仿射变换矩阵为2*3的变换矩阵,透视变换为3*3的变换矩阵。除此之外,其调用流程基本一致。下面给出仿射变换与透视变换的使用示例:
void affineAndPerspective()
{
cv::Mat src = cv::imread("1_src.bmp", cv::IMREAD_COLOR);
if (src.empty()) return; // 检查是否成功读取图像
// 使用getAffineTransform获取仿射变换矩阵
// 需要3对点确定变换矩阵!
cv::Point2f srcTri[] = {
cv::Point2f(0, 0),
cv::Point2f(src.cols - 1, 0),
cv::Point2f(0, src.rows - 1)
};
cv::Point2f dstTri[] = {
cv::Point2f(src.cols * 0.f, src.rows * .3f),
cv::Point2f(src.cols * .9f, src.rows * .2f),
cv::Point2f(src.cols * .15f, src.rows * .8f)
};
cv::Mat M = cv::getAffineTransform(srcTri, dstTri);
cv::Mat dst;
cv::warpAffine(src, dst, M, src.size(), cv::INTER_CUBIC);
cv::imwrite("2_affine.bmp", dst);
// 使用getPerspectiveTransform获取透视变换矩阵
// 需要4对点确定变换矩阵!
cv::Point2f srcTri2[] = {
cv::Point2f(0, 0),
cv::Point2f(src.cols - 1, 0),
cv::Point2f(0, src.rows - 1),
cv::Point2f(src.cols - 1, src.rows - 1)
};
cv::Point2f dstTri2[] = {
cv::Point2f(src.cols * 0.f, src.rows * .3f),
cv::Point2f(src.cols * .9f, src.rows * .2f),
cv::Point2f(src.cols * .15f, src.rows * .8f),
cv::Point2f(src.cols * .7f, src.rows * .7f)
};
cv::Mat M2 = cv::getPerspectiveTransform(srcTri2, dstTri2);
cv::Mat dst2;
cv::warpPerspective(src, dst2, M2, src.size(), cv::INTER_CUBIC);
cv::imwrite("3_perspective.bmp", dst2);
}
我们首先通过变换点对计算变换矩阵。对于平面上的仿射变换来说,只需要3个不共线的点就可以确定一个仿射变换矩阵。对于平面上的透视变换来说,需要不共线的4个点确保一个透视变换矩阵。仿射变换保持了平行性,透视变换不再保持平行性。


深入理解图像几何变换
在解析图像几何变换:从欧式到仿射再到透视中,我们对图像几何变换进行了详细的描述,包括几何变换相关的数学原理以及完整的示例代码,您可以参考该博文以深入理解图像几何变换相关内容。