OpenCV图像旋转的原理与技巧
点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达
转自|OpenCV学堂
01
引言
初学图像处理,很多人遇到的第一关就是图像旋转,图像旋转是图像几何变换中最具代表性的操作,包含了插值、背景处理、三角函数等相关知识,一个变换矩阵跟计算图像旋转之后的大小公式就让很多开发者最后直接调用函数了事,但是其实这个东西并没有这么难懂,可以说主要是之前别人写的公式太吓人,小编很久以前第一次接触的也是被吓晕了!所以决定从程序员可以接受的角度从新介绍一下图像旋转基本原理与OpenCV中图像旋转函数操作的基本技巧。
图像旋转基本原理
旋转涉及到两个问题,一个是图像旋转之后的大小会发生改变,会产生背景,通过背景填充方式都是填充黑色,此外旋转还是产生像素的位置迁移,新的位置像素需要通过插值计算获得,常见的插值方式有最近邻、线性插值、立方插值等。
首先看旋转之后的图像宽高变化,如下图所示:
这个是正常的平面坐标系中的旋转矩阵,可以简写为:
是一个2x3的矩阵,但是在图像中左上角是原点,要实现围绕图像的中心位置旋转,M就要重新计算,所以OpenCV中的图像旋转矩阵为:
其中scale是表示矩阵支持旋转+放缩,这里可以把Scale=1。第三列是图像旋转之后中心位置平移量。
函数支持
OpenCV中支持图像旋转的函数有两个,一个是直接支持旋转的函数,但是它支持的是90,180,270这样的特殊角度旋转。
void cv::rotate (
InputArray src,
OutputArray dst,
int rotateCode
)
其中rotateCode参数必须为:
ROTATE_180,
ROTATE_90_CLOCKWISE
ROTATE_90_COUNTERCLOCKWISE
函数warpAffine支持任意角度的旋转,通过定义M矩阵实现
void cv::warpAffine(
InputArray src, // 输入图像
OutputArray dst, // 输出图像
InputArray M, // 旋转矩阵
Size dsize, // 输出图像大小
int flags = INTER_LINEAR, // 像素插值方式
int borderMode = BORDER_CONSTANT, // 背景填充默认为常量
const Scalar & borderValue = Scalar() // 填充颜色默认为黑色
)
但是M如何生成与获取,OpenCV中提供了一个函数根据输入的参数自动生成旋转矩阵M,该函数为:
Mat cv::getRotationMatrix2D(
Point2f center,
double angle,
double scale
)
代码演示
原图
使用自定义的M矩阵实现图像旋转
h, w, c = src.shape
# 定义矩阵
M = np.zeros((2, 3), dtype=np.float32)
# 定义角度
alpha = np.cos(np.pi / 4.0)
beta = np.sin(np.pi / 4.0)
print("alpha : ", alpha)
# 初始化矩阵
M[0, 0] = alpha
M[1, 1] = alpha
M[0, 1] = beta
M[1, 0] = -beta
cx = w / 2
cy = h / 2
tx = (1-alpha)*cx - beta*cy
ty = beta*cx + (1-alpha)*cy
M[0,2] = tx
M[1,2] = ty
# 执行旋转
dst = cv.warpAffine(src, M, (w, h))
cv.imshow("rotate-center-demo", dst)
重新计算旋转之后的图像大小,实现无Crop版本的图像旋转
h, w, c = src.shape
M = np.zeros((2, 3), dtype=np.float32)
alpha = np.cos(np.pi / 4.0)
beta = np.sin(np.pi / 4.0)
print("alpha : ", alpha)
# 初始旋转矩阵
M[0, 0] = alpha
M[1, 1] = alpha
M[0, 1] = beta
M[1, 0] = -beta
cx = w / 2
cy = h / 2
tx = (1-alpha)*cx - beta*cy
ty = beta*cx + (1-alpha)*cy
M[0,2] = tx
M[1,2] = ty
# change with full size
bound_w = int(h * np.abs(beta) + w * np.abs(alpha))
bound_h = int(h * np.abs(alpha) + w * np.abs(beta))
# 添加中心位置迁移
M[0, 2] += bound_w / 2 - cx
M[1, 2] += bound_h / 2 - cy
dst = cv.warpAffine(src, M, (bound_w, bound_h))
cv.imshow("rotate without cropping", dst)
背景随便变化+无Crop版本的图像旋转动态演示
degree = 1.0
d1 = np.pi / 180.0
while True:
alpha = np.cos(d1*degree)
beta = np.sin(d1*degree)
M[0, 0] = alpha
M[1, 1] = alpha
M[0, 1] = beta
M[1, 0] = -beta
cx = w / 2
cy = h / 2
tx = (1 - alpha) * cx - beta * cy
ty = beta * cx + (1 - alpha) * cy
M[0, 2] = tx
M[1, 2] = ty
# change with full size
bound_w = int(h * np.abs(beta) + w * np.abs(alpha))
bound_h = int(h * np.abs(alpha) + w * np.abs(beta))
M[0, 2] += bound_w / 2 - cx
M[1, 2] += bound_h / 2 - cy
red = np.random.randint(0, 255)
green = np.random.randint(0, 255)
blue = np.random.randint(0, 255)
dst = cv.warpAffine(src, M, (bound_w, bound_h), borderMode=cv.BORDER_CONSTANT, borderValue=(blue, green, red))
cv.imshow("rotate+background", dst)
c = cv.waitKey(1000)
if c == 27:
break
degree += 1
print("degree", degree)
if degree > 360:
degree = degree % 360
交流群
欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~