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

news2025/1/11 7:04:45

一. 背景

假如我们有一个标准文件,我们对其进行文字识别、版面分析或者其他下游任务就比较容易。然而,当图片是手机拍照获取的,图片中往往有阴影、摩尔纹、弯曲。
那么,如何通过标准的文档,获得类似相机拍照的图片呢?
这里介绍的就是文档数据增强,用标准文档模拟相机拍照场景。该方法不仅能用于文档各场景的数据增强,用于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 nearest


class 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 z


def 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.9
    max_crop_size = 20
    mask_h, mask_w = mask.shape[0], mask.shape[1]
    y0 = max_crop_size
    for i in range(0, max_crop_size):
        if np.count_nonzero(mask[i]) / mask_w > max_nonzero_ratio:
            y0 = i
            break

    y1 = mask_h - 1 - max_crop_size
    for i in range(mask_h - 1, y1, -1):
        if np.count_nonzero(mask[i]) / mask_w > max_nonzero_ratio:
            y1 = i
            break

    crop_mask = mask[y0:y1]
    mask_h, mask_w = crop_mask.shape[0], crop_mask.shape[1]
    x0 = max_crop_size
    for i in range(0, x0):
        if np.count_nonzero(mask[:, i]) / mask_h > max_nonzero_ratio:
            x0 = i
            break

    x1 = mask_w - 1 - max_crop_size
    for i in range(mask_w - 1, x1, -1):
        if np.count_nonzero(mask[:, i]) / mask_h > max_nonzero_ratio:
            x1 = i
            break
    flow = flow[y0:y1, x0:x1]
    return flow


def 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.shape
    flow = 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.0
        points[:, 1] /= h*1.0
        points_warp = flow_2_points(flow, points)
        points_warp[:, 0] *= w
        points_warp[:, 1] *= h
        points_list_warp.append(points_warp)

    bm_flow = flow / 2 + 0.5
    bm_flow[..., 0] = bm_flow[..., 0] * w
    bm_flow[..., 1] = bm_flow[..., 1] * h
    bm_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_warp


def 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 * w
        x2 = cx + 0.5 * w
        y1 = cy - 0.5 * h
        y2 = cy + 0.5 * h
        points = np.array([[x1,y1], [x2,y1], [x2,y2], [x1,y2]], np.int32)
        obj_list.append(points)
    return obj_list


def add_background(img, img_background):
    height, width, _ = img.shape
    background = cv2.resize(img_background, (width, height))
    img_res = img * 0.5 + background * 0.5
    img_res = np.clip(img_res, 0, 255)
    return img_res


if __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.coloradmin.cn/o/1606959.html

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

相关文章

24华中杯C题10页论文+代码+思路

问题1:估算传感点的曲率 问题2:重构平面曲线 问题3:重构平面曲线并分析误差 详细资料如图所示 10页论文 需要的宝子们:2024华中杯A题思路数据可执行代码参考论文https://mbd.pub/o/bread/ZZ6am5dw 2024华中杯B题思路数据可执行…

C语言简单的数据结构:双向链表的实现

目录: 1.双向链表的结构和初始化1.1双向链表的结构1.2双向链表的初始化 2.双向链表的相关操作2.1双向链表的尾插、打印和头插2.11双向链表的尾插2.12双向链表的打印2.13双向链表的头插 2.2双向链表的尾删和头删2.21双向链表的尾删2.22双向链表的头删 2.3双向链表查找…

Linux 网络测速

