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

一. 背景

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

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

在这里插入图片描述

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

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

相关文章

Java 网络编程之TCP:基于BIO

环境: jdk 17 IntelliJ IDEA 2023.1.1 (Ultimate Edition) Windows 10 专业版 22H2 TCP:面向连接的,可靠的数据传送协议 Java中的TCP网络编程,其实就是基于常用的BIO和NIO来实现的,本文先讨论BIO; BIO…

C# - 反射动态添加/删除Attribute特性

API: TypeDescriptor.AddAttributes TypeDescriptor.GetAttributes 注意:TypeDescriptor.AddAttributes添加的特性需要使用 TypeDescriptor.GetAttributes获取 根据api可以看到,该接口不仅可以给指定类(Type)添加特性&#xf…

CLSRSC-400: A system reboot is required to continue installing

RHEL 7.9ORACLE RAC 12.2.0.1.0,在运行root.sh脚本时,出现CLSRSC-400: A system reboot is required to continue installing报错 # /u01/app/12.2.0/grid/root.sh Performing root user operation.The following environment variables are set as:ORA…

【吊打面试官系列】Java高并发篇 -为什么使用 Executor 框架比使用应用创建和管理线程好?

大家好,我是锋哥。今天分享关于 【为什么使用 Executor 框架比使用应用创建和管理线程好?】面试题,希望对大家有帮助; 为什么使用 Executor 框架比使用应用创建和管理线程好? 为什么要使用 Executor 线程池框架 1、每…

MySQL 锁机制全面解析

目录 1. MySQL的锁类型1.1 全局锁1.2 表锁1.3 行锁1.4 共享锁(读锁)1.5 排它锁(写锁)1.6 死锁 2 乐观锁和悲观锁2.1 乐观锁2.2 悲观锁 3 意向锁4 间隙锁5 临键锁6. 事务隔离级别对锁的影响6.1 读未提交(Read Uncommitt…

Day92:系统攻防-WindowsLinux远程探针本地自检任意执行权限提升入口点

目录 操作系统-远程漏扫-Nessus&Nexpose&Goby Nessus Nexpose 知识点: 1、远程漏扫-Nessus&Nexpose&Goby 2、本地漏扫-Wesng&Tiquan&Suggester 3、利用场景-远程利用&本地利用&利用条件 操作系统-远程漏扫-Nessus&Nexpose&a…

#LLM入门|RAG#3.5_基于文档的问答

大语言模型(LLM)构建的问答系统,通过整合用户文档,提供个性化和专业化回答。系统可检索内部文档或产品说明书,结合语言模型生成精准答案。 这种方法让语言模型利用外部文档专业信息,显著提升回答的质量和适…

RedHat9 KVM虚拟技术

以下有使用RedHat9单独的虚拟机也有使用RHEL9学员练习机和RHEL7学员练习机 KVM虚拟技术介绍 Linux的KVM(Kernel-based Virtual Machine)虚拟技术是一种基于Linux内核的虚拟化解决方案。它允许在单个物理服务器上创建和运行多个隔离的虚拟机,每个虚拟机都有自己的操作系统和…

常见嵌入式存储器学习

这里写目录标题 1. FPGA内部存储器1.1 RAM1.2 ROM 2. 外部存储器 1. FPGA内部存储器 1.1 RAM RAM即随机存取存储器(Random Acccess Memory),数据不是线性依次存储,可以自由指定地址进行数据读写。RAM掉电数据丢失,速…

Docker 镜像仓库常见命令

Docker Registry (镜像仓库) 常用命令 docker login 功能:登录到一个 Docker 镜像仓库,如果没有指定镜像仓库的地址,默认就是官方的 Docker Hub 仓库。 语法: docker login [options] [server]选项: -u:登…

java生成数据库数据到excel当做下拉选择,copy就完事~

背景:由于需要下载模板,模板包含下拉选择框,但是下拉选择框不想手写,并且需要从数据库读取,由于直接设置excel会有单元格最大255个字符长度限制,所以用到以下部分代码。 思路:由于数据模板在sh…

MySQL 的事务概念

事务概念 MySQL事务是一个或者多个的数据库操作,要么全部执行成功,要么全部失败回滚。 事务是通过事务日志来实现的,事务日志包括:redo log和undo log。 事务状态 事务有以下五种状态: 活动的部分提交的失败的中止的…

字母加密(C语言)

一、题目; 为使电文保密,往往按一定规律将其转换成密码,收报人再按约定的规律将其译回原文。例如,可以按以下规律将电文变成密码:将字母A变成字母E,a变成e,即变成其后的第4个字母,W…

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

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

开源项目|使用go语言搭建高效的环信 IM Rest接口(附源码)

项目背景 环信 Server SDK 是对环信 IM REST API 的封装, 可以节省服务器端开发者对接环信 API 的时间,只需要配置自己的 App Key 相关信息即可使用。 环信目前提供java和PHP版本的Server SDK,此项目使用go语言对环信 IM REST API 进行封装…

B端:再探列表页,这20个组件能让列表页功能完备,体验过关。

有很多小伙伴反馈设计列表页的时候,好看是好看了,但是用户体验不佳,处理数据十分不方便,这样好看也就失去了意义,贝格前端工场分析这个原因大概率是没有用好列表页的组件,丢三落四的情况比较多导致的&#…

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

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

「Word 论文排版」插入分节符导致word转PDF后出现空白页

问题 word转PDF后出现空白页 解决 但是此方法会让页面页脚标记出错 TODO 如下图所示 在论文目录后有一个分节符,转成PDF之后就多了一个空白页 文件-打印-页面设置-选中封面那一页-版式-从偶数页开始 再导出空白页就没了

旅游陪同翻译难吗, 旅游翻译英译中哪家好?

近来,随着中国旅游业的蓬勃发展,旅游陪同翻译的需求也水涨船高,这些专业的翻译服务者为中外游客搭建起友谊的桥梁,引领他们共同探索中国这片古老而神秘的土地 。那么,旅游陪同翻译英译中难吗?我们如何在众多…