使用Pytoch实现Opencv warpAffine方法

2023-12-04 07:20

本文主要是介绍使用Pytoch实现Opencv warpAffine方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

随着深度学习的不断发展,GPU/NPU的算力也越来越强,对于一些传统CV计算也希望能够直接在GPU/NPU上进行,例如Opencv的warpAffine方法。Opencv的warpAffine的功能主要是做仿射变换,如果不了解仿射变换的请自行了解。由于Pytorch的图像坐标系(图像左上角对应坐标(-1, -1)右下角对应坐标(1, 1))与Opencv的坐标系(图像左上角对应坐标(0, 0)右下角对应坐标(w - 1, h - 1))有差异,故无法直接使用Opencv的warp矩阵对Pytorch数据进行变换。
主要参考文章:https://zhuanlan.zhihu.com/p/349741938


本文逻辑推理部分主要是参照上述的参考文章,这里再简单推导一遍。后面会给出基于该公式推导的Pytorch实现。

下面公式简单介绍了原始图片中 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)点通过仿射变化到输出图片 ( x 2 , y 2 ) (x_2, y_2) (x2,y2)点的过程,假设 ( x , y ) (x, y) (x,y)对应Opencv图像坐标系。

[ x 2 y 2 1 ] = [ a b c d e f 0 0 1 ] [ x 1 y 1 1 ] \begin{bmatrix} x_2\\ y_2 \\ 1 \end{bmatrix} = \begin{bmatrix} a & b & c\\ d & e & f\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x_1\\ y_1 \\ 1 \end{bmatrix} x2y21 = ad0be0cf1 x1y11
现在要将Opencv图像坐标系下的 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)点映射到Pytorch的图像坐标系下 ( u 1 , v 1 ) (u_1, v_1) (u1,v1)点,由于Pytorch的图像坐标系是从-1到1,所以对Opencv的坐标做如下变化即可。注,由于Opencv坐标从0开始,所以对于原图宽为src_w,高为src_h实际右下角的坐标应该是 ( s r c w − 1 , s r c h − 1 ) (src_w - 1, src_h - 1) (srcw1,srch1)
u 1 = x 1 − s r c w − 1 2 s r c w − 1 2 = 2 x 1 s r c w − 1 − 1 u_1 = \frac{x_1 - \frac{src_w - 1}{2} }{\frac{src_w - 1}{2}} = \frac{2x_1}{src_w - 1} -1 u1=2srcw1x12srcw1=srcw12x11
v 1 = y 1 − s r c h − 1 2 s r c h − 1 2 = 2 y 1 s r c h − 1 − 1 v_1 = \frac{y_1 - \frac{src_h - 1}{2} }{\frac{src_h - 1}{2}} = \frac{2y_1}{src_h - 1} -1 v1=2srch1y12srch1=srch12y11
写成矩阵乘的形式:
[ u 1 v 1 1 ] = [ 2 s r c w − 1 0 − 1 0 2 s r c h − 1 − 1 0 0 1 ] [ x 1 y 1 1 ] \begin{bmatrix} u_1\\ v_1 \\ 1 \end{bmatrix} = \begin{bmatrix} \frac{2}{src_w - 1} & 0 & -1\\ 0 & \frac{2}{src_h - 1} & -1\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x_1\\ y_1 \\ 1 \end{bmatrix} u1v11 = srcw12000srch120111 x1y11