1.开发背景 网络测速,为了测试开发板的网络速度是否达标的通用测试方法 2.开发需求 搭建 iperf3 ,在 ubuntu 下安装服务端,在板卡上安装客户端,服务端和客户端互发 3.开发环境 ubuntu20.04 嵌入式开发板(debian 千…

了解MySQL InnoDB多版本MVCC(Multi-Version Concurrency Control)

了解MySQL InnoDB多版本MVCC(Multi-Version Concurrency Control) 在数据库管理系统中,多版本并发控制(MVCC)是一种用于实现高并发和事务隔离的技术。MySQL的InnoDB存储引擎支持MVCC,这使得它可以在提供高…

22长安杯电子取证复现(检材一,二)

检材一 先用VC容器挂载,拿到完整的检材 从检材一入手,火眼创建案件,打开检材一 1.检材1的SHA256值为 计算SHA256值,直接用火眼计算哈希计算 9E48BB2CAE5C1D93BAF572E3646D2ECD26080B70413DC7DC4131F88289F49E34 2.分析检材1&am…

Spring (三) 之Aop及事务控制

文章目录 目标 一、AOP 思想和重要术语(理解)1、需求问题2、AOP3、AOP 术语 二、AOP 实现及 Pointcut 表达式(了解)1、AOP 规范及实现2、AspectJ3、AspectJ 切入点语法(掌握)3.1、切入点语法通配符3.2、切入…

Linux 网络基本命令

一、查看网络信息 ifconfig 二、关闭网络 ifdown ens33 (有的电脑不一定是ens33,具体看上图画线的地方) 三、开启网络 ifup ens33

【电路笔记】-数字逻辑门总结

数字逻辑门总结 文章目录 数字逻辑门总结1、概述2、逻辑门真值表3、总结 数字逻辑门有三种基本类型:与门、或门和非门。 1、概述 我们还看到,数字逻辑门具有与其相反或互补的形式,分别为“与非门”、“或非门”和“缓冲器”,并且…

RK3588 Android13 鼠标风格自定义动态切换

前言 电视产品,客户提供了三套鼠标图标过来,要求替换系统中原有丑陋风格且要支持动态切换, 并且在 TvSetting 中要有菜单,客户说啥就是啥呗,开整。 效果图 test framework 部分修改文件清单 png 为鼠标风格资源图片,这里就不提供了,可自由找一个替换一下就行 framew…

C语言野指针【入门详解】

目录 一、什么是野指针 二、野指针的成因 2.1 指针未初始化 2.2 指针越界访问 2.3 指针指向的空间释放 三、如何规避野指针 3.1 初始化指针 3.2 小心越界访问 3.3 当指针不用时,及时置为空 3.4 避免返回局部变量的地址 *结语: 希望这篇关于指…

[SWPUCTF 2021 新生赛]jicao、easy_md5

目录 一、[SWPUCTF 2021 新生赛]jicao 什么是JSON? JSON语法: [SWPUCTF 2021 新生赛]jicao 二、[SWPUCTF 2021 新生赛]easy_md5 PHP弱类型和强类型 1.弱类型比较() 2.强类型比较() [SWPUCTF 2021 …

OceanBase数据库日常运维快速上手

这里为大家汇总了从租户创建、连接数据库,到数据库的备份、归档、资源配置调整等,在OceanBase数据库日常运维中的操作指南。 创建租户 方法一:通过OCP 创建 确认可分配资源 想要了解具体可分配的内存量,可以通过【资源管理】功…

华为OD机试 - 结队编程(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA)真题(A卷B卷C卷)》。 刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试…

Google DeepMind: Many-Shot vs. Few-Shot

本文介绍了如何通过增大上下文窗口,利用大型语言模型(LLMs)进行多实例上下文学习(Many-Shot In-Context Learning,ICL)的方法。主要描述了现有的几实例上下文学习方法虽然在推理时能够通过少量例子学习&…

MicroSIP电话呼叫软件使用及配置方法

MicroSIP是一款开源的SIP协议电话软件,它可以帮助你在计算机上进行语音和视频通话。下面是关于如何使用和配置MicroSIP的一些基本步骤: 安装MicroSIP 从MicroSIP官方网站下载适合你操作系统的安装包23。 解压下载的文件,并运行安装程序。 …

可以与 FastAPI 不分伯仲的 Python 著名的 Web 框架

正如你所理解的,任何领域都不可能停止进步,不断使用相同的工具意味着不思进取。这一点在信息技术领域,尤其是网络开发行业非常明显。 关于网络框架,不论是 Django 和 Flask 等传统框架还是 Python 的新型高级框架,一直…

算法课程笔记——常用库函数

memset初始化 设置成0是可以每个设置为0 而1时会特别大 -1的补码是11111111 要先排序 unique得到的是地址 地址减去得到下标 结果会放到后面 如果这样非相邻 会出错 要先用sort排序 O(n)被O(nlogn)覆盖

【智能算法】饥饿游戏搜索算法(HGS)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2021年,Yang等人受到自然界饥饿驱动的活动和动物的行为选择启发,提出了饥饿游戏搜索算法(Hunger Games Search, HGS)。 2.算法原理 2.1算法思想 HGS…

【Java网络编程】网络编程概述、UDP通信(DatagramPacket 与 DatagramSocket)

目录 1、网络编程 1.1、概述 1.1、网络编程三要素 2、UDP通信 2.1、DatagramPacket 与 DatagramSocket 2.1、 UDP发收数据示例 1、网络编程 1.1、概述 在网络通信协议下,不同计算机上运行的程序,进行的数据传输应用场景:即时通信、网游…

韩顺平Java | C27 正则表达式

入门介绍 需求:提取文本中某类字符 传统方法:遍历每个字符,判断其是否在ASCII码中某种类型得编码范围内,代码量大,效率不高 正则表达式(RegExp, regular expression):处理文本的利器,是对字符…