chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:42 +08:00
commit e25996e7db
15472 changed files with 3536181 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import paddle # noqa: F401
from paddle import nn # noqa: F401
from . import ( # noqa: F401
datasets,
models,
ops,
transforms,
)
from .datasets import ( # noqa: F401
MNIST,
VOC2012,
Cifar10,
Cifar100,
DatasetFolder,
FashionMNIST,
Flowers,
ImageFolder,
)
from .image import (
get_image_backend,
image_load,
set_image_backend,
)
from .models import ( # noqa: F401
VGG,
AlexNet,
DenseNet,
GoogLeNet,
InceptionV3,
LeNet,
MobileNetV1,
MobileNetV2,
MobileNetV3Large,
MobileNetV3Small,
ResNet,
ShuffleNetV2,
SqueezeNet,
alexnet,
densenet121,
densenet161,
densenet169,
densenet201,
densenet264,
googlenet,
inception_v3,
mobilenet_v1,
mobilenet_v2,
mobilenet_v3_large,
mobilenet_v3_small,
resnet18,
resnet34,
resnet50,
resnet101,
resnet152,
resnext50_32x4d,
resnext50_64x4d,
resnext101_32x4d,
resnext101_64x4d,
resnext152_32x4d,
resnext152_64x4d,
shufflenet_v2_swish,
shufflenet_v2_x0_5,
shufflenet_v2_x0_25,
shufflenet_v2_x0_33,
shufflenet_v2_x1_0,
shufflenet_v2_x1_5,
shufflenet_v2_x2_0,
squeezenet1_0,
squeezenet1_1,
vgg11,
vgg13,
vgg16,
vgg19,
wide_resnet50_2,
wide_resnet101_2,
)
from .transforms import ( # noqa: F401
BaseTransform,
BrightnessTransform,
CenterCrop,
ColorJitter,
Compose,
ContrastTransform,
Grayscale,
HueTransform,
Normalize,
Pad,
RandomCrop,
RandomHorizontalFlip,
RandomResizedCrop,
RandomRotation,
RandomVerticalFlip,
Resize,
SaturationTransform,
ToTensor,
Transpose,
adjust_brightness,
adjust_contrast,
adjust_hue,
center_crop,
crop,
hflip,
normalize,
pad,
resize,
rotate,
to_grayscale,
to_tensor,
vflip,
)
__all__ = ['set_image_backend', 'get_image_backend', 'image_load']
+30
View File
@@ -0,0 +1,30 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .cifar import Cifar10, Cifar100
from .flowers import Flowers
from .folder import DatasetFolder, ImageFolder
from .mnist import MNIST, FashionMNIST
from .voc2012 import VOC2012
__all__ = [
'DatasetFolder',
'ImageFolder',
'MNIST',
'FashionMNIST',
'Flowers',
'Cifar10',
'Cifar100',
'VOC2012',
]
+314
View File
@@ -0,0 +1,314 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import os
import pickle
import tarfile
from functools import lru_cache
from typing import TYPE_CHECKING, Any, Literal
import numpy as np
import numpy.typing as npt
from PIL import Image
import paddle
from paddle.dataset.common import _check_exists_and_download, md5file
from paddle.io import Dataset
if TYPE_CHECKING:
import numpy.typing as npt
from paddle._typing.dtype_like import _DTypeLiteral
from paddle.vision.transforms.transforms import _Transform
from ..image import _ImageBackend, _ImageDataType
_DatasetMode = Literal["train", "test"]
__all__ = []
URL_PREFIX = 'https://dataset.bj.bcebos.com/cifar/'
CIFAR10_URL = URL_PREFIX + 'cifar-10-python.tar.gz'
CIFAR10_MD5 = 'c58f30108f718f92721af3b95e74349a'
CIFAR100_URL = URL_PREFIX + 'cifar-100-python.tar.gz'
CIFAR100_MD5 = 'eb9058c3a382ffc7106e4002c42a8d85'
MODE_FLAG_MAP = {
'train10': 'data_batch',
'test10': 'test_batch',
'train100': 'train',
'test100': 'test',
}
@lru_cache(maxsize=8)
def _cached_md5file(path, _mtime_ns, _size):
return md5file(path)
def _check_local_cifar_md5(path, expected_md5):
path = os.path.abspath(path)
stat = os.stat(path)
file_md5 = _cached_md5file(path, stat.st_mtime_ns, stat.st_size)
if file_md5 != expected_md5:
raise ValueError(
"Loading unverified local CIFAR pickle archive is disabled. "
f"Please use the official archive with MD5 {expected_md5}."
)
class Cifar10(Dataset[tuple["_ImageDataType", "npt.NDArray[Any]"]]):
"""
Implementation of `Cifar-10 <https://www.cs.toronto.edu/~kriz/cifar.html>`_
dataset, which has 10 categories.
Args:
data_file (str|None, optional): Path to data file, can be set None if
:attr:`download` is True. Default None, default data path: ~/.cache/paddle/dataset/cifar
mode (str, optional): Either train or test mode. Default 'train'.
transform (Callable|None, optional): transform to perform on image, None for no transform. Default: None.
download (bool, optional): download dataset automatically if :attr:`data_file` is None. Default True.
backend (str|None, optional): Specifies which type of image to be returned:
PIL.Image or numpy.ndarray. Should be one of {'pil', 'cv2'}.
If this option is not set, will get backend from :ref:`paddle.vision.get_image_backend <api_paddle_vision_get_image_backend>`,
default backend is 'pil'. Default: None.
Returns:
:ref:`api_paddle_io_Dataset`. An instance of Cifar10 dataset.
Examples:
.. code-block:: pycon
>>> # doctest: +TIMEOUT(60)
>>> import itertools
>>> import paddle
>>> import paddle.vision.transforms as T
>>> from paddle.vision.datasets import Cifar10
>>> cifar10 = Cifar10()
>>> print(len(cifar10))
50000
>>> for i in range(5): # only show first 5 images
... img, label = cifar10[i]
... # do something with img and label
... print(type(img), img.size, label)
... # <class 'PIL.Image.Image'> (32, 32) 6
>>> transform = T.Compose(
... [
... T.Resize(64),
... T.ToTensor(),
... T.Normalize(
... mean=[0.5, 0.5, 0.5],
... std=[0.5, 0.5, 0.5],
... to_rgb=True,
... ),
... ]
... )
>>> cifar10_test = Cifar10(
... mode="test",
... transform=transform, # apply transform to every image
... backend="cv2", # use OpenCV as image transform backend
... )
>>> print(len(cifar10_test))
10000
>>> for img, label in itertools.islice(iter(cifar10_test), 5): # only show first 5 images
... # do something with img and label
... assert isinstance(img, paddle.Tensor)
... print(type(img), img.shape, label)
... # <class 'paddle.Tensor'> [3, 64, 64] 3
"""
mode: _DatasetMode
backend: _ImageBackend
data_file: str | None
transform: _Transform[Any, Any] | None
dtype: _DTypeLiteral
def __init__(
self,
data_file: str | None = None,
mode: _DatasetMode = 'train',
transform: _Transform[Any, Any] | None = None,
download: bool = True,
backend: _ImageBackend | None = None,
) -> None:
assert mode.lower() in [
'train',
'test',
], f"mode.lower() should be 'train' or 'test', but got {mode}"
self.mode = mode.lower()
if backend is None:
backend = paddle.vision.get_image_backend()
if backend not in ['pil', 'cv2']:
raise ValueError(
f"Expected backend are one of ['pil', 'cv2'], but got {backend}"
)
self.backend = backend
self._init_url_md5_flag()
self.data_file = data_file
if self.data_file is None:
assert download, (
"data_file is not set and downloading automatically is disabled"
)
self.data_file = _check_exists_and_download(
data_file, self.data_url, self.data_md5, 'cifar', download
)
elif not os.path.exists(self.data_file):
raise ValueError(
f"Local CIFAR archive does not exist: {self.data_file}."
)
else:
_check_local_cifar_md5(self.data_file, self.data_md5)
self.transform = transform
# read dataset into memory
self._load_data()
self.dtype = paddle.get_default_dtype()
def _init_url_md5_flag(self):
self.data_url = CIFAR10_URL
self.data_md5 = CIFAR10_MD5
self.flag = MODE_FLAG_MAP[self.mode + '10']
def _load_data(self):
self.data = []
with tarfile.open(self.data_file, mode='r') as f:
names = (
each_item.name for each_item in f if self.flag in each_item.name
)
names = sorted(names)
for name in names:
batch = pickle.load(f.extractfile(name), encoding='bytes')
data = batch[b'data']
labels = batch.get(b'labels', batch.get(b'fine_labels', None))
assert labels is not None
for sample, label in zip(data, labels):
self.data.append((sample, label))
def __getitem__(self, idx: int) -> tuple[_ImageDataType, npt.NDArray[Any]]:
image, label = self.data[idx]
image = np.reshape(image, [3, 32, 32])
image = image.transpose([1, 2, 0])
if self.backend == 'pil':
image = Image.fromarray(image.astype('uint8'))
if self.transform is not None:
image = self.transform(image)
if self.backend == 'pil':
return image, np.array(label).astype('int64')
return image.astype(self.dtype), np.array(label).astype('int64')
def __len__(self):
return len(self.data)
class Cifar100(Cifar10):
"""
Implementation of `Cifar-100 <https://www.cs.toronto.edu/~kriz/cifar.html>`_
dataset, which has 100 categories.
Args:
data_file (str|None, optional): path to data file, can be set None if
:attr:`download` is True. Default: None, default data path: ~/.cache/paddle/dataset/cifar
mode (str, optional): Either train or test mode. Default 'train'.
transform (Callable|None, optional): transform to perform on image, None for no transform. Default: None.
download (bool, optional): download dataset automatically if :attr:`data_file` is None. Default True.
backend (str|None, optional): Specifies which type of image to be returned:
PIL.Image or numpy.ndarray. Should be one of {'pil', 'cv2'}.
If this option is not set, will get backend from :ref:`paddle.vision.get_image_backend <api_paddle_vision_get_image_backend>`,
default backend is 'pil'. Default: None.
Returns:
:ref:`api_paddle_io_Dataset`. An instance of Cifar100 dataset.
Examples:
.. code-block:: pycon
>>> # doctest: +TIMEOUT(60)
>>> import itertools
>>> import paddle
>>> import paddle.vision.transforms as T
>>> from paddle.vision.datasets import Cifar100
>>> cifar100 = Cifar100()
>>> print(len(cifar100))
50000
>>> for i in range(5): # only show first 5 images
... img, label = cifar100[i]
... # do something with img and label
... print(type(img), img.size, label)
... # <class 'PIL.Image.Image'> (32, 32) 19
>>> transform = T.Compose(
... [
... T.Resize(64),
... T.ToTensor(),
... T.Normalize(
... mean=[0.5, 0.5, 0.5],
... std=[0.5, 0.5, 0.5],
... to_rgb=True,
... ),
... ]
... )
>>> cifar100_test = Cifar100(
... mode="test",
... transform=transform, # apply transform to every image
... backend="cv2", # use OpenCV as image transform backend
... )
>>> print(len(cifar100_test))
10000
>>> for img, label in itertools.islice(iter(cifar100_test), 5): # only show first 5 images
... # do something with img and label
... assert isinstance(img, paddle.Tensor)
... print(type(img), img.shape, label)
... # <class 'paddle.Tensor'> [3, 64, 64] 49
"""
def __init__(
self,
data_file: str | None = None,
mode: _DatasetMode = 'train',
transform: _Transform[Any, Any] | None = None,
download: bool = True,
backend: _ImageBackend | None = None,
) -> None:
super().__init__(data_file, mode, transform, download, backend)
def _init_url_md5_flag(self):
self.data_url = CIFAR100_URL
self.data_md5 = CIFAR100_MD5
self.flag = MODE_FLAG_MAP[self.mode + '100']
+216
View File
@@ -0,0 +1,216 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal
if TYPE_CHECKING:
import numpy.typing as npt
from paddle.vision.transforms.transforms import _Transform
from ..image import _ImageBackend, _ImageDataType
_DatasetMode = Literal["train", "valid", "test"]
import os
import tarfile
import numpy as np
from PIL import Image
import paddle
from paddle.dataset.common import _check_exists_and_download
from paddle.io import Dataset
from paddle.utils import try_import
from paddle.utils.download import _safe_extract_tar
__all__ = []
DATA_URL = 'http://paddlemodels.bj.bcebos.com/flowers/102flowers.tgz'
LABEL_URL = 'http://paddlemodels.bj.bcebos.com/flowers/imagelabels.mat'
SETID_URL = 'http://paddlemodels.bj.bcebos.com/flowers/setid.mat'
DATA_MD5 = '52808999861908f626f3c1f4e79d11fa'
LABEL_MD5 = 'e0620be6f572b9609742df49c70aed4d'
SETID_MD5 = 'a5357ecc9cb78c4bef273ce3793fc85c'
# In official 'readme', tstid is the flag of test data
# and trnid is the flag of train data. But test data is more than train data.
# So we exchange the train data and test data.
MODE_FLAG_MAP = {'train': 'tstid', 'test': 'trnid', 'valid': 'valid'}
class Flowers(Dataset[tuple["_ImageDataType", "npt.NDArray[np.int64]"]]):
"""
Implementation of `Flowers102 <https://www.robots.ox.ac.uk/~vgg/data/flowers/>`_
dataset.
Args:
data_file (str|None, optional): Path to data file, can be set None if
:attr:`download` is True. Default: None, default data path: ~/.cache/paddle/dataset/flowers/.
label_file (str|None, optional): Path to label file, can be set None if
:attr:`download` is True. Default: None, default data path: ~/.cache/paddle/dataset/flowers/.
setid_file (str|None, optional): Path to subset index file, can be set
None if :attr:`download` is True. Default: None, default data path: ~/.cache/paddle/dataset/flowers/.
mode (str, optional): Either train or test mode. Default 'train'.
transform (Callable|None, optional): transform to perform on image, None for no transform. Default: None.
download (bool|None, optional): download dataset automatically if :attr:`data_file` is None. Default: True.
backend (str|None, optional): Specifies which type of image to be returned:
PIL.Image or numpy.ndarray. Should be one of {'pil', 'cv2'}.
If this option is not set, will get backend from :ref:`paddle.vision.get_image_backend <api_paddle_vision_get_image_backend>`,
default backend is 'pil'. Default: None.
Returns:
:ref:`api_paddle_io_Dataset`. An instance of Flowers dataset.
Examples:
.. code-block:: pycon
>>> # doctest: +TIMEOUT(60)
>>> import itertools
>>> import paddle
>>> import paddle.vision.transforms as T
>>> from paddle.vision.datasets import Flowers
>>> flowers = Flowers()
>>> print(len(flowers))
6149
>>> for i in range(5): # only show first 5 images
... img, label = flowers[i]
... # do something with img and label
... print(type(img), img.size, label)
... # <class 'PIL.JpegImagePlugin.JpegImageFile'> (523, 500) [1]
>>> transform = T.Compose(
... [
... T.Resize(64),
... T.ToTensor(),
... T.Normalize(
... mean=[0.5, 0.5, 0.5],
... std=[0.5, 0.5, 0.5],
... to_rgb=True,
... ),
... ]
... )
>>> flowers_test = Flowers(
... mode="test",
... transform=transform, # apply transform to every image
... backend="cv2", # use OpenCV as image transform backend
... )
>>> print(len(flowers_test))
1020
>>> for img, label in itertools.islice(iter(flowers_test), 5): # only show first 5 images
... # do something with img and label
... assert isinstance(img, paddle.Tensor)
... print(type(img), img.shape, label)
... # <class 'paddle.Tensor'> [3, 64, 96] [1]
"""
backend: _ImageBackend
data_file: str | None
label_file: str | None
setid_file: str | None
mode: _DatasetMode
transform: _Transform[Any, Any] | None
def __init__(
self,
data_file: str | None = None,
label_file: str | None = None,
setid_file: str | None = None,
mode: _DatasetMode = 'train',
transform: _Transform[Any, Any] | None = None,
download: bool = True,
backend: _ImageBackend | None = None,
) -> None:
assert mode.lower() in [
'train',
'valid',
'test',
], f"mode should be 'train', 'valid' or 'test', but got {mode}"
if backend is None:
backend = paddle.vision.get_image_backend()
if backend not in ['pil', 'cv2']:
raise ValueError(
f"Expected backend are one of ['pil', 'cv2'], but got {backend}"
)
self.backend = backend
flag = MODE_FLAG_MAP[mode.lower()]
if not data_file:
assert download, (
"data_file is not set and downloading automatically is disabled"
)
data_file = _check_exists_and_download(
data_file, DATA_URL, DATA_MD5, 'flowers', download
)
if not label_file:
assert download, (
"label_file is not set and downloading automatically is disabled"
)
label_file = _check_exists_and_download(
label_file, LABEL_URL, LABEL_MD5, 'flowers', download
)
if not setid_file:
assert download, (
"setid_file is not set and downloading automatically is disabled"
)
setid_file = _check_exists_and_download(
setid_file, SETID_URL, SETID_MD5, 'flowers', download
)
self.transform = transform
data_tar = tarfile.open(data_file)
self.data_path = data_file.replace(".tgz", "/")
if not os.path.exists(self.data_path):
os.mkdir(self.data_path)
jpg_path = os.path.join(self.data_path, "jpg")
if not os.path.exists(jpg_path):
_safe_extract_tar(data_tar, self.data_path, on_unsafe='raise')
scio = try_import('scipy.io')
self.labels = scio.loadmat(label_file)['labels'][0]
self.indexes = scio.loadmat(setid_file)[flag][0]
def __getitem__(
self, idx: int
) -> tuple[_ImageDataType, npt.NDArray[np.int64]]:
index = self.indexes[idx]
label = np.array([self.labels[index - 1]])
img_name = f"jpg/image_{index:05}.jpg"
image = os.path.join(self.data_path, img_name)
if self.backend == 'pil':
image = Image.open(image)
elif self.backend == 'cv2':
image = np.array(Image.open(image))
if self.transform is not None:
image = self.transform(image)
if self.backend == 'pil':
return image, label.astype('int64')
return image.astype(paddle.get_default_dtype()), label.astype('int64')
def __len__(self):
return len(self.indexes)
+538
View File
@@ -0,0 +1,538 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal, TypeAlias
if TYPE_CHECKING:
from collections.abc import Callable, Sequence
from paddle._typing.dtype_like import _DTypeLiteral
from paddle.vision.transforms.transforms import _Transform
from ..image import _ImageDataType
_AllowedExtensions: TypeAlias = Literal[
'.jpg',
'.jpeg',
'.png',
'.ppm',
'.bmp',
'.pgm',
'.tif',
'.tiff',
'.webp',
]
import os
from PIL import Image
import paddle
from paddle.io import Dataset
from paddle.utils import try_import
__all__ = []
def has_valid_extension(filename: str, extensions: Sequence[str]) -> bool:
"""Checks if a file is a valid extension.
Args:
filename (str): path to a file
extensions (list[str]|tuple[str]): extensions to consider
Returns:
bool: True if the filename ends with one of given extensions
"""
assert isinstance(extensions, (list, tuple)), (
"`extensions` must be list or tuple."
)
extensions = tuple([x.lower() for x in extensions])
return filename.lower().endswith(extensions)
def make_dataset(dir, class_to_idx, extensions, is_valid_file=None):
images = []
dir = os.path.expanduser(dir)
if extensions is not None:
def is_valid_file(x):
return has_valid_extension(x, extensions)
for target in sorted(class_to_idx.keys()):
d = os.path.join(dir, target)
if not os.path.isdir(d):
continue
for root, _, fnames in sorted(os.walk(d, followlinks=True)):
for fname in sorted(fnames):
path = os.path.join(root, fname)
if is_valid_file(path):
item = (path, class_to_idx[target])
images.append(item)
return images
class DatasetFolder(Dataset[tuple["_ImageDataType", int]]):
"""A generic data loader where the samples are arranged in this way:
.. code-block:: text
root/class_a/1.ext
root/class_a/2.ext
root/class_a/3.ext
root/class_b/123.ext
root/class_b/456.ext
root/class_b/789.ext
Args:
root (str): Root directory path.
loader (Callable|None, optional): A function to load a sample given its path. Default: None.
extensions (list[str]|tuple[str]|None, optional): A list of allowed extensions.
Both :attr:`extensions` and :attr:`is_valid_file` should not be passed.
If this value is not set, the default is to use ('.jpg', '.jpeg', '.png',
'.ppm', '.bmp', '.pgm', '.tif', '.tiff', '.webp'). Default: None.
transform (Callable|None, optional): A function/transform that takes in
a sample and returns a transformed version. Default: None.
is_valid_file (Callable|None, optional): A function that takes path of a file
and check if the file is a valid file. Both :attr:`extensions` and
:attr:`is_valid_file` should not be passed. Default: None.
Returns:
:ref:`api_paddle_io_Dataset`. An instance of DatasetFolder.
Attributes:
classes (list[str]): List of the class names.
class_to_idx (dict[str, int]): Dict with items (class_name, class_index).
samples (list[tuple[str, int]]): List of (sample_path, class_index) tuples.
targets (list[int]): The class_index value for each image in the dataset.
Example:
.. code-block:: pycon
>>> import shutil
>>> import tempfile
>>> import cv2
>>> import numpy as np
>>> import paddle
>>> import paddle.vision.transforms as T
>>> from pathlib import Path
>>> from paddle.vision.datasets import DatasetFolder
>>> def make_fake_file(img_path: str):
... if img_path.endswith((".jpg", ".png", ".jpeg")):
... fake_img = np.random.randint(0, 256, (32, 32, 3), dtype=np.uint8)
... cv2.imwrite(img_path, fake_img)
... elif img_path.endswith(".txt"):
... with open(img_path, "w") as f:
... f.write("This is a fake file.")
>>> def make_directory(root, directory_hierarchy, file_maker=make_fake_file):
... root = Path(root)
... root.mkdir(parents=True, exist_ok=True)
... for subpath in directory_hierarchy:
... if isinstance(subpath, str):
... filepath = root / subpath
... file_maker(str(filepath))
... else:
... dirname = list(subpath.keys())[0]
... make_directory(root / dirname, subpath[dirname])
>>> directory_hierarchy = [
... {"class_0": [
... "abc.jpg",
... "def.png"]},
... {"class_1": [
... "ghi.jpeg",
... "jkl.png",
... {"mno": [
... "pqr.jpeg",
... "stu.jpg"]}]},
... "this_will_be_ignored.txt",
... ] # fmt: skip
>>> # You can replace this with any directory to explore the structure
>>> # of generated data. e.g. fake_data_dir = "./temp_dir"
>>> fake_data_dir = tempfile.mkdtemp()
>>> make_directory(fake_data_dir, directory_hierarchy)
>>> data_folder_1 = DatasetFolder(fake_data_dir)
>>> print(data_folder_1.classes)
['class_0', 'class_1']
>>> print(data_folder_1.class_to_idx)
{'class_0': 0, 'class_1': 1}
>>> print(data_folder_1.samples)
>>> # doctest: +SKIP(it's different with windows)
[('./temp_dir/class_0/abc.jpg', 0), ('./temp_dir/class_0/def.png', 0),
('./temp_dir/class_1/ghi.jpeg', 1), ('./temp_dir/class_1/jkl.png', 1),
('./temp_dir/class_1/mno/pqr.jpeg', 1), ('./temp_dir/class_1/mno/stu.jpg', 1)]
>>> # doctest: -SKIP
>>> print(data_folder_1.targets)
[0, 0, 1, 1, 1, 1]
>>> print(len(data_folder_1))
6
>>> for i in range(len(data_folder_1)):
... img, label = data_folder_1[i]
... # do something with img and label
... print(type(img), img.size, label)
... # <class 'PIL.Image.Image'> (32, 32) 0
>>> transform = T.Compose(
... [
... T.Resize(64),
... T.ToTensor(),
... T.Normalize(
... mean=[0.5, 0.5, 0.5],
... std=[0.5, 0.5, 0.5],
... to_rgb=True,
... ),
... ]
... )
>>> def cv2_loader(path: str):
... image = cv2.imread(path)
... assert image is not None
... return image
>>> data_folder_2 = DatasetFolder(
... fake_data_dir,
... loader=cv2_loader, # load image with OpenCV
... extensions=(".jpg",), # only load *.jpg files
... transform=transform, # apply transform to every image
... )
>>> print([img_path for img_path, label in data_folder_2.samples])
>>> # doctest: +SKIP(it's different with windows)
['./temp_dir/class_0/abc.jpg', './temp_dir/class_1/mno/stu.jpg']
>>> # doctest: -SKIP
>>> print(len(data_folder_2))
2
>>> for img, label in iter(data_folder_2):
... # do something with img and label
... assert isinstance(img, paddle.Tensor)
... print(type(img), img.shape, label)
... # <class 'paddle.Tensor'> [3, 64, 64] 0
>>> shutil.rmtree(fake_data_dir)
"""
loader: Callable[..., _ImageDataType] | None
extensions: Sequence[_AllowedExtensions] | None
transform: _Transform[Any, Any] | None
classes: list[str]
class_to_idx: dict[str, int]
samples: list[tuple[str, int]]
targets: list[str]
dtype: _DTypeLiteral
def __init__(
self,
root: str,
loader: Callable[..., _ImageDataType] | None = None,
extensions: Sequence[_AllowedExtensions] | None = None,
transform: _Transform[Any, Any] | None = None,
is_valid_file: _ImageDataType | None = None,
) -> None:
self.root = root
self.transform = transform
if extensions is None:
extensions = IMG_EXTENSIONS
classes, class_to_idx = self._find_classes(self.root)
samples = make_dataset(
self.root, class_to_idx, extensions, is_valid_file
)
if len(samples) == 0:
raise (
RuntimeError(
"Found 0 directories in subfolders of: " + self.root + "\n"
"Supported extensions are: " + ",".join(extensions)
)
)
self.loader = default_loader if loader is None else loader
self.extensions = extensions
self.classes = classes
self.class_to_idx = class_to_idx
self.samples = samples
self.targets = [s[1] for s in samples]
self.dtype = paddle.get_default_dtype()
def _find_classes(self, dir: str) -> tuple[list[str], dict[str, int]]:
"""
Finds the class folders in a dataset.
Args:
dir (string): Root directory path.
Returns:
tuple: (classes, class_to_idx) where classes are relative to (dir),
and class_to_idx is a dictionary.
"""
classes = [d.name for d in os.scandir(dir) if d.is_dir()]
classes.sort()
class_to_idx = {classes[i]: i for i in range(len(classes))}
return classes, class_to_idx
def __getitem__(self, index: int) -> tuple[_ImageDataType, int]:
"""
Args:
index (int): Index
Returns:
tuple: (sample, target) where target is class_index of the target class.
"""
path, target = self.samples[index]
sample = self.loader(path)
if self.transform is not None:
sample = self.transform(sample)
return sample, target
def __len__(self):
return len(self.samples)
IMG_EXTENSIONS = (
'.jpg',
'.jpeg',
'.png',
'.ppm',
'.bmp',
'.pgm',
'.tif',
'.tiff',
'.webp',
)
def pil_loader(path):
with open(path, 'rb') as f:
img = Image.open(f)
return img.convert('RGB')
def cv2_loader(path):
cv2 = try_import('cv2')
return cv2.cvtColor(cv2.imread(path), cv2.COLOR_BGR2RGB)
def default_loader(path):
from paddle.vision import get_image_backend
if get_image_backend() == 'cv2':
return cv2_loader(path)
else:
return pil_loader(path)
class ImageFolder(Dataset[list["_ImageDataType"]]):
"""A generic data loader where the samples are arranged in this way:
.. code-block:: text
root/1.ext
root/2.ext
root/sub_dir/3.ext
Args:
root (str): Root directory path.
loader (Callable|None, optional): A function to load a sample given its path. Default: None.
extensions (list[str]|tuple[str]|None, optional): A list of allowed extensions.
Both :attr:`extensions` and :attr:`is_valid_file` should not be passed.
If this value is not set, the default is to use ('.jpg', '.jpeg', '.png',
'.ppm', '.bmp', '.pgm', '.tif', '.tiff', '.webp'). Default: None.
transform (Callable|None, optional): A function/transform that takes in
a sample and returns a transformed version. Default: None.
is_valid_file (Callable|None, optional): A function that takes path of a file
and check if the file is a valid file. Both :attr:`extensions` and
:attr:`is_valid_file` should not be passed. Default: None.
Returns:
:ref:`api_paddle_io_Dataset`. An instance of ImageFolder.
Attributes:
samples (list[str]): List of sample path.
Example:
.. code-block:: pycon
>>> import shutil
>>> import tempfile
>>> import cv2
>>> import numpy as np
>>> import paddle
>>> import paddle.vision.transforms as T
>>> from pathlib import Path
>>> from paddle.vision.datasets import ImageFolder
>>> def make_fake_file(img_path: str):
... if img_path.endswith((".jpg", ".png", ".jpeg")):
... fake_img = np.random.randint(0, 256, (32, 32, 3), dtype=np.uint8)
... cv2.imwrite(img_path, fake_img)
... elif img_path.endswith(".txt"):
... with open(img_path, "w") as f:
... f.write("This is a fake file.")
>>> def make_directory(root, directory_hierarchy, file_maker=make_fake_file):
... root = Path(root)
... root.mkdir(parents=True, exist_ok=True)
... for subpath in directory_hierarchy:
... if isinstance(subpath, str):
... filepath = root / subpath
... file_maker(str(filepath))
... else:
... dirname = list(subpath.keys())[0]
... make_directory(root / dirname, subpath[dirname])
>>> directory_hierarchy = [
... "abc.jpg",
... "def.png",
... {"ghi": [
... "jkl.jpeg",
... {"mno": [
... "pqr.jpg"]}]},
... "this_will_be_ignored.txt",
... ] # fmt: skip
>>> # You can replace this with any directory to explore the structure
>>> # of generated data. e.g. fake_data_dir = "./temp_dir"
>>> fake_data_dir = tempfile.mkdtemp()
>>> make_directory(fake_data_dir, directory_hierarchy)
>>> image_folder_1 = ImageFolder(fake_data_dir)
>>> print(image_folder_1.samples)
>>> # doctest: +SKIP(it's different with windows)
['./temp_dir/abc.jpg', './temp_dir/def.png',
'./temp_dir/ghi/jkl.jpeg', './temp_dir/ghi/mno/pqr.jpg']
>>> # doctest: -SKIP
>>> print(len(image_folder_1))
4
>>> for i in range(len(image_folder_1)):
... (img,) = image_folder_1[i]
... # do something with img
... print(type(img), img.size)
... # <class 'PIL.Image.Image'> (32, 32)
>>> transform = T.Compose(
... [
... T.Resize(64),
... T.ToTensor(),
... T.Normalize(
... mean=[0.5, 0.5, 0.5],
... std=[0.5, 0.5, 0.5],
... to_rgb=True,
... ),
... ]
... )
>>> def cv2_loader(path: str):
... image = cv2.imread(path)
... assert image is not None
... return image
>>> image_folder_2 = ImageFolder(
... fake_data_dir,
... loader=cv2_loader, # load image with OpenCV
... extensions=(".jpg",), # only load *.jpg files
... transform=transform, # apply transform to every image
... )
>>> print(image_folder_2.samples)
>>> # doctest: +SKIP(it's different with windows)
['./temp_dir/abc.jpg', './temp_dir/ghi/mno/pqr.jpg']
>>> # doctest: -SKIP
>>> print(len(image_folder_2))
2
>>> for (img,) in iter(image_folder_2):
... # do something with img
... assert isinstance(img, paddle.Tensor)
... print(type(img), img.shape)
... # <class 'paddle.Tensor'> [3, 64, 64]
>>> shutil.rmtree(fake_data_dir)
"""
loader: Callable[..., _ImageDataType] | None
extensions: Sequence[_AllowedExtensions] | None
samples: list[str]
transform: _Transform[Any, Any] | None
def __init__(
self,
root: str,
loader: Callable[..., _ImageDataType] | None = None,
extensions: Sequence[_AllowedExtensions] | None = None,
transform: _Transform[Any, Any] | None = None,
is_valid_file: _ImageDataType | None = None,
) -> None:
self.root = root
if extensions is None:
extensions = IMG_EXTENSIONS
samples = []
path = os.path.expanduser(root)
if extensions is not None:
def is_valid_file(x):
return has_valid_extension(x, extensions)
for root, _, fnames in sorted(os.walk(path, followlinks=True)):
for fname in sorted(fnames):
f = os.path.join(root, fname)
if is_valid_file(f):
samples.append(f)
if len(samples) == 0:
raise (
RuntimeError(
"Found 0 files in subfolders of: " + self.root + "\n"
"Supported extensions are: " + ",".join(extensions)
)
)
self.loader = default_loader if loader is None else loader
self.extensions = extensions
self.samples = samples
self.transform = transform
def __getitem__(self, index: int) -> list[_ImageDataType]:
"""
Args:
index (int): Index
Returns:
sample of specific index.
"""
path = self.samples[index]
sample = self.loader(path)
if self.transform is not None:
sample = self.transform(sample)
return [sample]
def __len__(self) -> int:
return len(self.samples)
+339
View File
@@ -0,0 +1,339 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal
if TYPE_CHECKING:
import numpy.typing as npt
from paddle._typing.dtype_like import _DTypeLiteral
from paddle.vision.transforms.transforms import _Transform
from ..image import _ImageBackend, _ImageDataType
_DatasetMode = Literal["train", "test"]
import gzip
import struct
import numpy as np
from PIL import Image
import paddle
from paddle.dataset.common import _check_exists_and_download
from paddle.io import Dataset
__all__ = []
class MNIST(Dataset[tuple["_ImageDataType", "npt.NDArray[np.int64]"]]):
"""
Implementation of `MNIST <http://yann.lecun.com/exdb/mnist/>`_ dataset.
Args:
image_path (str|None, optional): Path to image file, can be set None if
:attr:`download` is True. Default: None, default data path: ~/.cache/paddle/dataset/mnist.
label_path (str|None, optional): Path to label file, can be set None if
:attr:`download` is True. Default: None, default data path: ~/.cache/paddle/dataset/mnist.
mode (str, optional): Either train or test mode. Default 'train'.
transform (Callable|None, optional): Transform to perform on image, None for no transform. Default: None.
download (bool, optional): Download dataset automatically if
:attr:`image_path` :attr:`label_path` is not set. Default: True.
backend (str|None, optional): Specifies which type of image to be returned:
PIL.Image or numpy.ndarray. Should be one of {'pil', 'cv2'}.
If this option is not set, will get backend from :ref:`paddle.vision.get_image_backend <api_paddle_vision_get_image_backend>`,
default backend is 'pil'. Default: None.
Returns:
:ref:`api_paddle_io_Dataset`. An instance of MNIST dataset.
Examples:
.. code-block:: pycon
>>> import itertools
>>> import paddle
>>> import paddle.vision.transforms as T
>>> from paddle.vision.datasets import MNIST
>>> mnist = MNIST()
>>> print(len(mnist))
60000
>>> for i in range(5): # only show first 5 images
... img, label = mnist[i]
... # do something with img and label
... print(type(img), img.size, label)
... # <class 'PIL.Image.Image'> (28, 28) [5]
>>> transform = T.Compose(
... [
... T.ToTensor(),
... T.Normalize(
... mean=[127.5],
... std=[127.5],
... ),
... ]
... )
>>> mnist_test = MNIST(
... mode="test",
... transform=transform, # apply transform to every image
... backend="cv2", # use OpenCV as image transform backend
... )
>>> print(len(mnist_test))
10000
>>> for img, label in itertools.islice(iter(mnist_test), 5): # only show first 5 images
... # do something with img and label
... assert isinstance(img, paddle.Tensor)
... print(type(img), img.shape, label)
... # <class 'paddle.Tensor'> [1, 28, 28] [7]
"""
NAME = 'mnist'
URL_PREFIX = 'https://dataset.bj.bcebos.com/mnist/'
TEST_IMAGE_URL = URL_PREFIX + 't10k-images-idx3-ubyte.gz'
TEST_IMAGE_MD5 = '9fb629c4189551a2d022fa330f9573f3'
TEST_LABEL_URL = URL_PREFIX + 't10k-labels-idx1-ubyte.gz'
TEST_LABEL_MD5 = 'ec29112dd5afa0611ce80d1b7f02629c'
TRAIN_IMAGE_URL = URL_PREFIX + 'train-images-idx3-ubyte.gz'
TRAIN_IMAGE_MD5 = 'f68b3c2dcbeaaa9fbdd348bbdeb94873'
TRAIN_LABEL_URL = URL_PREFIX + 'train-labels-idx1-ubyte.gz'
TRAIN_LABEL_MD5 = 'd53e105ee54ea40749a09fcbcd1e9432'
mode: _DatasetMode
image_path: str | None
label_path: str | None
transform: _Transform[Any, Any] | None
backend: _ImageBackend
dtype: _DTypeLiteral
labels: list
images: list
def __init__(
self,
image_path: str | None = None,
label_path: str | None = None,
mode: _DatasetMode = 'train',
transform: _Transform[Any, Any] | None = None,
download: bool = True,
backend: _ImageBackend | None = None,
) -> None:
assert mode.lower() in [
'train',
'test',
], f"mode should be 'train' or 'test', but got {mode}"
if backend is None:
backend = paddle.vision.get_image_backend()
if backend not in ['pil', 'cv2']:
raise ValueError(
f"Expected backend are one of ['pil', 'cv2'], but got {backend}"
)
self.backend = backend
self.mode = mode.lower()
self.image_path = image_path
if self.image_path is None:
assert download, (
"image_path is not set and downloading automatically is disabled"
)
image_url = (
self.TRAIN_IMAGE_URL if mode == 'train' else self.TEST_IMAGE_URL
)
image_md5 = (
self.TRAIN_IMAGE_MD5 if mode == 'train' else self.TEST_IMAGE_MD5
)
self.image_path = _check_exists_and_download(
image_path, image_url, image_md5, self.NAME, download
)
self.label_path = label_path
if self.label_path is None:
assert download, (
"label_path is not set and downloading automatically is disabled"
)
label_url = (
self.TRAIN_LABEL_URL
if self.mode == 'train'
else self.TEST_LABEL_URL
)
label_md5 = (
self.TRAIN_LABEL_MD5
if self.mode == 'train'
else self.TEST_LABEL_MD5
)
self.label_path = _check_exists_and_download(
label_path, label_url, label_md5, self.NAME, download
)
self.transform = transform
# read dataset into memory
self._parse_dataset()
self.dtype = paddle.get_default_dtype()
def _parse_dataset(self, buffer_size=100):
self.images = []
self.labels = []
with gzip.GzipFile(self.image_path, 'rb') as image_file:
img_buf = image_file.read()
with gzip.GzipFile(self.label_path, 'rb') as label_file:
lab_buf = label_file.read()
step_label = 0
offset_img = 0
# read from Big-endian
# get file info from magic byte
# image file : 16B
magic_byte_img = '>IIII'
magic_img, image_num, rows, cols = struct.unpack_from(
magic_byte_img, img_buf, offset_img
)
offset_img += struct.calcsize(magic_byte_img)
offset_lab = 0
# label file : 8B
magic_byte_lab = '>II'
magic_lab, label_num = struct.unpack_from(
magic_byte_lab, lab_buf, offset_lab
)
offset_lab += struct.calcsize(magic_byte_lab)
while True:
if step_label >= label_num:
break
fmt_label = '>' + str(buffer_size) + 'B'
labels = struct.unpack_from(fmt_label, lab_buf, offset_lab)
offset_lab += struct.calcsize(fmt_label)
step_label += buffer_size
fmt_images = '>' + str(buffer_size * rows * cols) + 'B'
images_temp = struct.unpack_from(
fmt_images, img_buf, offset_img
)
images = np.reshape(
images_temp, (buffer_size, rows * cols)
).astype('float32')
offset_img += struct.calcsize(fmt_images)
for i in range(buffer_size):
self.images.append(images[i, :])
self.labels.append(
np.array([labels[i]]).astype('int64')
)
def __getitem__(
self, idx: int
) -> tuple[_ImageDataType, npt.NDArray[np.int64]]:
image, label = self.images[idx], self.labels[idx]
image = np.reshape(image, [28, 28])
if self.backend == 'pil':
image = Image.fromarray(image.astype('uint8'), mode='L')
if self.transform is not None:
image = self.transform(image)
if self.backend == 'pil':
return image, label.astype('int64')
return image.astype(self.dtype), label.astype('int64')
def __len__(self) -> int:
return len(self.labels)
class FashionMNIST(MNIST):
"""
Implementation of `Fashion-MNIST <https://github.com/zalandoresearch/fashion-mnist>`_ dataset.
Args:
image_path (str, optional): Path to image file, can be set None if
:attr:`download` is True. Default: None, default data path: ~/.cache/paddle/dataset/fashion-mnist.
label_path (str, optional): Path to label file, can be set None if
:attr:`download` is True. Default: None, default data path: ~/.cache/paddle/dataset/fashion-mnist.
mode (str, optional): Either train or test mode. Default 'train'.
transform (Callable, optional): Transform to perform on image, None for no transform. Default: None.
download (bool, optional): Whether to download dataset automatically if
:attr:`image_path` :attr:`label_path` is not set. Default: True.
backend (str, optional): Specifies which type of image to be returned:
PIL.Image or numpy.ndarray. Should be one of {'pil', 'cv2'}.
If this option is not set, will get backend from :ref:`paddle.vision.get_image_backend <api_paddle_vision_get_image_backend>`,
default backend is 'pil'. Default: None.
Returns:
:ref:`api_paddle_io_Dataset`. An instance of FashionMNIST dataset.
Examples:
.. code-block:: pycon
>>> import itertools
>>> import paddle
>>> import paddle.vision.transforms as T
>>> from paddle.vision.datasets import FashionMNIST
>>> fashion_mnist = FashionMNIST()
>>> print(len(fashion_mnist))
60000
>>> for i in range(5): # only show first 5 images
... img, label = fashion_mnist[i]
... # do something with img and label
... print(type(img), img.size, label)
... # <class 'PIL.Image.Image'> (28, 28) [9]
>>> transform = T.Compose(
... [
... T.ToTensor(),
... T.Normalize(
... mean=[127.5],
... std=[127.5],
... ),
... ]
... )
>>> fashion_mnist_test = FashionMNIST(
... mode="test",
... transform=transform, # apply transform to every image
... backend="cv2", # use OpenCV as image transform backend
... )
>>> print(len(fashion_mnist_test))
10000
>>> for img, label in itertools.islice(iter(fashion_mnist_test), 5): # only show first 5 images
... # do something with img and label
... assert isinstance(img, paddle.Tensor)
... print(type(img), img.shape, label)
... # <class 'paddle.Tensor'> [1, 28, 28] [9]
"""
NAME = 'fashion-mnist'
URL_PREFIX = 'https://dataset.bj.bcebos.com/fashion_mnist/'
TEST_IMAGE_URL = URL_PREFIX + 't10k-images-idx3-ubyte.gz'
TEST_IMAGE_MD5 = 'bef4ecab320f06d8554ea6380940ec79'
TEST_LABEL_URL = URL_PREFIX + 't10k-labels-idx1-ubyte.gz'
TEST_LABEL_MD5 = 'bb300cfdad3c16e7a12a480ee83cd310'
TRAIN_IMAGE_URL = URL_PREFIX + 'train-images-idx3-ubyte.gz'
TRAIN_IMAGE_MD5 = '8d4fb7e6c68d591d4c3dfef9ec88bf0d'
TRAIN_LABEL_URL = URL_PREFIX + 'train-labels-idx1-ubyte.gz'
TRAIN_LABEL_MD5 = '25c81989df183df01b3e8a0aad5dffbe'
+215
View File
@@ -0,0 +1,215 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import io
import tarfile
from typing import TYPE_CHECKING, Any, Literal
import numpy as np
from PIL import Image
import paddle
from paddle.dataset.common import _check_exists_and_download
from paddle.io import Dataset
if TYPE_CHECKING:
import numpy.typing as npt
from paddle._typing import DTypeLike
from paddle.vision.transforms.transforms import _Transform
from ..image import _ImageDataType
_ImageBackend = Literal["cv2", "pil"]
_DatasetMode = Literal["train", "valid", "test"]
__all__ = []
VOC_URL = 'https://dataset.bj.bcebos.com/voc/VOCtrainval_11-May-2012.tar'
VOC_MD5 = '6cd6e144f989b92b3379bac3b3de84fd'
SET_FILE = 'VOCdevkit/VOC2012/ImageSets/Segmentation/{}.txt'
DATA_FILE = 'VOCdevkit/VOC2012/JPEGImages/{}.jpg'
LABEL_FILE = 'VOCdevkit/VOC2012/SegmentationClass/{}.png'
CACHE_DIR = 'voc2012'
MODE_FLAG_MAP = {'train': 'trainval', 'test': 'train', 'valid': "val"}
class VOC2012(Dataset[tuple["_ImageDataType", "npt.NDArray[Any]"]]):
"""
Implementation of `VOC2012 <http://host.robots.ox.ac.uk/pascal/VOC/voc2012/>`_ dataset.
Args:
data_file (str|None, optional): Path to data file, can be set None if
:attr:`download` is True. Default: None, default data path: ~/.cache/paddle/dataset/voc2012.
mode (str, optional): Either train or test mode. Default 'train'.
transform (Callable|None, optional): Transform to perform on image, None for no transform. Default: None.
download (bool, optional): Download dataset automatically if :attr:`data_file` is None. Default: True.
backend (str|None, optional): Specifies which type of image to be returned:
PIL.Image or numpy.ndarray. Should be one of {'pil', 'cv2'}.
If this option is not set, will get backend from :ref:`paddle.vision.get_image_backend <api_paddle_vision_get_image_backend>`,
default backend is 'pil'. Default: None.
Returns:
:ref:`api_paddle_io_Dataset`. An instance of VOC2012 dataset.
Examples:
.. code-block:: pycon
>>> # doctest: +TIMEOUT(120)
>>> import itertools
>>> import paddle
>>> import paddle.vision.transforms as T
>>> from paddle.vision.datasets import VOC2012
>>> voc2012 = VOC2012()
>>> print(len(voc2012))
2913
>>> for i in range(5): # only show first 5 images
... img, label = voc2012[i]
... # do something with img and label
... print(type(img), img.size)
... # <class 'PIL.JpegImagePlugin.JpegImageFile'> (500, 281)
... print(type(label), label.size)
... # <class 'PIL.PngImagePlugin.PngImageFile'> (500, 281)
>>> transform = T.Compose(
... [
... T.ToTensor(),
... T.Normalize(
... mean=[0.5, 0.5, 0.5],
... std=[0.5, 0.5, 0.5],
... to_rgb=True,
... ),
... ]
... )
>>> voc2012_test = VOC2012(
... mode="test",
... transform=transform, # apply transform to every image
... backend="cv2", # use OpenCV as image transform backend
... )
>>> print(len(voc2012_test))
1464
>>> for img, label in itertools.islice(iter(voc2012_test), 5): # only show first 5 images
... # do something with img and label
... assert isinstance(img, paddle.Tensor)
... print(type(img), img.shape)
... # <class 'paddle.Tensor'> [3, 281, 500]
... print(type(label), label.shape)
... # <class 'numpy.ndarray'> (281, 500)
"""
data_file: str | None
mode: _DatasetMode
transform: _Transform[Any, Any] | None
backend: _ImageBackend
flag: str
dtype: DTypeLike
def __init__(
self,
data_file: str | None = None,
mode: _DatasetMode = 'train',
transform: _Transform[Any, Any] | None = None,
download: bool = True,
backend: _ImageBackend | None = None,
) -> None:
assert mode.lower() in [
'train',
'valid',
'test',
], f"mode should be 'train', 'valid' or 'test', but got {mode}"
if backend is None:
backend = paddle.vision.get_image_backend()
if backend not in ['pil', 'cv2']:
raise ValueError(
f"Expected backend are one of ['pil', 'cv2'], but got {backend}"
)
self.backend = backend
self.flag = MODE_FLAG_MAP[mode.lower()]
self.data_file = data_file
if self.data_file is None:
assert download, (
"data_file is not set and downloading automatically is disabled"
)
self.data_file = _check_exists_and_download(
data_file, VOC_URL, VOC_MD5, CACHE_DIR, download
)
self.transform = transform
# read dataset into memory
self._load_anno()
self.dtype = paddle.get_default_dtype()
def _load_anno(self):
self.name2mem = {}
self.data_tar = tarfile.open(self.data_file)
for ele in self.data_tar.getmembers():
self.name2mem[ele.name] = ele
set_file = SET_FILE.format(self.flag)
sets = self.data_tar.extractfile(self.name2mem[set_file])
self.data = []
self.labels = []
for line in sets:
line = line.strip()
data = DATA_FILE.format(line.decode('utf-8'))
label = LABEL_FILE.format(line.decode('utf-8'))
self.data.append(data)
self.labels.append(label)
def __getitem__(self, idx: int) -> tuple[_ImageDataType, npt.NDArray[Any]]:
data_file = self.data[idx]
label_file = self.labels[idx]
data = self.data_tar.extractfile(self.name2mem[data_file]).read()
label = self.data_tar.extractfile(self.name2mem[label_file]).read()
data = Image.open(io.BytesIO(data))
label = Image.open(io.BytesIO(label))
if self.backend == 'cv2':
data = np.array(data)
label = np.array(label)
if self.transform is not None:
data = self.transform(data)
if self.backend == 'cv2':
return data.astype(self.dtype), label.astype(self.dtype)
return data, label
def __len__(self) -> int:
return len(self.data)
def __del__(self) -> None:
if self.data_tar:
self.data_tar.close()
+179
View File
@@ -0,0 +1,179 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal, TypeAlias
from PIL import Image
from paddle.utils import try_import
if TYPE_CHECKING:
import numpy.typing as npt
from PIL.Image import Image as PILImage
from paddle import Tensor
_ImageBackend: TypeAlias = Literal["pil", "cv2", "tensor"]
_ImageDataType: TypeAlias = Tensor | PILImage | npt.NDArray[Any]
__all__ = []
_image_backend: _ImageBackend = 'pil'
def set_image_backend(backend: _ImageBackend) -> None:
"""
Specifies the backend used to load images in class :ref:`api_paddle_datasets_ImageFolder`
and :ref:`api_paddle_datasets_DatasetFolder` . Now support backends are pillow and opencv.
If backend not set, will use 'pil' as default.
Args:
backend (str): Name of the image load backend, should be one of {'pil', 'cv2'}.
Examples:
.. code-block:: pycon
>>> import os
>>> import shutil
>>> import tempfile
>>> import numpy as np
>>> from PIL import Image
>>> from paddle.vision import DatasetFolder
>>> from paddle.vision import set_image_backend
>>> set_image_backend('pil')
>>> def make_fake_dir():
... data_dir = tempfile.mkdtemp()
...
... for i in range(2):
... sub_dir = os.path.join(data_dir, 'class_' + str(i))
... if not os.path.exists(sub_dir):
... os.makedirs(sub_dir)
... for j in range(2):
... fake_img = Image.fromarray((np.random.random((32, 32, 3)) * 255).astype('uint8'))
... fake_img.save(os.path.join(sub_dir, str(j) + '.png'))
... return data_dir
>>> temp_dir = make_fake_dir()
>>> pil_data_folder = DatasetFolder(temp_dir)
>>> for items in pil_data_folder:
... break
>>> print(type(items[0]))
<class 'PIL.Image.Image'>
>>> # use opencv as backend
>>> set_image_backend('cv2')
>>> cv2_data_folder = DatasetFolder(temp_dir)
>>> for items in cv2_data_folder:
... break
>>> print(type(items[0]))
<class 'numpy.ndarray'>
>>> shutil.rmtree(temp_dir)
"""
global _image_backend
if backend not in ['pil', 'cv2', 'tensor']:
raise ValueError(
f"Expected backend are one of ['pil', 'cv2', 'tensor'], but got {backend}"
)
_image_backend = backend
def get_image_backend() -> _ImageBackend:
"""
Gets the name of the package used to load images
Returns:
str: backend of image load.
Examples:
.. code-block:: pycon
>>> from paddle.vision import get_image_backend
>>> backend = get_image_backend()
>>> print(backend)
pil
"""
return _image_backend
def image_load(
path: str, backend: _ImageBackend | None = None
) -> _ImageDataType | None:
"""Load an image.
Args:
path (str): Path of the image.
backend (str, optional): The image decoding backend type. Options are
`cv2`, `pil`, `None`. If backend is None, the global _imread_backend
specified by :ref:`api_paddle_vision_set_image_backend` will be used. Default: None.
Returns:
PIL.Image or np.array: Loaded image.
Examples:
.. code-block:: pycon
>>> import numpy as np
>>> from PIL import Image
>>> from paddle.vision import image_load, set_image_backend
>>> fake_img = Image.fromarray((np.random.random((32, 32, 3)) * 255).astype('uint8'))
>>> path = 'temp.png'
>>> fake_img.save(path)
>>> set_image_backend('pil')
>>> pil_img = image_load(path).convert('RGB') # type: ignore
>>> print(type(pil_img))
<class 'PIL.Image.Image'>
>>> # use opencv as backend
>>> set_image_backend('cv2')
>>> np_img = image_load(path)
>>> print(type(np_img))
<class 'numpy.ndarray'>
"""
if backend is None:
backend = _image_backend
if backend not in ['pil', 'cv2', 'tensor']:
raise ValueError(
f"Expected backend are one of ['pil', 'cv2', 'tensor'], but got {backend}"
)
if backend == 'pil':
return Image.open(path)
elif backend == 'cv2':
cv2 = try_import('cv2')
return cv2.imread(path)
+116
View File
@@ -0,0 +1,116 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .alexnet import AlexNet, alexnet
from .densenet import (
DenseNet,
densenet121,
densenet161,
densenet169,
densenet201,
densenet264,
)
from .googlenet import GoogLeNet, googlenet
from .inceptionv3 import InceptionV3, inception_v3
from .lenet import LeNet
from .mobilenetv1 import MobileNetV1, mobilenet_v1
from .mobilenetv2 import MobileNetV2, mobilenet_v2
from .mobilenetv3 import (
MobileNetV3Large,
MobileNetV3Small,
mobilenet_v3_large,
mobilenet_v3_small,
)
from .resnet import (
ResNet,
resnet18,
resnet34,
resnet50,
resnet101,
resnet152,
resnext50_32x4d,
resnext50_64x4d,
resnext101_32x4d,
resnext101_64x4d,
resnext152_32x4d,
resnext152_64x4d,
wide_resnet50_2,
wide_resnet101_2,
)
from .shufflenetv2 import (
ShuffleNetV2,
shufflenet_v2_swish,
shufflenet_v2_x0_5,
shufflenet_v2_x0_25,
shufflenet_v2_x0_33,
shufflenet_v2_x1_0,
shufflenet_v2_x1_5,
shufflenet_v2_x2_0,
)
from .squeezenet import SqueezeNet, squeezenet1_0, squeezenet1_1
from .vgg import VGG, vgg11, vgg13, vgg16, vgg19
__all__ = [
'ResNet',
'resnet18',
'resnet34',
'resnet50',
'resnet101',
'resnet152',
'resnext50_32x4d',
'resnext50_64x4d',
'resnext101_32x4d',
'resnext101_64x4d',
'resnext152_32x4d',
'resnext152_64x4d',
'wide_resnet50_2',
'wide_resnet101_2',
'VGG',
'vgg11',
'vgg13',
'vgg16',
'vgg19',
'MobileNetV1',
'mobilenet_v1',
'MobileNetV2',
'mobilenet_v2',
'MobileNetV3Small',
'MobileNetV3Large',
'mobilenet_v3_small',
'mobilenet_v3_large',
'LeNet',
'DenseNet',
'densenet121',
'densenet161',
'densenet169',
'densenet201',
'densenet264',
'AlexNet',
'alexnet',
'InceptionV3',
'inception_v3',
'SqueezeNet',
'squeezenet1_0',
'squeezenet1_1',
'GoogLeNet',
'googlenet',
'ShuffleNetV2',
'shufflenet_v2_x0_25',
'shufflenet_v2_x0_33',
'shufflenet_v2_x0_5',
'shufflenet_v2_x1_0',
'shufflenet_v2_x1_5',
'shufflenet_v2_x2_0',
'shufflenet_v2_swish',
]
+113
View File
@@ -0,0 +1,113 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from collections import OrderedDict
import paddle
from paddle import nn
def _make_divisible(v, divisor=8, min_value=None):
"""
This function ensures that all layers have a channel number that is divisible by divisor.
You can also see at https://github.com/keras-team/keras/blob/8ecef127f70db723c158dbe9ed3268b3d610ab55/keras/applications/mobilenet_v2.py#L505
Args:
divisor (int, optional): The divisor for number of channels. Default: 8.
min_value (int, optional): The minimum value of number of channels, if it is None,
the default is divisor. Default: None.
"""
if min_value is None:
min_value = divisor
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
# Make sure that round down does not go down by more than 10%.
if new_v < 0.9 * v:
new_v += divisor
return new_v
class IntermediateLayerGetter(nn.LayerDict):
"""
Layer wrapper that returns intermediate layers from a model.
It has a strong assumption that the layers have been registered into the model in the
same order as they are used. This means that one should **not** reuse the same nn.Layer
twice in the forward if you want this to work.
Additionally, it is only able to query sublayer that are directly assigned to the model.
So if `model` is passed, `model.feature1` can be returned, but not `model.feature1.layer2`.
Args:
model (nn.Layer): Model on which we will extract the features.
return_layers (Dict[name, new_name]): A dict containing the names of the layers for
which the activations will be returned as the key of the dict, and the value of the
dict is the name of the returned activation (which the user can specify).
Examples:
.. code-block:: pycon
>>> import paddle
>>> m = paddle.vision.models.resnet18(pretrained=False)
>>> # extract layer1 and layer3, giving as names `feat1` and feat2`
>>> new_m = paddle.vision.models._utils.IntermediateLayerGetter(
... m,
... {
... 'layer1': 'feat1',
... 'layer3': 'feat2',
... },
... )
>>> out = new_m(paddle.rand([1, 3, 224, 224]))
>>> print([(k, v.shape) for k, v in out.items()])
[('feat1', [1, 64, 56, 56]), ('feat2', [1, 256, 14, 14])]
"""
return_layers: dict[str, str]
def __init__(self, model: nn.Layer, return_layers: dict[str, str]) -> None:
if not set(return_layers).issubset(
[name for name, _ in model.named_children()]
):
raise ValueError("return_layers are not present in model")
orig_return_layers = return_layers
return_layers = {str(k): str(v) for k, v in return_layers.items()}
layers = OrderedDict()
for name, module in model.named_children():
layers[name] = module
if name in return_layers:
del return_layers[name]
if not return_layers:
break
super().__init__(layers)
self.return_layers = orig_return_layers
def forward(self, x):
out = OrderedDict()
for name, module in self.items():
if (isinstance(module, nn.Linear) and x.ndim == 4) or (
len(module.sublayers()) > 0
and isinstance(module.sublayers()[0], nn.Linear)
and x.ndim == 4
):
x = paddle.flatten(x, 1)
x = module(x)
if name in self.return_layers:
out_name = self.return_layers[name]
out[out_name] = x
return out
+241
View File
@@ -0,0 +1,241 @@
# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import math
from typing import (
TYPE_CHECKING,
TypedDict,
)
from typing_extensions import NotRequired, Unpack
import paddle
import paddle.nn.functional as F
from paddle import nn
from paddle.base.param_attr import ParamAttr
from paddle.nn import Conv2D, Dropout, Linear, MaxPool2D, ReLU
from paddle.nn.initializer import Uniform
from paddle.utils.download import get_weights_path_from_url
model_urls = {
"alexnet": (
"https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/AlexNet_pretrained.pdparams",
"7f0f9f737132e02732d75a1459d98a43",
)
}
__all__ = []
if TYPE_CHECKING:
from paddle import Tensor
from paddle._typing import Size2
class _AlexNetOptions(TypedDict):
num_classes: NotRequired[int]
class ConvPoolLayer(nn.Layer):
def __init__(
self,
input_channels: int,
output_channels: int,
filter_size: Size2,
stride: Size2,
padding: Size2,
stdv: float,
groups: int = 1,
act: str | None = None,
) -> None:
super().__init__()
self.relu = ReLU() if act == "relu" else None
self._conv = Conv2D(
in_channels=input_channels,
out_channels=output_channels,
kernel_size=filter_size,
stride=stride,
padding=padding,
groups=groups,
weight_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
bias_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
)
self._pool = MaxPool2D(kernel_size=3, stride=2, padding=0)
def forward(self, inputs: Tensor) -> Tensor:
x = self._conv(inputs)
if self.relu is not None:
x = self.relu(x)
x = self._pool(x)
return x
class AlexNet(nn.Layer):
"""AlexNet model from
`"ImageNet Classification with Deep Convolutional Neural Networks"
<https://proceedings.neurips.cc/paper/2012/file/c399862d3b9d6b76c8436e924a68c45b-Paper.pdf>`_.
Args:
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of AlexNet model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import AlexNet
>>> alexnet = AlexNet()
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = alexnet(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
num_classes: int
def __init__(self, num_classes: int = 1000) -> None:
super().__init__()
self.num_classes = num_classes
stdv = 1.0 / math.sqrt(3 * 11 * 11)
self._conv1 = ConvPoolLayer(3, 64, 11, 4, 2, stdv, act="relu")
stdv = 1.0 / math.sqrt(64 * 5 * 5)
self._conv2 = ConvPoolLayer(64, 192, 5, 1, 2, stdv, act="relu")
stdv = 1.0 / math.sqrt(192 * 3 * 3)
self._conv3 = Conv2D(
192,
384,
3,
stride=1,
padding=1,
weight_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
bias_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
)
stdv = 1.0 / math.sqrt(384 * 3 * 3)
self._conv4 = Conv2D(
384,
256,
3,
stride=1,
padding=1,
weight_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
bias_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
)
stdv = 1.0 / math.sqrt(256 * 3 * 3)
self._conv5 = ConvPoolLayer(256, 256, 3, 1, 1, stdv, act="relu")
if self.num_classes > 0:
stdv = 1.0 / math.sqrt(256 * 6 * 6)
self._drop1 = Dropout(p=0.5, mode="downscale_in_infer")
self._fc6 = Linear(
in_features=256 * 6 * 6,
out_features=4096,
weight_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
bias_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
)
self._drop2 = Dropout(p=0.5, mode="downscale_in_infer")
self._fc7 = Linear(
in_features=4096,
out_features=4096,
weight_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
bias_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
)
self._fc8 = Linear(
in_features=4096,
out_features=num_classes,
weight_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
bias_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
)
def forward(self, inputs: Tensor) -> Tensor:
x = self._conv1(inputs)
x = self._conv2(x)
x = self._conv3(x)
x = F.relu(x)
x = self._conv4(x)
x = F.relu(x)
x = self._conv5(x)
if self.num_classes > 0:
x = paddle.flatten(x, start_axis=1, stop_axis=-1)
x = self._drop1(x)
x = self._fc6(x)
x = F.relu(x)
x = self._drop2(x)
x = self._fc7(x)
x = F.relu(x)
x = self._fc8(x)
return x
def _alexnet(
arch: str, pretrained: bool, **kwargs: Unpack[_AlexNetOptions]
) -> AlexNet:
model = AlexNet(**kwargs)
if pretrained:
assert arch in model_urls, (
f"{arch} model do not have a pretrained model now, you should set pretrained=False"
)
weight_path = get_weights_path_from_url(
model_urls[arch][0], model_urls[arch][1]
)
param = paddle.load(weight_path)
model.load_dict(param)
return model
def alexnet(
pretrained: bool = False, **kwargs: Unpack[_AlexNetOptions]
) -> AlexNet:
"""AlexNet model from
`"ImageNet Classification with Deep Convolutional Neural Networks"
<https://proceedings.neurips.cc/paper/2012/file/c399862d3b9d6b76c8436e924a68c45b-Paper.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`AlexNet <api_paddle_vision_AlexNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of AlexNet model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import alexnet
>>> # Build model
>>> model = alexnet()
>>> # Build model and load imagenet pretrained weight
>>> # model = alexnet(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _alexnet('alexnet', pretrained, **kwargs)
+571
View File
@@ -0,0 +1,571 @@
# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import math
from typing import TYPE_CHECKING
from typing_extensions import Unpack
import paddle
from paddle import nn
from paddle.base.param_attr import ParamAttr
from paddle.nn import (
AdaptiveAvgPool2D,
AvgPool2D,
BatchNorm,
Conv2D,
Dropout,
Linear,
MaxPool2D,
)
from paddle.nn.initializer import Uniform
from paddle.utils.download import get_weights_path_from_url
if TYPE_CHECKING:
from typing import Literal, TypedDict
from typing_extensions import NotRequired
from paddle import Tensor
from paddle._typing import Size2
_DenseNetArch = Literal[
"densenet121",
"densenet161",
"densenet169",
"densenet201",
"densenet264",
]
class _DenseNetOptions(TypedDict):
bn_size: NotRequired[int]
dropout: NotRequired[float]
num_classes: NotRequired[int]
with_pool: NotRequired[bool]
__all__ = []
model_urls: dict[str, tuple[str, str]] = {
'densenet121': (
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DenseNet121_pretrained.pdparams',
'db1b239ed80a905290fd8b01d3af08e4',
),
'densenet161': (
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DenseNet161_pretrained.pdparams',
'62158869cb315098bd25ddbfd308a853',
),
'densenet169': (
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DenseNet169_pretrained.pdparams',
'82cc7c635c3f19098c748850efb2d796',
),
'densenet201': (
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DenseNet201_pretrained.pdparams',
'16ca29565a7712329cf9e36e02caaf58',
),
'densenet264': (
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DenseNet264_pretrained.pdparams',
'3270ce516b85370bba88cfdd9f60bff4',
),
}
class BNACConvLayer(nn.Layer):
def __init__(
self,
num_channels: int,
num_filters: int,
filter_size: Size2,
stride: Size2 = 1,
pad: Size2 = 0,
groups: int = 1,
act: str = "relu",
) -> None:
super().__init__()
self._batch_norm = BatchNorm(num_channels, act=act)
self._conv = Conv2D(
in_channels=num_channels,
out_channels=num_filters,
kernel_size=filter_size,
stride=stride,
padding=pad,
groups=groups,
weight_attr=ParamAttr(),
bias_attr=False,
)
def forward(self, input: Tensor) -> Tensor:
y = self._batch_norm(input)
y = self._conv(y)
return y
class DenseLayer(nn.Layer):
dropout: float
def __init__(
self, num_channels: int, growth_rate: int, bn_size: int, dropout: float
) -> None:
super().__init__()
self.dropout = dropout
self.bn_ac_func1 = BNACConvLayer(
num_channels=num_channels,
num_filters=bn_size * growth_rate,
filter_size=1,
pad=0,
stride=1,
)
self.bn_ac_func2 = BNACConvLayer(
num_channels=bn_size * growth_rate,
num_filters=growth_rate,
filter_size=3,
pad=1,
stride=1,
)
if dropout:
self.dropout_func = Dropout(p=dropout, mode="downscale_in_infer")
def forward(self, input: Tensor) -> Tensor:
conv = self.bn_ac_func1(input)
conv = self.bn_ac_func2(conv)
if self.dropout:
conv = self.dropout_func(conv)
conv = paddle.concat([input, conv], axis=1)
return conv
class DenseBlock(nn.Layer):
dropout: float
def __init__(
self,
num_channels: int,
num_layers: int,
bn_size: int,
growth_rate: int,
dropout: float,
name: str | None = None,
) -> None:
super().__init__()
self.dropout = dropout
self.dense_layer_func = []
pre_channel = num_channels
for layer in range(num_layers):
self.dense_layer_func.append(
self.add_sublayer(
f"{name}_{layer + 1}",
DenseLayer(
num_channels=pre_channel,
growth_rate=growth_rate,
bn_size=bn_size,
dropout=dropout,
),
)
)
pre_channel = pre_channel + growth_rate
def forward(self, input: Tensor) -> Tensor:
conv = input
for func in self.dense_layer_func:
conv = func(conv)
return conv
class TransitionLayer(nn.Layer):
def __init__(self, num_channels: int, num_output_features: int) -> None:
super().__init__()
self.conv_ac_func = BNACConvLayer(
num_channels=num_channels,
num_filters=num_output_features,
filter_size=1,
pad=0,
stride=1,
)
self.pool2d_avg = AvgPool2D(kernel_size=2, stride=2, padding=0)
def forward(self, input: Tensor) -> Tensor:
y = self.conv_ac_func(input)
y = self.pool2d_avg(y)
return y
class ConvBNLayer(nn.Layer):
def __init__(
self,
num_channels: int,
num_filters: int,
filter_size: Size2,
stride: Size2 = 1,
pad: Size2 = 0,
groups: int = 1,
act: str = "relu",
) -> None:
super().__init__()
self._conv = Conv2D(
in_channels=num_channels,
out_channels=num_filters,
kernel_size=filter_size,
stride=stride,
padding=pad,
groups=groups,
weight_attr=ParamAttr(),
bias_attr=False,
)
self._batch_norm = BatchNorm(num_filters, act=act)
def forward(self, input: Tensor) -> Tensor:
y = self._conv(input)
y = self._batch_norm(y)
return y
class DenseNet(nn.Layer):
"""DenseNet model from
`"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_.
Args:
layers (int, optional): Layers of DenseNet. Default: 121.
bn_size (int, optional): Expansion of growth rate in the middle layer. Default: 4.
dropout (float, optional): Dropout rate. Default: :math:`0.0`.
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last fc layer or not. Default: True.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of DenseNet model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import DenseNet
>>> # Build model
>>> densenet = DenseNet()
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = densenet(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
num_classes: int
with_pool: bool
def __init__(
self,
layers: int = 121,
bn_size: int = 4,
dropout: float = 0.0,
num_classes: int = 1000,
with_pool: bool = True,
) -> None:
super().__init__()
self.num_classes = num_classes
self.with_pool = with_pool
supported_layers = [121, 161, 169, 201, 264]
assert layers in supported_layers, (
f"supported layers are {supported_layers} but input layer is {layers}"
)
densenet_spec = {
121: (64, 32, [6, 12, 24, 16]),
161: (96, 48, [6, 12, 36, 24]),
169: (64, 32, [6, 12, 32, 32]),
201: (64, 32, [6, 12, 48, 32]),
264: (64, 32, [6, 12, 64, 48]),
}
num_init_features, growth_rate, block_config = densenet_spec[layers]
self.conv1_func = ConvBNLayer(
num_channels=3,
num_filters=num_init_features,
filter_size=7,
stride=2,
pad=3,
act='relu',
)
self.pool2d_max = MaxPool2D(kernel_size=3, stride=2, padding=1)
self.block_config = block_config
self.dense_block_func_list = []
self.transition_func_list = []
pre_num_channels = num_init_features
num_features = num_init_features
for i, num_layers in enumerate(block_config):
self.dense_block_func_list.append(
self.add_sublayer(
f"db_conv_{i + 2}",
DenseBlock(
num_channels=pre_num_channels,
num_layers=num_layers,
bn_size=bn_size,
growth_rate=growth_rate,
dropout=dropout,
name='conv' + str(i + 2),
),
)
)
num_features = num_features + num_layers * growth_rate
pre_num_channels = num_features
if i != len(block_config) - 1:
self.transition_func_list.append(
self.add_sublayer(
f"tr_conv{i + 2}_blk",
TransitionLayer(
num_channels=pre_num_channels,
num_output_features=num_features // 2,
),
)
)
pre_num_channels = num_features // 2
num_features = num_features // 2
self.batch_norm = BatchNorm(num_features, act="relu")
if self.with_pool:
self.pool2d_avg = AdaptiveAvgPool2D(1)
if self.num_classes > 0:
stdv = 1.0 / math.sqrt(num_features * 1.0)
self.out = Linear(
num_features,
num_classes,
weight_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
bias_attr=ParamAttr(),
)
def forward(self, input: Tensor) -> Tensor:
conv = self.conv1_func(input)
conv = self.pool2d_max(conv)
for i, num_layers in enumerate(self.block_config):
conv = self.dense_block_func_list[i](conv)
if i != len(self.block_config) - 1:
conv = self.transition_func_list[i](conv)
conv = self.batch_norm(conv)
if self.with_pool:
y = self.pool2d_avg(conv)
if self.num_classes > 0:
y = paddle.flatten(y, start_axis=1, stop_axis=-1)
y = self.out(y)
return y
def _densenet(
arch: _DenseNetArch,
layers: int,
pretrained: bool,
**kwargs: Unpack[_DenseNetOptions],
) -> DenseNet:
model = DenseNet(layers=layers, **kwargs)
if pretrained:
assert arch in model_urls, (
f"{arch} model do not have a pretrained model now, you should set pretrained=False"
)
weight_path = get_weights_path_from_url(
model_urls[arch][0], model_urls[arch][1]
)
param = paddle.load(weight_path)
model.set_dict(param)
return model
def densenet121(
pretrained: bool = False, **kwargs: Unpack[_DenseNetOptions]
) -> DenseNet:
"""DenseNet 121-layer model from
`"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`DenseNet <api_paddle_vision_models_DenseNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of DenseNet 121-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import densenet121
>>> # Build model
>>> model = densenet121()
>>> # Build model and load imagenet pretrained weight
>>> # model = densenet121(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _densenet('densenet121', 121, pretrained, **kwargs)
def densenet161(
pretrained: bool = False, **kwargs: Unpack[_DenseNetOptions]
) -> DenseNet:
"""DenseNet 161-layer model from
`"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`DenseNet <api_paddle_vision_models_DenseNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of DenseNet 161-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import densenet161
>>> # Build model
>>> model = densenet161()
>>> # Build model and load imagenet pretrained weight
>>> # model = densenet161(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _densenet('densenet161', 161, pretrained, **kwargs)
def densenet169(
pretrained: bool = False, **kwargs: Unpack[_DenseNetOptions]
) -> DenseNet:
"""DenseNet 169-layer model from
`"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`DenseNet <api_paddle_vision_models_DenseNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of DenseNet 169-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import densenet169
>>> # Build model
>>> model = densenet169()
>>> # Build model and load imagenet pretrained weight
>>> # model = densenet169(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _densenet('densenet169', 169, pretrained, **kwargs)
def densenet201(
pretrained: bool = False, **kwargs: Unpack[_DenseNetOptions]
) -> DenseNet:
"""DenseNet 201-layer model from
`"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`DenseNet <api_paddle_vision_models_DenseNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of DenseNet 201-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import densenet201
>>> # Build model
>>> model = densenet201()
>>> # Build model and load imagenet pretrained weight
>>> # model = densenet201(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _densenet('densenet201', 201, pretrained, **kwargs)
def densenet264(
pretrained: bool = False, **kwargs: Unpack[_DenseNetOptions]
) -> DenseNet:
"""DenseNet 264-layer model from
`"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`DenseNet <api_paddle_vision_models_DenseNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of DenseNet 264-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import densenet264
>>> # Build model
>>> model = densenet264()
>>> # Build model and load imagenet pretrained weight
>>> # model = densenet264(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _densenet('densenet264', 264, pretrained, **kwargs)
+303
View File
@@ -0,0 +1,303 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import (
TYPE_CHECKING,
TypedDict,
)
from typing_extensions import NotRequired, Unpack
import paddle
import paddle.nn.functional as F
from paddle import nn
from paddle.base.param_attr import ParamAttr
from paddle.nn import (
AdaptiveAvgPool2D,
AvgPool2D,
Conv2D,
Dropout,
Linear,
MaxPool2D,
)
from paddle.nn.initializer import Uniform
from paddle.utils.download import get_weights_path_from_url
if TYPE_CHECKING:
from paddle import Tensor
from paddle._typing import Size2
class _GoogLeNetOptions(TypedDict):
num_classes: NotRequired[int]
with_pool: NotRequired[bool]
__all__ = []
model_urls = {
"googlenet": (
"https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/GoogLeNet_pretrained.pdparams",
"80c06f038e905c53ab32c40eca6e26ae",
)
}
def xavier(channels: int, filter_size: int) -> ParamAttr:
stdv = (3.0 / (filter_size**2 * channels)) ** 0.5
param_attr = ParamAttr(initializer=Uniform(-stdv, stdv))
return param_attr
class ConvLayer(nn.Layer):
def __init__(
self,
num_channels: int,
num_filters: int,
filter_size: int,
stride: Size2 = 1,
groups: int = 1,
):
super().__init__()
self._conv = Conv2D(
in_channels=num_channels,
out_channels=num_filters,
kernel_size=filter_size,
stride=stride,
padding=(filter_size - 1) // 2,
groups=groups,
bias_attr=False,
)
def forward(self, inputs: Tensor) -> Tensor:
y = self._conv(inputs)
return y
class Inception(nn.Layer):
def __init__(
self,
input_channels: int,
output_channels: int,
filter1: int,
filter3R: int,
filter3: int,
filter5R: int,
filter5: int,
proj: int,
):
super().__init__()
self._conv1 = ConvLayer(input_channels, filter1, 1)
self._conv3r = ConvLayer(input_channels, filter3R, 1)
self._conv3 = ConvLayer(filter3R, filter3, 3)
self._conv5r = ConvLayer(input_channels, filter5R, 1)
self._conv5 = ConvLayer(filter5R, filter5, 5)
self._pool = MaxPool2D(kernel_size=3, stride=1, padding=1)
self._convprj = ConvLayer(input_channels, proj, 1)
def forward(self, inputs: Tensor) -> Tensor:
conv1 = self._conv1(inputs)
conv3r = self._conv3r(inputs)
conv3 = self._conv3(conv3r)
conv5r = self._conv5r(inputs)
conv5 = self._conv5(conv5r)
pool = self._pool(inputs)
convprj = self._convprj(pool)
cat = paddle.concat([conv1, conv3, conv5, convprj], axis=1)
cat = F.relu(cat)
return cat
class GoogLeNet(nn.Layer):
"""GoogLeNet (Inception v1) model architecture from
`"Going Deeper with Convolutions" <https://arxiv.org/pdf/1409.4842.pdf>`_.
Args:
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last fc layer or not. Default: True.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of GoogLeNet (Inception v1) model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import GoogLeNet
>>> # Build model
>>> model = GoogLeNet()
>>> x = paddle.rand([1, 3, 224, 224])
>>> out, out1, out2 = model(x)
>>> print(out.shape, out1.shape, out2.shape)
paddle.Size([1, 1000]) paddle.Size([1, 1000]) paddle.Size([1, 1000])
"""
num_classes: int
with_pool: bool
def __init__(self, num_classes: int = 1000, with_pool: bool = True) -> None:
super().__init__()
self.num_classes = num_classes
self.with_pool = with_pool
self._conv = ConvLayer(3, 64, 7, 2)
self._pool = MaxPool2D(kernel_size=3, stride=2)
self._conv_1 = ConvLayer(64, 64, 1)
self._conv_2 = ConvLayer(64, 192, 3)
self._ince3a = Inception(192, 192, 64, 96, 128, 16, 32, 32)
self._ince3b = Inception(256, 256, 128, 128, 192, 32, 96, 64)
self._ince4a = Inception(480, 480, 192, 96, 208, 16, 48, 64)
self._ince4b = Inception(512, 512, 160, 112, 224, 24, 64, 64)
self._ince4c = Inception(512, 512, 128, 128, 256, 24, 64, 64)
self._ince4d = Inception(512, 512, 112, 144, 288, 32, 64, 64)
self._ince4e = Inception(528, 528, 256, 160, 320, 32, 128, 128)
self._ince5a = Inception(832, 832, 256, 160, 320, 32, 128, 128)
self._ince5b = Inception(832, 832, 384, 192, 384, 48, 128, 128)
if with_pool:
# out
self._pool_5 = AdaptiveAvgPool2D(1)
# out1
self._pool_o1 = AvgPool2D(kernel_size=5, stride=3)
# out2
self._pool_o2 = AvgPool2D(kernel_size=5, stride=3)
if num_classes > 0:
# out
self._drop = Dropout(p=0.4, mode="downscale_in_infer")
self._fc_out = Linear(
1024, num_classes, weight_attr=xavier(1024, 1)
)
# out1
self._conv_o1 = ConvLayer(512, 128, 1)
self._fc_o1 = Linear(1152, 1024, weight_attr=xavier(2048, 1))
self._drop_o1 = Dropout(p=0.7, mode="downscale_in_infer")
self._out1 = Linear(1024, num_classes, weight_attr=xavier(1024, 1))
# out2
self._conv_o2 = ConvLayer(528, 128, 1)
self._fc_o2 = Linear(1152, 1024, weight_attr=xavier(2048, 1))
self._drop_o2 = Dropout(p=0.7, mode="downscale_in_infer")
self._out2 = Linear(1024, num_classes, weight_attr=xavier(1024, 1))
def forward(self, inputs: Tensor) -> tuple[Tensor, Tensor, Tensor]:
x = self._conv(inputs)
x = self._pool(x)
x = self._conv_1(x)
x = self._conv_2(x)
x = self._pool(x)
x = self._ince3a(x)
x = self._ince3b(x)
x = self._pool(x)
ince4a = self._ince4a(x)
x = self._ince4b(ince4a)
x = self._ince4c(x)
ince4d = self._ince4d(x)
x = self._ince4e(ince4d)
x = self._pool(x)
x = self._ince5a(x)
ince5b = self._ince5b(x)
out, out1, out2 = ince5b, ince4a, ince4d
if self.with_pool:
out = self._pool_5(out)
out1 = self._pool_o1(out1)
out2 = self._pool_o2(out2)
if self.num_classes > 0:
out = self._drop(out)
out = paddle.squeeze(out, axis=[2, 3])
out = self._fc_out(out)
out1 = self._conv_o1(out1)
out1 = paddle.flatten(out1, start_axis=1, stop_axis=-1)
out1 = self._fc_o1(out1)
out1 = F.relu(out1)
out1 = self._drop_o1(out1)
out1 = self._out1(out1)
out2 = self._conv_o2(out2)
out2 = paddle.flatten(out2, start_axis=1, stop_axis=-1)
out2 = self._fc_o2(out2)
out2 = self._drop_o2(out2)
out2 = self._out2(out2)
return out, out1, out2
def googlenet(
pretrained: bool = False, **kwargs: Unpack[_GoogLeNetOptions]
) -> GoogLeNet:
"""GoogLeNet (Inception v1) model architecture from
`"Going Deeper with Convolutions" <https://arxiv.org/pdf/1409.4842.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`GoogLeNet <api_paddle_vision_models_GoogLeNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of GoogLeNet (Inception v1) model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import googlenet
>>> # Build model
>>> model = googlenet()
>>> # Build model and load imagenet pretrained weight
>>> # model = googlenet(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out, out1, out2 = model(x)
>>> print(out.shape, out1.shape, out2.shape)
paddle.Size([1, 1000]) paddle.Size([1, 1000]) paddle.Size([1, 1000])
"""
model = GoogLeNet(**kwargs)
arch = "googlenet"
if pretrained:
assert arch in model_urls, (
f"{arch} model do not have a pretrained model now, you should set pretrained=False"
)
weight_path = get_weights_path_from_url(
model_urls[arch][0], model_urls[arch][1]
)
param = paddle.load(weight_path)
model.set_dict(param)
return model
+654
View File
@@ -0,0 +1,654 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import math
from typing import (
TYPE_CHECKING,
TypedDict,
)
from typing_extensions import (
NotRequired,
Unpack,
)
import paddle
from paddle import nn
from paddle.base.param_attr import ParamAttr
from paddle.nn import AdaptiveAvgPool2D, AvgPool2D, Dropout, Linear, MaxPool2D
from paddle.nn.initializer import Uniform
from paddle.utils.download import get_weights_path_from_url
from ..ops import ConvNormActivation
if TYPE_CHECKING:
from paddle import Tensor
class _InceptionV3Options(TypedDict):
num_classes: NotRequired[int]
with_pool: NotRequired[bool]
__all__ = []
model_urls = {
"inception_v3": (
"https://paddle-hapi.bj.bcebos.com/models/inception_v3.pdparams",
"649a4547c3243e8b59c656f41fe330b8",
)
}
class InceptionStem(nn.Layer):
def __init__(self) -> None:
super().__init__()
self.conv_1a_3x3 = ConvNormActivation(
in_channels=3,
out_channels=32,
kernel_size=3,
stride=2,
padding=0,
activation_layer=nn.ReLU,
)
self.conv_2a_3x3 = ConvNormActivation(
in_channels=32,
out_channels=32,
kernel_size=3,
stride=1,
padding=0,
activation_layer=nn.ReLU,
)
self.conv_2b_3x3 = ConvNormActivation(
in_channels=32,
out_channels=64,
kernel_size=3,
padding=1,
activation_layer=nn.ReLU,
)
self.max_pool = MaxPool2D(kernel_size=3, stride=2, padding=0)
self.conv_3b_1x1 = ConvNormActivation(
in_channels=64,
out_channels=80,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.conv_4a_3x3 = ConvNormActivation(
in_channels=80,
out_channels=192,
kernel_size=3,
padding=0,
activation_layer=nn.ReLU,
)
def forward(self, x: Tensor) -> Tensor:
x = self.conv_1a_3x3(x)
x = self.conv_2a_3x3(x)
x = self.conv_2b_3x3(x)
x = self.max_pool(x)
x = self.conv_3b_1x1(x)
x = self.conv_4a_3x3(x)
x = self.max_pool(x)
return x
class InceptionA(nn.Layer):
def __init__(self, num_channels: int, pool_features: int) -> None:
super().__init__()
self.branch1x1 = ConvNormActivation(
in_channels=num_channels,
out_channels=64,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch5x5_1 = ConvNormActivation(
in_channels=num_channels,
out_channels=48,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch5x5_2 = ConvNormActivation(
in_channels=48,
out_channels=64,
kernel_size=5,
padding=2,
activation_layer=nn.ReLU,
)
self.branch3x3dbl_1 = ConvNormActivation(
in_channels=num_channels,
out_channels=64,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch3x3dbl_2 = ConvNormActivation(
in_channels=64,
out_channels=96,
kernel_size=3,
padding=1,
activation_layer=nn.ReLU,
)
self.branch3x3dbl_3 = ConvNormActivation(
in_channels=96,
out_channels=96,
kernel_size=3,
padding=1,
activation_layer=nn.ReLU,
)
self.branch_pool = AvgPool2D(
kernel_size=3, stride=1, padding=1, exclusive=False
)
self.branch_pool_conv = ConvNormActivation(
in_channels=num_channels,
out_channels=pool_features,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
def forward(self, x: Tensor) -> Tensor:
branch1x1 = self.branch1x1(x)
branch5x5 = self.branch5x5_1(x)
branch5x5 = self.branch5x5_2(branch5x5)
branch3x3dbl = self.branch3x3dbl_1(x)
branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl)
branch3x3dbl = self.branch3x3dbl_3(branch3x3dbl)
branch_pool = self.branch_pool(x)
branch_pool = self.branch_pool_conv(branch_pool)
x = paddle.concat(
[branch1x1, branch5x5, branch3x3dbl, branch_pool], axis=1
)
return x
class InceptionB(nn.Layer):
def __init__(self, num_channels: int) -> None:
super().__init__()
self.branch3x3 = ConvNormActivation(
in_channels=num_channels,
out_channels=384,
kernel_size=3,
stride=2,
padding=0,
activation_layer=nn.ReLU,
)
self.branch3x3dbl_1 = ConvNormActivation(
in_channels=num_channels,
out_channels=64,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch3x3dbl_2 = ConvNormActivation(
in_channels=64,
out_channels=96,
kernel_size=3,
padding=1,
activation_layer=nn.ReLU,
)
self.branch3x3dbl_3 = ConvNormActivation(
in_channels=96,
out_channels=96,
kernel_size=3,
stride=2,
padding=0,
activation_layer=nn.ReLU,
)
self.branch_pool = MaxPool2D(kernel_size=3, stride=2)
def forward(self, x: Tensor) -> Tensor:
branch3x3 = self.branch3x3(x)
branch3x3dbl = self.branch3x3dbl_1(x)
branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl)
branch3x3dbl = self.branch3x3dbl_3(branch3x3dbl)
branch_pool = self.branch_pool(x)
x = paddle.concat([branch3x3, branch3x3dbl, branch_pool], axis=1)
return x
class InceptionC(nn.Layer):
def __init__(self, num_channels: int, channels_7x7: int) -> None:
super().__init__()
self.branch1x1 = ConvNormActivation(
in_channels=num_channels,
out_channels=192,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch7x7_1 = ConvNormActivation(
in_channels=num_channels,
out_channels=channels_7x7,
kernel_size=1,
stride=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch7x7_2 = ConvNormActivation(
in_channels=channels_7x7,
out_channels=channels_7x7,
kernel_size=(1, 7),
stride=1,
padding=(0, 3),
activation_layer=nn.ReLU,
)
self.branch7x7_3 = ConvNormActivation(
in_channels=channels_7x7,
out_channels=192,
kernel_size=(7, 1),
stride=1,
padding=(3, 0),
activation_layer=nn.ReLU,
)
self.branch7x7dbl_1 = ConvNormActivation(
in_channels=num_channels,
out_channels=channels_7x7,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch7x7dbl_2 = ConvNormActivation(
in_channels=channels_7x7,
out_channels=channels_7x7,
kernel_size=(7, 1),
padding=(3, 0),
activation_layer=nn.ReLU,
)
self.branch7x7dbl_3 = ConvNormActivation(
in_channels=channels_7x7,
out_channels=channels_7x7,
kernel_size=(1, 7),
padding=(0, 3),
activation_layer=nn.ReLU,
)
self.branch7x7dbl_4 = ConvNormActivation(
in_channels=channels_7x7,
out_channels=channels_7x7,
kernel_size=(7, 1),
padding=(3, 0),
activation_layer=nn.ReLU,
)
self.branch7x7dbl_5 = ConvNormActivation(
in_channels=channels_7x7,
out_channels=192,
kernel_size=(1, 7),
padding=(0, 3),
activation_layer=nn.ReLU,
)
self.branch_pool = AvgPool2D(
kernel_size=3, stride=1, padding=1, exclusive=False
)
self.branch_pool_conv = ConvNormActivation(
in_channels=num_channels,
out_channels=192,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
def forward(self, x: Tensor) -> Tensor:
branch1x1 = self.branch1x1(x)
branch7x7 = self.branch7x7_1(x)
branch7x7 = self.branch7x7_2(branch7x7)
branch7x7 = self.branch7x7_3(branch7x7)
branch7x7dbl = self.branch7x7dbl_1(x)
branch7x7dbl = self.branch7x7dbl_2(branch7x7dbl)
branch7x7dbl = self.branch7x7dbl_3(branch7x7dbl)
branch7x7dbl = self.branch7x7dbl_4(branch7x7dbl)
branch7x7dbl = self.branch7x7dbl_5(branch7x7dbl)
branch_pool = self.branch_pool(x)
branch_pool = self.branch_pool_conv(branch_pool)
x = paddle.concat(
[branch1x1, branch7x7, branch7x7dbl, branch_pool], axis=1
)
return x
class InceptionD(nn.Layer):
def __init__(self, num_channels: int) -> None:
super().__init__()
self.branch3x3_1 = ConvNormActivation(
in_channels=num_channels,
out_channels=192,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch3x3_2 = ConvNormActivation(
in_channels=192,
out_channels=320,
kernel_size=3,
stride=2,
padding=0,
activation_layer=nn.ReLU,
)
self.branch7x7x3_1 = ConvNormActivation(
in_channels=num_channels,
out_channels=192,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch7x7x3_2 = ConvNormActivation(
in_channels=192,
out_channels=192,
kernel_size=(1, 7),
padding=(0, 3),
activation_layer=nn.ReLU,
)
self.branch7x7x3_3 = ConvNormActivation(
in_channels=192,
out_channels=192,
kernel_size=(7, 1),
padding=(3, 0),
activation_layer=nn.ReLU,
)
self.branch7x7x3_4 = ConvNormActivation(
in_channels=192,
out_channels=192,
kernel_size=3,
stride=2,
padding=0,
activation_layer=nn.ReLU,
)
self.branch_pool = MaxPool2D(kernel_size=3, stride=2)
def forward(self, x: Tensor) -> Tensor:
branch3x3 = self.branch3x3_1(x)
branch3x3 = self.branch3x3_2(branch3x3)
branch7x7x3 = self.branch7x7x3_1(x)
branch7x7x3 = self.branch7x7x3_2(branch7x7x3)
branch7x7x3 = self.branch7x7x3_3(branch7x7x3)
branch7x7x3 = self.branch7x7x3_4(branch7x7x3)
branch_pool = self.branch_pool(x)
x = paddle.concat([branch3x3, branch7x7x3, branch_pool], axis=1)
return x
class InceptionE(nn.Layer):
def __init__(self, num_channels: int) -> None:
super().__init__()
self.branch1x1 = ConvNormActivation(
in_channels=num_channels,
out_channels=320,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch3x3_1 = ConvNormActivation(
in_channels=num_channels,
out_channels=384,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch3x3_2a = ConvNormActivation(
in_channels=384,
out_channels=384,
kernel_size=(1, 3),
padding=(0, 1),
activation_layer=nn.ReLU,
)
self.branch3x3_2b = ConvNormActivation(
in_channels=384,
out_channels=384,
kernel_size=(3, 1),
padding=(1, 0),
activation_layer=nn.ReLU,
)
self.branch3x3dbl_1 = ConvNormActivation(
in_channels=num_channels,
out_channels=448,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
self.branch3x3dbl_2 = ConvNormActivation(
in_channels=448,
out_channels=384,
kernel_size=3,
padding=1,
activation_layer=nn.ReLU,
)
self.branch3x3dbl_3a = ConvNormActivation(
in_channels=384,
out_channels=384,
kernel_size=(1, 3),
padding=(0, 1),
activation_layer=nn.ReLU,
)
self.branch3x3dbl_3b = ConvNormActivation(
in_channels=384,
out_channels=384,
kernel_size=(3, 1),
padding=(1, 0),
activation_layer=nn.ReLU,
)
self.branch_pool = AvgPool2D(
kernel_size=3, stride=1, padding=1, exclusive=False
)
self.branch_pool_conv = ConvNormActivation(
in_channels=num_channels,
out_channels=192,
kernel_size=1,
padding=0,
activation_layer=nn.ReLU,
)
def forward(self, x: Tensor) -> Tensor:
branch1x1 = self.branch1x1(x)
branch3x3 = self.branch3x3_1(x)
branch3x3 = [
self.branch3x3_2a(branch3x3),
self.branch3x3_2b(branch3x3),
]
branch3x3 = paddle.concat(branch3x3, axis=1)
branch3x3dbl = self.branch3x3dbl_1(x)
branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl)
branch3x3dbl = [
self.branch3x3dbl_3a(branch3x3dbl),
self.branch3x3dbl_3b(branch3x3dbl),
]
branch3x3dbl = paddle.concat(branch3x3dbl, axis=1)
branch_pool = self.branch_pool(x)
branch_pool = self.branch_pool_conv(branch_pool)
x = paddle.concat(
[branch1x1, branch3x3, branch3x3dbl, branch_pool], axis=1
)
return x
class InceptionV3(nn.Layer):
"""Inception v3 model from
`"Rethinking the Inception Architecture for Computer Vision" <https://arxiv.org/pdf/1512.00567.pdf>`_.
Args:
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last fc layer or not. Default: True.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of Inception v3 model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import InceptionV3
>>> inception_v3 = InceptionV3()
>>> x = paddle.rand([1, 3, 299, 299])
>>> out = inception_v3(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
num_classes: int
with_pool: bool
def __init__(self, num_classes: int = 1000, with_pool: bool = True) -> None:
super().__init__()
self.num_classes = num_classes
self.with_pool = with_pool
self.layers_config = {
"inception_a": [[192, 256, 288], [32, 64, 64]],
"inception_b": [288],
"inception_c": [[768, 768, 768, 768], [128, 160, 160, 192]],
"inception_d": [768],
"inception_e": [1280, 2048],
}
inception_a_list = self.layers_config["inception_a"]
inception_c_list = self.layers_config["inception_c"]
inception_b_list = self.layers_config["inception_b"]
inception_d_list = self.layers_config["inception_d"]
inception_e_list = self.layers_config["inception_e"]
self.inception_stem = InceptionStem()
self.inception_block_list = nn.LayerList()
for i in range(len(inception_a_list[0])):
inception_a = InceptionA(
inception_a_list[0][i], inception_a_list[1][i]
)
self.inception_block_list.append(inception_a)
for i in range(len(inception_b_list)):
inception_b = InceptionB(inception_b_list[i])
self.inception_block_list.append(inception_b)
for i in range(len(inception_c_list[0])):
inception_c = InceptionC(
inception_c_list[0][i], inception_c_list[1][i]
)
self.inception_block_list.append(inception_c)
for i in range(len(inception_d_list)):
inception_d = InceptionD(inception_d_list[i])
self.inception_block_list.append(inception_d)
for i in range(len(inception_e_list)):
inception_e = InceptionE(inception_e_list[i])
self.inception_block_list.append(inception_e)
if with_pool:
self.avg_pool = AdaptiveAvgPool2D(1)
if num_classes > 0:
self.dropout = Dropout(p=0.2, mode="downscale_in_infer")
stdv = 1.0 / math.sqrt(2048 * 1.0)
self.fc = Linear(
2048,
num_classes,
weight_attr=ParamAttr(initializer=Uniform(-stdv, stdv)),
bias_attr=ParamAttr(),
)
def forward(self, x: Tensor) -> Tensor:
x = self.inception_stem(x)
for inception_block in self.inception_block_list:
x = inception_block(x)
if self.with_pool:
x = self.avg_pool(x)
if self.num_classes > 0:
x = paddle.reshape(x, shape=[-1, 2048])
x = self.dropout(x)
x = self.fc(x)
return x
def inception_v3(
pretrained: bool = False, **kwargs: Unpack[_InceptionV3Options]
) -> InceptionV3:
"""Inception v3 model from
`"Rethinking the Inception Architecture for Computer Vision" <https://arxiv.org/pdf/1512.00567.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`InceptionV3 <api_paddle_vision_models_InceptionV3>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of Inception v3 model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import inception_v3
>>> # Build model
>>> model = inception_v3()
>>> # Build model and load imagenet pretrained weight
>>> # model = inception_v3(pretrained=True)
>>> x = paddle.rand([1, 3, 299, 299])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
model = InceptionV3(**kwargs)
arch = "inception_v3"
if pretrained:
assert arch in model_urls, (
f"{arch} model do not have a pretrained model now, you should set pretrained=False"
)
weight_path = get_weights_path_from_url(
model_urls[arch][0], model_urls[arch][1]
)
param = paddle.load(weight_path)
model.set_dict(param)
return model
+83
View File
@@ -0,0 +1,83 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import (
TYPE_CHECKING,
)
import paddle
from paddle import nn
if TYPE_CHECKING:
from paddle import Tensor
__all__ = []
class LeNet(nn.Layer):
"""LeNet model from
`"Gradient-based learning applied to document recognition" <https://ieeexplore.ieee.org/document/726791>`_.
Args:
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 10.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of LeNet model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import LeNet
>>> model = LeNet()
>>> x = paddle.rand([1, 1, 28, 28])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 10])
"""
num_classes: int
def __init__(self, num_classes: int = 10) -> None:
super().__init__()
self.num_classes = num_classes
self.features = nn.Sequential(
nn.Conv2D(1, 6, 3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2D(2, 2),
nn.Conv2D(6, 16, 5, stride=1, padding=0),
nn.ReLU(),
nn.MaxPool2D(2, 2),
)
if num_classes > 0:
self.fc = nn.Sequential(
nn.Linear(400, 120),
nn.Linear(120, 84),
nn.Linear(84, num_classes),
)
def forward(self, inputs: Tensor) -> Tensor:
x = self.features(inputs)
if self.num_classes > 0:
x = paddle.flatten(x, 1)
x = self.fc(x)
return x
+334
View File
@@ -0,0 +1,334 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import (
TYPE_CHECKING,
TypedDict,
)
from typing_extensions import NotRequired, Unpack
import paddle
from paddle import nn
from paddle.utils.download import get_weights_path_from_url
from ..ops import ConvNormActivation
if TYPE_CHECKING:
from paddle import Tensor
from paddle._typing import Size2
class _MobileNetV1Options(TypedDict):
num_classes: NotRequired[int]
with_pool: NotRequired[bool]
__all__ = []
model_urls = {
'mobilenetv1_1.0': (
'https://paddle-hapi.bj.bcebos.com/models/mobilenetv1_1.0.pdparams',
'3033ab1975b1670bef51545feb65fc45',
)
}
class DepthwiseSeparable(nn.Layer):
def __init__(
self,
in_channels: int,
out_channels1: int,
out_channels2: int,
num_groups: int,
stride: Size2,
scale: float,
) -> None:
super().__init__()
self._depthwise_conv = ConvNormActivation(
in_channels,
int(out_channels1 * scale),
kernel_size=3,
stride=stride,
padding=1,
groups=int(num_groups * scale),
)
self._pointwise_conv = ConvNormActivation(
int(out_channels1 * scale),
int(out_channels2 * scale),
kernel_size=1,
stride=1,
padding=0,
)
def forward(self, x: Tensor) -> Tensor:
x = self._depthwise_conv(x)
x = self._pointwise_conv(x)
return x
class MobileNetV1(nn.Layer):
"""MobileNetV1 model from
`"MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications" <https://arxiv.org/abs/1704.04861>`_.
Args:
scale (float, optional): Scale of channels in each layer. Default: 1.0.
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last fc layer or not. Default: True.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of MobileNetV1 model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import MobileNetV1
>>> model = MobileNetV1()
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
scale: float
num_classes: int
with_pool: bool
def __init__(
self,
scale: float = 1.0,
num_classes: int = 1000,
with_pool: bool = True,
) -> None:
super().__init__()
self.scale = scale
self.dwsl = []
self.num_classes = num_classes
self.with_pool = with_pool
self.conv1 = ConvNormActivation(
in_channels=3,
out_channels=int(32 * scale),
kernel_size=3,
stride=2,
padding=1,
)
dws21 = self.add_sublayer(
sublayer=DepthwiseSeparable(
in_channels=int(32 * scale),
out_channels1=32,
out_channels2=64,
num_groups=32,
stride=1,
scale=scale,
),
name="conv2_1",
)
self.dwsl.append(dws21)
dws22 = self.add_sublayer(
sublayer=DepthwiseSeparable(
in_channels=int(64 * scale),
out_channels1=64,
out_channels2=128,
num_groups=64,
stride=2,
scale=scale,
),
name="conv2_2",
)
self.dwsl.append(dws22)
dws31 = self.add_sublayer(
sublayer=DepthwiseSeparable(
in_channels=int(128 * scale),
out_channels1=128,
out_channels2=128,
num_groups=128,
stride=1,
scale=scale,
),
name="conv3_1",
)
self.dwsl.append(dws31)
dws32 = self.add_sublayer(
sublayer=DepthwiseSeparable(
in_channels=int(128 * scale),
out_channels1=128,
out_channels2=256,
num_groups=128,
stride=2,
scale=scale,
),
name="conv3_2",
)
self.dwsl.append(dws32)
dws41 = self.add_sublayer(
sublayer=DepthwiseSeparable(
in_channels=int(256 * scale),
out_channels1=256,
out_channels2=256,
num_groups=256,
stride=1,
scale=scale,
),
name="conv4_1",
)
self.dwsl.append(dws41)
dws42 = self.add_sublayer(
sublayer=DepthwiseSeparable(
in_channels=int(256 * scale),
out_channels1=256,
out_channels2=512,
num_groups=256,
stride=2,
scale=scale,
),
name="conv4_2",
)
self.dwsl.append(dws42)
for i in range(5):
tmp = self.add_sublayer(
sublayer=DepthwiseSeparable(
in_channels=int(512 * scale),
out_channels1=512,
out_channels2=512,
num_groups=512,
stride=1,
scale=scale,
),
name="conv5_" + str(i + 1),
)
self.dwsl.append(tmp)
dws56 = self.add_sublayer(
sublayer=DepthwiseSeparable(
in_channels=int(512 * scale),
out_channels1=512,
out_channels2=1024,
num_groups=512,
stride=2,
scale=scale,
),
name="conv5_6",
)
self.dwsl.append(dws56)
dws6 = self.add_sublayer(
sublayer=DepthwiseSeparable(
in_channels=int(1024 * scale),
out_channels1=1024,
out_channels2=1024,
num_groups=1024,
stride=1,
scale=scale,
),
name="conv6",
)
self.dwsl.append(dws6)
if with_pool:
self.pool2d_avg = nn.AdaptiveAvgPool2D(1)
if num_classes > 0:
self.fc = nn.Linear(int(1024 * scale), num_classes)
def forward(self, x: Tensor) -> Tensor:
x = self.conv1(x)
for dws in self.dwsl:
x = dws(x)
if self.with_pool:
x = self.pool2d_avg(x)
if self.num_classes > 0:
x = paddle.flatten(x, 1)
x = self.fc(x)
return x
def _mobilenet(
arch: str, pretrained: bool = False, **kwargs: Unpack[_MobileNetV1Options]
) -> MobileNetV1:
model = MobileNetV1(**kwargs)
if pretrained:
assert arch in model_urls, (
f"{arch} model do not have a pretrained model now, you should set pretrained=False"
)
weight_path = get_weights_path_from_url(
model_urls[arch][0], model_urls[arch][1]
)
param = paddle.load(weight_path)
model.load_dict(param)
return model
def mobilenet_v1(
pretrained: bool = False,
scale: float = 1.0,
**kwargs: Unpack[_MobileNetV1Options],
) -> MobileNetV1:
"""MobileNetV1 from
`"MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications" <https://arxiv.org/abs/1704.04861>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
scale (float, optional): Scale of channels in each layer. Default: 1.0.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`MobileNetV1 <api_paddle_vision_models_MobileNetV1>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of MobileNetV1 model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import mobilenet_v1
>>> # Build model
>>> model = mobilenet_v1()
>>> # Build model and load imagenet pretrained weight
>>> # model = mobilenet_v1(pretrained=True)
>>> # build mobilenet v1 with scale=0.5
>>> model_scale = mobilenet_v1(scale=0.5)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
model = _mobilenet(
'mobilenetv1_' + str(scale), pretrained, scale=scale, **kwargs
)
return model
+276
View File
@@ -0,0 +1,276 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import (
TYPE_CHECKING,
TypedDict,
)
from typing_extensions import NotRequired, Unpack
import paddle
from paddle import nn
from paddle.utils.download import get_weights_path_from_url
from ..ops import ConvNormActivation
from ._utils import _make_divisible
if TYPE_CHECKING:
from collections.abc import Callable
from paddle import Tensor
class _MobileNetV2Options(TypedDict):
num_classes: NotRequired[int]
with_pool: NotRequired[bool]
__all__ = []
model_urls = {
'mobilenetv2_1.0': (
'https://paddle-hapi.bj.bcebos.com/models/mobilenet_v2_x1.0.pdparams',
'0340af0a901346c8d46f4529882fb63d',
)
}
class InvertedResidual(nn.Layer):
def __init__(
self,
inp: int,
oup: int,
stride: int,
expand_ratio: float,
norm_layer: Callable[..., nn.Layer] = nn.BatchNorm2D,
) -> None:
super().__init__()
self.stride = stride
assert stride in [1, 2]
hidden_dim = int(round(inp * expand_ratio))
self.use_res_connect = self.stride == 1 and inp == oup
layers = []
if expand_ratio != 1:
layers.append(
ConvNormActivation(
inp,
hidden_dim,
kernel_size=1,
norm_layer=norm_layer,
activation_layer=nn.ReLU6,
)
)
layers.extend(
[
ConvNormActivation(
hidden_dim,
hidden_dim,
stride=stride,
groups=hidden_dim,
norm_layer=norm_layer,
activation_layer=nn.ReLU6,
),
nn.Conv2D(hidden_dim, oup, 1, 1, 0, bias_attr=False),
norm_layer(oup),
]
)
self.conv = nn.Sequential(*layers)
def forward(self, x: Tensor) -> Tensor:
if self.use_res_connect:
return x + self.conv(x)
else:
return self.conv(x)
class MobileNetV2(nn.Layer):
"""MobileNetV2 model from
`"MobileNetV2: Inverted Residuals and Linear Bottlenecks" <https://arxiv.org/abs/1801.04381>`_.
Args:
scale (float, optional): Scale of channels in each layer. Default: 1.0.
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last fc layer or not. Default: True.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of MobileNetV2 model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import MobileNetV2
>>> model = MobileNetV2()
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
num_classes: int
with_pool: bool
def __init__(
self,
scale: float = 1.0,
num_classes: int = 1000,
with_pool: bool = True,
) -> None:
super().__init__()
self.num_classes = num_classes
self.with_pool = with_pool
input_channel = 32
last_channel = 1280
block = InvertedResidual
round_nearest = 8
norm_layer = nn.BatchNorm2D
inverted_residual_setting = [
[1, 16, 1, 1],
[6, 24, 2, 2],
[6, 32, 3, 2],
[6, 64, 4, 2],
[6, 96, 3, 1],
[6, 160, 3, 2],
[6, 320, 1, 1],
]
input_channel = _make_divisible(input_channel * scale, round_nearest)
self.last_channel = _make_divisible(
last_channel * max(1.0, scale), round_nearest
)
features = [
ConvNormActivation(
3,
input_channel,
stride=2,
norm_layer=norm_layer,
activation_layer=nn.ReLU6,
)
]
for t, c, n, s in inverted_residual_setting:
output_channel = _make_divisible(c * scale, round_nearest)
for i in range(n):
stride = s if i == 0 else 1
features.append(
block(
input_channel,
output_channel,
stride,
expand_ratio=t,
norm_layer=norm_layer,
)
)
input_channel = output_channel
features.append(
ConvNormActivation(
input_channel,
self.last_channel,
kernel_size=1,
norm_layer=norm_layer,
activation_layer=nn.ReLU6,
)
)
self.features = nn.Sequential(*features)
if with_pool:
self.pool2d_avg = nn.AdaptiveAvgPool2D(1)
if self.num_classes > 0:
self.classifier = nn.Sequential(
nn.Dropout(0.2), nn.Linear(self.last_channel, num_classes)
)
def forward(self, x: Tensor) -> Tensor:
x = self.features(x)
if self.with_pool:
x = self.pool2d_avg(x)
if self.num_classes > 0:
x = paddle.flatten(x, 1)
x = self.classifier(x)
return x
def _mobilenet(
arch: str, pretrained: bool = False, **kwargs: Unpack[_MobileNetV2Options]
) -> MobileNetV2:
model = MobileNetV2(**kwargs)
if pretrained:
assert arch in model_urls, (
f"{arch} model do not have a pretrained model now, you should set pretrained=False"
)
weight_path = get_weights_path_from_url(
model_urls[arch][0], model_urls[arch][1]
)
param = paddle.load(weight_path)
model.load_dict(param)
return model
def mobilenet_v2(
pretrained: bool = False,
scale: float = 1.0,
**kwargs: Unpack[_MobileNetV2Options],
) -> MobileNetV2:
"""MobileNetV2 from
`"MobileNetV2: Inverted Residuals and Linear Bottlenecks" <https://arxiv.org/abs/1801.04381>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained on ImageNet. Default: False.
scale (float, optional): Scale of channels in each layer. Default: 1.0.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`MobileNetV2 <api_paddle_vision_models_MobileNetV2>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of MobileNetV2 model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import mobilenet_v2
>>> # Build model
>>> model = mobilenet_v2()
>>> # Build model and load imagenet pretrained weight
>>> # model = mobilenet_v2(pretrained=True)
>>> # Build mobilenet v2 with scale=0.5
>>> model = mobilenet_v2(scale=0.5)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
model = _mobilenet(
'mobilenetv2_' + str(scale), pretrained, scale=scale, **kwargs
)
return model
+547
View File
@@ -0,0 +1,547 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from functools import partial
from typing import (
TYPE_CHECKING,
TypedDict,
)
from typing_extensions import NotRequired, Unpack
import paddle
from paddle import nn
from paddle.utils.download import get_weights_path_from_url
from ..ops import ConvNormActivation
from ._utils import _make_divisible
if TYPE_CHECKING:
from collections.abc import Callable
from paddle import Tensor
class _MobileNetV3Options(TypedDict):
num_classes: NotRequired[int]
with_pool: NotRequired[bool]
__all__ = []
model_urls = {
"mobilenet_v3_small_x1.0": (
"https://paddle-hapi.bj.bcebos.com/models/mobilenet_v3_small_x1.0.pdparams",
"34fe0e7c1f8b00b2b056ad6788d0590c",
),
"mobilenet_v3_large_x1.0": (
"https://paddle-hapi.bj.bcebos.com/models/mobilenet_v3_large_x1.0.pdparams",
"118db5792b4e183b925d8e8e334db3df",
),
}
class SqueezeExcitation(nn.Layer):
"""
This block implements the Squeeze-and-Excitation block from https://arxiv.org/abs/1709.01507 (see Fig. 1).
Parameters ``activation``, and ``scale_activation`` correspond to ``delta`` and ``sigma`` in eq. 3.
This code is based on the torchvision code with modifications.
You can also see at https://github.com/pytorch/vision/blob/main/torchvision/ops/misc.py#L127
Args:
input_channels (int): Number of channels in the input image.
squeeze_channels (int): Number of squeeze channels.
activation (Callable[..., paddle.nn.Layer], optional): ``delta`` activation. Default: ``paddle.nn.ReLU``.
scale_activation (Callable[..., paddle.nn.Layer]): ``sigma`` activation. Default: ``paddle.nn.Sigmoid``.
"""
def __init__(
self,
input_channels: int,
squeeze_channels: int,
activation: Callable[..., nn.Layer] = nn.ReLU,
scale_activation: Callable[..., nn.Layer] = nn.Sigmoid,
) -> None:
super().__init__()
self.avgpool = nn.AdaptiveAvgPool2D(1)
self.fc1 = nn.Conv2D(input_channels, squeeze_channels, 1)
self.fc2 = nn.Conv2D(squeeze_channels, input_channels, 1)
self.activation = activation()
self.scale_activation = scale_activation()
def _scale(self, input: Tensor) -> Tensor:
scale = self.avgpool(input)
scale = self.fc1(scale)
scale = self.activation(scale)
scale = self.fc2(scale)
return self.scale_activation(scale)
def forward(self, input: Tensor) -> Tensor:
scale = self._scale(input)
return scale * input
class InvertedResidualConfig:
def __init__(
self,
in_channels: int,
kernel: int,
expanded_channels: int,
out_channels: int,
use_se: bool,
activation: str,
stride: int,
scale: float = 1.0,
):
self.in_channels = self.adjust_channels(in_channels, scale=scale)
self.kernel = kernel
self.expanded_channels = self.adjust_channels(
expanded_channels, scale=scale
)
self.out_channels = self.adjust_channels(out_channels, scale=scale)
self.use_se = use_se
if activation is None:
self.activation_layer = None
elif activation == "relu":
self.activation_layer = nn.ReLU
elif activation == "hardswish":
self.activation_layer = nn.Hardswish
else:
raise RuntimeError(
f"The activation function is not supported: {activation}"
)
self.stride = stride
@staticmethod
def adjust_channels(channels, scale=1.0):
return _make_divisible(channels * scale, 8)
class InvertedResidual(nn.Layer):
def __init__(
self,
in_channels: int,
expanded_channels: int,
out_channels: int,
filter_size: int,
stride: int,
use_se: bool,
activation_layer: Callable[..., nn.Layer],
norm_layer: Callable[..., nn.Layer],
) -> None:
super().__init__()
self.use_res_connect = stride == 1 and in_channels == out_channels
self.use_se = use_se
self.expand = in_channels != expanded_channels
if self.expand:
self.expand_conv = ConvNormActivation(
in_channels=in_channels,
out_channels=expanded_channels,
kernel_size=1,
stride=1,
padding=0,
norm_layer=norm_layer,
activation_layer=activation_layer,
)
self.bottleneck_conv = ConvNormActivation(
in_channels=expanded_channels,
out_channels=expanded_channels,
kernel_size=filter_size,
stride=stride,
padding=int((filter_size - 1) // 2),
groups=expanded_channels,
norm_layer=norm_layer,
activation_layer=activation_layer,
)
if self.use_se:
self.mid_se = SqueezeExcitation(
expanded_channels,
_make_divisible(expanded_channels // 4),
scale_activation=nn.Hardsigmoid,
)
self.linear_conv = ConvNormActivation(
in_channels=expanded_channels,
out_channels=out_channels,
kernel_size=1,
stride=1,
padding=0,
norm_layer=norm_layer,
activation_layer=None,
)
def forward(self, x: Tensor) -> Tensor:
identity = x
if self.expand:
x = self.expand_conv(x)
x = self.bottleneck_conv(x)
if self.use_se:
x = self.mid_se(x)
x = self.linear_conv(x)
if self.use_res_connect:
x = paddle.add(identity, x)
return x
class MobileNetV3(nn.Layer):
"""MobileNetV3 model from
`"Searching for MobileNetV3" <https://arxiv.org/abs/1905.02244>`_.
Args:
config (list[InvertedResidualConfig]): MobileNetV3 depthwise blocks config.
last_channel (int): The number of channels on the penultimate layer.
scale (float, optional): Scale of channels in each layer. Default: 1.0.
num_classes (int, optional): Output dim of last fc layer. If num_classes <=0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last fc layer or not. Default: True.
"""
scale: float
num_classes: int
with_pool: bool
def __init__(
self,
config: list[InvertedResidualConfig],
last_channel: int,
scale: float = 1.0,
num_classes: int = 1000,
with_pool: bool = True,
) -> None:
super().__init__()
self.config = config
self.scale = scale
self.last_channel = last_channel
self.num_classes = num_classes
self.with_pool = with_pool
self.firstconv_in_channels = config[0].in_channels
self.lastconv_in_channels = config[-1].in_channels
self.lastconv_out_channels = self.lastconv_in_channels * 6
norm_layer = partial(nn.BatchNorm2D, epsilon=0.001, momentum=0.99)
self.conv = ConvNormActivation(
in_channels=3,
out_channels=self.firstconv_in_channels,
kernel_size=3,
stride=2,
padding=1,
groups=1,
activation_layer=nn.Hardswish,
norm_layer=norm_layer,
)
self.blocks = nn.Sequential(
*[
InvertedResidual(
in_channels=cfg.in_channels,
expanded_channels=cfg.expanded_channels,
out_channels=cfg.out_channels,
filter_size=cfg.kernel,
stride=cfg.stride,
use_se=cfg.use_se,
activation_layer=cfg.activation_layer,
norm_layer=norm_layer,
)
for cfg in self.config
]
)
self.lastconv = ConvNormActivation(
in_channels=self.lastconv_in_channels,
out_channels=self.lastconv_out_channels,
kernel_size=1,
stride=1,
padding=0,
groups=1,
norm_layer=norm_layer,
activation_layer=nn.Hardswish,
)
if with_pool:
self.avgpool = nn.AdaptiveAvgPool2D(1)
if num_classes > 0:
self.classifier = nn.Sequential(
nn.Linear(self.lastconv_out_channels, self.last_channel),
nn.Hardswish(),
nn.Dropout(p=0.2),
nn.Linear(self.last_channel, num_classes),
)
def forward(self, x: Tensor) -> Tensor:
x = self.conv(x)
x = self.blocks(x)
x = self.lastconv(x)
if self.with_pool:
x = self.avgpool(x)
if self.num_classes > 0:
x = paddle.flatten(x, 1)
x = self.classifier(x)
return x
class MobileNetV3Small(MobileNetV3):
"""MobileNetV3 Small architecture model from
`"Searching for MobileNetV3" <https://arxiv.org/abs/1905.02244>`_.
Args:
scale (float, optional): Scale of channels in each layer. Default: 1.0.
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last fc layer or not. Default: True.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of MobileNetV3 Small architecture model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import MobileNetV3Small
>>> # Build model
>>> model = MobileNetV3Small(scale=1.0)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
def __init__(
self,
scale: float = 1.0,
num_classes: int = 1000,
with_pool: bool = True,
) -> None:
config = [
InvertedResidualConfig(16, 3, 16, 16, True, "relu", 2, scale),
InvertedResidualConfig(16, 3, 72, 24, False, "relu", 2, scale),
InvertedResidualConfig(24, 3, 88, 24, False, "relu", 1, scale),
InvertedResidualConfig(24, 5, 96, 40, True, "hardswish", 2, scale),
InvertedResidualConfig(40, 5, 240, 40, True, "hardswish", 1, scale),
InvertedResidualConfig(40, 5, 240, 40, True, "hardswish", 1, scale),
InvertedResidualConfig(40, 5, 120, 48, True, "hardswish", 1, scale),
InvertedResidualConfig(48, 5, 144, 48, True, "hardswish", 1, scale),
InvertedResidualConfig(48, 5, 288, 96, True, "hardswish", 2, scale),
InvertedResidualConfig(96, 5, 576, 96, True, "hardswish", 1, scale),
InvertedResidualConfig(96, 5, 576, 96, True, "hardswish", 1, scale),
]
last_channel = _make_divisible(1024 * scale, 8)
super().__init__(
config,
last_channel=last_channel,
scale=scale,
with_pool=with_pool,
num_classes=num_classes,
)
class MobileNetV3Large(MobileNetV3):
"""MobileNetV3 Large architecture model from
`"Searching for MobileNetV3" <https://arxiv.org/abs/1905.02244>`_.
Args:
scale (float, optional): Scale of channels in each layer. Default: 1.0.
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last fc layer or not. Default: True.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of MobileNetV3 Large architecture model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import MobileNetV3Large
>>> # Build model
>>> model = MobileNetV3Large(scale=1.0)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
def __init__(
self,
scale: float = 1.0,
num_classes: int = 1000,
with_pool: bool = True,
) -> None:
config = [
InvertedResidualConfig(16, 3, 16, 16, False, "relu", 1, scale),
InvertedResidualConfig(16, 3, 64, 24, False, "relu", 2, scale),
InvertedResidualConfig(24, 3, 72, 24, False, "relu", 1, scale),
InvertedResidualConfig(24, 5, 72, 40, True, "relu", 2, scale),
InvertedResidualConfig(40, 5, 120, 40, True, "relu", 1, scale),
InvertedResidualConfig(40, 5, 120, 40, True, "relu", 1, scale),
InvertedResidualConfig(
40, 3, 240, 80, False, "hardswish", 2, scale
),
InvertedResidualConfig(
80, 3, 200, 80, False, "hardswish", 1, scale
),
InvertedResidualConfig(
80, 3, 184, 80, False, "hardswish", 1, scale
),
InvertedResidualConfig(
80, 3, 184, 80, False, "hardswish", 1, scale
),
InvertedResidualConfig(
80, 3, 480, 112, True, "hardswish", 1, scale
),
InvertedResidualConfig(
112, 3, 672, 112, True, "hardswish", 1, scale
),
InvertedResidualConfig(
112, 5, 672, 160, True, "hardswish", 2, scale
),
InvertedResidualConfig(
160, 5, 960, 160, True, "hardswish", 1, scale
),
InvertedResidualConfig(
160, 5, 960, 160, True, "hardswish", 1, scale
),
]
last_channel = _make_divisible(1280 * scale, 8)
super().__init__(
config,
last_channel=last_channel,
scale=scale,
with_pool=with_pool,
num_classes=num_classes,
)
def _mobilenet_v3(
arch: str,
pretrained: bool = False,
scale: float = 1.0,
**kwargs: Unpack[_MobileNetV3Options],
) -> MobileNetV3:
if arch == "mobilenet_v3_large":
model = MobileNetV3Large(scale=scale, **kwargs)
else:
model = MobileNetV3Small(scale=scale, **kwargs)
if pretrained:
arch = f"{arch}_x{scale}"
assert arch in model_urls, (
f"{arch} model do not have a pretrained model now, you should set pretrained=False"
)
weight_path = get_weights_path_from_url(
model_urls[arch][0], model_urls[arch][1]
)
param = paddle.load(weight_path)
model.set_dict(param)
return model
def mobilenet_v3_small(
pretrained: bool = False,
scale: float = 1.0,
**kwargs: Unpack[_MobileNetV3Options],
) -> MobileNetV3Small:
"""MobileNetV3 Small architecture model from
`"Searching for MobileNetV3" <https://arxiv.org/abs/1905.02244>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained on ImageNet. Default: False.
scale (float, optional): Scale of channels in each layer. Default: 1.0.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`MobileNetV3Small <api_paddle_vision_models_MobileNetV3Small>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of MobileNetV3 Small architecture model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import mobilenet_v3_small
>>> # Build model
>>> model = mobilenet_v3_small()
>>> # Build model and load imagenet pretrained weight
>>> # model = mobilenet_v3_small(pretrained=True)
>>> # Build mobilenet v3 small model with scale=0.5
>>> model = mobilenet_v3_small(scale=0.5)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
model = _mobilenet_v3(
"mobilenet_v3_small", scale=scale, pretrained=pretrained, **kwargs
)
return model
def mobilenet_v3_large(
pretrained: bool = False,
scale: float = 1.0,
**kwargs: Unpack[_MobileNetV3Options],
) -> MobileNetV3Large:
"""MobileNetV3 Large architecture model from
`"Searching for MobileNetV3" <https://arxiv.org/abs/1905.02244>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained on ImageNet. Default: False.
scale (float, optional): Scale of channels in each layer. Default: 1.0.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`MobileNetV3Large <api_paddle_vision_models_MobileNetV3Large>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of MobileNetV3 Large architecture model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import mobilenet_v3_large
>>> # Build model
>>> model = mobilenet_v3_large()
>>> # Build model and load imagenet pretrained weight
>>> # model = mobilenet_v3_large(pretrained=True)
>>> # Build mobilenet v3 large model with scale=0.5
>>> model = mobilenet_v3_large(scale=0.5)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
model = _mobilenet_v3(
"mobilenet_v3_large", scale=scale, pretrained=pretrained, **kwargs
)
return model
+898
View File
@@ -0,0 +1,898 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import paddle
from paddle import nn
from paddle.utils.download import get_weights_path_from_url
if TYPE_CHECKING:
from collections.abc import Callable
from typing import Literal, TypedDict
from typing_extensions import NotRequired, Unpack
from paddle import Tensor
from paddle._typing import Size2
_ResNetArch = Literal[
'resnet18',
'resnet34',
'resnet50',
'resnet101',
'resnet152',
'resnext50_32x4d',
'resnext50_64x4d',
'resnext101_32x4d',
'resnext101_64x4d',
'resnext152_32x4d',
'resnext152_64x4d',
'wide_resnet50_2',
'wide_resnet101_2',
]
class _ResNetOptions(TypedDict):
width: NotRequired[int]
num_classes: NotRequired[int]
with_pool: NotRequired[bool]
groups: NotRequired[int]
__all__ = []
model_urls: dict[str, tuple[str, str]] = {
'resnet18': (
'https://paddle-hapi.bj.bcebos.com/models/resnet18.pdparams',
'cf548f46534aa3560945be4b95cd11c4',
),
'resnet34': (
'https://paddle-hapi.bj.bcebos.com/models/resnet34.pdparams',
'8d2275cf8706028345f78ac0e1d31969',
),
'resnet50': (
'https://paddle-hapi.bj.bcebos.com/models/resnet50.pdparams',
'ca6f485ee1ab0492d38f323885b0ad80',
),
'resnet101': (
'https://paddle-hapi.bj.bcebos.com/models/resnet101.pdparams',
'02f35f034ca3858e1e54d4036443c92d',
),
'resnet152': (
'https://paddle-hapi.bj.bcebos.com/models/resnet152.pdparams',
'7ad16a2f1e7333859ff986138630fd7a',
),
'resnext50_32x4d': (
'https://paddle-hapi.bj.bcebos.com/models/resnext50_32x4d.pdparams',
'dc47483169be7d6f018fcbb7baf8775d',
),
"resnext50_64x4d": (
'https://paddle-hapi.bj.bcebos.com/models/resnext50_64x4d.pdparams',
'063d4b483e12b06388529450ad7576db',
),
'resnext101_32x4d': (
'https://paddle-hapi.bj.bcebos.com/models/resnext101_32x4d.pdparams',
'967b090039f9de2c8d06fe994fb9095f',
),
'resnext101_64x4d': (
'https://paddle-hapi.bj.bcebos.com/models/resnext101_64x4d.pdparams',
'98e04e7ca616a066699230d769d03008',
),
'resnext152_32x4d': (
'https://paddle-hapi.bj.bcebos.com/models/resnext152_32x4d.pdparams',
'18ff0beee21f2efc99c4b31786107121',
),
'resnext152_64x4d': (
'https://paddle-hapi.bj.bcebos.com/models/resnext152_64x4d.pdparams',
'77c4af00ca42c405fa7f841841959379',
),
'wide_resnet50_2': (
'https://paddle-hapi.bj.bcebos.com/models/wide_resnet50_2.pdparams',
'0282f804d73debdab289bd9fea3fa6dc',
),
'wide_resnet101_2': (
'https://paddle-hapi.bj.bcebos.com/models/wide_resnet101_2.pdparams',
'd4360a2d23657f059216f5d5a1a9ac93',
),
}
class BasicBlock(nn.Layer):
expansion: int = 1
def __init__(
self,
inplanes: int,
planes: int,
stride: Size2 = 1,
downsample: nn.Layer | None = None,
groups: int = 1,
base_width: int = 64,
dilation: int = 1,
norm_layer: Callable[..., nn.Layer] | None = None,
) -> None:
super().__init__()
if norm_layer is None:
norm_layer: type[nn.BatchNorm2D] = nn.BatchNorm2D
if dilation > 1:
raise NotImplementedError(
"Dilation > 1 not supported in BasicBlock"
)
self.conv1 = nn.Conv2D(
inplanes, planes, 3, padding=1, stride=stride, bias_attr=False
)
self.bn1 = norm_layer(planes)
self.relu = nn.ReLU()
self.conv2 = nn.Conv2D(planes, planes, 3, padding=1, bias_attr=False)
self.bn2 = norm_layer(planes)
self.downsample = downsample
self.stride = stride
def forward(self, x: Tensor) -> Tensor:
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
class BottleneckBlock(nn.Layer):
expansion: int = 4
def __init__(
self,
inplanes: int,
planes: int,
stride: Size2 = 1,
downsample: nn.Layer | None = None,
groups: int = 1,
base_width: int = 64,
dilation: int = 1,
norm_layer: Callable[..., nn.Layer] | None = None,
) -> None:
super().__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2D
width = int(planes * (base_width / 64.0)) * groups
self.conv1 = nn.Conv2D(inplanes, width, 1, bias_attr=False)
self.bn1 = norm_layer(width)
self.conv2 = nn.Conv2D(
width,
width,
3,
padding=dilation,
stride=stride,
groups=groups,
dilation=dilation,
bias_attr=False,
)
self.bn2 = norm_layer(width)
self.conv3 = nn.Conv2D(
width, planes * self.expansion, 1, bias_attr=False
)
self.bn3 = norm_layer(planes * self.expansion)
self.relu = nn.ReLU()
self.downsample = downsample
self.stride = stride
def forward(self, x: Tensor) -> Tensor:
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
class ResNet(nn.Layer):
"""ResNet model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_.
Args:
Block (BasicBlock|BottleneckBlock): Block module of model.
depth (int, optional): Layers of ResNet, Default: 50.
width (int, optional): Base width per convolution group for each convolution block, Default: 64.
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last fc layer or not. Default: True.
groups (int, optional): Number of groups for each convolution block, Default: 1.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNet model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import ResNet
>>> from paddle.vision.models.resnet import (
... BottleneckBlock,
... BasicBlock,
... )
>>> # build ResNet with 18 layers
>>> resnet18 = ResNet(BasicBlock, 18)
>>> # build ResNet with 50 layers
>>> resnet50 = ResNet(BottleneckBlock, 50)
>>> # build Wide ResNet model
>>> wide_resnet50_2 = ResNet(BottleneckBlock, 50, width=64 * 2)
>>> # build ResNeXt model
>>> resnext50_32x4d = ResNet(BottleneckBlock, 50, width=4, groups=32)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = resnet18(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
groups: int
base_width: int
num_classes: int
with_pool: bool
inplanes: int
dilation: int
def __init__(
self,
block: type[BasicBlock | BottleneckBlock],
depth: int = 50,
width: int = 64,
num_classes: int = 1000,
with_pool: bool = True,
groups: int = 1,
) -> None:
super().__init__()
layer_cfg = {
18: [2, 2, 2, 2],
34: [3, 4, 6, 3],
50: [3, 4, 6, 3],
101: [3, 4, 23, 3],
152: [3, 8, 36, 3],
}
layers = layer_cfg[depth]
self.groups = groups
self.base_width = width
self.num_classes = num_classes
self.with_pool = with_pool
self._norm_layer = nn.BatchNorm2D
self.inplanes = 64
self.dilation = 1
self.conv1 = nn.Conv2D(
3,
self.inplanes,
kernel_size=7,
stride=2,
padding=3,
bias_attr=False,
)
self.bn1 = self._norm_layer(self.inplanes)
self.relu = nn.ReLU()
self.maxpool = nn.MaxPool2D(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
if with_pool:
self.avgpool = nn.AdaptiveAvgPool2D((1, 1))
if num_classes > 0:
self.fc = nn.Linear(512 * block.expansion, num_classes)
def _make_layer(
self,
block: type[BasicBlock | BottleneckBlock],
planes: int,
blocks: int,
stride: int = 1,
dilate: bool = False,
) -> nn.Sequential:
norm_layer = self._norm_layer
downsample = None
previous_dilation = self.dilation
if dilate:
self.dilation *= stride
stride = 1
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
nn.Conv2D(
self.inplanes,
planes * block.expansion,
1,
stride=stride,
bias_attr=False,
),
norm_layer(planes * block.expansion),
)
layers = []
layers.append(
block(
self.inplanes,
planes,
stride,
downsample,
self.groups,
self.base_width,
previous_dilation,
norm_layer,
)
)
self.inplanes = planes * block.expansion
for _ in range(1, blocks):
layers.append(
block(
self.inplanes,
planes,
groups=self.groups,
base_width=self.base_width,
norm_layer=norm_layer,
)
)
return nn.Sequential(*layers)
def forward(self, x: Tensor) -> Tensor:
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
if self.with_pool:
x = self.avgpool(x)
if self.num_classes > 0:
x = paddle.flatten(x, 1)
x = self.fc(x)
return x
def _resnet(
arch: _ResNetArch,
Block: type[BasicBlock | BottleneckBlock],
depth: int,
pretrained: bool,
**kwargs: Unpack[_ResNetOptions],
) -> ResNet:
model = ResNet(Block, depth, **kwargs)
if pretrained:
assert arch in model_urls, (
f"{arch} model do not have a pretrained model now, you should set pretrained=False"
)
weight_path = get_weights_path_from_url(
model_urls[arch][0], model_urls[arch][1]
)
param = paddle.load(weight_path)
model.set_dict(param)
return model
def resnet18(pretrained=False, **kwargs: Unpack[_ResNetOptions]) -> ResNet:
"""ResNet 18-layer model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNet 18-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import resnet18
>>> # build model
>>> model = resnet18()
>>> # build model and load imagenet pretrained weight
>>> # model = resnet18(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _resnet('resnet18', BasicBlock, 18, pretrained, **kwargs)
def resnet34(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""ResNet 34-layer model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNet 34-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import resnet34
>>> # build model
>>> model = resnet34()
>>> # build model and load imagenet pretrained weight
>>> # model = resnet34(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _resnet('resnet34', BasicBlock, 34, pretrained, **kwargs)
def resnet50(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""ResNet 50-layer model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNet 50-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import resnet50
>>> # build model
>>> model = resnet50()
>>> # build model and load imagenet pretrained weight
>>> # model = resnet50(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _resnet('resnet50', BottleneckBlock, 50, pretrained, **kwargs)
def resnet101(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""ResNet 101-layer model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNet 101-layer.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import resnet101
>>> # build model
>>> model = resnet101()
>>> # build model and load imagenet pretrained weight
>>> # model = resnet101(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _resnet('resnet101', BottleneckBlock, 101, pretrained, **kwargs)
def resnet152(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""ResNet 152-layer model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNet 152-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import resnet152
>>> # build model
>>> model = resnet152()
>>> # build model and load imagenet pretrained weight
>>> # model = resnet152(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _resnet('resnet152', BottleneckBlock, 152, pretrained, **kwargs)
def resnext50_32x4d(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""ResNeXt-50 32x4d model from
`"Aggregated Residual Transformations for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNeXt-50 32x4d model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import resnext50_32x4d
>>> # build model
>>> model = resnext50_32x4d()
>>> # build model and load imagenet pretrained weight
>>> # model = resnext50_32x4d(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
kwargs['groups'] = 32
kwargs['width'] = 4
return _resnet('resnext50_32x4d', BottleneckBlock, 50, pretrained, **kwargs)
def resnext50_64x4d(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""ResNeXt-50 64x4d model from
`"Aggregated Residual Transformations for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNeXt-50 64x4d model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import resnext50_64x4d
>>> # build model
>>> model = resnext50_64x4d()
>>> # build model and load imagenet pretrained weight
>>> # model = resnext50_64x4d(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
kwargs['groups'] = 64
kwargs['width'] = 4
return _resnet('resnext50_64x4d', BottleneckBlock, 50, pretrained, **kwargs)
def resnext101_32x4d(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""ResNeXt-101 32x4d model from
`"Aggregated Residual Transformations for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNeXt-101 32x4d model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import resnext101_32x4d
>>> # build model
>>> model = resnext101_32x4d()
>>> # build model and load imagenet pretrained weight
>>> # model = resnext101_32x4d(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
kwargs['groups'] = 32
kwargs['width'] = 4
return _resnet(
'resnext101_32x4d', BottleneckBlock, 101, pretrained, **kwargs
)
def resnext101_64x4d(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""ResNeXt-101 64x4d model from
`"Aggregated Residual Transformations for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNeXt-101 64x4d model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import resnext101_64x4d
>>> # build model
>>> model = resnext101_64x4d()
>>> # build model and load imagenet pretrained weight
>>> # model = resnext101_64x4d(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
kwargs['groups'] = 64
kwargs['width'] = 4
return _resnet(
'resnext101_64x4d', BottleneckBlock, 101, pretrained, **kwargs
)
def resnext152_32x4d(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""ResNeXt-152 32x4d model from
`"Aggregated Residual Transformations for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNeXt-152 32x4d model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import resnext152_32x4d
>>> # build model
>>> model = resnext152_32x4d()
>>> # build model and load imagenet pretrained weight
>>> # model = resnext152_32x4d(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
kwargs['groups'] = 32
kwargs['width'] = 4
return _resnet(
'resnext152_32x4d', BottleneckBlock, 152, pretrained, **kwargs
)
def resnext152_64x4d(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""ResNeXt-152 64x4d model from
`"Aggregated Residual Transformations for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ResNeXt-152 64x4d model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import resnext152_64x4d
>>> # build model
>>> model = resnext152_64x4d()
>>> # build model and load imagenet pretrained weight
>>> # model = resnext152_64x4d(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
kwargs['groups'] = 64
kwargs['width'] = 4
return _resnet(
'resnext152_64x4d', BottleneckBlock, 152, pretrained, **kwargs
)
def wide_resnet50_2(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""Wide ResNet-50-2 model from
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of Wide ResNet-50-2 model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import wide_resnet50_2
>>> # build model
>>> model = wide_resnet50_2()
>>> # build model and load imagenet pretrained weight
>>> # model = wide_resnet50_2(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
kwargs['width'] = 64 * 2
return _resnet('wide_resnet50_2', BottleneckBlock, 50, pretrained, **kwargs)
def wide_resnet101_2(
pretrained: bool = False, **kwargs: Unpack[_ResNetOptions]
) -> ResNet:
"""Wide ResNet-101-2 model from
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ResNet <api_paddle_vision_models_ResNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of Wide ResNet-101-2 model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import wide_resnet101_2
>>> # build model
>>> model = wide_resnet101_2()
>>> # build model and load imagenet pretrained weight
>>> # model = wide_resnet101_2(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
kwargs['width'] = 64 * 2
return _resnet(
'wide_resnet101_2', BottleneckBlock, 101, pretrained, **kwargs
)
+649
View File
@@ -0,0 +1,649 @@
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
import paddle
from paddle import nn
from paddle.nn import AdaptiveAvgPool2D, Linear, MaxPool2D
from paddle.utils.download import get_weights_path_from_url
from ..ops import ConvNormActivation
if TYPE_CHECKING:
from collections.abc import Callable
from typing import Literal, TypedDict
from typing_extensions import NotRequired, Unpack
from paddle import Tensor
from paddle._typing import Size2
_ShuffleNetArch = Literal[
'shufflenet_v2_x0_25',
'shufflenet_v2_x0_33',
'shufflenet_v2_x0_5',
'shufflenet_v2_x1_0',
'shufflenet_v2_x1_5',
'shufflenet_v2_x2_0',
'shufflenet_v2_swish',
]
_ActivationType = Literal['relu', 'swish']
class _ShuffleNetOptions(TypedDict):
act: NotRequired[_ActivationType | None]
with_pool: NotRequired[bool]
num_classes: NotRequired[int]
class _ShuffleNetSwishOptions(TypedDict):
with_pool: NotRequired[bool]
num_classes: NotRequired[int]
__all__ = []
model_urls: dict[str, tuple[str, str]] = {
"shufflenet_v2_x0_25": (
"https://paddle-hapi.bj.bcebos.com/models/shufflenet_v2_x0_25.pdparams",
"1e509b4c140eeb096bb16e214796d03b",
),
"shufflenet_v2_x0_33": (
"https://paddle-hapi.bj.bcebos.com/models/shufflenet_v2_x0_33.pdparams",
"3d7b3ab0eaa5c0927ff1026d31b729bd",
),
"shufflenet_v2_x0_5": (
"https://paddle-hapi.bj.bcebos.com/models/shufflenet_v2_x0_5.pdparams",
"5e5cee182a7793c4e4c73949b1a71bd4",
),
"shufflenet_v2_x1_0": (
"https://paddle-hapi.bj.bcebos.com/models/shufflenet_v2_x1_0.pdparams",
"122d42478b9e81eb49f8a9ede327b1a4",
),
"shufflenet_v2_x1_5": (
"https://paddle-hapi.bj.bcebos.com/models/shufflenet_v2_x1_5.pdparams",
"faced5827380d73531d0ee027c67826d",
),
"shufflenet_v2_x2_0": (
"https://paddle-hapi.bj.bcebos.com/models/shufflenet_v2_x2_0.pdparams",
"cd3dddcd8305e7bcd8ad14d1c69a5784",
),
"shufflenet_v2_swish": (
"https://paddle-hapi.bj.bcebos.com/models/shufflenet_v2_swish.pdparams",
"adde0aa3b023e5b0c94a68be1c394b84",
),
}
def create_activation_layer(act: _ActivationType | None) -> nn.Layer | None:
if act == "swish":
return nn.Swish
elif act == "relu":
return nn.ReLU
elif act is None:
return None
else:
raise RuntimeError(f"The activation function is not supported: {act}")
def channel_shuffle(x: Tensor, groups: int) -> Tensor:
batch_size, num_channels, height, width = x.shape[0:4]
channels_per_group = num_channels // groups
# reshape
x = paddle.reshape(
x, shape=[batch_size, groups, channels_per_group, height, width]
)
# transpose
x = paddle.transpose(x, perm=[0, 2, 1, 3, 4])
# flatten
x = paddle.reshape(x, shape=[batch_size, num_channels, height, width])
return x
class InvertedResidual(nn.Layer):
def __init__(
self,
in_channels: int,
out_channels: int,
stride: Size2,
activation_layer: Callable[..., nn.Layer] = nn.ReLU,
) -> None:
super().__init__()
self._conv_pw = ConvNormActivation(
in_channels=in_channels // 2,
out_channels=out_channels // 2,
kernel_size=1,
stride=1,
padding=0,
groups=1,
activation_layer=activation_layer,
)
self._conv_dw = ConvNormActivation(
in_channels=out_channels // 2,
out_channels=out_channels // 2,
kernel_size=3,
stride=stride,
padding=1,
groups=out_channels // 2,
activation_layer=None,
)
self._conv_linear = ConvNormActivation(
in_channels=out_channels // 2,
out_channels=out_channels // 2,
kernel_size=1,
stride=1,
padding=0,
groups=1,
activation_layer=activation_layer,
)
def forward(self, inputs: Tensor) -> Tensor:
x1, x2 = paddle.split(
inputs,
num_or_sections=[inputs.shape[1] // 2, inputs.shape[1] // 2],
axis=1,
)
x2 = self._conv_pw(x2)
x2 = self._conv_dw(x2)
x2 = self._conv_linear(x2)
out = paddle.concat([x1, x2], axis=1)
return channel_shuffle(out, 2)
class InvertedResidualDS(nn.Layer):
def __init__(
self,
in_channels: int,
out_channels: int,
stride: Size2,
activation_layer: Callable[..., nn.Layer] = nn.ReLU,
) -> None:
super().__init__()
# branch1
self._conv_dw_1 = ConvNormActivation(
in_channels=in_channels,
out_channels=in_channels,
kernel_size=3,
stride=stride,
padding=1,
groups=in_channels,
activation_layer=None,
)
self._conv_linear_1 = ConvNormActivation(
in_channels=in_channels,
out_channels=out_channels // 2,
kernel_size=1,
stride=1,
padding=0,
groups=1,
activation_layer=activation_layer,
)
# branch2
self._conv_pw_2 = ConvNormActivation(
in_channels=in_channels,
out_channels=out_channels // 2,
kernel_size=1,
stride=1,
padding=0,
groups=1,
activation_layer=activation_layer,
)
self._conv_dw_2 = ConvNormActivation(
in_channels=out_channels // 2,
out_channels=out_channels // 2,
kernel_size=3,
stride=stride,
padding=1,
groups=out_channels // 2,
activation_layer=None,
)
self._conv_linear_2 = ConvNormActivation(
in_channels=out_channels // 2,
out_channels=out_channels // 2,
kernel_size=1,
stride=1,
padding=0,
groups=1,
activation_layer=activation_layer,
)
def forward(self, inputs: Tensor) -> Tensor:
x1 = self._conv_dw_1(inputs)
x1 = self._conv_linear_1(x1)
x2 = self._conv_pw_2(inputs)
x2 = self._conv_dw_2(x2)
x2 = self._conv_linear_2(x2)
out = paddle.concat([x1, x2], axis=1)
return channel_shuffle(out, 2)
class ShuffleNetV2(nn.Layer):
"""ShuffleNetV2 model from
`"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" <https://arxiv.org/pdf/1807.11164.pdf>`_.
Args:
scale (float, optional): Scale of output channels. Default: True.
act (str, optional): Activation function of neural network. Default: "relu".
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last fc layer or not. Default: True.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ShuffleNetV2 model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import ShuffleNetV2
>>> shufflenet_v2_swish = ShuffleNetV2(scale=1.0, act="swish")
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = shufflenet_v2_swish(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
scale: float
num_classes: int
with_pool: bool
def __init__(
self,
scale: float = 1.0,
act: _ActivationType | None = "relu",
num_classes: int = 1000,
with_pool: bool = True,
) -> None:
super().__init__()
self.scale = scale
self.num_classes = num_classes
self.with_pool = with_pool
stage_repeats = [4, 8, 4]
activation_layer = create_activation_layer(act)
if scale == 0.25:
stage_out_channels = [-1, 24, 24, 48, 96, 512]
elif scale == 0.33:
stage_out_channels = [-1, 24, 32, 64, 128, 512]
elif scale == 0.5:
stage_out_channels = [-1, 24, 48, 96, 192, 1024]
elif scale == 1.0:
stage_out_channels = [-1, 24, 116, 232, 464, 1024]
elif scale == 1.5:
stage_out_channels = [-1, 24, 176, 352, 704, 1024]
elif scale == 2.0:
stage_out_channels = [-1, 24, 224, 488, 976, 2048]
else:
raise NotImplementedError(
"This scale size:[" + str(scale) + "] is not implemented!"
)
# 1. conv1
self._conv1 = ConvNormActivation(
in_channels=3,
out_channels=stage_out_channels[1],
kernel_size=3,
stride=2,
padding=1,
activation_layer=activation_layer,
)
self._max_pool = MaxPool2D(kernel_size=3, stride=2, padding=1)
# 2. bottleneck sequences
self._block_list = []
for stage_id, num_repeat in enumerate(stage_repeats):
for i in range(num_repeat):
if i == 0:
block = self.add_sublayer(
sublayer=InvertedResidualDS(
in_channels=stage_out_channels[stage_id + 1],
out_channels=stage_out_channels[stage_id + 2],
stride=2,
activation_layer=activation_layer,
),
name=str(stage_id + 2) + "_" + str(i + 1),
)
else:
block = self.add_sublayer(
sublayer=InvertedResidual(
in_channels=stage_out_channels[stage_id + 2],
out_channels=stage_out_channels[stage_id + 2],
stride=1,
activation_layer=activation_layer,
),
name=str(stage_id + 2) + "_" + str(i + 1),
)
self._block_list.append(block)
# 3. last_conv
self._last_conv = ConvNormActivation(
in_channels=stage_out_channels[-2],
out_channels=stage_out_channels[-1],
kernel_size=1,
stride=1,
padding=0,
activation_layer=activation_layer,
)
# 4. pool
if with_pool:
self._pool2d_avg = AdaptiveAvgPool2D(1)
# 5. fc
if num_classes > 0:
self._out_c = stage_out_channels[-1]
self._fc = Linear(stage_out_channels[-1], num_classes)
def forward(self, inputs: Tensor) -> Tensor:
x = self._conv1(inputs)
x = self._max_pool(x)
for inv in self._block_list:
x = inv(x)
x = self._last_conv(x)
if self.with_pool:
x = self._pool2d_avg(x)
if self.num_classes > 0:
x = paddle.flatten(x, start_axis=1, stop_axis=-1)
x = self._fc(x)
return x
def _shufflenet_v2(
arch: _ShuffleNetArch,
pretrained: bool = False,
scale: float = 1.0,
**kwargs: Unpack[_ShuffleNetOptions],
) -> ShuffleNetV2:
model = ShuffleNetV2(scale=scale, **kwargs)
if pretrained:
assert arch in model_urls, (
f"{arch} model do not have a pretrained model now, you should set pretrained=False"
)
weight_path = get_weights_path_from_url(
model_urls[arch][0], model_urls[arch][1]
)
param = paddle.load(weight_path)
model.set_dict(param)
return model
def shufflenet_v2_x0_25(
pretrained: bool = False, **kwargs: Unpack[_ShuffleNetOptions]
) -> ShuffleNetV2:
"""ShuffleNetV2 with 0.25x output channels, as described in
`"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" <https://arxiv.org/pdf/1807.11164.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ShuffleNetV2 <api_paddle_vision_models_ShuffleNetV2>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ShuffleNetV2 with 0.25x output channels.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import shufflenet_v2_x0_25
>>> # build model
>>> model = shufflenet_v2_x0_25()
>>> # build model and load imagenet pretrained weight
>>> # model = shufflenet_v2_x0_25(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _shufflenet_v2(
"shufflenet_v2_x0_25", scale=0.25, pretrained=pretrained, **kwargs
)
def shufflenet_v2_x0_33(
pretrained: bool = False, **kwargs: Unpack[_ShuffleNetOptions]
) -> ShuffleNetV2:
"""ShuffleNetV2 with 0.33x output channels, as described in
`"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" <https://arxiv.org/pdf/1807.11164.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ShuffleNetV2 <api_paddle_vision_models_ShuffleNetV2>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ShuffleNetV2 with 0.33x output channels.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import shufflenet_v2_x0_33
>>> # build model
>>> model = shufflenet_v2_x0_33()
>>> # build model and load imagenet pretrained weight
>>> # model = shufflenet_v2_x0_33(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _shufflenet_v2(
"shufflenet_v2_x0_33", scale=0.33, pretrained=pretrained, **kwargs
)
def shufflenet_v2_x0_5(
pretrained: bool = False, **kwargs: Unpack[_ShuffleNetOptions]
) -> ShuffleNetV2:
"""ShuffleNetV2 with 0.5x output channels, as described in
`"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" <https://arxiv.org/pdf/1807.11164.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ShuffleNetV2 <api_paddle_vision_models_ShuffleNetV2>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ShuffleNetV2 with 0.5x output channels.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import shufflenet_v2_x0_5
>>> # build model
>>> model = shufflenet_v2_x0_5()
>>> # build model and load imagenet pretrained weight
>>> # model = shufflenet_v2_x0_5(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _shufflenet_v2(
"shufflenet_v2_x0_5", scale=0.5, pretrained=pretrained, **kwargs
)
def shufflenet_v2_x1_0(
pretrained: bool = False, **kwargs: Unpack[_ShuffleNetOptions]
) -> ShuffleNetV2:
"""ShuffleNetV2 with 1.0x output channels, as described in
`"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" <https://arxiv.org/pdf/1807.11164.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ShuffleNetV2 <api_paddle_vision_models_ShuffleNetV2>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ShuffleNetV2 with 1.0x output channels.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import shufflenet_v2_x1_0
>>> # build model
>>> model = shufflenet_v2_x1_0()
>>> # build model and load imagenet pretrained weight
>>> # model = shufflenet_v2_x1_0(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _shufflenet_v2(
"shufflenet_v2_x1_0", scale=1.0, pretrained=pretrained, **kwargs
)
def shufflenet_v2_x1_5(
pretrained: bool = False, **kwargs: Unpack[_ShuffleNetOptions]
) -> ShuffleNetV2:
"""ShuffleNetV2 with 1.5x output channels, as described in
`"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" <https://arxiv.org/pdf/1807.11164.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ShuffleNetV2 <api_paddle_vision_models_ShuffleNetV2>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ShuffleNetV2 with 1.5x output channels.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import shufflenet_v2_x1_5
>>> # build model
>>> model = shufflenet_v2_x1_5()
>>> # build model and load imagenet pretrained weight
>>> # model = shufflenet_v2_x1_5(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _shufflenet_v2(
"shufflenet_v2_x1_5", scale=1.5, pretrained=pretrained, **kwargs
)
def shufflenet_v2_x2_0(
pretrained: bool = False, **kwargs: Unpack[_ShuffleNetOptions]
) -> ShuffleNetV2:
"""ShuffleNetV2 with 2.0x output channels, as described in
`"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" <https://arxiv.org/pdf/1807.11164.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ShuffleNetV2 <api_paddle_vision_models_ShuffleNetV2>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ShuffleNetV2 with 2.0x output channels.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import shufflenet_v2_x2_0
>>> # build model
>>> model = shufflenet_v2_x2_0()
>>> # build model and load imagenet pretrained weight
>>> # model = shufflenet_v2_x2_0(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _shufflenet_v2(
"shufflenet_v2_x2_0", scale=2.0, pretrained=pretrained, **kwargs
)
def shufflenet_v2_swish(
pretrained: bool = False, **kwargs: Unpack[_ShuffleNetSwishOptions]
) -> ShuffleNetV2:
"""ShuffleNetV2 with swish activation function, as described in
`"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" <https://arxiv.org/pdf/1807.11164.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`ShuffleNetV2 <api_paddle_vision_models_ShuffleNetV2>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of ShuffleNetV2 with swish activation function.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import shufflenet_v2_swish
>>> # build model
>>> model = shufflenet_v2_swish()
>>> # build model and load imagenet pretrained weight
>>> # model = shufflenet_v2_swish(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _shufflenet_v2(
"shufflenet_v2_swish",
scale=1.0,
act="swish",
pretrained=pretrained,
**kwargs,
)
+320
View File
@@ -0,0 +1,320 @@
# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import (
TYPE_CHECKING,
TypedDict,
)
from typing_extensions import NotRequired, Unpack
import paddle
import paddle.nn.functional as F
from paddle import nn
from paddle.base.param_attr import ParamAttr
from paddle.nn import AdaptiveAvgPool2D, Conv2D, Dropout, MaxPool2D
from paddle.utils.download import get_weights_path_from_url
if TYPE_CHECKING:
from paddle import Tensor
from paddle._typing import Size2
class _SqueezeNetOptions(TypedDict):
num_classes: NotRequired[int]
with_pool: NotRequired[bool]
__all__ = []
model_urls = {
'squeezenet1_0': (
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/SqueezeNet1_0_pretrained.pdparams',
'30b95af60a2178f03cf9b66cd77e1db1',
),
'squeezenet1_1': (
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/SqueezeNet1_1_pretrained.pdparams',
'a11250d3a1f91d7131fd095ebbf09eee',
),
}
class MakeFireConv(nn.Layer):
def __init__(
self,
input_channels: int,
output_channels: int,
filter_size: Size2,
padding: Size2 = 0,
) -> None:
super().__init__()
self._conv = Conv2D(
input_channels,
output_channels,
filter_size,
padding=padding,
weight_attr=ParamAttr(),
bias_attr=ParamAttr(),
)
def forward(self, x: Tensor) -> Tensor:
x = self._conv(x)
x = F.relu(x)
return x
class MakeFire(nn.Layer):
def __init__(
self,
input_channels: int,
squeeze_channels: int,
expand1x1_channels: int,
expand3x3_channels: int,
) -> None:
super().__init__()
self._conv = MakeFireConv(input_channels, squeeze_channels, 1)
self._conv_path1 = MakeFireConv(squeeze_channels, expand1x1_channels, 1)
self._conv_path2 = MakeFireConv(
squeeze_channels, expand3x3_channels, 3, padding=1
)
def forward(self, inputs: Tensor) -> Tensor:
x = self._conv(inputs)
x1 = self._conv_path1(x)
x2 = self._conv_path2(x)
return paddle.concat([x1, x2], axis=1)
class SqueezeNet(nn.Layer):
"""SqueezeNet model from
`"SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and <0.5MB model size"
<https://arxiv.org/pdf/1602.07360.pdf>`_.
Args:
version (str): Version of SqueezeNet, which can be "1.0" or "1.1".
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last fc layer or not. Default: True.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of SqueezeNet model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import SqueezeNet
>>> # build v1.0 model
>>> model = SqueezeNet(version='1.0')
>>> # build v1.1 model
>>> # model = SqueezeNet(version='1.1')
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
version: str
num_classes: int
with_pool: bool
def __init__(
self, version: str, num_classes: int = 1000, with_pool: bool = True
) -> None:
super().__init__()
self.version = version
self.num_classes = num_classes
self.with_pool = with_pool
supported_versions = ['1.0', '1.1']
assert version in supported_versions, (
f"supported versions are {supported_versions} but input version is {version}"
)
if self.version == "1.0":
self._conv = Conv2D(
3,
96,
7,
stride=2,
weight_attr=ParamAttr(),
bias_attr=ParamAttr(),
)
self._pool = MaxPool2D(kernel_size=3, stride=2, padding=0)
self._conv1 = MakeFire(96, 16, 64, 64)
self._conv2 = MakeFire(128, 16, 64, 64)
self._conv3 = MakeFire(128, 32, 128, 128)
self._conv4 = MakeFire(256, 32, 128, 128)
self._conv5 = MakeFire(256, 48, 192, 192)
self._conv6 = MakeFire(384, 48, 192, 192)
self._conv7 = MakeFire(384, 64, 256, 256)
self._conv8 = MakeFire(512, 64, 256, 256)
else:
self._conv = Conv2D(
3,
64,
3,
stride=2,
padding=1,
weight_attr=ParamAttr(),
bias_attr=ParamAttr(),
)
self._pool = MaxPool2D(kernel_size=3, stride=2, padding=0)
self._conv1 = MakeFire(64, 16, 64, 64)
self._conv2 = MakeFire(128, 16, 64, 64)
self._conv3 = MakeFire(128, 32, 128, 128)
self._conv4 = MakeFire(256, 32, 128, 128)
self._conv5 = MakeFire(256, 48, 192, 192)
self._conv6 = MakeFire(384, 48, 192, 192)
self._conv7 = MakeFire(384, 64, 256, 256)
self._conv8 = MakeFire(512, 64, 256, 256)
self._drop = Dropout(p=0.5, mode="downscale_in_infer")
self._conv9 = Conv2D(
512, num_classes, 1, weight_attr=ParamAttr(), bias_attr=ParamAttr()
)
self._avg_pool = AdaptiveAvgPool2D(1)
def forward(self, inputs: Tensor) -> Tensor:
x = self._conv(inputs)
x = F.relu(x)
x = self._pool(x)
if self.version == "1.0":
x = self._conv1(x)
x = self._conv2(x)
x = self._conv3(x)
x = self._pool(x)
x = self._conv4(x)
x = self._conv5(x)
x = self._conv6(x)
x = self._conv7(x)
x = self._pool(x)
x = self._conv8(x)
else:
x = self._conv1(x)
x = self._conv2(x)
x = self._pool(x)
x = self._conv3(x)
x = self._conv4(x)
x = self._pool(x)
x = self._conv5(x)
x = self._conv6(x)
x = self._conv7(x)
x = self._conv8(x)
if self.num_classes > 0:
x = self._drop(x)
x = self._conv9(x)
if self.with_pool:
x = F.relu(x)
x = self._avg_pool(x)
x = paddle.squeeze(x, axis=[2, 3])
return x
def _squeezenet(
arch: str,
version: str,
pretrained: bool,
**kwargs: Unpack[_SqueezeNetOptions],
) -> SqueezeNet:
model = SqueezeNet(version, **kwargs)
if pretrained:
assert arch in model_urls, (
f"{arch} model do not have a pretrained model now, you should set pretrained=False"
)
weight_path = get_weights_path_from_url(
model_urls[arch][0], model_urls[arch][1]
)
param = paddle.load(weight_path)
model.set_dict(param)
return model
def squeezenet1_0(
pretrained: bool = False, **kwargs: Unpack[_SqueezeNetOptions]
) -> SqueezeNet:
"""SqueezeNet v1.0 model from
`"SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and <0.5MB model size"
<https://arxiv.org/pdf/1602.07360.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`SqueezeNet <api_paddle_vision_models_SqueezeNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of SqueezeNet v1.0 model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import squeezenet1_0
>>> # build model
>>> model = squeezenet1_0()
>>> # build model and load imagenet pretrained weight
>>> # model = squeezenet1_0(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _squeezenet('squeezenet1_0', '1.0', pretrained, **kwargs)
def squeezenet1_1(
pretrained: bool = False, **kwargs: Unpack[_SqueezeNetOptions]
) -> SqueezeNet:
"""SqueezeNet v1.1 model from
`"SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and <0.5MB model size"
<https://arxiv.org/pdf/1602.07360.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`SqueezeNet <api_paddle_vision_models_SqueezeNet>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of SqueezeNet v1.1 model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import squeezenet1_1
>>> # build model
>>> model = squeezenet1_1()
>>> # build model and load imagenet pretrained weight
>>> # model = squeezenet1_1(pretrained=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
return _squeezenet('squeezenet1_1', '1.1', pretrained, **kwargs)
+371
View File
@@ -0,0 +1,371 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import (
TYPE_CHECKING,
Literal,
TypedDict,
)
from typing_extensions import NotRequired, Unpack
import paddle
from paddle import nn
from paddle.utils.download import get_weights_path_from_url
if TYPE_CHECKING:
from paddle import Tensor
from paddle.nn import Layer, Sequential
class _VGGOptions(TypedDict):
num_classes: NotRequired[int]
with_pool: NotRequired[bool]
__all__ = []
model_urls = {
'vgg16': (
'https://paddle-hapi.bj.bcebos.com/models/vgg16.pdparams',
'89bbffc0f87d260be9b8cdc169c991c4',
),
'vgg19': (
'https://paddle-hapi.bj.bcebos.com/models/vgg19.pdparams',
'23b18bb13d8894f60f54e642be79a0dd',
),
}
class VGG(nn.Layer):
"""VGG model from
`"Very Deep Convolutional Networks For Large-Scale Image Recognition" <https://arxiv.org/pdf/1409.1556.pdf>`_.
Args:
features (nn.Layer): Vgg features create by function make_layers.
num_classes (int, optional): Output dim of last fc layer. If num_classes <= 0, last fc layer
will not be defined. Default: 1000.
with_pool (bool, optional): Use pool before the last three fc layer or not. Default: True.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of VGG model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import VGG
>>> from paddle.vision.models.vgg import make_layers
>>> vgg11_cfg = [
... 64,
... 'M',
... 128,
... 'M',
... 256,
... 256,
... 'M',
... 512,
... 512,
... 'M',
... 512,
... 512,
... 'M',
... ]
>>> features = make_layers(vgg11_cfg) # type: ignore
>>> vgg11 = VGG(features)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = vgg11(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
num_classes: int
with_pool: bool
def __init__(
self, features: Layer, num_classes: int = 1000, with_pool: bool = True
) -> None:
super().__init__()
self.features = features
self.num_classes = num_classes
self.with_pool = with_pool
if with_pool:
self.avgpool = nn.AdaptiveAvgPool2D((7, 7))
if num_classes > 0:
self.classifier = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(),
nn.Linear(4096, num_classes),
)
def forward(self, x: Tensor) -> Tensor:
x = self.features(x)
if self.with_pool:
x = self.avgpool(x)
if self.num_classes > 0:
x = paddle.flatten(x, 1)
x = self.classifier(x)
return x
def make_layers(
cfg: list[int | Literal['M']], batch_norm: bool = False
) -> Sequential:
layers = []
in_channels = 3
for v in cfg:
if v == 'M':
layers += [nn.MaxPool2D(kernel_size=2, stride=2)]
else:
conv2d = nn.Conv2D(in_channels, v, kernel_size=3, padding=1)
if batch_norm:
layers += [conv2d, nn.BatchNorm2D(v), nn.ReLU()]
else:
layers += [conv2d, nn.ReLU()]
in_channels = v
return nn.Sequential(*layers)
cfgs = {
'A': [
64, 'M',
128, 'M',
256, 256, 'M',
512, 512, 'M',
512, 512, 'M',
],
'B': [
64, 64, 'M',
128, 128, 'M',
256, 256, 'M',
512, 512, 'M',
512, 512, 'M',
],
'D': [
64, 64, 'M',
128, 128, 'M',
256, 256, 256, 'M',
512, 512, 512, 'M',
512, 512, 512, 'M',
],
'E': [
64, 64, 'M',
128, 128, 'M',
256, 256, 256, 256, 'M',
512, 512, 512, 512, 'M',
512, 512, 512, 512, 'M',
],
} # fmt: skip
def _vgg(
arch: str,
cfg: Literal["A", "B", "D", "E"],
batch_norm: bool,
pretrained: bool,
**kwargs: Unpack[_VGGOptions],
) -> VGG:
model = VGG(make_layers(cfgs[cfg], batch_norm=batch_norm), **kwargs)
if pretrained:
assert arch in model_urls, (
f"{arch} model do not have a pretrained model now, you should set pretrained=False"
)
weight_path = get_weights_path_from_url(
model_urls[arch][0], model_urls[arch][1]
)
param = paddle.load(weight_path)
model.load_dict(param)
return model
def vgg11(
pretrained: bool = False,
batch_norm: bool = False,
**kwargs: Unpack[_VGGOptions],
) -> VGG:
"""VGG 11-layer model from
`"Very Deep Convolutional Networks For Large-Scale Image Recognition" <https://arxiv.org/pdf/1409.1556.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
batch_norm (bool, optional): If True, returns a model with batch_norm layer. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`VGG <api_paddle_vision_models_VGG>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of VGG 11-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import vgg11
>>> # build model
>>> model = vgg11()
>>> # build vgg11 model with batch_norm
>>> model = vgg11(batch_norm=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
model_name = 'vgg11'
if batch_norm:
model_name += '_bn'
return _vgg(model_name, 'A', batch_norm, pretrained, **kwargs)
def vgg13(
pretrained: bool = False,
batch_norm: bool = False,
**kwargs: Unpack[_VGGOptions],
) -> VGG:
"""VGG 13-layer model from
`"Very Deep Convolutional Networks For Large-Scale Image Recognition" <https://arxiv.org/pdf/1409.1556.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
batch_norm (bool): If True, returns a model with batch_norm layer. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`VGG <api_paddle_vision_models_VGG>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of VGG 13-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import vgg13
>>> # build model
>>> model = vgg13()
>>> # build vgg13 model with batch_norm
>>> model = vgg13(batch_norm=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
model_name = 'vgg13'
if batch_norm:
model_name += '_bn'
return _vgg(model_name, 'B', batch_norm, pretrained, **kwargs)
def vgg16(
pretrained: bool = False,
batch_norm: bool = False,
**kwargs: Unpack[_VGGOptions],
) -> VGG:
"""VGG 16-layer model from
`"Very Deep Convolutional Networks For Large-Scale Image Recognition" <https://arxiv.org/pdf/1409.1556.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
batch_norm (bool, optional): If True, returns a model with batch_norm layer. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`VGG <api_paddle_vision_models_VGG>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of VGG 16-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import vgg16
>>> # build model
>>> model = vgg16()
>>> # build vgg16 model with batch_norm
>>> model = vgg16(batch_norm=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
model_name = 'vgg16'
if batch_norm:
model_name += '_bn'
return _vgg(model_name, 'D', batch_norm, pretrained, **kwargs)
def vgg19(
pretrained: bool = False,
batch_norm: bool = False,
**kwargs: Unpack[_VGGOptions],
) -> VGG:
"""VGG 19-layer model from
`"Very Deep Convolutional Networks For Large-Scale Image Recognition" <https://arxiv.org/pdf/1409.1556.pdf>`_.
Args:
pretrained (bool, optional): Whether to load pre-trained weights. If True, returns a model pre-trained
on ImageNet. Default: False.
batch_norm (bool, optional): If True, returns a model with batch_norm layer. Default: False.
**kwargs (optional): Additional keyword arguments. For details, please refer to :ref:`VGG <api_paddle_vision_models_VGG>`.
Returns:
:ref:`api_paddle_nn_Layer`. An instance of VGG 19-layer model.
Examples:
.. code-block:: pycon
>>> import paddle
>>> from paddle.vision.models import vgg19
>>> # build model
>>> model = vgg19()
>>> # build vgg19 model with batch_norm
>>> model = vgg19(batch_norm=True)
>>> x = paddle.rand([1, 3, 224, 224])
>>> out = model(x)
>>> print(out.shape)
paddle.Size([1, 1000])
"""
model_name = 'vgg19'
if batch_norm:
model_name += '_bn'
return _vgg(model_name, 'E', batch_norm, pretrained, **kwargs)
+2621
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,97 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .functional import (
adjust_brightness,
adjust_contrast,
adjust_hue,
affine,
center_crop,
crop,
erase,
hflip,
normalize,
pad,
perspective,
resize,
rotate,
to_grayscale,
to_tensor,
vflip,
)
from .transforms import (
BaseTransform,
BrightnessTransform,
CenterCrop,
ColorJitter,
Compose,
ContrastTransform,
Grayscale,
HueTransform,
Normalize,
Pad,
RandomAffine,
RandomCrop,
RandomErasing,
RandomHorizontalFlip,
RandomPerspective,
RandomResizedCrop,
RandomRotation,
RandomVerticalFlip,
Resize,
SaturationTransform,
ToTensor,
Transpose,
)
__all__ = [
'BaseTransform',
'Compose',
'Resize',
'RandomResizedCrop',
'CenterCrop',
'RandomHorizontalFlip',
'RandomVerticalFlip',
'Transpose',
'Normalize',
'BrightnessTransform',
'SaturationTransform',
'ContrastTransform',
'HueTransform',
'ColorJitter',
'RandomCrop',
'Pad',
'RandomAffine',
'RandomRotation',
'RandomPerspective',
'Grayscale',
'ToTensor',
'RandomErasing',
'to_tensor',
'hflip',
'vflip',
'resize',
'pad',
'affine',
'rotate',
'perspective',
'to_grayscale',
'crop',
'center_crop',
'adjust_brightness',
'adjust_contrast',
'adjust_hue',
'normalize',
'erase',
]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,732 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import math
import numbers
from collections.abc import Iterable, Sequence
import numpy as np
import paddle
from paddle.utils import try_import
__all__ = []
def to_tensor(pic, data_format='CHW'):
"""Converts a ``numpy.ndarray`` to paddle.Tensor.
See ``ToTensor`` for more details.
Args:
pic (np.ndarray): Image to be converted to tensor.
data_format (str, optional): Data format of output tensor, should be 'HWC' or
'CHW'. Default: 'CHW'.
Returns:
Tensor: Converted image.
"""
if data_format not in ['CHW', 'HWC']:
raise ValueError(f'data_format should be CHW or HWC. Got {data_format}')
if pic.ndim == 2:
pic = pic[:, :, None]
if data_format == 'CHW':
img = paddle.to_tensor(pic.transpose((2, 0, 1)))
else:
img = paddle.to_tensor(pic)
if paddle.base.data_feeder.convert_dtype(img.dtype) == 'uint8':
return paddle.cast(img, np.float32) / 255.0
else:
return img
def resize(img, size, interpolation='bilinear'):
"""
Resizes the image to given size
Args:
input (np.ndarray): Image to be resized.
size (int|list|tuple): Target size of input data, with (height, width) shape.
interpolation (int|str, optional): Interpolation method. when use cv2 backend,
support method are as following:
- "nearest": cv2.INTER_NEAREST,
- "bilinear": cv2.INTER_LINEAR,
- "area": cv2.INTER_AREA,
- "bicubic": cv2.INTER_CUBIC,
- "lanczos": cv2.INTER_LANCZOS4
Returns:
np.array: Resized image.
"""
cv2 = try_import('cv2')
_cv2_interp_from_str = {
'nearest': cv2.INTER_NEAREST,
'bilinear': cv2.INTER_LINEAR,
'area': cv2.INTER_AREA,
'bicubic': cv2.INTER_CUBIC,
'lanczos': cv2.INTER_LANCZOS4,
}
if not (
isinstance(size, int) or (isinstance(size, Iterable) and len(size) == 2)
):
raise TypeError(f'Got inappropriate size arg: {size}')
h, w = img.shape[:2]
if isinstance(size, int):
if (w <= h and w == size) or (h <= w and h == size):
return img
if w < h:
ow = size
oh = int(size * h / w)
output = cv2.resize(
img,
dsize=(ow, oh),
interpolation=_cv2_interp_from_str[interpolation],
)
else:
oh = size
ow = int(size * w / h)
output = cv2.resize(
img,
dsize=(ow, oh),
interpolation=_cv2_interp_from_str[interpolation],
)
else:
output = cv2.resize(
img,
dsize=(size[1], size[0]),
interpolation=_cv2_interp_from_str[interpolation],
)
if len(img.shape) == 3 and img.shape[2] == 1:
return output[:, :, np.newaxis]
else:
return output
def pad(img, padding, fill=0, padding_mode='constant'):
"""
Pads the given numpy.array on all sides with specified padding mode and fill value.
Args:
img (np.array): Image to be padded.
padding (int|list|tuple): Padding on each border. If a single int is provided this
is used to pad all borders. If list/tuple of length 2 is provided this is the padding
on left/right and top/bottom respectively. If a list/tuple of length 4 is provided
this is the padding for the left, top, right and bottom borders
respectively.
fill (float, optional): Pixel fill value for constant fill. If a tuple of
length 3, it is used to fill R, G, B channels respectively.
This value is only used when the padding_mode is constant. Default: 0.
padding_mode: Type of padding. Should be: constant, edge, reflect or symmetric. Default: 'constant'.
- constant: pads with a constant value, this value is specified with fill
- edge: pads with the last value on the edge of the image
- reflect: pads with reflection of image (without repeating the last value on the edge)
padding [1, 2, 3, 4] with 2 elements on both sides in reflect mode
will result in [3, 2, 1, 2, 3, 4, 3, 2]
- symmetric: pads with reflection of image (repeating the last value on the edge)
padding [1, 2, 3, 4] with 2 elements on both sides in symmetric mode
will result in [2, 1, 1, 2, 3, 4, 4, 3]
Returns:
np.array: Padded image.
"""
cv2 = try_import('cv2')
_cv2_pad_from_str = {
'constant': cv2.BORDER_CONSTANT,
'edge': cv2.BORDER_REPLICATE,
'reflect': cv2.BORDER_REFLECT_101,
'symmetric': cv2.BORDER_REFLECT,
}
if not isinstance(padding, (numbers.Number, list, tuple)):
raise TypeError('Got inappropriate padding arg')
if not isinstance(fill, (numbers.Number, str, list, tuple)):
raise TypeError('Got inappropriate fill arg')
if not isinstance(padding_mode, str):
raise TypeError('Got inappropriate padding_mode arg')
if isinstance(padding, Sequence) and len(padding) not in [2, 4]:
raise ValueError(
"Padding must be an int or a 2, or 4 element tuple, not a "
+ f"{len(padding)} element tuple"
)
assert padding_mode in [
'constant',
'edge',
'reflect',
'symmetric',
], 'Padding mode should be either constant, edge, reflect or symmetric'
if isinstance(padding, list):
padding = tuple(padding)
if isinstance(padding, int):
pad_left = pad_right = pad_top = pad_bottom = padding
if isinstance(padding, Sequence) and len(padding) == 2:
pad_left = pad_right = padding[0]
pad_top = pad_bottom = padding[1]
if isinstance(padding, Sequence) and len(padding) == 4:
pad_left = padding[0]
pad_top = padding[1]
pad_right = padding[2]
pad_bottom = padding[3]
if len(img.shape) == 3 and img.shape[2] == 1:
return cv2.copyMakeBorder(
img,
top=pad_top,
bottom=pad_bottom,
left=pad_left,
right=pad_right,
borderType=_cv2_pad_from_str[padding_mode],
value=fill,
)[:, :, np.newaxis]
else:
return cv2.copyMakeBorder(
img,
top=pad_top,
bottom=pad_bottom,
left=pad_left,
right=pad_right,
borderType=_cv2_pad_from_str[padding_mode],
value=fill,
)
def crop(img, top, left, height, width):
"""Crops the given image.
Args:
img (np.array): Image to be cropped. (0,0) denotes the top left
corner of the image.
top (int): Vertical component of the top left corner of the crop box.
left (int): Horizontal component of the top left corner of the crop box.
height (int): Height of the crop box.
width (int): Width of the crop box.
Returns:
np.array: Cropped image.
"""
return img[top : top + height, left : left + width, :]
def center_crop(img, output_size):
"""Crops the given image and resize it to desired size.
Args:
img (np.array): Image to be cropped. (0,0) denotes the top left corner of the image.
output_size (sequence or int): (height, width) of the crop box. If int,
it is used for both directions
backend (str, optional): The image process backend type. Options are `pil`, `cv2`. Default: 'pil'.
Returns:
np.array: Cropped image.
"""
if isinstance(output_size, numbers.Number):
output_size = (int(output_size), int(output_size))
h, w = img.shape[0:2]
th, tw = output_size
i = int(round((h - th) / 2.0))
j = int(round((w - tw) / 2.0))
return crop(img, i, j, th, tw)
def hflip(img):
"""Horizontally flips the given image.
Args:
img (np.array): Image to be flipped.
Returns:
np.array: Horizontally flipped image.
"""
cv2 = try_import('cv2')
return cv2.flip(img, 1)
def vflip(img):
"""Vertically flips the given np.array.
Args:
img (np.array): Image to be flipped.
Returns:
np.array: Vertically flipped image.
"""
cv2 = try_import('cv2')
if len(img.shape) == 3 and img.shape[2] == 1:
return cv2.flip(img, 0)[:, :, np.newaxis]
else:
return cv2.flip(img, 0)
def adjust_brightness(img, brightness_factor):
"""Adjusts brightness of an image.
Args:
img (np.array): Image to be adjusted.
brightness_factor (float): How much to adjust the brightness. Can be
any non negative number. 0 gives a black image, 1 gives the
original image while 2 increases the brightness by a factor of 2.
Returns:
np.array: Brightness adjusted image.
"""
cv2 = try_import('cv2')
table = (
np.array([i * brightness_factor for i in range(0, 256)])
.clip(0, 255)
.astype('uint8')
)
if len(img.shape) == 3 and img.shape[2] == 1:
return cv2.LUT(img, table)[:, :, np.newaxis]
else:
return cv2.LUT(img, table)
def adjust_contrast(img, contrast_factor):
"""Adjusts contrast of an image.
Args:
img (np.array): Image to be adjusted.
contrast_factor (float): How much to adjust the contrast. Can be any
non negative number. 0 gives a solid gray image, 1 gives the
original image while 2 increases the contrast by a factor of 2.
Returns:
np.array: Contrast adjusted image.
"""
cv2 = try_import('cv2')
table = (
np.array([(i - 74) * contrast_factor + 74 for i in range(0, 256)])
.clip(0, 255)
.astype('uint8')
)
if len(img.shape) == 3 and img.shape[2] == 1:
return cv2.LUT(img, table)[:, :, np.newaxis]
else:
return cv2.LUT(img, table)
def adjust_saturation(img, saturation_factor):
"""Adjusts color saturation of an image.
Args:
img (np.array): Image to be adjusted.
saturation_factor (float): How much to adjust the saturation. 0 will
give a black and white image, 1 will give the original image while
2 will enhance the saturation by a factor of 2.
Returns:
np.array: Saturation adjusted image.
"""
cv2 = try_import('cv2')
dtype = img.dtype
img = img.astype(np.float32)
alpha = np.random.uniform(
max(0, 1 - saturation_factor), 1 + saturation_factor
)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_img = gray_img[..., np.newaxis]
img = img * alpha + gray_img * (1 - alpha)
return img.clip(0, 255).astype(dtype)
def adjust_hue(img, hue_factor):
"""Adjusts hue of an image.
The image hue is adjusted by converting the image to HSV and
cyclically shifting the intensities in the hue channel (H).
The image is then converted back to original image mode.
`hue_factor` is the amount of shift in H channel and must be in the
interval `[-0.5, 0.5]`.
Args:
img (np.array): Image to be adjusted.
hue_factor (float): How much to shift the hue channel. Should be in
[-0.5, 0.5]. 0.5 and -0.5 give complete reversal of hue channel in
HSV space in positive and negative direction respectively.
0 means no shift. Therefore, both -0.5 and 0.5 will give an image
with complementary colors while 0 gives the original image.
Returns:
np.array: Hue adjusted image.
"""
cv2 = try_import('cv2')
if not (-0.5 <= hue_factor <= 0.5):
raise ValueError(f'hue_factor:{hue_factor} is not in [-0.5, 0.5].')
dtype = img.dtype
img = img.astype(np.uint8)
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV_FULL)
h, s, v = cv2.split(hsv_img)
alpha = hue_factor
h = h.astype(np.int32) # Convert to int32 to prevent overflow
# uint8 addition takes care of rotation across boundaries
h = (h + int(alpha * 255)) % 256 # Ensure values are within [0, 255]
h = h.astype(np.uint8) # Convert back to uint8
hsv_img = cv2.merge([h, s, v])
return cv2.cvtColor(hsv_img, cv2.COLOR_HSV2BGR_FULL).astype(dtype)
def affine(
img,
angle,
translate,
scale,
shear,
interpolation='nearest',
fill=0,
center=None,
):
"""Affine the image by matrix.
Args:
img (PIL.Image): Image to be affined.
translate (sequence or int): horizontal and vertical translations
scale (float): overall scale ratio
shear (sequence or float): shear angle value in degrees between -180 to 180, clockwise direction.
If a sequence is specified, the first value corresponds to a shear parallel to the x axis, while
the second value corresponds to a shear parallel to the y axis.
interpolation (int|str, optional): Interpolation method. If omitted, or if the
image has only one channel, it is set to cv2.INTER_NEAREST.
when use cv2 backend, support method are as following:
- "nearest": cv2.INTER_NEAREST,
- "bilinear": cv2.INTER_LINEAR,
- "bicubic": cv2.INTER_CUBIC
fill (3-tuple or int): RGB pixel fill value for area outside the affined image.
If int, it is used for all channels respectively.
center (sequence, optional): Optional center of rotation. Origin is the upper left corner.
Default is the center of the image.
Returns:
np.array: Affined image.
"""
cv2 = try_import('cv2')
_cv2_interp_from_str = {
'nearest': cv2.INTER_NEAREST,
'bilinear': cv2.INTER_LINEAR,
'area': cv2.INTER_AREA,
'bicubic': cv2.INTER_CUBIC,
'lanczos': cv2.INTER_LANCZOS4,
}
h, w = img.shape[0:2]
if isinstance(fill, int):
fill = tuple([fill] * 3)
if center is None:
center = (w / 2.0, h / 2.0)
M = np.ones([2, 3])
# Rotate and Scale
R = cv2.getRotationMatrix2D(angle=angle, center=center, scale=scale)
# Shear
sx = math.tan(shear[0] * math.pi / 180)
sy = math.tan(shear[1] * math.pi / 180)
M[0] = R[0] + sy * R[1]
M[1] = R[1] + sx * R[0]
# Translation
tx, ty = translate
M[0, 2] = tx
M[1, 2] = ty
if len(img.shape) == 3 and img.shape[2] == 1:
return cv2.warpAffine(
img,
M,
dsize=(w, h),
flags=_cv2_interp_from_str[interpolation],
borderValue=fill,
)[:, :, np.newaxis]
else:
return cv2.warpAffine(
img,
M,
dsize=(w, h),
flags=_cv2_interp_from_str[interpolation],
borderValue=fill,
)
def rotate(
img, angle, interpolation='nearest', expand=False, center=None, fill=0
):
"""Rotates the image by angle.
Args:
img (np.array): Image to be rotated.
angle (float or int): In degrees degrees counter clockwise order.
interpolation (int|str, optional): Interpolation method. If omitted, or if the
image has only one channel, it is set to cv2.INTER_NEAREST.
when use cv2 backend, support method are as following:
- "nearest": cv2.INTER_NEAREST,
- "bilinear": cv2.INTER_LINEAR,
- "bicubic": cv2.INTER_CUBIC
expand (bool, optional): Optional expansion flag.
If true, expands the output image to make it large enough to hold the entire rotated image.
If false or omitted, make the output image the same size as the input image.
Note that the expand flag assumes rotation around the center and no translation.
center (2-tuple, optional): Optional center of rotation.
Origin is the upper left corner.
Default is the center of the image.
fill (3-tuple or int): RGB pixel fill value for area outside the rotated image.
If int, it is used for all channels respectively.
Returns:
np.array: Rotated image.
"""
cv2 = try_import('cv2')
_cv2_interp_from_str = {
'nearest': cv2.INTER_NEAREST,
'bilinear': cv2.INTER_LINEAR,
'area': cv2.INTER_AREA,
'bicubic': cv2.INTER_CUBIC,
'lanczos': cv2.INTER_LANCZOS4,
}
h, w = img.shape[0:2]
if center is None:
center = (w / 2.0, h / 2.0)
M = cv2.getRotationMatrix2D(center, angle, 1)
if expand:
def transform(x, y, matrix):
(a, b, c, d, e, f) = matrix
return a * x + b * y + c, d * x + e * y + f
# calculate output size
xx = []
yy = []
angle = -math.radians(angle)
expand_matrix = [
round(math.cos(angle), 15),
round(math.sin(angle), 15),
0.0,
round(-math.sin(angle), 15),
round(math.cos(angle), 15),
0.0,
]
post_trans = (0, 0)
expand_matrix[2], expand_matrix[5] = transform(
-center[0] - post_trans[0],
-center[1] - post_trans[1],
expand_matrix,
)
expand_matrix[2] += center[0]
expand_matrix[5] += center[1]
for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
x, y = transform(x, y, expand_matrix)
xx.append(x)
yy.append(y)
nw = math.ceil(max(xx)) - math.floor(min(xx))
nh = math.ceil(max(yy)) - math.floor(min(yy))
M[0, 2] += (nw - w) * 0.5
M[1, 2] += (nh - h) * 0.5
w, h = int(nw), int(nh)
if len(img.shape) == 3 and img.shape[2] == 1:
return cv2.warpAffine(
img,
M,
(w, h),
flags=_cv2_interp_from_str[interpolation],
borderValue=fill,
)[:, :, np.newaxis]
else:
return cv2.warpAffine(
img,
M,
(w, h),
flags=_cv2_interp_from_str[interpolation],
borderValue=fill,
)
def perspective(img, startpoints, endpoints, interpolation='nearest', fill=0):
"""Perspective the image.
Args:
img (np.array): Image to be perspectived.
startpoints (list[list[int]]): [top-left, top-right, bottom-right, bottom-left] of the original image,
endpoints (list[list[int]]): [top-left, top-right, bottom-right, bottom-left] of the transformed image.
interpolation (int|str, optional): Interpolation method. If omitted, or if the
image has only one channel, it is set to cv2.INTER_NEAREST.
when use cv2 backend, support method are as following:
- "nearest": cv2.INTER_NEAREST,
- "bilinear": cv2.INTER_LINEAR,
- "bicubic": cv2.INTER_CUBIC
fill (3-tuple or int): RGB pixel fill value for area outside the rotated image.
If int, it is used for all channels respectively.
Returns:
np.array: Perspectived image.
"""
cv2 = try_import('cv2')
_cv2_interp_from_str = {
'nearest': cv2.INTER_NEAREST,
'bilinear': cv2.INTER_LINEAR,
'area': cv2.INTER_AREA,
'bicubic': cv2.INTER_CUBIC,
'lanczos': cv2.INTER_LANCZOS4,
}
h, w = img.shape[0:2]
startpoints = np.array(startpoints, dtype="float32")
endpoints = np.array(endpoints, dtype="float32")
matrix = cv2.getPerspectiveTransform(startpoints, endpoints)
if len(img.shape) == 3 and img.shape[2] == 1:
return cv2.warpPerspective(
img,
matrix,
dsize=(w, h),
flags=_cv2_interp_from_str[interpolation],
borderValue=fill,
)[:, :, np.newaxis]
else:
return cv2.warpPerspective(
img,
matrix,
dsize=(w, h),
flags=_cv2_interp_from_str[interpolation],
borderValue=fill,
)
def to_grayscale(img, num_output_channels=1):
"""Converts image to grayscale version of image.
Args:
img (np.array): Image to be converted to grayscale.
Returns:
np.array: Grayscale version of the image.
if num_output_channels = 1 : returned image is single channel
if num_output_channels = 3 : returned image is 3 channel with r = g = b
"""
cv2 = try_import('cv2')
if num_output_channels == 1:
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)[:, :, np.newaxis]
elif num_output_channels == 3:
# much faster than doing cvtColor to go back to gray
img = np.broadcast_to(
cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)[:, :, np.newaxis], img.shape
)
else:
raise ValueError('num_output_channels should be either 1 or 3')
return img
def normalize(img, mean, std, data_format='CHW', to_rgb=False):
"""Normalizes a ndarray image or image with mean and standard deviation.
Args:
img (np.array): input data to be normalized.
mean (list|tuple): Sequence of means for each channel.
std (list|tuple): Sequence of standard deviations for each channel.
data_format (str, optional): Data format of img, should be 'HWC' or
'CHW'. Default: 'CHW'.
to_rgb (bool, optional): Whether to convert to rgb. Default: False.
Returns:
np.array: Normalized mage.
"""
if data_format == 'CHW':
mean = np.float32(np.array(mean).reshape(-1, 1, 1))
std = np.float32(np.array(std).reshape(-1, 1, 1))
else:
mean = np.float32(np.array(mean).reshape(1, 1, -1))
std = np.float32(np.array(std).reshape(1, 1, -1))
if to_rgb:
# inplace
img = img[..., ::-1]
img = (img - mean) / std
return img
def erase(img, i, j, h, w, v, inplace=False):
"""Erase the pixels of selected area in input image array with given value.
Args:
img (np.array): input image array, which shape is (H, W, C).
i (int): y coordinate of the top-left point of erased region.
j (int): x coordinate of the top-left point of erased region.
h (int): Height of the erased region.
w (int): Width of the erased region.
v (np.array): value used to replace the pixels in erased region.
inplace (bool, optional): Whether this transform is inplace. Default: False.
Returns:
np.array: Erased image.
"""
if not inplace:
img = img.copy()
img[i : i + h, j : j + w, ...] = v
return img
@@ -0,0 +1,567 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import numbers
from collections.abc import Iterable, Sequence
import numpy as np
from PIL import Image, ImageEnhance, ImageOps
import paddle
try:
# PIL version >= "9.1.0"
_pil_interp_from_str = {
'nearest': Image.Resampling.NEAREST,
'bilinear': Image.Resampling.BILINEAR,
'bicubic': Image.Resampling.BICUBIC,
'box': Image.Resampling.BOX,
'lanczos': Image.Resampling.LANCZOS,
'hamming': Image.Resampling.HAMMING,
}
except:
_pil_interp_from_str = {
'nearest': Image.NEAREST,
'bilinear': Image.BILINEAR,
'bicubic': Image.BICUBIC,
'box': Image.BOX,
'lanczos': Image.LANCZOS,
'hamming': Image.HAMMING,
}
__all__ = []
def to_tensor(pic, data_format='CHW'):
"""Converts a ``PIL.Image`` to paddle.Tensor.
See ``ToTensor`` for more details.
Args:
pic (PIL.Image): Image to be converted to tensor.
data_format (str, optional): Data format of output tensor, should be 'HWC' or
'CHW'. Default: 'CHW'.
Returns:
Tensor: Converted image.
"""
if data_format not in ['CHW', 'HWC']:
raise ValueError(f'data_format should be CHW or HWC. Got {data_format}')
# PIL Image
if pic.mode == 'I':
img = paddle.to_tensor(np.asarray(pic, np.int32))
elif pic.mode == 'I;16':
# cast and reshape not support int16
img = paddle.to_tensor(np.asarray(pic, np.int32))
elif pic.mode == 'F':
img = paddle.to_tensor(np.asarray(pic, np.float32))
elif pic.mode == '1':
img = 255 * paddle.to_tensor(np.asarray(pic, np.uint8))
else:
img = paddle.to_tensor(np.asarray(pic))
if pic.mode == 'YCbCr':
nchannel = 3
elif pic.mode == 'I;16':
nchannel = 1
else:
nchannel = len(pic.mode)
dtype = paddle.base.data_feeder.convert_dtype(img.dtype)
if dtype == 'uint8':
img = paddle.cast(img, np.float32) / 255.0
img = img.reshape([pic.size[1], pic.size[0], nchannel])
if data_format == 'CHW':
img = img.transpose([2, 0, 1])
return img
def resize(img, size, interpolation='bilinear'):
"""
Resizes the image to given size
Args:
input (PIL.Image): Image to be resized.
size (int|list|tuple): Target size of input data, with (height, width) shape.
interpolation (int|str, optional): Interpolation method. when use pil backend,
support method are as following:
- "nearest": Image.NEAREST,
- "bilinear": Image.BILINEAR,
- "bicubic": Image.BICUBIC,
- "box": Image.BOX,
- "lanczos": Image.LANCZOS,
- "hamming": Image.HAMMING
Returns:
PIL.Image: Resized image.
"""
if not (
isinstance(size, int) or (isinstance(size, Iterable) and len(size) == 2)
):
raise TypeError(f'Got inappropriate size arg: {size}')
if isinstance(size, int):
w, h = img.size
if (w <= h and w == size) or (h <= w and h == size):
return img
if w < h:
ow = size
oh = int(size * h / w)
return img.resize((ow, oh), _pil_interp_from_str[interpolation])
else:
oh = size
ow = int(size * w / h)
return img.resize((ow, oh), _pil_interp_from_str[interpolation])
else:
return img.resize(size[::-1], _pil_interp_from_str[interpolation])
def pad(img, padding, fill=0, padding_mode='constant'):
"""
Pads the given PIL.Image on all sides with specified padding mode and fill value.
Args:
img (PIL.Image): Image to be padded.
padding (int|list|tuple): Padding on each border. If a single int is provided this
is used to pad all borders. If list/tuple of length 2 is provided this is the padding
on left/right and top/bottom respectively. If a list/tuple of length 4 is provided
this is the padding for the left, top, right and bottom borders
respectively.
fill (float, optional): Pixel fill value for constant fill. If a tuple of
length 3, it is used to fill R, G, B channels respectively.
This value is only used when the padding_mode is constant. Default: 0.
padding_mode: Type of padding. Should be: constant, edge, reflect or symmetric. Default: 'constant'.
- constant: pads with a constant value, this value is specified with fill
- edge: pads with the last value on the edge of the image
- reflect: pads with reflection of image (without repeating the last value on the edge)
padding [1, 2, 3, 4] with 2 elements on both sides in reflect mode
will result in [3, 2, 1, 2, 3, 4, 3, 2]
- symmetric: pads with reflection of image (repeating the last value on the edge)
padding [1, 2, 3, 4] with 2 elements on both sides in symmetric mode
will result in [2, 1, 1, 2, 3, 4, 4, 3]
Returns:
PIL.Image: Padded image.
"""
if not isinstance(padding, (numbers.Number, list, tuple)):
raise TypeError('Got inappropriate padding arg')
if not isinstance(fill, (numbers.Number, str, list, tuple)):
raise TypeError('Got inappropriate fill arg')
if not isinstance(padding_mode, str):
raise TypeError('Got inappropriate padding_mode arg')
if isinstance(padding, Sequence) and len(padding) not in [2, 4]:
raise ValueError(
"Padding must be an int or a 2, or 4 element tuple, not a "
+ f"{len(padding)} element tuple"
)
assert padding_mode in [
'constant',
'edge',
'reflect',
'symmetric',
], 'Padding mode should be either constant, edge, reflect or symmetric'
if isinstance(padding, list):
padding = tuple(padding)
if isinstance(padding, int):
pad_left = pad_right = pad_top = pad_bottom = padding
if isinstance(padding, Sequence) and len(padding) == 2:
pad_left = pad_right = padding[0]
pad_top = pad_bottom = padding[1]
if isinstance(padding, Sequence) and len(padding) == 4:
pad_left = padding[0]
pad_top = padding[1]
pad_right = padding[2]
pad_bottom = padding[3]
if padding_mode == 'constant':
if img.mode == 'P':
palette = img.getpalette()
image = ImageOps.expand(img, border=padding, fill=fill)
image.putpalette(palette)
return image
return ImageOps.expand(img, border=padding, fill=fill)
else:
if img.mode == 'P':
palette = img.getpalette()
img = np.asarray(img)
img = np.pad(
img,
((pad_top, pad_bottom), (pad_left, pad_right)),
padding_mode,
)
img = Image.fromarray(img)
img.putpalette(palette)
return img
img = np.asarray(img)
# RGB image
if len(img.shape) == 3:
img = np.pad(
img,
((pad_top, pad_bottom), (pad_left, pad_right), (0, 0)),
padding_mode,
)
# Grayscale image
if len(img.shape) == 2:
img = np.pad(
img,
((pad_top, pad_bottom), (pad_left, pad_right)),
padding_mode,
)
return Image.fromarray(img)
def crop(img, top, left, height, width):
"""Crops the given PIL Image.
Args:
img (PIL.Image): Image to be cropped. (0,0) denotes the top left
corner of the image.
top (int): Vertical component of the top left corner of the crop box.
left (int): Horizontal component of the top left corner of the crop box.
height (int): Height of the crop box.
width (int): Width of the crop box.
Returns:
PIL.Image: Cropped image.
"""
return img.crop((left, top, left + width, top + height))
def center_crop(img, output_size):
"""Crops the given PIL Image and resize it to desired size.
Args:
img (PIL.Image): Image to be cropped. (0,0) denotes the top left corner of the image.
output_size (sequence or int): (height, width) of the crop box. If int,
it is used for both directions
backend (str, optional): The image process backend type. Options are `pil`, `cv2`. Default: 'pil'.
Returns:
PIL.Image: Cropped image.
"""
if isinstance(output_size, numbers.Number):
output_size = (int(output_size), int(output_size))
image_width, image_height = img.size
crop_height, crop_width = output_size
crop_top = int(round((image_height - crop_height) / 2.0))
crop_left = int(round((image_width - crop_width) / 2.0))
return crop(img, crop_top, crop_left, crop_height, crop_width)
def hflip(img):
"""Horizontally flips the given PIL Image.
Args:
img (PIL.Image): Image to be flipped.
Returns:
PIL.Image: Horizontally flipped image.
"""
return img.transpose(Image.FLIP_LEFT_RIGHT)
def vflip(img):
"""Vertically flips the given PIL Image.
Args:
img (PIL.Image): Image to be flipped.
Returns:
PIL.Image: Vertically flipped image.
"""
return img.transpose(Image.FLIP_TOP_BOTTOM)
def adjust_brightness(img, brightness_factor):
"""Adjusts brightness of an Image.
Args:
img (PIL.Image): PIL Image to be adjusted.
brightness_factor (float): How much to adjust the brightness. Can be
any non negative number. 0 gives a black image, 1 gives the
original image while 2 increases the brightness by a factor of 2.
Returns:
PIL.Image: Brightness adjusted image.
"""
enhancer = ImageEnhance.Brightness(img)
img = enhancer.enhance(brightness_factor)
return img
def adjust_contrast(img, contrast_factor):
"""Adjusts contrast of an Image.
Args:
img (PIL.Image): PIL Image to be adjusted.
contrast_factor (float): How much to adjust the contrast. Can be any
non negative number. 0 gives a solid gray image, 1 gives the
original image while 2 increases the contrast by a factor of 2.
Returns:
PIL.Image: Contrast adjusted image.
"""
enhancer = ImageEnhance.Contrast(img)
img = enhancer.enhance(contrast_factor)
return img
def adjust_saturation(img, saturation_factor):
"""Adjusts color saturation of an image.
Args:
img (PIL.Image): PIL Image to be adjusted.
saturation_factor (float): How much to adjust the saturation. 0 will
give a black and white image, 1 will give the original image while
2 will enhance the saturation by a factor of 2.
Returns:
PIL.Image: Saturation adjusted image.
"""
enhancer = ImageEnhance.Color(img)
img = enhancer.enhance(saturation_factor)
return img
def adjust_hue(img, hue_factor):
"""Adjusts hue of an image.
The image hue is adjusted by converting the image to HSV and
cyclically shifting the intensities in the hue channel (H).
The image is then converted back to original image mode.
`hue_factor` is the amount of shift in H channel and must be in the
interval `[-0.5, 0.5]`.
Args:
img (PIL.Image): PIL Image to be adjusted.
hue_factor (float): How much to shift the hue channel. Should be in
[-0.5, 0.5]. 0.5 and -0.5 give complete reversal of hue channel in
HSV space in positive and negative direction respectively.
0 means no shift. Therefore, both -0.5 and 0.5 will give an image
with complementary colors while 0 gives the original image.
Returns:
PIL.Image: Hue adjusted image.
"""
if not (-0.5 <= hue_factor <= 0.5):
raise ValueError(f'hue_factor:{hue_factor} is not in [-0.5, 0.5].')
input_mode = img.mode
if input_mode in {'L', '1', 'I', 'F'}:
return img
h, s, v = img.convert('HSV').split()
np_h = np.array(h, dtype=np.uint8)
np_h = np_h.astype(np.int16)
np_h = (np_h + int(hue_factor * 255)) % 256
np_h = np_h.astype(np.uint8)
h = Image.fromarray(np_h, 'L')
img = Image.merge('HSV', (h, s, v)).convert(input_mode)
return img
def affine(img, matrix, interpolation="nearest", fill=0):
"""Affine the image by matrix.
Args:
img (PIL.Image): Image to be affined.
matrix (float or int): Affine matrix.
interpolation (str, optional): Interpolation method. If omitted, or if the
image has only one channel, it is set to PIL.Image.NEAREST . when use pil backend,
support method are as following:
- "nearest": Image.NEAREST,
- "bilinear": Image.BILINEAR,
- "bicubic": Image.BICUBIC
fill (3-tuple or int): RGB pixel fill value for area outside the affined image.
If int, it is used for all channels respectively.
Returns:
PIL.Image: Affined image.
"""
if isinstance(fill, int):
fill = tuple([fill] * 3)
return img.transform(
img.size,
Image.AFFINE,
matrix,
_pil_interp_from_str[interpolation],
fill,
)
def rotate(
img, angle, interpolation="nearest", expand=False, center=None, fill=0
):
"""Rotates the image by angle.
Args:
img (PIL.Image): Image to be rotated.
angle (float or int): In degrees degrees counter clockwise order.
interpolation (str, optional): Interpolation method. If omitted, or if the
image has only one channel, it is set to PIL.Image.NEAREST . when use pil backend,
support method are as following:
- "nearest": Image.NEAREST,
- "bilinear": Image.BILINEAR,
- "bicubic": Image.BICUBIC
expand (bool, optional): Optional expansion flag.
If true, expands the output image to make it large enough to hold the entire rotated image.
If false or omitted, make the output image the same size as the input image.
Note that the expand flag assumes rotation around the center and no translation.
center (2-tuple, optional): Optional center of rotation.
Origin is the upper left corner.
Default is the center of the image.
fill (3-tuple or int): RGB pixel fill value for area outside the rotated image.
If int, it is used for all channels respectively.
Returns:
PIL.Image: Rotated image.
"""
if isinstance(fill, int):
fill = tuple([fill] * 3)
return img.rotate(
angle,
_pil_interp_from_str[interpolation],
expand,
center,
fillcolor=fill,
)
def perspective(img, coeffs, interpolation="nearest", fill=0):
"""Perspective the image.
Args:
img (PIL.Image): Image to be perspectived.
coeffs (list[float]): coefficients (a, b, c, d, e, f, g, h) of the perspective transforms.
interpolation (str, optional): Interpolation method. If omitted, or if the
image has only one channel, it is set to PIL.Image.NEAREST . when use pil backend,
support method are as following:
- "nearest": Image.NEAREST,
- "bilinear": Image.BILINEAR,
- "bicubic": Image.BICUBIC
fill (3-tuple or int): RGB pixel fill value for area outside the rotated image.
If int, it is used for all channels respectively.
Returns:
PIL.Image: Perspectived image.
"""
if isinstance(fill, int):
fill = tuple([fill] * 3)
return img.transform(
img.size,
Image.PERSPECTIVE,
coeffs,
_pil_interp_from_str[interpolation],
fill,
)
def to_grayscale(img, num_output_channels=1):
"""Converts image to grayscale version of image.
Args:
img (PIL.Image): Image to be converted to grayscale.
backend (str, optional): The image process backend type. Options are `pil`,
`cv2`. Default: 'pil'.
Returns:
PIL.Image: Grayscale version of the image.
if num_output_channels = 1 : returned image is single channel
if num_output_channels = 3 : returned image is 3 channel with r = g = b
"""
if num_output_channels == 1:
img = img.convert('L')
elif num_output_channels == 3:
img = img.convert('L')
np_img = np.array(img, dtype=np.uint8)
np_img = np.dstack([np_img, np_img, np_img])
img = Image.fromarray(np_img, 'RGB')
else:
raise ValueError('num_output_channels should be either 1 or 3')
return img
def erase(img, i, j, h, w, v, inplace=False):
"""Erase the pixels of selected area in input image with given value. PIL format is
not support inplace.
Args:
img (PIL.Image): input image, which shape is (C, H, W).
i (int): y coordinate of the top-left point of erased region.
j (int): x coordinate of the top-left point of erased region.
h (int): Height of the erased region.
w (int): Width of the erased region.
v (np.array): value used to replace the pixels in erased region.
inplace (bool, optional): Whether this transform is inplace. Default: False.
Returns:
PIL.Image: Erased image.
"""
np_img = np.array(img, dtype=np.uint8)
np_img[i : i + h, j : j + w, ...] = v
img = Image.fromarray(np_img, 'RGB')
return img
@@ -0,0 +1,956 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import math
import numbers
import numpy as np
import paddle
import paddle.nn.functional as F
from ...base.framework import Variable
from ...base.libpaddle.pir import Value
__all__ = []
def _assert_image_tensor(img, data_format):
if (
not isinstance(img, (paddle.Tensor, Variable, Value))
or img.ndim < 3
or img.ndim > 4
or data_format.lower() not in ('chw', 'hwc')
):
raise RuntimeError(
f'not support [type={type(img)}, ndim={img.ndim}, data_format={data_format}] paddle image'
)
def _get_image_h_axis(data_format):
if data_format.lower() == 'chw':
return -2
elif data_format.lower() == 'hwc':
return -3
def _get_image_w_axis(data_format):
if data_format.lower() == 'chw':
return -1
elif data_format.lower() == 'hwc':
return -2
def _get_image_c_axis(data_format):
if data_format.lower() == 'chw':
return -3
elif data_format.lower() == 'hwc':
return -1
def _get_image_n_axis(data_format):
if len(data_format) == 3:
return None
elif len(data_format) == 4:
return 0
def _is_channel_last(data_format):
return _get_image_c_axis(data_format) == -1
def _is_channel_first(data_format):
return _get_image_c_axis(data_format) == -3
def _get_image_num_batches(img, data_format):
if _get_image_n_axis(data_format):
return img.shape[_get_image_n_axis(data_format)]
return None
def _get_image_num_channels(img, data_format):
return img.shape[_get_image_c_axis(data_format)]
def _get_image_size(img, data_format):
return (
img.shape[_get_image_w_axis(data_format)],
img.shape[_get_image_h_axis(data_format)],
)
def _rgb_to_hsv(img):
"""Convert a image Tensor from RGB to HSV. This implementation is based on Pillow (
https://github.com/python-pillow/Pillow/blob/main/src/libImaging/Convert.c)
"""
maxc = img.max(axis=-3)
minc = img.min(axis=-3)
is_equal = paddle.equal(maxc, minc)
one_divisor = paddle.ones_like(maxc)
c_delta = maxc - minc
# s is 0 when maxc == minc, set the divisor to 1 to avoid zero divide.
s = c_delta / paddle.where(is_equal, one_divisor, maxc)
r, g, b = img.unbind(axis=-3)
c_delta_divisor = paddle.where(is_equal, one_divisor, c_delta)
# when maxc == minc, there is r == g == b, set the divisor to 1 to avoid zero divide.
rc = (maxc - r) / c_delta_divisor
gc = (maxc - g) / c_delta_divisor
bc = (maxc - b) / c_delta_divisor
hr = (maxc == r).astype(maxc.dtype) * (bc - gc)
hg = ((maxc == g) & (maxc != r)).astype(maxc.dtype) * (rc - bc + 2.0)
hb = ((maxc != r) & (maxc != g)).astype(maxc.dtype) * (gc - rc + 4.0)
h = (hr + hg + hb) / 6.0 + 1.0
h = h - h.trunc()
return paddle.stack([h, s, maxc], axis=-3)
def _hsv_to_rgb(img):
"""Convert a image Tensor from HSV to RGB."""
h, s, v = img.unbind(axis=-3)
f = h * 6.0
i = paddle.floor(f)
f = f - i
i = i.astype(paddle.int32) % 6
p = paddle.clip(v * (1.0 - s), 0.0, 1.0)
q = paddle.clip(v * (1.0 - s * f), 0.0, 1.0)
t = paddle.clip(v * (1.0 - s * (1.0 - f)), 0.0, 1.0)
mask = paddle.equal(
i.unsqueeze(axis=-3),
paddle.arange(6, dtype=i.dtype).reshape((-1, 1, 1)),
).astype(img.dtype)
matrix = paddle.stack(
[
paddle.stack([v, q, p, p, t, v], axis=-3),
paddle.stack([t, v, v, q, p, p], axis=-3),
paddle.stack([p, p, t, v, v, q], axis=-3),
],
axis=-4,
)
return paddle.einsum("...ijk, ...xijk -> ...xjk", mask, matrix)
def _blend_images(img1, img2, ratio):
max_value = 1.0 if paddle.is_floating_point(img1) else 255.0
return (
paddle.lerp(img2, img1, float(ratio))
.clip(0, max_value)
.astype(img1.dtype)
)
def normalize(img, mean, std, data_format='CHW'):
"""Normalizes a tensor image given mean and standard deviation.
Args:
img (paddle.Tensor): input data to be normalized.
mean (list|tuple): Sequence of means for each channel.
std (list|tuple): Sequence of standard deviations for each channel.
data_format (str, optional): Data format of img, should be 'HWC' or
'CHW'. Default: 'CHW'.
Returns:
Tensor: Normalized mage.
"""
_assert_image_tensor(img, data_format)
mean = paddle.to_tensor(mean, place=img.place)
std = paddle.to_tensor(std, place=img.place)
if _is_channel_first(data_format):
mean = mean.reshape([-1, 1, 1])
std = std.reshape([-1, 1, 1])
return (img - mean) / std
def to_grayscale(img, num_output_channels=1, data_format='CHW'):
"""Converts image to grayscale version of image.
Args:
img (paddle.Tensor): Image to be converted to grayscale.
num_output_channels (int, optional[1, 3]):
if num_output_channels = 1 : returned image is single channel
if num_output_channels = 3 : returned image is 3 channel
data_format (str, optional): Data format of img, should be 'HWC' or
'CHW'. Default: 'CHW'.
Returns:
paddle.Tensor: Grayscale version of the image.
"""
_assert_image_tensor(img, data_format)
if num_output_channels not in (1, 3):
raise ValueError('num_output_channels should be either 1 or 3')
rgb_weights = paddle.to_tensor(
[0.2989, 0.5870, 0.1140], place=img.place
).astype(img.dtype)
if _is_channel_first(data_format):
rgb_weights = rgb_weights.reshape((-1, 1, 1))
_c_index = _get_image_c_axis(data_format)
img = (img * rgb_weights).sum(axis=_c_index, keepdim=True)
_shape = img.shape
_shape[_c_index] = num_output_channels
return img.expand(_shape)
def _affine_grid(theta, w, h, ow, oh):
d = 0.5
base_grid = paddle.ones((1, oh, ow, 3), dtype=theta.dtype)
x_grid = paddle.linspace(-ow * 0.5 + d, ow * 0.5 + d - 1, ow)
if paddle.in_dynamic_mode():
y_grid = paddle.linspace(
-oh * 0.5 + d, oh * 0.5 + d - 1, oh
).unsqueeze_(-1)
base_grid[..., 0] = x_grid
base_grid[..., 1] = y_grid
tmp = paddle.to_tensor([0.5 * w, 0.5 * h])
else:
# To eliminate the warning:
# In static mode, unsqueeze_() is the same as unsqueeze() and does not perform inplace operation.
y_grid = paddle.linspace(-oh * 0.5 + d, oh * 0.5 + d - 1, oh).unsqueeze(
-1
)
base_grid = paddle.static.setitem(base_grid, (..., 0), x_grid)
base_grid = paddle.static.setitem(base_grid, (..., 1), y_grid)
tmp = paddle.assign(np.array([0.5 * w, 0.5 * h], dtype="float32"))
scaled_theta = theta.transpose((0, 2, 1)) / tmp
output_grid = base_grid.reshape((1, oh * ow, 3)).bmm(scaled_theta)
return output_grid.reshape((1, oh, ow, 2))
def _grid_transform(img, grid, mode, fill):
if img.shape[0] > 1:
grid = grid.expand(
shape=[img.shape[0], grid.shape[1], grid.shape[2], grid.shape[3]]
)
if fill is not None:
dummy = paddle.ones(
(img.shape[0], 1, img.shape[2], img.shape[3]), dtype=img.dtype
)
img = paddle.concat((img, dummy), axis=1)
img = F.grid_sample(
img, grid, mode=mode, padding_mode="zeros", align_corners=False
)
# Fill with required color
if fill is not None:
mask = img[:, -1:, :, :] # n 1 h w
img = img[:, :-1, :, :] # n c h w
mask = mask.tile([1, img.shape[1], 1, 1])
len_fill = len(fill) if isinstance(fill, (tuple, list)) else 1
if paddle.in_dynamic_mode():
fill_img = (
paddle.to_tensor(fill)
.reshape((1, len_fill, 1, 1))
.astype(img.dtype)
.expand_as(img)
)
else:
fill = np.array(fill).reshape(len_fill).astype("float32")
fill_img = paddle.ones_like(img) * paddle.assign(fill).reshape(
[1, len_fill, 1, 1]
)
if mode == 'nearest':
mask = paddle.cast(mask < 0.5, img.dtype)
img = img * (1.0 - mask) + mask * fill_img
else: # 'bilinear'
img = img * mask + (1.0 - mask) * fill_img
return img
def affine(img, matrix, interpolation="nearest", fill=None, data_format='CHW'):
"""Affine to the image by matrix.
Args:
img (paddle.Tensor): Image to be rotated.
matrix (float or int): Affine matrix.
interpolation (str, optional): Interpolation method. If omitted, or if the
image has only one channel, it is set NEAREST . when use pil backend,
support method are as following:
- "nearest"
- "bilinear"
- "bicubic"
fill (3-tuple or int): RGB pixel fill value for area outside the rotated image.
If int, it is used for all channels respectively.
data_format (str, optional): Data format of img, should be 'HWC' or
'CHW'. Default: 'CHW'.
Returns:
paddle.Tensor: Affined image.
"""
ndim = len(img.shape)
if ndim == 3:
img = img.unsqueeze(0)
img = img if data_format.lower() == 'chw' else img.transpose((0, 3, 1, 2))
matrix = paddle.to_tensor(matrix, place=img.place)
matrix = matrix.reshape((1, 2, 3))
shape = img.shape
grid = _affine_grid(
matrix, w=shape[-1], h=shape[-2], ow=shape[-1], oh=shape[-2]
)
if isinstance(fill, int):
fill = tuple([fill] * 3)
out = _grid_transform(img, grid, mode=interpolation, fill=fill)
out = out if data_format.lower() == 'chw' else out.transpose((0, 2, 3, 1))
out = out.squeeze(0) if ndim == 3 else out
return out
def rotate(
img,
angle,
interpolation='nearest',
expand=False,
center=None,
fill=None,
data_format='CHW',
):
"""Rotates the image by angle.
Args:
img (paddle.Tensor): Image to be rotated.
angle (float or int): In degrees degrees counter clockwise order.
interpolation (str, optional): Interpolation method. If omitted, or if the
image has only one channel, it is set NEAREST . when use pil backend,
support method are as following:
- "nearest"
- "bilinear"
- "bicubic"
expand (bool, optional): Optional expansion flag.
If true, expands the output image to make it large enough to hold the entire rotated image.
If false or omitted, make the output image the same size as the input image.
Note that the expand flag assumes rotation around the center and no translation.
center (2-tuple, optional): Optional center of rotation.
Origin is the upper left corner.
Default is the center of the image.
fill (3-tuple or int): RGB pixel fill value for area outside the rotated image.
If int, it is used for all channels respectively.
Returns:
paddle.Tensor: Rotated image.
"""
angle = -angle % 360
img = img.unsqueeze(0)
# n, c, h, w = img.shape
w, h = _get_image_size(img, data_format=data_format)
img = img if data_format.lower() == 'chw' else img.transpose((0, 3, 1, 2))
post_trans = [0, 0]
if center is None:
rotn_center = [0, 0]
else:
rotn_center = [(p - s * 0.5) for p, s in zip(center, [w, h])]
if paddle.in_dynamic_mode():
angle = math.radians(angle)
matrix = [
math.cos(angle),
math.sin(angle),
0.0,
-math.sin(angle),
math.cos(angle),
0.0,
]
matrix = paddle.to_tensor(matrix, place=img.place)
matrix[2] += (
matrix[0] * (-rotn_center[0] - post_trans[0])
+ matrix[1] * (-rotn_center[1] - post_trans[1])
+ rotn_center[0]
)
matrix[5] += (
matrix[3] * (-rotn_center[0] - post_trans[0])
+ matrix[4] * (-rotn_center[1] - post_trans[1])
+ rotn_center[1]
)
else:
angle = angle / 180 * math.pi
matrix = paddle.concat(
[
paddle.cos(angle),
paddle.sin(angle),
paddle.zeros([1]),
-paddle.sin(angle),
paddle.cos(angle),
paddle.zeros([1]),
]
)
matrix = paddle.static.setitem(
matrix,
2,
matrix[2]
+ matrix[0] * (-rotn_center[0] - post_trans[0])
+ matrix[1] * (-rotn_center[1] - post_trans[1])
+ rotn_center[0],
)
matrix = paddle.static.setitem(
matrix,
5,
matrix[5]
+ matrix[3] * (-rotn_center[0] - post_trans[0])
+ matrix[4] * (-rotn_center[1] - post_trans[1])
+ rotn_center[1],
)
matrix = matrix.reshape((1, 2, 3))
if expand:
# calculate output size
if paddle.in_dynamic_mode():
corners = paddle.to_tensor(
[
[-0.5 * w, -0.5 * h, 1.0],
[-0.5 * w, 0.5 * h, 1.0],
[0.5 * w, 0.5 * h, 1.0],
[0.5 * w, -0.5 * h, 1.0],
],
place=matrix.place,
).astype(matrix.dtype)
else:
corners = paddle.assign(
[
[-0.5 * w, -0.5 * h, 1.0],
[-0.5 * w, 0.5 * h, 1.0],
[0.5 * w, 0.5 * h, 1.0],
[0.5 * w, -0.5 * h, 1.0],
],
).astype(matrix.dtype)
_pos = (
corners.reshape((1, -1, 3))
.bmm(matrix.transpose((0, 2, 1)))
.reshape((1, -1, 2))
)
_min = _pos.min(axis=-2).floor()
_max = _pos.max(axis=-2).ceil()
npos = _max - _min
nw = npos[0][0]
nh = npos[0][1]
if paddle.in_dynamic_mode():
ow, oh = int(nw), int(nh)
else:
ow, oh = nw.astype("int32"), nh.astype("int32")
else:
ow, oh = w, h
grid = _affine_grid(matrix, w, h, ow, oh)
out = _grid_transform(img, grid, mode=interpolation, fill=fill)
out = out if data_format.lower() == 'chw' else out.transpose((0, 2, 3, 1))
return out.squeeze(0)
def _perspective_grid(img, coeffs, ow, oh, dtype):
theta1 = coeffs[:6].reshape([1, 2, 3])
tmp = paddle.tile(coeffs[6:].reshape([1, 2]), repeat_times=[2, 1])
dummy = paddle.ones((2, 1), dtype=dtype)
theta2 = paddle.concat((tmp, dummy), axis=1).unsqueeze(0)
d = 0.5
base_grid = paddle.ones((1, oh, ow, 3), dtype=dtype)
x_grid = paddle.linspace(d, ow * 1.0 + d - 1.0, ow)
base_grid[..., 0] = x_grid
y_grid = paddle.linspace(d, oh * 1.0 + d - 1.0, oh).unsqueeze_(-1)
base_grid[..., 1] = y_grid
scaled_theta1 = theta1.transpose((0, 2, 1)) / paddle.to_tensor(
[0.5 * ow, 0.5 * oh]
)
output_grid1 = base_grid.reshape((1, oh * ow, 3)).bmm(scaled_theta1)
output_grid2 = base_grid.reshape((1, oh * ow, 3)).bmm(
theta2.transpose((0, 2, 1))
)
output_grid = output_grid1 / output_grid2 - 1.0
return output_grid.reshape((1, oh, ow, 2))
def perspective(
img, coeffs, interpolation="nearest", fill=None, data_format='CHW'
):
"""Perspective the image.
Args:
img (paddle.Tensor): Image to be rotated.
coeffs (list[float]): coefficients (a, b, c, d, e, f, g, h) of the perspective transforms.
interpolation (str, optional): Interpolation method. If omitted, or if the
image has only one channel, it is set NEAREST. When use pil backend,
support method are as following:
- "nearest"
- "bilinear"
- "bicubic"
fill (3-tuple or int): RGB pixel fill value for area outside the rotated image.
If int, it is used for all channels respectively.
Returns:
paddle.Tensor: Perspectived image.
"""
ndim = len(img.shape)
if ndim == 3:
img = img.unsqueeze(0)
img = img if data_format.lower() == 'chw' else img.transpose((0, 3, 1, 2))
ow, oh = img.shape[-1], img.shape[-2]
dtype = img.dtype if paddle.is_floating_point(img) else paddle.float32
coeffs = paddle.to_tensor(coeffs, place=img.place)
grid = _perspective_grid(img, coeffs, ow=ow, oh=oh, dtype=dtype)
out = _grid_transform(img, grid, mode=interpolation, fill=fill)
out = out if data_format.lower() == 'chw' else out.transpose((0, 2, 3, 1))
out = out.squeeze(0) if ndim == 3 else out
return out
def vflip(img, data_format='CHW'):
"""Vertically flips the given paddle tensor.
Args:
img (paddle.Tensor): Image to be flipped.
data_format (str, optional): Data format of img, should be 'HWC' or
'CHW'. Default: 'CHW'.
Returns:
paddle.Tensor: Vertically flipped image.
"""
_assert_image_tensor(img, data_format)
h_axis = _get_image_h_axis(data_format)
return img.flip(axis=[h_axis])
def hflip(img, data_format='CHW'):
"""Horizontally flips the given paddle.Tensor Image.
Args:
img (paddle.Tensor): Image to be flipped.
data_format (str, optional): Data format of img, should be 'HWC' or
'CHW'. Default: 'CHW'.
Returns:
paddle.Tensor: Horizontally flipped image.
"""
_assert_image_tensor(img, data_format)
w_axis = _get_image_w_axis(data_format)
return img.flip(axis=[w_axis])
def crop(img, top, left, height, width, data_format='CHW'):
"""Crops the given paddle.Tensor Image.
Args:
img (paddle.Tensor): Image to be cropped. (0,0) denotes the top left
corner of the image.
top (int): Vertical component of the top left corner of the crop box.
left (int): Horizontal component of the top left corner of the crop box.
height (int): Height of the crop box.
width (int): Width of the crop box.
data_format (str, optional): Data format of img, should be 'HWC' or
'CHW'. Default: 'CHW'.
Returns:
paddle.Tensor: Cropped image.
"""
_assert_image_tensor(img, data_format)
if _is_channel_first(data_format):
return img[:, top : top + height, left : left + width]
else:
return img[top : top + height, left : left + width, :]
def erase(img, i, j, h, w, v, inplace=False):
"""Erase the pixels of selected area in input Tensor image with given value.
Args:
img (paddle.Tensor): input Tensor image.
i (int): y coordinate of the top-left point of erased region.
j (int): x coordinate of the top-left point of erased region.
h (int): Height of the erased region.
w (int): Width of the erased region.
v (paddle.Tensor): value used to replace the pixels in erased region.
inplace (bool, optional): Whether this transform is inplace. Default: False.
Returns:
paddle.Tensor: Erased image.
"""
_assert_image_tensor(img, 'CHW')
if not inplace:
img = img.clone()
if paddle.in_dynamic_mode():
img[..., i : i + h, j : j + w] = v
else:
img = paddle.static.setitem(
img, (..., slice(i, i + h), slice(j, j + w)), v
)
return img
def center_crop(img, output_size, data_format='CHW'):
"""Crops the given paddle.Tensor Image and resize it to desired size.
Args:
img (paddle.Tensor): Image to be cropped. (0,0) denotes the top left corner of the image.
output_size (sequence or int): (height, width) of the crop box. If int,
it is used for both directions
data_format (str, optional): Data format of img, should be 'HWC' or
'CHW'. Default: 'CHW'.
Returns:
paddle.Tensor: Cropped image.
"""
_assert_image_tensor(img, data_format)
if isinstance(output_size, numbers.Number):
output_size = (int(output_size), int(output_size))
image_width, image_height = _get_image_size(img, data_format)
crop_height, crop_width = output_size
crop_top = int(round((image_height - crop_height) / 2.0))
crop_left = int(round((image_width - crop_width) / 2.0))
return crop(
img,
crop_top,
crop_left,
crop_height,
crop_width,
data_format=data_format,
)
def pad(img, padding, fill=0, padding_mode='constant', data_format='CHW'):
"""
Pads the given paddle.Tensor on all sides with specified padding mode and fill value.
Args:
img (paddle.Tensor): Image to be padded.
padding (int|list|tuple): Padding on each border. If a single int is provided this
is used to pad all borders. If tuple of length 2 is provided this is the padding
on left/right and top/bottom respectively. If a tuple of length 4 is provided
this is the padding for the left, top, right and bottom borders
respectively.
fill (float, optional): Pixel fill value for constant fill. If a tuple of
length 3, it is used to fill R, G, B channels respectively.
This value is only used when the padding_mode is constant. Default: 0.
padding_mode: Type of padding. Should be: constant, edge, reflect or symmetric. Default: 'constant'.
- constant: pads with a constant value, this value is specified with fill
- edge: pads with the last value on the edge of the image
- reflect: pads with reflection of image (without repeating the last value on the edge)
padding [1, 2, 3, 4] with 2 elements on both sides in reflect mode
will result in [3, 2, 1, 2, 3, 4, 3, 2]
- symmetric: pads with reflection of image (repeating the last value on the edge)
padding [1, 2, 3, 4] with 2 elements on both sides in symmetric mode
will result in [2, 1, 1, 2, 3, 4, 4, 3]
Returns:
paddle.Tensor: Padded image.
"""
_assert_image_tensor(img, data_format)
if not isinstance(padding, (numbers.Number, list, tuple)):
raise TypeError('Got inappropriate padding arg')
if not isinstance(fill, (numbers.Number, str, list, tuple)):
raise TypeError('Got inappropriate fill arg')
if not isinstance(padding_mode, str):
raise TypeError('Got inappropriate padding_mode arg')
if isinstance(padding, (list, tuple)) and len(padding) not in [2, 4]:
raise ValueError(
"Padding must be an int or a 2, or 4 element tuple, not a "
+ f"{len(padding)} element tuple"
)
assert padding_mode in [
'constant',
'edge',
'reflect',
'symmetric',
], 'Padding mode should be either constant, edge, reflect or symmetric'
if isinstance(padding, int):
pad_left = pad_right = pad_top = pad_bottom = padding
elif len(padding) == 2:
pad_left = pad_right = padding[0]
pad_top = pad_bottom = padding[1]
else:
pad_left = padding[0]
pad_top = padding[1]
pad_right = padding[2]
pad_bottom = padding[3]
padding = [pad_left, pad_right, pad_top, pad_bottom]
if padding_mode == 'edge':
padding_mode = 'replicate'
elif padding_mode == 'symmetric':
raise ValueError('Do not support symmetric mode')
img = img.unsqueeze(0)
# 'constant', 'reflect', 'replicate', 'circular'
img = F.pad(
img,
pad=padding,
mode=padding_mode,
value=float(fill),
data_format='N' + data_format,
)
return img.squeeze(0)
def resize(img, size, interpolation='bilinear', data_format='CHW'):
"""
Resizes the image to given size
Args:
input (paddle.Tensor): Image to be resized.
size (int|list|tuple): Target size of input data, with (height, width) shape.
interpolation (int|str, optional): Interpolation method. when use paddle backend,
support method are as following:
- "nearest"
- "bilinear"
- "bicubic"
- "trilinear"
- "area"
- "linear"
data_format (str, optional): paddle.Tensor format
- 'CHW'
- 'HWC'
Returns:
paddle.Tensor: Resized image.
"""
_assert_image_tensor(img, data_format)
if not (
isinstance(size, int)
or (isinstance(size, (tuple, list)) and len(size) == 2)
):
raise TypeError(f'Got inappropriate size arg: {size}')
if isinstance(size, int):
w, h = _get_image_size(img, data_format)
# TODO(Aurelius84): In static graph mode, w and h will be -1 for dynamic shape.
# We should consider to support this case in future.
if w <= 0 or h <= 0:
raise NotImplementedError(
f"Not support while w<=0 or h<=0, but received w={w}, h={h}"
)
if (w <= h and w == size) or (h <= w and h == size):
return img
if w < h:
ow = size
oh = int(size * h / w)
else:
oh = size
ow = int(size * w / h)
else:
oh, ow = size
img = img.unsqueeze(0)
img = F.interpolate(
img,
size=(oh, ow),
mode=interpolation.lower(),
data_format='N' + data_format.upper(),
)
return img.squeeze(0)
def adjust_brightness(img, brightness_factor):
"""Adjusts brightness of an Image.
Args:
img (paddle.Tensor): Image to be adjusted.
brightness_factor (float): How much to adjust the brightness. Can be
any non negative number. 0 gives a black image, 1 gives the
original image while 2 increases the brightness by a factor of 2.
Returns:
paddle.Tensor: Brightness adjusted image.
"""
_assert_image_tensor(img, 'CHW')
assert brightness_factor >= 0, "brightness_factor should be non-negative."
assert _get_image_num_channels(img, 'CHW') in [
1,
3,
], "channels of input should be either 1 or 3."
extreme_target = paddle.zeros_like(img, img.dtype)
return _blend_images(img, extreme_target, brightness_factor)
def adjust_contrast(img, contrast_factor):
"""Adjusts contrast of an image.
Args:
img (paddle.Tensor): Image to be adjusted.
contrast_factor (float): How much to adjust the contrast. Can be any
non negative number. 0 gives a solid gray image, 1 gives the
original image while 2 increases the contrast by a factor of 2.
Returns:
paddle.Tensor: Contrast adjusted image.
"""
_assert_image_tensor(img, 'chw')
assert contrast_factor >= 0, "contrast_factor should be non-negative."
channels = _get_image_num_channels(img, 'CHW')
dtype = img.dtype if paddle.is_floating_point(img) else paddle.float32
if channels == 1:
extreme_target = paddle.mean(
img.astype(dtype), axis=(-3, -2, -1), keepdim=True
)
elif channels == 3:
extreme_target = paddle.mean(
to_grayscale(img).astype(dtype), axis=(-3, -2, -1), keepdim=True
)
else:
raise ValueError("channels of input should be either 1 or 3.")
return _blend_images(img, extreme_target, contrast_factor)
def adjust_saturation(img, saturation_factor):
"""Adjusts color saturation of an image.
Args:
img (paddle.Tensor): Image to be adjusted.
saturation_factor (float): How much to adjust the saturation. 0 will
give a black and white image, 1 will give the original image while
2 will enhance the saturation by a factor of 2.
Returns:
paddle.Tensor: Saturation adjusted image.
"""
_assert_image_tensor(img, 'CHW')
assert saturation_factor >= 0, "saturation_factor should be non-negative."
channels = _get_image_num_channels(img, 'CHW')
if channels == 1:
return img
elif channels == 3:
extreme_target = to_grayscale(img)
else:
raise ValueError("channels of input should be either 1 or 3.")
return _blend_images(img, extreme_target, saturation_factor)
def adjust_hue(img, hue_factor):
"""Adjusts hue of an image.
The image hue is adjusted by converting the image to HSV and
cyclically shifting the intensities in the hue channel (H).
The image is then converted back to original image mode.
`hue_factor` is the amount of shift in H channel and must be in the
interval `[-0.5, 0.5]`.
Args:
img (paddle.Tensor): Image to be adjusted.
hue_factor (float): How much to shift the hue channel. Should be in
[-0.5, 0.5]. 0.5 and -0.5 give complete reversal of hue channel in
HSV space in positive and negative direction respectively.
0 means no shift. Therefore, both -0.5 and 0.5 will give an image
with complementary colors while 0 gives the original image.
Returns:
paddle.Tensor: Hue adjusted image.
"""
_assert_image_tensor(img, 'CHW')
assert hue_factor >= -0.5 and hue_factor <= 0.5, (
"hue_factor should be in range [-0.5, 0.5]"
)
channels = _get_image_num_channels(img, 'CHW')
if channels == 1:
return img
elif channels == 3:
dtype = img.dtype
if dtype == paddle.uint8:
img = img.astype(paddle.float32) / 255.0
img_hsv = _rgb_to_hsv(img)
h, s, v = img_hsv.unbind(axis=-3)
h = h + hue_factor
h = h - h.floor()
img_adjusted = _hsv_to_rgb(paddle.stack([h, s, v], axis=-3))
if dtype == paddle.uint8:
img_adjusted = (img_adjusted * 255.0).astype(dtype)
else:
raise ValueError("channels of input should be either 1 or 3.")
return img_adjusted
File diff suppressed because it is too large Load Diff