From 09ede1af25a403fdff0ecf9782681375b1ab31ed Mon Sep 17 00:00:00 2001 From: liuyebo <1515783401@qq.com> Date: Mon, 18 Aug 2025 14:21:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4doc=5Fdewarp=E6=89=AD?= =?UTF-8?q?=E6=9B=B2=E7=9F=AB=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc_dewarp/.gitignore | 167 -------- doc_dewarp/.pre-commit-config.yaml | 34 -- doc_dewarp/GeoTr.py | 398 ------------------ doc_dewarp/README.md | 73 ---- doc_dewarp/__init__.py | 4 - doc_dewarp/data_visualization.py | 133 ------ doc_dewarp/dewarp.py | 21 - doc_dewarp/doc/download_dataset.sh | 161 ------- .../doc/imgs/document_image_rectification.jpg | Bin 77910 -> 0 bytes doc_dewarp/doc3d_dataset.py | 129 ------ doc_dewarp/export.py | 66 --- doc_dewarp/extractor.py | 110 ----- doc_dewarp/plots.py | 48 --- doc_dewarp/position_encoding.py | 124 ------ doc_dewarp/predict.py | 69 --- doc_dewarp/requirements.txt | 7 - doc_dewarp/split_dataset.py | 57 --- doc_dewarp/train.py | 381 ----------------- doc_dewarp/train.sh | 10 - doc_dewarp/utils.py | 67 --- doc_dewarp/weight_init.py | 152 ------- 21 files changed, 2211 deletions(-) delete mode 100644 doc_dewarp/.gitignore delete mode 100644 doc_dewarp/.pre-commit-config.yaml delete mode 100644 doc_dewarp/GeoTr.py delete mode 100644 doc_dewarp/README.md delete mode 100644 doc_dewarp/__init__.py delete mode 100644 doc_dewarp/data_visualization.py delete mode 100644 doc_dewarp/dewarp.py delete mode 100644 doc_dewarp/doc/download_dataset.sh delete mode 100644 doc_dewarp/doc/imgs/document_image_rectification.jpg delete mode 100644 doc_dewarp/doc3d_dataset.py delete mode 100644 doc_dewarp/export.py delete mode 100644 doc_dewarp/extractor.py delete mode 100644 doc_dewarp/plots.py delete mode 100644 doc_dewarp/position_encoding.py delete mode 100644 doc_dewarp/predict.py delete mode 100644 doc_dewarp/requirements.txt delete mode 100644 doc_dewarp/split_dataset.py delete mode 100644 doc_dewarp/train.py delete mode 100644 doc_dewarp/train.sh delete mode 100644 doc_dewarp/utils.py delete mode 100644 doc_dewarp/weight_init.py diff --git a/doc_dewarp/.gitignore b/doc_dewarp/.gitignore deleted file mode 100644 index 7179701..0000000 --- a/doc_dewarp/.gitignore +++ /dev/null @@ -1,167 +0,0 @@ -input/ -output/ - -runs/ -*_dump/ -*_log/ - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -.idea/ diff --git a/doc_dewarp/.pre-commit-config.yaml b/doc_dewarp/.pre-commit-config.yaml deleted file mode 100644 index 07921f0..0000000 --- a/doc_dewarp/.pre-commit-config.yaml +++ /dev/null @@ -1,34 +0,0 @@ -repos: -# Common hooks -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: check-added-large-files - - id: check-merge-conflict - - id: check-symlinks - - id: detect-private-key - - id: end-of-file-fixer - - id: trailing-whitespace -- repo: https://github.com/Lucas-C/pre-commit-hooks.git - rev: v1.5.1 - hooks: - - id: remove-crlf - - id: remove-tabs - name: Tabs remover (Python) - files: (.*\.(py|bzl)|BUILD|.*\.BUILD|WORKSPACE)$ - args: [--whitespaces-count, '4'] -# For Python files -- repo: https://github.com/psf/black.git - rev: 23.3.0 - hooks: - - id: black - files: (.*\.(py|pyi|bzl)|BUILD|.*\.BUILD|WORKSPACE)$ -- repo: https://github.com/pycqa/isort - rev: 5.11.5 - hooks: - - id: isort -- repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.272 - hooks: - - id: ruff - args: [--fix, --exit-non-zero-on-fix, --no-cache] diff --git a/doc_dewarp/GeoTr.py b/doc_dewarp/GeoTr.py deleted file mode 100644 index 44cd01a..0000000 --- a/doc_dewarp/GeoTr.py +++ /dev/null @@ -1,398 +0,0 @@ -import copy -from typing import Optional - -import paddle -import paddle.nn as nn -import paddle.nn.functional as F - -from .extractor import BasicEncoder -from .position_encoding import build_position_encoding -from .weight_init import weight_init_ - - -class attnLayer(nn.Layer): - def __init__( - self, - d_model, - nhead=8, - dim_feedforward=2048, - dropout=0.1, - activation="relu", - normalize_before=False, - ): - super().__init__() - - self.self_attn = nn.MultiHeadAttention(d_model, nhead, dropout=dropout) - self.multihead_attn_list = nn.LayerList( - [ - copy.deepcopy(nn.MultiHeadAttention(d_model, nhead, dropout=dropout)) - for i in range(2) - ] - ) - - self.linear1 = nn.Linear(d_model, dim_feedforward) - self.dropout = nn.Dropout(p=dropout) - self.linear2 = nn.Linear(dim_feedforward, d_model) - - self.norm1 = nn.LayerNorm(d_model) - self.norm2_list = nn.LayerList( - [copy.deepcopy(nn.LayerNorm(d_model)) for i in range(2)] - ) - - self.norm3 = nn.LayerNorm(d_model) - self.dropout1 = nn.Dropout(p=dropout) - self.dropout2_list = nn.LayerList( - [copy.deepcopy(nn.Dropout(p=dropout)) for i in range(2)] - ) - self.dropout3 = nn.Dropout(p=dropout) - - self.activation = _get_activation_fn(activation) - self.normalize_before = normalize_before - - def with_pos_embed(self, tensor, pos: Optional[paddle.Tensor]): - return tensor if pos is None else tensor + pos - - def forward_post( - self, - tgt, - memory_list, - tgt_mask=None, - memory_mask=None, - pos=None, - memory_pos=None, - ): - q = k = self.with_pos_embed(tgt, pos) - tgt2 = self.self_attn( - q.transpose((1, 0, 2)), - k.transpose((1, 0, 2)), - value=tgt.transpose((1, 0, 2)), - attn_mask=tgt_mask, - ) - tgt2 = tgt2.transpose((1, 0, 2)) - tgt = tgt + self.dropout1(tgt2) - tgt = self.norm1(tgt) - - for memory, multihead_attn, norm2, dropout2, m_pos in zip( - memory_list, - self.multihead_attn_list, - self.norm2_list, - self.dropout2_list, - memory_pos, - ): - tgt2 = multihead_attn( - query=self.with_pos_embed(tgt, pos).transpose((1, 0, 2)), - key=self.with_pos_embed(memory, m_pos).transpose((1, 0, 2)), - value=memory.transpose((1, 0, 2)), - attn_mask=memory_mask, - ).transpose((1, 0, 2)) - - tgt = tgt + dropout2(tgt2) - tgt = norm2(tgt) - - tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt)))) - tgt = tgt + self.dropout3(tgt2) - tgt = self.norm3(tgt) - - return tgt - - def forward_pre( - self, - tgt, - memory, - tgt_mask=None, - memory_mask=None, - pos=None, - memory_pos=None, - ): - tgt2 = self.norm1(tgt) - - q = k = self.with_pos_embed(tgt2, pos) - tgt2 = self.self_attn(q, k, value=tgt2, attn_mask=tgt_mask) - tgt = tgt + self.dropout1(tgt2) - tgt2 = self.norm2(tgt) - - tgt2 = self.multihead_attn( - query=self.with_pos_embed(tgt2, pos), - key=self.with_pos_embed(memory, memory_pos), - value=memory, - attn_mask=memory_mask, - ) - tgt = tgt + self.dropout2(tgt2) - tgt2 = self.norm3(tgt) - - tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt2)))) - tgt = tgt + self.dropout3(tgt2) - - return tgt - - def forward( - self, - tgt, - memory_list, - tgt_mask=None, - memory_mask=None, - pos=None, - memory_pos=None, - ): - if self.normalize_before: - return self.forward_pre( - tgt, - memory_list, - tgt_mask, - memory_mask, - pos, - memory_pos, - ) - return self.forward_post( - tgt, - memory_list, - tgt_mask, - memory_mask, - pos, - memory_pos, - ) - - -def _get_clones(module, N): - return nn.LayerList([copy.deepcopy(module) for i in range(N)]) - - -def _get_activation_fn(activation): - """Return an activation function given a string""" - if activation == "relu": - return F.relu - if activation == "gelu": - return F.gelu - if activation == "glu": - return F.glu - raise RuntimeError(f"activation should be relu/gelu, not {activation}.") - - -class TransDecoder(nn.Layer): - def __init__(self, num_attn_layers: int, hidden_dim: int = 128): - super(TransDecoder, self).__init__() - - attn_layer = attnLayer(hidden_dim) - self.layers = _get_clones(attn_layer, num_attn_layers) - self.position_embedding = build_position_encoding(hidden_dim) - - def forward(self, image: paddle.Tensor, query_embed: paddle.Tensor): - pos = self.position_embedding( - paddle.ones([image.shape[0], image.shape[2], image.shape[3]], dtype="bool") - ) - - b, c, h, w = image.shape - - image = image.flatten(2).transpose(perm=[2, 0, 1]) - pos = pos.flatten(2).transpose(perm=[2, 0, 1]) - - for layer in self.layers: - query_embed = layer(query_embed, [image], pos=pos, memory_pos=[pos, pos]) - - query_embed = query_embed.transpose(perm=[1, 2, 0]).reshape([b, c, h, w]) - - return query_embed - - -class TransEncoder(nn.Layer): - def __init__(self, num_attn_layers: int, hidden_dim: int = 128): - super(TransEncoder, self).__init__() - - attn_layer = attnLayer(hidden_dim) - self.layers = _get_clones(attn_layer, num_attn_layers) - self.position_embedding = build_position_encoding(hidden_dim) - - def forward(self, image: paddle.Tensor): - pos = self.position_embedding( - paddle.ones([image.shape[0], image.shape[2], image.shape[3]], dtype="bool") - ) - - b, c, h, w = image.shape - - image = image.flatten(2).transpose(perm=[2, 0, 1]) - pos = pos.flatten(2).transpose(perm=[2, 0, 1]) - - for layer in self.layers: - image = layer(image, [image], pos=pos, memory_pos=[pos, pos]) - - image = image.transpose(perm=[1, 2, 0]).reshape([b, c, h, w]) - - return image - - -class FlowHead(nn.Layer): - def __init__(self, input_dim=128, hidden_dim=256): - super(FlowHead, self).__init__() - - self.conv1 = nn.Conv2D(input_dim, hidden_dim, 3, padding=1) - self.conv2 = nn.Conv2D(hidden_dim, 2, 3, padding=1) - self.relu = nn.ReLU() - - def forward(self, x): - return self.conv2(self.relu(self.conv1(x))) - - -class UpdateBlock(nn.Layer): - def __init__(self, hidden_dim: int = 128): - super(UpdateBlock, self).__init__() - - self.flow_head = FlowHead(hidden_dim, hidden_dim=256) - - self.mask = nn.Sequential( - nn.Conv2D(hidden_dim, 256, 3, padding=1), - nn.ReLU(), - nn.Conv2D(256, 64 * 9, 1, padding=0), - ) - - def forward(self, image, coords): - mask = 0.25 * self.mask(image) - dflow = self.flow_head(image) - coords = coords + dflow - return mask, coords - - -def coords_grid(batch, ht, wd): - coords = paddle.meshgrid(paddle.arange(end=ht), paddle.arange(end=wd)) - coords = paddle.stack(coords[::-1], axis=0).astype(dtype="float32") - return coords[None].tile([batch, 1, 1, 1]) - - -def upflow8(flow, mode="bilinear"): - new_size = 8 * flow.shape[2], 8 * flow.shape[3] - return 8 * F.interpolate(flow, size=new_size, mode=mode, align_corners=True) - - -class OverlapPatchEmbed(nn.Layer): - """Image to Patch Embedding""" - - def __init__(self, img_size=224, patch_size=7, stride=4, in_chans=3, embed_dim=768): - super().__init__() - - img_size = img_size if isinstance(img_size, tuple) else (img_size, img_size) - patch_size = ( - patch_size if isinstance(patch_size, tuple) else (patch_size, patch_size) - ) - - self.H, self.W = img_size[0] // patch_size[0], img_size[1] // patch_size[1] - self.num_patches = self.H * self.W - - self.proj = nn.Conv2D( - in_chans, - embed_dim, - patch_size, - stride=stride, - padding=(patch_size[0] // 2, patch_size[1] // 2), - ) - self.norm = nn.LayerNorm(embed_dim) - - self.apply(self._init_weights) - - def _init_weights(self, m): - if isinstance(m, nn.Linear): - weight_init_(m, "trunc_normal_", std=0.02) - elif isinstance(m, nn.LayerNorm): - weight_init_(m, "Constant", value=1.0) - elif isinstance(m, nn.Conv2D): - weight_init_( - m.weight, "kaiming_normal_", mode="fan_out", nonlinearity="relu" - ) - - def forward(self, x): - x = self.proj(x) - _, _, H, W = x.shape - x = x.flatten(2) - - perm = list(range(x.ndim)) - perm[1] = 2 - perm[2] = 1 - x = x.transpose(perm=perm) - - x = self.norm(x) - - return x, H, W - - -class GeoTr(nn.Layer): - def __init__(self): - super(GeoTr, self).__init__() - - self.hidden_dim = hdim = 256 - - self.fnet = BasicEncoder(output_dim=hdim, norm_fn="instance") - - self.encoder_block = [("encoder_block" + str(i)) for i in range(3)] - for i in self.encoder_block: - self.__setattr__(i, TransEncoder(2, hidden_dim=hdim)) - - self.down_layer = [("down_layer" + str(i)) for i in range(2)] - for i in self.down_layer: - self.__setattr__(i, nn.Conv2D(256, 256, 3, stride=2, padding=1)) - - self.decoder_block = [("decoder_block" + str(i)) for i in range(3)] - for i in self.decoder_block: - self.__setattr__(i, TransDecoder(2, hidden_dim=hdim)) - - self.up_layer = [("up_layer" + str(i)) for i in range(2)] - for i in self.up_layer: - self.__setattr__( - i, nn.Upsample(scale_factor=2, mode="bilinear", align_corners=True) - ) - - self.query_embed = nn.Embedding(81, self.hidden_dim) - - self.update_block = UpdateBlock(self.hidden_dim) - - def initialize_flow(self, img): - N, _, H, W = img.shape - coodslar = coords_grid(N, H, W) - coords0 = coords_grid(N, H // 8, W // 8) - coords1 = coords_grid(N, H // 8, W // 8) - return coodslar, coords0, coords1 - - def upsample_flow(self, flow, mask): - N, _, H, W = flow.shape - - mask = mask.reshape([N, 1, 9, 8, 8, H, W]) - mask = F.softmax(mask, axis=2) - - up_flow = F.unfold(8 * flow, [3, 3], paddings=1) - up_flow = up_flow.reshape([N, 2, 9, 1, 1, H, W]) - - up_flow = paddle.sum(mask * up_flow, axis=2) - up_flow = up_flow.transpose(perm=[0, 1, 4, 2, 5, 3]) - - return up_flow.reshape([N, 2, 8 * H, 8 * W]) - - def forward(self, image): - fmap = self.fnet(image) - fmap = F.relu(fmap) - - fmap1 = self.__getattr__(self.encoder_block[0])(fmap) - fmap1d = self.__getattr__(self.down_layer[0])(fmap1) - fmap2 = self.__getattr__(self.encoder_block[1])(fmap1d) - fmap2d = self.__getattr__(self.down_layer[1])(fmap2) - fmap3 = self.__getattr__(self.encoder_block[2])(fmap2d) - - query_embed0 = self.query_embed.weight.unsqueeze(1).tile([1, fmap3.shape[0], 1]) - - fmap3d_ = self.__getattr__(self.decoder_block[0])(fmap3, query_embed0) - fmap3du_ = ( - self.__getattr__(self.up_layer[0])(fmap3d_) - .flatten(2) - .transpose(perm=[2, 0, 1]) - ) - fmap2d_ = self.__getattr__(self.decoder_block[1])(fmap2, fmap3du_) - fmap2du_ = ( - self.__getattr__(self.up_layer[1])(fmap2d_) - .flatten(2) - .transpose(perm=[2, 0, 1]) - ) - fmap_out = self.__getattr__(self.decoder_block[2])(fmap1, fmap2du_) - - coodslar, coords0, coords1 = self.initialize_flow(image) - coords1 = coords1.detach() - mask, coords1 = self.update_block(fmap_out, coords1) - flow_up = self.upsample_flow(coords1 - coords0, mask) - bm_up = coodslar + flow_up - - return bm_up diff --git a/doc_dewarp/README.md b/doc_dewarp/README.md deleted file mode 100644 index 60127c2..0000000 --- a/doc_dewarp/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# DocTrPP: DocTr++ in PaddlePaddle - -## Introduction - -This is a PaddlePaddle implementation of DocTr++. The original paper is [DocTr++: Deep Unrestricted Document Image Rectification](https://arxiv.org/abs/2304.08796). The original code is [here](https://github.com/fh2019ustc/DocTr-Plus). - -![demo](https://github.com/GreatV/DocTrPP/assets/17264618/4e491512-bfc4-4e69-a833-fd1c6e17158c) - -## Requirements - -You need to install the latest version of PaddlePaddle, which is done through this [link](https://www.paddlepaddle.org.cn/). - -## Training - -1. Data Preparation - -To prepare datasets, refer to [doc3D](https://github.com/cvlab-stonybrook/doc3D-dataset). - -2. Training - -```shell -sh train.sh -``` - -or - -```shell -export OPENCV_IO_ENABLE_OPENEXR=1 -export CUDA_VISIBLE_DEVICES=0 - -python train.py --img-size 288 \ - --name "DocTr++" \ - --batch-size 12 \ - --lr 2.5e-5 \ - --exist-ok \ - --use-vdl -``` - -3. Load Trained Model and Continue Training - -```shell -export OPENCV_IO_ENABLE_OPENEXR=1 -export CUDA_VISIBLE_DEVICES=0 - -python train.py --img-size 288 \ - --name "DocTr++" \ - --batch-size 12 \ - --lr 2.5e-5 \ - --resume "runs/train/DocTr++/weights/last.ckpt" \ - --exist-ok \ - --use-vdl -``` - -## Test and Inference - -Test the dewarp result on a single image: - -```shell -python predict.py -i "crop/12_2 copy.png" -m runs/train/DocTr++/weights/best.ckpt -o 12.2.png -``` -![document image rectification](https://raw.githubusercontent.com/greatv/DocTrPP/main/doc/imgs/document_image_rectification.jpg) - -## Export to onnx - -``` -pip install paddle2onnx - -python export.py -m ./best.ckpt --format onnx -``` - -## Model Download - -The trained model can be downloaded from [here](https://github.com/GreatV/DocTrPP/releases/download/v0.0.2/best.ckpt). diff --git a/doc_dewarp/__init__.py b/doc_dewarp/__init__.py deleted file mode 100644 index c98d39c..0000000 --- a/doc_dewarp/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from onnxruntime import InferenceSession - -DOC_TR = InferenceSession("model/dewarp_model/doc_tr_pp.onnx", - providers=["CUDAExecutionProvider"], provider_options=[{"device_id": 0}]) diff --git a/doc_dewarp/data_visualization.py b/doc_dewarp/data_visualization.py deleted file mode 100644 index ad716ad..0000000 --- a/doc_dewarp/data_visualization.py +++ /dev/null @@ -1,133 +0,0 @@ -import argparse -import os -import random - -import cv2 -import hdf5storage as h5 -import matplotlib.pyplot as plt -import numpy as np -import paddle -import paddle.nn.functional as F - -os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1" - -parser = argparse.ArgumentParser() -parser.add_argument( - "--data_root", - nargs="?", - type=str, - default="~/datasets/doc3d/", - help="Path to the downloaded dataset", -) -parser.add_argument( - "--folder", nargs="?", type=int, default=1, help="Folder ID to read from" -) -parser.add_argument( - "--output", - nargs="?", - type=str, - default="output.png", - help="Output filename for the image", -) - -args = parser.parse_args() - -root = os.path.expanduser(args.data_root) -folder = args.folder -dirname = os.path.join(root, "img", str(folder)) - -choices = [f for f in os.listdir(dirname) if "png" in f] -fname = random.choice(choices) - -# Read Image -img_path = os.path.join(dirname, fname) -img = cv2.imread(img_path) - -# Read 3D Coords -wc_path = os.path.join(root, "wc", str(folder), fname[:-3] + "exr") -wc = cv2.imread(wc_path, cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH) -# scale wc -# value obtained from the entire dataset -xmx, xmn, ymx, ymn, zmx, zmn = ( - 1.2539363, - -1.2442188, - 1.2396319, - -1.2289206, - 0.6436657, - -0.67492497, -) -wc[:, :, 0] = (wc[:, :, 0] - zmn) / (zmx - zmn) -wc[:, :, 1] = (wc[:, :, 1] - ymn) / (ymx - ymn) -wc[:, :, 2] = (wc[:, :, 2] - xmn) / (xmx - xmn) - -# Read Backward Map -bm_path = os.path.join(root, "bm", str(folder), fname[:-3] + "mat") -bm = h5.loadmat(bm_path)["bm"] - -# Read UV Map -uv_path = os.path.join(root, "uv", str(folder), fname[:-3] + "exr") -uv = cv2.imread(uv_path, cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH) - -# Read Depth Map -dmap_path = os.path.join(root, "dmap", str(folder), fname[:-3] + "exr") -dmap = cv2.imread(dmap_path, cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH)[:, :, 0] -# do some clipping and scaling to display it -dmap[dmap > 30.0] = 30 -dmap = 1 - ((dmap - np.min(dmap)) / (np.max(dmap) - np.min(dmap))) - -# Read Normal Map -norm_path = os.path.join(root, "norm", str(folder), fname[:-3] + "exr") -norm = cv2.imread(norm_path, cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH) - -# Read Albedo -alb_path = os.path.join(root, "alb", str(folder), fname[:-3] + "png") -alb = cv2.imread(alb_path) - -# Read Checkerboard Image -recon_path = os.path.join(root, "recon", str(folder), fname[:-8] + "chess480001.png") -recon = cv2.imread(recon_path) - -# Display image and GTs - -# use the backward mapping to dewarp the image -# scale bm to -1.0 to 1.0 -bm_ = bm / np.array([448, 448]) -bm_ = (bm_ - 0.5) * 2 -bm_ = np.reshape(bm_, (1, 448, 448, 2)) -bm_ = paddle.to_tensor(bm_, dtype="float32") -img_ = alb.transpose((2, 0, 1)).astype(np.float32) / 255.0 -img_ = np.expand_dims(img_, 0) -img_ = paddle.to_tensor(img_, dtype="float32") -uw = F.grid_sample(img_, bm_) -uw = uw[0].numpy().transpose((1, 2, 0)) - -f, axrr = plt.subplots(2, 5) -for ax in axrr: - for a in ax: - a.set_xticks([]) - a.set_yticks([]) - -axrr[0][0].imshow(img) -axrr[0][0].title.set_text("image") -axrr[0][1].imshow(wc) -axrr[0][1].title.set_text("3D coords") -axrr[0][2].imshow(bm[:, :, 0]) -axrr[0][2].title.set_text("bm 0") -axrr[0][3].imshow(bm[:, :, 1]) -axrr[0][3].title.set_text("bm 1") -if uv is None: - uv = np.zeros_like(img) -axrr[0][4].imshow(uv) -axrr[0][4].title.set_text("uv map") -axrr[1][0].imshow(dmap) -axrr[1][0].title.set_text("depth map") -axrr[1][1].imshow(norm) -axrr[1][1].title.set_text("normal map") -axrr[1][2].imshow(alb) -axrr[1][2].title.set_text("albedo") -axrr[1][3].imshow(recon) -axrr[1][3].title.set_text("checkerboard") -axrr[1][4].imshow(uw) -axrr[1][4].title.set_text("gt unwarped") -plt.tight_layout() -plt.savefig(args.output) diff --git a/doc_dewarp/dewarp.py b/doc_dewarp/dewarp.py deleted file mode 100644 index 923f56f..0000000 --- a/doc_dewarp/dewarp.py +++ /dev/null @@ -1,21 +0,0 @@ -import cv2 -import numpy as np -import paddle - -from . import DOC_TR -from .utils import to_tensor, to_image - - -def dewarp_image(image): - img = cv2.resize(image, (288, 288)).astype(np.float32) - y = to_tensor(image) - - img = np.transpose(img, (2, 0, 1)) - bm = DOC_TR.run(None, {"image": img[None,]})[0] - bm = paddle.to_tensor(bm) - bm = paddle.nn.functional.interpolate( - bm, y.shape[2:], mode="bilinear", align_corners=False - ) - bm_nhwc = np.transpose(bm, (0, 2, 3, 1)) - out = paddle.nn.functional.grid_sample(y, (bm_nhwc / 288 - 0.5) * 2) - return to_image(out) diff --git a/doc_dewarp/doc/download_dataset.sh b/doc_dewarp/doc/download_dataset.sh deleted file mode 100644 index 7afe858..0000000 --- a/doc_dewarp/doc/download_dataset.sh +++ /dev/null @@ -1,161 +0,0 @@ -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_1.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_2.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_3.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_4.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_5.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_6.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_7.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_8.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_9.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_10.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_11.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_12.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_13.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_14.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_15.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_16.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_17.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_18.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_19.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_20.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/img_21.zip" - -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_1.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_2.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_3.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_4.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_5.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_6.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_7.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_8.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_9.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_10.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_11.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_12.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_13.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_14.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_15.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_16.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_17.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_18.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_19.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_20.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/wc_21.zip" - -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_1.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_2.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_3.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_4.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_5.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_6.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_7.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_8.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_9.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_10.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_11.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_12.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_13.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_14.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_15.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_16.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_17.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_18.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_19.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_20.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/bm_21.zip" - -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_1.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_2.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_3.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_4.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_5.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_6.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_7.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_8.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_10.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_11.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_12.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_13.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_14.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_15.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_16.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_17.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_18.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_19.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_20.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/uv_21.zip" - -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/alb_1.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/alb_2.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/alb_3.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/alb_4.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/alb_5.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/alb_6.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/alb_7.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/alb_8.zip" - -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_1.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_2.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_3.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_4.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_5.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_6.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_7.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_8.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_9.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_10.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_11.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_12.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_13.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_14.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_15.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_16.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_17.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_18.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_19.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_20.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/recon_21.zip" - -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_1.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_2.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_3.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_4.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_5.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_6.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_7.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_8.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_9.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_10.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_11.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_12.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_13.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_14.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_15.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_16.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_17.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_18.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_19.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_20.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/norm_21.zip" - -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_1.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_2.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_3.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_4.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_5.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_6.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_7.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_8.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_9.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_10.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_11.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_12.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_13.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_14.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_15.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_16.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_17.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_18.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_19.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_20.zip" -wget "https://bj.bcebos.com/paddleseg/paddleseg/datasets/doc3d/dmap_21.zip" diff --git a/doc_dewarp/doc/imgs/document_image_rectification.jpg b/doc_dewarp/doc/imgs/document_image_rectification.jpg deleted file mode 100644 index e5a8b26b2a416f879d7eecf8be5e0382c5a4a4c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77910 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 1 else ("blue", "bold", input[0]) - ) # color arguments, string - - colors = { - "black": "\033[30m", # basic colors - "red": "\033[31m", - "green": "\033[32m", - "yellow": "\033[33m", - "blue": "\033[34m", - "magenta": "\033[35m", - "cyan": "\033[36m", - "white": "\033[37m", - "bright_black": "\033[90m", # bright colors - "bright_red": "\033[91m", - "bright_green": "\033[92m", - "bright_yellow": "\033[93m", - "bright_blue": "\033[94m", - "bright_magenta": "\033[95m", - "bright_cyan": "\033[96m", - "bright_white": "\033[97m", - "end": "\033[0m", # misc - "bold": "\033[1m", - "underline": "\033[4m", - } - - return "".join(colors[x] for x in args) + f"{string}" + colors["end"] - - -def print_args(args: Optional[dict] = None, show_file=True, show_func=False): - # Print function arguments (optional args dict) - x = inspect.currentframe().f_back # previous frame - file, _, func, _, _ = inspect.getframeinfo(x) - if args is None: # get args automatically - args, _, _, frm = inspect.getargvalues(x) - args = {k: v for k, v in frm.items() if k in args} - try: - file = Path(file).resolve().relative_to(ROOT).with_suffix("") - except ValueError: - file = Path(file).stem - s = (f"{file}: " if show_file else "") + (f"{func}: " if show_func else "") - logger.info(colorstr(s) + ", ".join(f"{k}={v}" for k, v in args.items())) - - -def increment_path(path, exist_ok=False, sep="", mkdir=False): - # Increment file or directory path, - # i.e. runs/exp --> runs/exp{sep}2, runs/exp{sep}3, ... etc. - path = Path(path) # os-agnostic - if path.exists() and not exist_ok: - path, suffix = ( - (path.with_suffix(""), path.suffix) if path.is_file() else (path, "") - ) - - for n in range(2, 9999): - p = f"{path}{sep}{n}{suffix}" # increment path - if not os.path.exists(p): - break - path = Path(p) - - if mkdir: - path.mkdir(parents=True, exist_ok=True) # make directory - - return path - - -def train(args): - save_dir = Path(args.save_dir) - - use_vdl = args.use_vdl - if use_vdl: - from visualdl import LogWriter - - log_dir = save_dir / "vdl" - vdl_writer = LogWriter(str(log_dir)) - - # Directories - weights_dir = save_dir / "weights" - weights_dir.parent.mkdir(parents=True, exist_ok=True) - - last = weights_dir / "last.ckpt" - best = weights_dir / "best.ckpt" - - # Hyperparameters - - # Config - init_seeds(args.seed) - - # Train loader - train_dataset = Doc3dDataset( - args.data_root, - split="train", - is_augment=True, - image_size=args.img_size, - ) - - train_loader = DataLoader( - train_dataset, - batch_size=args.batch_size, - shuffle=True, - num_workers=args.workers, - ) - - # Validation loader - val_dataset = Doc3dDataset( - args.data_root, - split="val", - is_augment=False, - image_size=args.img_size, - ) - - val_loader = DataLoader( - val_dataset, batch_size=args.batch_size, num_workers=args.workers - ) - - # Model - model = GeoTr() - - if use_vdl: - vdl_writer.add_graph( - model, - input_spec=[ - paddle.static.InputSpec([1, 3, args.img_size, args.img_size], "float32") - ], - ) - - # Data Parallel Mode - if RANK == -1 and paddle.device.cuda.device_count() > 1: - model = paddle.DataParallel(model) - - # Scheduler - scheduler = optim.lr.OneCycleLR( - max_learning_rate=args.lr, - total_steps=args.epochs * len(train_loader), - phase_pct=0.1, - end_learning_rate=args.lr / 2.5e5, - ) - - # Optimizer - optimizer = optim.AdamW( - learning_rate=scheduler, - parameters=model.parameters(), - ) - - # loss function - l1_loss_fn = nn.L1Loss() - mse_loss_fn = nn.MSELoss() - - # Resume - best_fitness, start_epoch = 0.0, 0 - if args.resume: - ckpt = paddle.load(args.resume) - model.set_state_dict(ckpt["model"]) - optimizer.set_state_dict(ckpt["optimizer"]) - scheduler.set_state_dict(ckpt["scheduler"]) - best_fitness = ckpt["best_fitness"] - start_epoch = ckpt["epoch"] + 1 - - # Train - for epoch in range(start_epoch, args.epochs): - model.train() - - for i, (img, target) in enumerate(train_loader): - img = paddle.to_tensor(img) # NCHW - target = paddle.to_tensor(target) # NHWC - - pred = model(img) # NCHW - pred_nhwc = pred.transpose([0, 2, 3, 1]) - - loss = l1_loss_fn(pred_nhwc, target) - mse_loss = mse_loss_fn(pred_nhwc, target) - - if use_vdl: - vdl_writer.add_scalar( - "Train/L1 Loss", float(loss), epoch * len(train_loader) + i - ) - vdl_writer.add_scalar( - "Train/MSE Loss", float(mse_loss), epoch * len(train_loader) + i - ) - vdl_writer.add_scalar( - "Train/Learning Rate", - float(scheduler.get_lr()), - epoch * len(train_loader) + i, - ) - - loss.backward() - optimizer.step() - scheduler.step() - optimizer.clear_grad() - - if i % 10 == 0: - logger.info( - f"[TRAIN MODE] Epoch: {epoch}, Iter: {i}, L1 Loss: {float(loss)}, " - f"MSE Loss: {float(mse_loss)}, LR: {float(scheduler.get_lr())}" - ) - - # Validation - model.eval() - - with paddle.no_grad(): - avg_ssim = paddle.zeros([]) - avg_ms_ssim = paddle.zeros([]) - avg_l1_loss = paddle.zeros([]) - avg_mse_loss = paddle.zeros([]) - - for i, (img, target) in enumerate(val_loader): - img = paddle.to_tensor(img) - target = paddle.to_tensor(target) - - pred = model(img) - pred_nhwc = pred.transpose([0, 2, 3, 1]) - - # predict image - out = F.grid_sample(img, (pred_nhwc / args.img_size - 0.5) * 2) - out_gt = F.grid_sample(img, (target / args.img_size - 0.5) * 2) - - # calculate ssim - ssim_val = ssim(out, out_gt, data_range=1.0) - ms_ssim_val = ms_ssim(out, out_gt, data_range=1.0) - - loss = l1_loss_fn(pred_nhwc, target) - mse_loss = mse_loss_fn(pred_nhwc, target) - - # calculate fitness - avg_ssim += ssim_val - avg_ms_ssim += ms_ssim_val - avg_l1_loss += loss - avg_mse_loss += mse_loss - - if i % 10 == 0: - logger.info( - f"[VAL MODE] Epoch: {epoch}, VAL Iter: {i}, " - f"L1 Loss: {float(loss)} MSE Loss: {float(mse_loss)}, " - f"MS-SSIM: {float(ms_ssim_val)}, SSIM: {float(ssim_val)}" - ) - - if use_vdl and i == 0: - img_0 = to_image(out[0]) - img_gt_0 = to_image(out_gt[0]) - vdl_writer.add_image("Val/Predicted Image No.0", img_0, epoch) - vdl_writer.add_image("Val/Target Image No.0", img_gt_0, epoch) - - img_1 = to_image(out[1]) - img_gt_1 = to_image(out_gt[1]) - img_gt_1 = img_gt_1.astype("uint8") - vdl_writer.add_image("Val/Predicted Image No.1", img_1, epoch) - vdl_writer.add_image("Val/Target Image No.1", img_gt_1, epoch) - - img_2 = to_image(out[2]) - img_gt_2 = to_image(out_gt[2]) - vdl_writer.add_image("Val/Predicted Image No.2", img_2, epoch) - vdl_writer.add_image("Val/Target Image No.2", img_gt_2, epoch) - - avg_ssim /= len(val_loader) - avg_ms_ssim /= len(val_loader) - avg_l1_loss /= len(val_loader) - avg_mse_loss /= len(val_loader) - - if use_vdl: - vdl_writer.add_scalar("Val/L1 Loss", float(loss), epoch) - vdl_writer.add_scalar("Val/MSE Loss", float(mse_loss), epoch) - vdl_writer.add_scalar("Val/SSIM", float(ssim_val), epoch) - vdl_writer.add_scalar("Val/MS-SSIM", float(ms_ssim_val), epoch) - - # Save - ckpt = { - "model": model.state_dict(), - "optimizer": optimizer.state_dict(), - "scheduler": scheduler.state_dict(), - "best_fitness": best_fitness, - "epoch": epoch, - } - - paddle.save(ckpt, str(last)) - - if best_fitness < avg_ssim: - best_fitness = avg_ssim - paddle.save(ckpt, str(best)) - - if use_vdl: - vdl_writer.close() - - -def main(args): - print_args(vars(args)) - - args.save_dir = str( - increment_path(Path(args.project) / args.name, exist_ok=args.exist_ok) - ) - - train(args) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Hyperparams") - parser.add_argument( - "--data-root", - nargs="?", - type=str, - default="~/datasets/doc3d", - help="The root path of the dataset", - ) - parser.add_argument( - "--img-size", - nargs="?", - type=int, - default=288, - help="The size of the input image", - ) - parser.add_argument( - "--epochs", - nargs="?", - type=int, - default=65, - help="The number of training epochs", - ) - parser.add_argument( - "--batch-size", nargs="?", type=int, default=12, help="Batch Size" - ) - parser.add_argument( - "--lr", nargs="?", type=float, default=1e-04, help="Learning Rate" - ) - parser.add_argument( - "--resume", - nargs="?", - type=str, - default=None, - help="Path to previous saved model to restart from", - ) - parser.add_argument("--workers", type=int, default=8, help="max dataloader workers") - parser.add_argument( - "--project", default=ROOT / "runs/train", help="save to project/name" - ) - parser.add_argument("--name", default="exp", help="save to project/name") - parser.add_argument( - "--exist-ok", - action="store_true", - help="existing project/name ok, do not increment", - ) - parser.add_argument("--seed", type=int, default=0, help="Global training seed") - parser.add_argument("--use-vdl", action="store_true", help="use VisualDL as logger") - args = parser.parse_args() - main(args) diff --git a/doc_dewarp/train.sh b/doc_dewarp/train.sh deleted file mode 100644 index 2b7da99..0000000 --- a/doc_dewarp/train.sh +++ /dev/null @@ -1,10 +0,0 @@ -export OPENCV_IO_ENABLE_OPENEXR=1 -export FLAGS_logtostderr=0 -export CUDA_VISIBLE_DEVICES=0 - -python train.py --img-size 288 \ - --name "DocTr++" \ - --batch-size 12 \ - --lr 1e-4 \ - --exist-ok \ - --use-vdl diff --git a/doc_dewarp/utils.py b/doc_dewarp/utils.py deleted file mode 100644 index e887908..0000000 --- a/doc_dewarp/utils.py +++ /dev/null @@ -1,67 +0,0 @@ -import numpy as np -import paddle -from paddle.nn import functional as F - - -def to_tensor(img: np.ndarray): - """ - Converts a numpy array image (HWC) to a Paddle tensor (NCHW). - - Args: - img (numpy.ndarray): The input image as a numpy array. - - Returns: - out (paddle.Tensor): The output tensor. - """ - img = img[:, :, ::-1] - img = img.astype("float32") / 255.0 - img = img.transpose(2, 0, 1) - out: paddle.Tensor = paddle.to_tensor(img) - out = paddle.unsqueeze(out, axis=0) - - return out - - -def to_image(x: paddle.Tensor): - """ - Converts a Paddle tensor (NCHW) to a numpy array image (HWC). - - Args: - x (paddle.Tensor): The input tensor. - - Returns: - out (numpy.ndarray): The output image as a numpy array. - """ - out: np.ndarray = x.squeeze().numpy() - out = out.transpose(1, 2, 0) - out = out * 255.0 - out = out.astype("uint8") - out = out[:, :, ::-1] - - return out - - -def unwarp(img, bm, bm_data_format="NCHW"): - """ - Unwarp an image using a flow field. - - Args: - img (paddle.Tensor): The input image. - bm (paddle.Tensor): The flow field. - - Returns: - out (paddle.Tensor): The output image. - """ - _, _, h, w = img.shape - - if bm_data_format == "NHWC": - bm = bm.transpose([0, 3, 1, 2]) - - # NCHW - bm = F.upsample(bm, size=(h, w), mode="bilinear", align_corners=True) - # NHWC - bm = bm.transpose([0, 2, 3, 1]) - # NCHW - out = F.grid_sample(img, bm) - - return out diff --git a/doc_dewarp/weight_init.py b/doc_dewarp/weight_init.py deleted file mode 100644 index c83178c..0000000 --- a/doc_dewarp/weight_init.py +++ /dev/null @@ -1,152 +0,0 @@ -import math - -import numpy as np -import paddle -import paddle.nn.initializer as init -from scipy import special - - -def weight_init_( - layer, func, weight_name=None, bias_name=None, bias_value=0.0, **kwargs -): - """ - In-place params init function. - Usage: - .. code-block:: python - - import paddle - import numpy as np - - data = np.ones([3, 4], dtype='float32') - linear = paddle.nn.Linear(4, 4) - input = paddle.to_tensor(data) - print(linear.weight) - linear(input) - - weight_init_(linear, 'Normal', 'fc_w0', 'fc_b0', std=0.01, mean=0.1) - print(linear.weight) - """ - - if hasattr(layer, "weight") and layer.weight is not None: - getattr(init, func)(**kwargs)(layer.weight) - if weight_name is not None: - # override weight name - layer.weight.name = weight_name - - if hasattr(layer, "bias") and layer.bias is not None: - init.Constant(bias_value)(layer.bias) - if bias_name is not None: - # override bias name - layer.bias.name = bias_name - - -def _no_grad_trunc_normal_(tensor, mean, std, a, b): - def norm_cdf(x): - # Computes standard normal cumulative distribution function - return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0 - - if (mean < a - 2 * std) or (mean > b + 2 * std): - print( - "mean is more than 2 std from [a, b] in nn.init.trunc_normal_. " - "The distribution of values may be incorrect." - ) - - with paddle.no_grad(): - # Values are generated by using a truncated uniform distribution and - # then using the inverse CDF for the normal distribution. - # Get upper and lower cdf values - lower = norm_cdf((a - mean) / std) - upper = norm_cdf((b - mean) / std) - - # Uniformly fill tensor with values from [l, u], then translate to [2l-1, 2u-1]. - tmp = np.random.uniform( - 2 * lower - 1, 2 * upper - 1, size=list(tensor.shape) - ).astype(np.float32) - - # Use inverse cdf transform for normal distribution to get truncated - # standard normal - tmp = special.erfinv(tmp) - - # Transform to proper mean, std - tmp *= std * math.sqrt(2.0) - tmp += mean - - # Clamp to ensure it's in the proper range - tmp = np.clip(tmp, a, b) - tensor.set_value(paddle.to_tensor(tmp)) - - return tensor - - -def _calculate_fan_in_and_fan_out(tensor): - dimensions = tensor.dim() - if dimensions < 2: - raise ValueError( - "Fan in and fan out can not be computed for tensor " - "with fewer than 2 dimensions" - ) - - num_input_fmaps = tensor.shape[1] - num_output_fmaps = tensor.shape[0] - receptive_field_size = 1 - if tensor.dim() > 2: - receptive_field_size = tensor[0][0].numel() - fan_in = num_input_fmaps * receptive_field_size - fan_out = num_output_fmaps * receptive_field_size - - return fan_in, fan_out - - -def trunc_normal_(tensor, mean=0.0, std=1.0, a=-2.0, b=2.0): - return _no_grad_trunc_normal_(tensor, mean, std, a, b) - - -def kaiming_normal_(tensor, a=0.0, mode="fan_in", nonlinearity="leaky_relu"): - def _calculate_correct_fan(tensor, mode): - mode = mode.lower() - valid_modes = ["fan_in", "fan_out"] - if mode not in valid_modes: - raise ValueError( - "Mode {} not supported, please use one of {}".format(mode, valid_modes) - ) - - fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor) - return fan_in if mode == "fan_in" else fan_out - - def calculate_gain(nonlinearity, param=None): - linear_fns = [ - "linear", - "conv1d", - "conv2d", - "conv3d", - "conv_transpose1d", - "conv_transpose2d", - "conv_transpose3d", - ] - if nonlinearity in linear_fns or nonlinearity == "sigmoid": - return 1 - elif nonlinearity == "tanh": - return 5.0 / 3 - elif nonlinearity == "relu": - return math.sqrt(2.0) - elif nonlinearity == "leaky_relu": - if param is None: - negative_slope = 0.01 - elif ( - not isinstance(param, bool) - and isinstance(param, int) - or isinstance(param, float) - ): - negative_slope = param - else: - raise ValueError("negative_slope {} not a valid number".format(param)) - return math.sqrt(2.0 / (1 + negative_slope**2)) - else: - raise ValueError("Unsupported nonlinearity {}".format(nonlinearity)) - - fan = _calculate_correct_fan(tensor, mode) - gain = calculate_gain(nonlinearity, a) - std = gain / math.sqrt(fan) - with paddle.no_grad(): - paddle.nn.initializer.Normal(0, std)(tensor) - return tensor