模拟相机拍照——对文档进行数据增强

一. 背景

假如我们有一个标准文件,我们对其进行文字识别、版面分析或者其他下游任务就比较容易。然而,当图片是手机拍照获取的,图片中往往有阴影、摩尔纹、弯曲。
那么,如何通过标准的文档,获得类似相机拍照的图片呢?
这里介绍的就是文档数据增强,用标准文档模拟相机拍照场景。该方法不仅能用于文档各场景的数据增强,用于OCR检测识别等任务;还能合成各种图片训练对,用于文档去阴影、文档去摩尔纹、文档弯曲矫正等各项任务。

二. 效果实现

首先给大家展示的是一个PDF截图和对应的标注(红色为标注框)
在这里插入图片描述
下面给标准图片分别添加阴影、摩尔纹、弯曲,效果如下:
在这里插入图片描述
摩尔纹+弯曲,并且把标注点映射到弯曲图片上,如下图所示:
在这里插入图片描述
阴影+弯曲,并且把标注点映射到弯曲图片上,如下图所示:
在这里插入图片描述

三. 算法原理与代码实现

原理:利用渲染工具(推荐blender),渲染出各种弯曲、阴影、摩尔纹,然后再pdf图片上进行合成。
最后,一定要代码实现(只给初级版本,完整版本比较复杂):

import os
import cv2
import json
import random
import numpy as np
from scipy.interpolate import LinearNDInterpolator as linterp
from scipy.interpolate import NearestNDInterpolator as nearestclass LinearNDInterpolatorExt(object):def __init__(self, points, values):self.funcinterp = linterp(points, values)self.funcnearest = nearest(points, values)def __call__(self, *args):z = self.funcinterp(*args)chk = np.isnan(z)if chk.any():return np.where(chk, self.funcnearest(*args), z)else:return zdef crop_flow_from_nan(flow):mask = ~np.any(np.isnan(flow), -1)x, y, w, h = cv2.boundingRect(mask.astype(np.uint8))flow = flow[y: y + h, x: x + w]mask = mask[y: y + h, x: x + w]max_nonzero_ratio = 0.9max_crop_size = 20mask_h, mask_w = mask.shape[0], mask.shape[1]y0 = max_crop_sizefor i in range(0, max_crop_size):if np.count_nonzero(mask[i]) / mask_w > max_nonzero_ratio:y0 = ibreaky1 = mask_h - 1 - max_crop_sizefor i in range(mask_h - 1, y1, -1):if np.count_nonzero(mask[i]) / mask_w > max_nonzero_ratio:y1 = ibreakcrop_mask = mask[y0:y1]mask_h, mask_w = crop_mask.shape[0], crop_mask.shape[1]x0 = max_crop_sizefor i in range(0, x0):if np.count_nonzero(mask[:, i]) / mask_h > max_nonzero_ratio:x0 = ibreakx1 = mask_w - 1 - max_crop_sizefor i in range(mask_w - 1, x1, -1):if np.count_nonzero(mask[:, i]) / mask_h > max_nonzero_ratio:x1 = ibreakflow = flow[y0:y1, x0:x1]return flowdef flow_2_points(flow, pts):"""根据flow映射场反向计算点的对应点:param flow: 前向、或后向映射场, range (-1,  1):param pts: 目标图、或原图的坐标点, 点经过归一化 range (0, 1),  shape: (n, 2):return: 原图、或目标图的坐标点, 经过归一化 range (0, 1), shape: (n, 2)"""mask = ~np.any(np.isnan(flow), -1)flow_masked = flow[mask]flow_w, flow_h = flow.shape[1], flow.shape[0]flow_xrange = np.arange(flow_w, dtype=np.float32)flow_yrange = np.arange(flow_h, dtype=np.float32)flow_xgrid, flow_ygrid = np.meshgrid(flow_xrange, flow_yrange)flow_xgrid_masked = flow_xgrid[mask]flow_ygrid_masked = flow_ygrid[mask]src_pts = (pts - 0.5) * 2  # (0-1) to (-1, 1)interpX = LinearNDInterpolatorExt(np.reshape(flow_masked, [-1, 2]), flow_xgrid_masked.reshape(-1))interpY = LinearNDInterpolatorExt(np.reshape(flow_masked, [-1, 2]), flow_ygrid_masked.reshape(-1))fm_x = interpX(src_pts)fm_y = interpY(src_pts)# fm_x, fm_y range is (0, flow_w-1)  and (0, flow_h-1), need convert to (0-1)fm_x = fm_x / (flow_w - 1)fm_y = fm_y / (flow_h - 1)return np.stack((fm_x, fm_y), axis=-1)def warp_img(img, flow, points_list):h, w, _ = img.shapeflow = crop_flow_from_nan(flow)flow = flow.astype(np.float32)flow = cv2.resize(flow, (256, 256))points_list_warp = []for points in points_list:points = points.astype(np.float64)points[:, 0] /= w*1.0points[:, 1] /= h*1.0points_warp = flow_2_points(flow, points)points_warp[:, 0] *= wpoints_warp[:, 1] *= hpoints_list_warp.append(points_warp)bm_flow = flow / 2 + 0.5bm_flow[..., 0] = bm_flow[..., 0] * wbm_flow[..., 1] = bm_flow[..., 1] * hbm_flow = np.nan_to_num(bm_flow, nan=-1)if bm_flow.shape[0] != h or bm_flow.shape[1] != w:bm_flow = cv2.resize(bm_flow, (w, h))warp_img = cv2.remap(img, bm_flow.astype(np.float32), None, cv2.INTER_LINEAR, borderValue=(255, 255, 255))return warp_img, points_list_warpdef json_2_points(json_path):with open(json_path, "r") as f:data = json.load(f)obj_list = []for obj in data[0]['annotations']:obj = obj['coordinates']cx, cy, w, h = obj['x'], obj['y'], obj['width'], obj['height']x1 = cx - 0.5 * wx2 = cx + 0.5 * wy1 = cy - 0.5 * hy2 = cy + 0.5 * hpoints = np.array([[x1,y1], [x2,y1], [x2,y2], [x1,y2]], np.int32)obj_list.append(points)return obj_listdef add_background(img, img_background):height, width, _ = img.shapebackground = cv2.resize(img_background, (width, height))img_res = img * 0.5 + background * 0.5img_res = np.clip(img_res, 0, 255)return img_resif __name__ == "__main__":img = cv2.imread("test.png")shadow = cv2.imread("./background/shadow.jpg")img = add_background(img, shadow)obj_list = json_2_points("test.json")flow = np.load("test.npy")warp_img, points_list_warp = warp_img(img, flow, obj_list)cv2.imwrite("warp_shadow.jpg", warp_img)for points in points_list_warp:cv2.polylines(warp_img, [points.astype(np.int32)], isClosed=True, color=(0, 0, 255), thickness=1)cv2.imwrite("warp_shadow_draw.jpg", warp_img)