那么同理将仿射变化后Opencv图像坐标系下的 ( x 2 , y 2 ) (x_2, y_2) (x2,y2)点映射到Pytorch的图像坐标系下 ( u 2 , v 2 ) (u_2, v_2) (u2,v2)点,其中dst_w为仿射变化后输出图片的宽度,dst_h为仿射变化后输出图片的高度:
[ u 2 v 2 1 ] = [ 2 d s t w − 1 0 − 1 0 2 d s t h − 1 − 1 0 0 1 ] [ x 2 y 2 1 ] \begin{bmatrix} u_2\\ v_2 \\ 1 \end{bmatrix} = \begin{bmatrix} \frac{2}{dst_w - 1} & 0 & -1\\ 0 & \frac{2}{dst_h - 1} & -1\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x_2\\ y_2 \\ 1 \end{bmatrix} u2v21 = dstw12000dsth120111 x2y21
然后将上面两个公式代入最开始的仿射变化公式中:
[ 2 d s t w − 1 0 − 1 0 2 d s t h − 1 − 1 0 0 1 ] − 1 [ u 2 v 2 1 ] = [ a b c d e f 0 0 1 ] [ 2 s r c w − 1 0 − 1 0 2 s r c h − 1 − 1 0 0 1 ] − 1 [ u 1 v 1 1 ] \begin{bmatrix} \frac{2}{dst_w - 1} & 0 & -1\\ 0 & \frac{2}{dst_h - 1} & -1\\ 0 & 0 & 1 \end{bmatrix}^{-1} \begin{bmatrix} u_2\\ v_2 \\ 1 \end{bmatrix} = \begin{bmatrix} a & b & c\\ d & e & f\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} \frac{2}{src_w - 1} & 0 & -1\\ 0 & \frac{2}{src_h - 1} & -1\\ 0 & 0 & 1 \end{bmatrix}^{-1} \begin{bmatrix} u_1\\ v_1 \\ 1 \end{bmatrix} dstw12000dsth120111 1 u2v21 = ad0be0cf1 srcw12000srch120111 1 u1v11
整理得到:
[ u 2 v 2 1 ] = [ 2 d s t w − 1 0 − 1 0 2 d s t h − 1 − 1 0 0 1 ] [ a b c d e f 0 0 1 ] [ 2 s r c w − 1 0 − 1 0 2 s r c h − 1 − 1 0 0 1 ] − 1 [ u 1 v 1 1 ] \begin{bmatrix} u_2\\ v_2 \\ 1 \end{bmatrix} = \begin{bmatrix} \frac{2}{dst_w - 1} & 0 & -1\\ 0 & \frac{2}{dst_h - 1} & -1\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} a & b & c\\ d & e & f\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} \frac{2}{src_w - 1} & 0 & -1\\ 0 & \frac{2}{src_h - 1} & -1\\ 0 & 0 & 1 \end{bmatrix}^{-1} \begin{bmatrix} u_1\\ v_1 \\ 1 \end{bmatrix} u2v21 = dstw12000dsth120111 ad0be0cf1 srcw12000srch120111 1 u1v11
引用参考文章中大佬的原话,这个暂时没在Pytorch官方文档中找到,但是通过实验,确实如此。

affine_grid定义为目标图到原图的变换

所以,Pytorch中使用的theta实际是从 ( u 2 , v 2 ) (u_2, v_2) (u2,v2) ( u 1 , v 1 ) (u_1, v_1) (u1,v1)的矩阵:

[ u 1 v 1 1 ] = [ 2 s r c w − 1 0 − 1 0 2 s r c h − 1 − 1 0 0 1 ] [ a b c d e f 0 0 1 ] − 1 [ 2 d s t w − 1 0 − 1 0 2 d s t h − 1 − 1 0 0 1 ] − 1 [ u 2 v 2 1 ] \begin{bmatrix} u_1\\ v_1 \\ 1 \end{bmatrix} = \begin{bmatrix} \frac{2}{src_w - 1} & 0 & -1\\ 0 & \frac{2}{src_h - 1} & -1\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} a & b & c\\ d & e & f\\ 0 & 0 & 1 \end{bmatrix}^{-1} \begin{bmatrix} \frac{2}{dst_w - 1} & 0 & -1\\ 0 & \frac{2}{dst_h - 1} & -1\\ 0 & 0 & 1 \end{bmatrix}^{-1} \begin{bmatrix} u_2\\ v_2 \\ 1 \end{bmatrix} u1v11 = srcw12000srch120111 ad0be0cf1 1 dstw12000dsth120111 1 u2v21
故Opencv使用的theta到Pytorch的theta变换过程如下:
t h e t a ( p y t o r c h ) = [ 2 s r c w − 1 0 − 1 0 2 s r c h − 1 − 1 0 0 1 ] t h e t a ( o p e n c v ) − 1 [ 2 d s t w − 1 0 − 1 0 2 d s t h − 1 − 1 0 0 1 ] − 1 theta_{(pytorch)} = \begin{bmatrix} \frac{2}{src_w - 1} & 0 & -1\\ 0 & \frac{2}{src_h - 1} & -1\\ 0 & 0 & 1 \end{bmatrix} {theta}^{-1}_{(opencv)} \begin{bmatrix} \frac{2}{dst_w - 1} & 0 & -1\\ 0 & \frac{2}{dst_h - 1} & -1\\ 0 & 0 & 1 \end{bmatrix}^{-1} theta(pytorch)= srcw12000srch120111 theta(opencv)1 dstw12000dsth120111 1

最后给出对应代码实现:

"""
pip install numpy
pip install opencv-python
pip install opencv-python-headless
"""
import numpy as np
import cv2
import torch
import torch.nn.functional as Fdef cal_torch_theta(opencv_theta: np.ndarray, src_h: int, src_w: int, dst_h: int, dst_w: int):m = np.concatenate([opencv_theta, np.array([[0., 0., 1.]], dtype=np.float32)])m_inv = np.linalg.inv(m)a = np.array([[2 / (src_w - 1), 0., -1.],[0., 2 / (src_h - 1), -1.],[0., 0., 1.]], dtype=np.float32)b = np.array([[2 / (dst_w - 1), 0., -1.],[0., 2 / (dst_h - 1), -1.],[0., 0., 1.]], dtype=np.float32)b_inv = np.linalg.inv(b)pytorch_m = a @ m_inv @ b_invreturn torch.as_tensor(pytorch_m[:2], dtype=torch.float32)def main():img_bgr = cv2.imread("1.png")src_h, src_w, _ = img_bgr.shapeprint(f"src image h:{src_h}, w:{src_w}")dst_h = src_h * 2dst_w = src_w * 2print(f"dst image h:{src_h}, w:{src_w}")theta = cv2.getRotationMatrix2D(center=(src_w // 2, src_h // 2), angle=-30, scale=2)# using opencv warpAffinewarp_img_bgr = cv2.warpAffine(src=img_bgr,M=theta,dsize=(dst_w, dst_h),flags=cv2.INTER_LINEAR,borderValue=(0, 0, 0))cv2.imwrite("warp_img.jpg", warp_img_bgr)# using pytorch grid_sampletorch_img_bgr = torch.as_tensor(img_bgr, dtype=torch.float32).unsqueeze(0).permute([0, 3, 1, 2])  # [N,C,H,W]torch_theta = cal_torch_theta(theta, src_h, src_w, dst_h, dst_w).unsqueeze(0)  # [N, 2, 3]grid = F.affine_grid(torch_theta, size=[1, 3, dst_h, dst_w])torch_warp_img_bgr = F.grid_sample(torch_img_bgr, grid=grid, mode="bilinear", padding_mode="zeros")torch_warp_img_bgr = torch_warp_img_bgr.permute([0, 2, 3, 1]).squeeze(0)  # [H, W, C]cv2.imwrite("torch_warp_img.jpg", torch_warp_img_bgr.numpy())# save concat imgcv2.imwrite("compare_warp_img.jpg",np.concatenate([warp_img_bgr, torch_warp_img_bgr.numpy()], axis=1))if __name__ == '__main__':main()

下图是生成的compare_warp_img.jpg图片,左边是通过Opencv warpAffine得到的图片,右边是通过Pytorch grid_sample得到的图片。可以看到基本是一致,如果使用专业的图像对比工具还是能看到像素差异(很难完全对齐)。
在这里插入图片描述

这篇关于使用Pytoch实现Opencv warpAffine方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/452621

相关文章

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

java如何分布式锁实现和选型

《java如何分布式锁实现和选型》文章介绍了分布式锁的重要性以及在分布式系统中常见的问题和需求,它详细阐述了如何使用分布式锁来确保数据的一致性和系统的高可用性,文章还提供了基于数据库、Redis和Zo... 目录引言:分布式锁的重要性与分布式系统中的常见问题和需求分布式锁的重要性分布式系统中常见的问题和需求

SpringBoot基于MyBatis-Plus实现Lambda Query查询的示例代码

《SpringBoot基于MyBatis-Plus实现LambdaQuery查询的示例代码》MyBatis-Plus是MyBatis的增强工具,简化了数据库操作,并提高了开发效率,它提供了多种查询方... 目录引言基础环境配置依赖配置(Maven)application.yml 配置表结构设计demo_st

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery

使用Python绘制蛇年春节祝福艺术图

《使用Python绘制蛇年春节祝福艺术图》:本文主要介绍如何使用Python的Matplotlib库绘制一幅富有创意的“蛇年有福”艺术图,这幅图结合了数字,蛇形,花朵等装饰,需要的可以参考下... 目录1. 绘图的基本概念2. 准备工作3. 实现代码解析3.1 设置绘图画布3.2 绘制数字“2025”3.3

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

Jsoncpp的安装与使用方式

《Jsoncpp的安装与使用方式》JsonCpp是一个用于解析和生成JSON数据的C++库,它支持解析JSON文件或字符串到C++对象,以及将C++对象序列化回JSON格式,安装JsonCpp可以通过... 目录安装jsoncppJsoncpp的使用Value类构造函数检测保存的数据类型提取数据对json数

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

el-select下拉选择缓存的实现

《el-select下拉选择缓存的实现》本文主要介绍了在使用el-select实现下拉选择缓存时遇到的问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录项目场景:问题描述解决方案:项目场景:从左侧列表中选取字段填入右侧下拉多选框,用户可以对右侧