使用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 F


def 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_inv
    return torch.as_tensor(pytorch_m[:2], dtype=torch.float32)


def main():
    img_bgr = cv2.imread("1.png")
    src_h, src_w, _ = img_bgr.shape
    print(f"src image h:{src_h}, w:{src_w}")
    dst_h = src_h * 2
    dst_w = src_w * 2
    print(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 warpAffine
    warp_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_sample
    torch_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 img
    cv2.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得到的图片。可以看到基本是一致,如果使用专业的图像对比工具还是能看到像素差异(很难完全对齐)。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/214547.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

web自动化 -- pyppeteer

由于Selenium流行已久,现在稍微有点反爬的网站都会对selenium和webdriver进行识别,网站只需要在前端js添加一下判断脚本,很容易就可以判断出是真人访问还是webdriver。虽然也可以通过中间代理的方式进行js注入屏蔽webdriver检测,但…

【算法套路】(数组中)等价转换

文章目录 例题——2488. 统计中位数为 K 的子数组⭐【套路】子数组统计问题常用技巧:等价转换 相似题目列表面试题 17.05. 字母与数字525. 连续数组1124. 表现良好的最长时间段解法1解法2——利用单调栈 例题——2488. 统计中位数为 K 的子数组⭐ https://leetcode…

了解大模型 RAG (Retrieval-Augmented Generation):大模型外挂知识库 (检索增强技术)

本心、输入输出、结果 文章目录 了解大模型 RAG (Retrieval-Augmented Generation):大模型外挂知识库 (检索增强技术)前言什么是检索增强技术 RAG (Retrieval-Augmented Generation)检索增强技术…

分享几个电视颜色测试图形卡

介绍 本文分享几个常见的电视颜色测试图形卡和一段matlab程序,完成JPG转FPGA烧写文件,便于把彩色图片预装载到FPGA内。 电视颜色测试图形卡 一种专业检测电视显示效果的工具。它通常由一张卡片和一些色块组成,可以根据标准色彩空间和颜色渐…

数据结构 | 查漏补缺之ASL、

目录 ASL 情形之一:二分查找 线索二叉树 哈夫曼树 大根堆 邻接表&邻接矩阵 ASL 参考博文 关于ASL(平均查找长度)的简单总结_平均查找长度asl-CSDN博客 情形之一:二分查找 线索二叉树 参考博文 线索二叉树(线索链表遍历,二叉树…

『亚马逊云科技产品测评』活动征文|基于亚马逊云EC2搭建私有网盘 Nextcloud系统

授权声明:本篇文章授权活动官方亚马逊云科技文章转发、改写权,包括不限于在 Developer Centre, 知乎,自媒体平台,第三方开发者媒体等亚马逊云科技官方渠道 亚马逊EC2云服务器(Elastic Compute Cloud)是亚马…

[架构之路-256]:目标系统 - 设计方法 - 软件工程 - 软件设计 - 架构设计 - 软件系统不同层次的复用与软件系统向越来越复杂的方向聚合

目录 前言: 一、CPU寄存器级的复用:CPU寄存器 二、指令级复用:二进制指令 三、过程级复用:汇编语言 四、函数级复用:C语言 五、对象级复用:C, Java, Python 六、组件级复用 七、服务级复用 八、微…

leetcode 202 快乐数

leetcode 202 快乐数 题目题解代码 题目 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为: 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变…

【尾递归】

尾递归 如果函数在返回前才进行递归调用,则该函数可以被编译器或解释器优化,使其在空间效率上与迭代相当。这种情况被称为「尾递归 tail recursion」。 普通递归:当函数返回到上一层级的函数后,需要继续执行代码,因此…

Android Init系统:引领设备启动的先锋

Android Init系统:引领设备启动的先锋 引言 Init系统是一个操作系统启动的必要组件,负责在启动时初始化所有系统资源、服务和应用程序。在Android设备中,Init系统起到了至关重要的作用,它是启动过程中的第一个进程,负…

C++分数计算器

C分数计算器各种分数计算类型都能计算 代码:https://mbd.pub/o/bread/ZZeZk5hx 一 目的 (1)定义一个整数类。 定义一个分数类,由整数类派生。能对分数进行各种计算和输入/输出。可进行分数的加、减、乘和除法等四则运算。 流程…

YOLOv8 区域计数 | 入侵检测 | 人员闯入

大家好,昨天的 YOLOv8 新增加了一个功能,区域计数,用这个功能我们能实现很多的任务, 比如入侵检测,流量统计,人员闯入等,使用方式也非常的方便,但是一定要使用最新版的 YOLOv8 代码(2023/12/03更新的代码)。 低版本是不具备这个功能的,上面是演示效果。 使用非常的方…

Leetcode2661. 找出叠涂元素

Every day a Leetcode 题目来源:2661. 找出叠涂元素 解法1:哈希 题目很绕,理解题意后就很简单。 由于矩阵 mat 中每一个元素都不同,并且都在数组 arr 中,所以首先我们用一个哈希表 hash 来存储 mat 中每一个元素的…

C语言中的动态内存管理

在C语言中,动态内存管理是通过一系列的标准库函数来实现的,这些函数包括malloc, free, calloc 和 realloc。它们允许程序在运行时动态地分配和释放内存,这是管理复杂数据结构(如链表、树等)时非常有用的功能。 为什么…

软件生命周期四个阶段SDLC

软件产品生命周期:指软件产品研发全部过程、活动和任务的结构框架。 产品的生命周期一般包括四个阶段:引入期、成长期、成熟期和衰退期,在不同的阶段中,市场对产品的反应不同,其销售特点不同,因而产品管理的…

【强化学习算法】Q-learning原理及实现

实现代码github仓库:RL-BaselineCode 代码库将持续更新,希望得到您的支持⭐,让我们一起进步! 文章目录 1. 原理讲解1.1 Q值更新公式1.2 ε-greedy随机方法 2. 算法实现2.1 算法简要流程2.2 游戏场景2.3 算法实现 3. 参考文章 1. 原…

数据挖掘实战-基于word2vec的短文本情感分析

🤵‍♂️ 个人主页:艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞&#x1f4…

专业爬虫框架 -- scrapy初识及基本应用

scrapy基本介绍 Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,使用它可以以快速、简单、可扩展的方式从网站中提取所需的数据。 但目前Scrapy的用途十分广泛,可用于如数据挖掘、监测和自动化测试等领域…

HCIP —— 双点重发布 + 路由策略 实验

目录 实验拓扑: 实验要求: 实验配置: 1.配置IP地址 2.配置动态路由协议 —— RIP 、 OSPF R1 RIP R4 OSPF R2 配置RIP、OSPF 双向重发布 R3配置RIP、OSPF 双向重发布 3.查询路由表学习情况 4.使用路由策略控制选路 R2 R3 5.检…

【Google2023】利用TiDE进行长期预测实战(时间序列密集编码器)

一、本文介绍 大家好,最近在搞论文所以在研究各种论文的思想,这篇文章给大家带来的是TiDE模型由Goggle在2023.8年发布,其主要的核心思想是:基于多层感知机(MLP)构建的编码器-解码器架构,核心创…
最新文章