致谢,在写代码过程中受到了鑫哥的启发,再次表示感谢!
欢迎小伙伴们技术交流~

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

家庭营销广告Criteo公司首次获得MRC零售媒体测量认证

家庭营销广告Criteo公司首次获得零售媒体测量MRC认证 商业媒体公司Criteo2024年3月28日宣布,它首次获得媒体评级委员会(MRC)的认证,在其企业零售媒体平台commerce Max和commerce Yield上,在桌面、移动网络和移动应用内…

4-Java方法详解

目录 Java方法详解 1、什么是方法 2、方法的定义及调用 3、方法重载 4、命令行传参 5、可变参数 6、递归 例题:代码实现一个计算机 Java方法详解 1、什么是方法 2、方法的定义及调用 形参:用来定义作用的 实参:实际调用传递给他的参数…

C#基于SSE传递消息给Vue前端实现即时单向通讯

一、简述 通常前端调用后端的API,调用到了,等待执行完,拿到返回的数据,进行渲染,流程就完事了。如果想要即时怎么办?如果你想问什么场景非要即时通讯,那可就很多了,比如在线聊天、实…

Java 笔记 01:Java 概述,MarkDown 常用语法整理

一、前言 记录时间 [2024-04-18] 昨天整理完 Docker 基础后略微思索了一下,还是决定把 Java 捡起来,系统地学习一遍,参考的学习课程是狂神说 Java 零基础,真诚感激此系列视频对笔者的帮助。 零基础可以学 Java 吗?只要…

JVS低代码平台表单引擎:字符串拼接与逻辑函数的完美结合

字符串拼接使用逻辑函数配置 示例场景:通过按钮触发逻辑使用函数将两个日期字段组合为范围时间类型。 选择开始日期和结束日期后,点击【合并】按钮自动处理回显至起止日期字段。在【合并】按钮上设置逻辑。 注意:这里【起止日期】组件是数组…

【web开发02】后端开发Maven

后端开发Maven 1 Maven是什么?1.1 Maven基础概念1.1.2 仓库1.1.3 坐标 2 配置maven环境3 创建maven项目4 导入maven项目4 依赖管理4.1 依赖配置4.2 依赖传递4.2.1 排除依赖 4.3 依赖范围4.4 生命周期4.4.1 运行生命周期 1 Maven是什么? Maven本质是项目…

二次元AI绘画生成器免费:教你生成精美图片

二次元AI绘画生成器,无疑是现代技术与艺术完美结合的典范。这些工具不仅将复杂的绘画过程简化,更让每一个艺术爱好者的创意得以充分展现。这些生成器能够精准捕捉大家的创意精髓,将其转化为细腻、独特的二次元画作。无论是角色设计、场景描绘…

SpringMVC(五)【拦截器】

前言 今天来把 SpringMVC 最后一部分学完,虽然课时很短,但是学起来还是很慢的,不过确收获很大。不得不感慨学大数据确实有必要把 SSM、SpringBoot 等各种 JavaEE 技术好好学一学,收获很大,尽管我们到现在 Java 代码写了…

Redis几种常见的应用方式

1.登录认证 redis最常见的应用就是,登录认证把。再首次登录返回给前端token,把用户名和登录状态缓存到redis一段时间,每次其他请求进来过滤器那这token解析出来的用户名或其他关键的key值,再redis里面查询缓存,有则直…

【算法】二分查找

快乐的流畅:个人主页 个人专栏:《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火,在为久候之人燃烧! 文章目录 引言一、二分查找二、查找元素的第一个和最后一个位置三、x的平方根四、搜索插入位置五、山脉数组的峰顶索引…

【Leetcode每日一题】 分治 - 排序数组(难度⭐⭐)(60)

1. 题目解析 题目链接:912. 排序数组 这个问题的理解其实相当简单,只需看一下示例,基本就能明白其含义了。 2.算法原理 算法思路: 快速排序作为一种经典的排序算法,其核心思想在于通过“分而治之”的策略&#xff…

eCongnition 根据栅格类别图分类分割结果

目录 1、导入标签文件 2、根据栅格类别计算对象类别 3、导出栅格计算的类别 1、导入标签文件 导入栅格类别文件Label.tif 参考:eCongnition 对图像进行多尺度分割-CSDN博客 2、根据栅格类别计算对象类别 对类别栅格创建 mode[Maximum] 特征,该特征…

SQL Serve---嵌套查询

定义 嵌套查询:主要用于复杂的查询中。在SQL语言中,一个Select From Where语句称为一个查询块,将一个查询块嵌套在另一个查询的Where子句或Having短语中的查询称为嵌套查询。 子查询的类型 使用别名的子查询 使用IN和NOT IN的子查询 使用比较…

基于SSM的列车订票管理系统(含源码+sql+视频导入教程+文档+PPT)

👉文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的列车订票管理系统3拥有两种角色;管理员、用户 管理员:用户管理、车票管理、购票指南管理、系统管理等 用户:发布帖子、登录注册、购票等 1.…

数据结构速成--串

由于是速成专题,因此内容不会十分全面,只会涵盖考试重点,各学校课程要求不同 ,大家可以按照考纲复习,不全面的内容,可以看一下小编主页数据结构初阶的内容,找到对应专题详细学习一下。 目录 …

【Linux冯诺依曼体系结构】

目录 1.冯诺依曼体系结构原理 1.冯诺依曼体系结构 我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。 截至目前,我们所认识的计算机,都是有一个个的硬件组件组成 输入单元&#…

最新SpringBoot项目财务管理系统

采用技术 最新SpringBoot项目财务管理系统的设计与实现~ 开发语言:Java 数据库:MySQL 技术:SpringBootMyBatis 工具:IDEA/Ecilpse、Navicat、Maven 页面展示效果 系统登录页面 管理员功能 管理员功能页面 员工管理页面 部…

SpringBoot多数据源(五)

SpringBoot多数据源-集成多个mybatis框架 1.基本框架2.使用2.1项目结构2.2 依赖导入2.3 application.yml配置2.4 创建读与写的SqlSessionFactoryBean 3.总结 1.基本框架 通过启动多个SqlSessionFactoryBean,每个SqlSessionFactoryBean对应一个datasource和指定位置的…

STM32G431RBT6移植FreeRTOS

引言: 本文专门为参加了蓝桥杯嵌入式赛道的同学准备, 大家可能会有这样一个问题, 比完赛之后, 对于像继续使用STM32G431RBT6学习FreeRTOS的, 发现网上的教程使用的板子基本上都是F1和F4的, 其实呢&#xff…

二叉树的最大深度 - LeetCode 热题 37

大家好!我是曾续缘😛 今天是《LeetCode 热题 100》系列 发车第 37 天 二叉树第 2 题 ❤️点赞 👍 收藏 ⭐再看,养成习惯 二叉树的最大深度 给定一个二叉树 root ,返回其最大深度。 二叉树的 最大深度 是指从根节点到最…