-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Add pil backend for vision transforms #28035
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 14 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
2f81c3b
add pil backend
LielinJiang e8d1f06
update comment
LielinJiang 3bb04a8
fix errors
LielinJiang 39dbd62
add example codes
LielinJiang 5e935fb
fix py2
LielinJiang 9858af4
fix unittest
LielinJiang c84449d
fix docs
LielinJiang b8196ba
fix docs, test=document_fix
LielinJiang 2617942
fix docs, test=document_fix
LielinJiang 1f6a7e6
fix docs, test=document_fix
LielinJiang 8b963fe
fix docs, test=document_fix
LielinJiang 9209514
fix docs, test=document_fix
LielinJiang 2ea1485
fix docs, test=document_fix
LielinJiang 28a9697
refine code
LielinJiang a3da96d
clean code, fix coverage
LielinJiang eadc85b
update comment
LielinJiang d203c26
add set_backend get_backend
LielinJiang 66c10a3
add set_backend get_backend
LielinJiang e537a56
fix sample code
LielinJiang b6e612e
add set_backend get_backend
LielinJiang 4df5b62
fix sample code
LielinJiang 16267c3
fix unittest
LielinJiang 4229e88
add image load
LielinJiang 2eb23f0
change utils to image
LielinJiang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,14 +18,19 @@ | |
| import cv2 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. paddle默认不安装cv2的话,这里的cv2依赖哪个特定的版本呢?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 感谢提醒,在functional中改为了lazy_import,这个是单测,无需lazy_import。默认还是让用户使用最新版本的opencv。 |
||
| import shutil | ||
| import numpy as np | ||
| from PIL import Image | ||
|
|
||
| import paddle | ||
| from paddle.vision import get_image_backend, set_image_backend | ||
| from paddle.vision.datasets import DatasetFolder | ||
| from paddle.vision.transforms import transforms | ||
| import paddle.vision.transforms.functional as F | ||
|
|
||
|
|
||
| class TestTransforms(unittest.TestCase): | ||
| class TestTransformsCV2(unittest.TestCase): | ||
| def setUp(self): | ||
| self.backend = self.get_backend() | ||
| set_image_backend(self.backend) | ||
| self.data_dir = tempfile.mkdtemp() | ||
| for i in range(2): | ||
| sub_dir = os.path.join(self.data_dir, 'class_' + str(i)) | ||
|
|
@@ -40,6 +45,22 @@ def setUp(self): | |
| (400, 300, 3)) * 255).astype('uint8') | ||
| cv2.imwrite(os.path.join(sub_dir, str(j) + '.jpg'), fake_img) | ||
|
|
||
| def get_backend(self): | ||
| return 'cv2' | ||
|
|
||
| def create_image(self, shape): | ||
| if self.backend == 'cv2': | ||
| return (np.random.rand(*shape) * 255).astype('uint8') | ||
| elif self.backend == 'pil': | ||
| return Image.fromarray((np.random.rand(*shape) * 255).astype( | ||
| 'uint8')) | ||
|
|
||
| def get_shape(self, img): | ||
| if self.backend == 'pil': | ||
| return np.array(img).shape | ||
|
|
||
| return img.shape | ||
|
|
||
| def tearDown(self): | ||
| shutil.rmtree(self.data_dir) | ||
|
|
||
|
|
@@ -51,41 +72,36 @@ def do_transform(self, trans): | |
|
|
||
| def test_trans_all(self): | ||
| normalize = transforms.Normalize( | ||
| mean=[123.675, 116.28, 103.53], std=[58.395, 57.120, 57.375]) | ||
| mean=[123.675, 116.28, 103.53], | ||
| std=[58.395, 57.120, 57.375], ) | ||
| trans = transforms.Compose([ | ||
| transforms.RandomResizedCrop(224), transforms.GaussianNoise(), | ||
| transforms.RandomResizedCrop(224), | ||
| transforms.ColorJitter( | ||
| brightness=0.4, contrast=0.4, saturation=0.4, | ||
| hue=0.4), transforms.RandomHorizontalFlip(), | ||
| transforms.Permute(mode='CHW'), normalize | ||
| brightness=0.4, contrast=0.4, saturation=0.4, hue=0.4), | ||
| transforms.RandomHorizontalFlip(), | ||
| transforms.Transpose(), | ||
| normalize, | ||
| ]) | ||
|
|
||
| self.do_transform(trans) | ||
|
|
||
| def test_normalize(self): | ||
| normalize = transforms.Normalize(mean=0.5, std=0.5) | ||
| trans = transforms.Compose([transforms.Permute(mode='CHW'), normalize]) | ||
| trans = transforms.Compose([transforms.Transpose(), normalize]) | ||
| self.do_transform(trans) | ||
|
|
||
| def test_trans_resize(self): | ||
| trans = transforms.Compose([ | ||
| transforms.Resize(300, [0, 1]), | ||
| transforms.Resize(300), | ||
| transforms.RandomResizedCrop((280, 280)), | ||
| transforms.Resize(280, [0, 1]), | ||
| transforms.Resize(280), | ||
| transforms.Resize((256, 200)), | ||
| transforms.Resize((180, 160)), | ||
| transforms.CenterCrop(128), | ||
| transforms.CenterCrop((128, 128)), | ||
| ]) | ||
| self.do_transform(trans) | ||
|
|
||
| def test_trans_centerCrop(self): | ||
| trans = transforms.Compose([ | ||
| transforms.CenterCropResize(224), | ||
| transforms.CenterCropResize(128, 160), | ||
| ]) | ||
| self.do_transform(trans) | ||
|
|
||
| def test_flip(self): | ||
| trans = transforms.Compose([ | ||
| transforms.RandomHorizontalFlip(1.0), | ||
|
|
@@ -96,7 +112,7 @@ def test_flip(self): | |
| self.do_transform(trans) | ||
|
|
||
| def test_color_jitter(self): | ||
| trans = transforms.BatchCompose([ | ||
| trans = transforms.Compose([ | ||
| transforms.BrightnessTransform(0.0), | ||
| transforms.HueTransform(0.0), | ||
| transforms.SaturationTransform(0.0), | ||
|
|
@@ -106,11 +122,11 @@ def test_color_jitter(self): | |
|
|
||
| def test_rotate(self): | ||
| trans = transforms.Compose([ | ||
| transforms.RandomRotate(90), | ||
| transforms.RandomRotate([-10, 10]), | ||
| transforms.RandomRotate( | ||
| transforms.RandomRotation(90), | ||
| transforms.RandomRotation([-10, 10]), | ||
| transforms.RandomRotation( | ||
| 45, expand=True), | ||
| transforms.RandomRotate( | ||
| transforms.RandomRotation( | ||
| 10, expand=True, center=(60, 80)), | ||
| ]) | ||
| self.do_transform(trans) | ||
|
|
@@ -119,20 +135,15 @@ def test_pad(self): | |
| trans = transforms.Compose([transforms.Pad(2)]) | ||
| self.do_transform(trans) | ||
|
|
||
| fake_img = np.random.rand(200, 150, 3).astype('float32') | ||
| fake_img = self.create_image((200, 150, 3)) | ||
| trans_pad = transforms.Pad(10) | ||
| fake_img_padded = trans_pad(fake_img) | ||
| np.testing.assert_equal(fake_img_padded.shape, (220, 170, 3)) | ||
| np.testing.assert_equal(self.get_shape(fake_img_padded), (220, 170, 3)) | ||
| trans_pad1 = transforms.Pad([1, 2]) | ||
| trans_pad2 = transforms.Pad([1, 2, 3, 4]) | ||
| img = trans_pad1(fake_img) | ||
| img = trans_pad2(img) | ||
|
|
||
| def test_erase(self): | ||
| trans = transforms.Compose( | ||
| [transforms.RandomErasing(), transforms.RandomErasing(value=0.0)]) | ||
| self.do_transform(trans) | ||
|
|
||
| def test_random_crop(self): | ||
| trans = transforms.Compose([ | ||
| transforms.RandomCrop(200), | ||
|
|
@@ -143,18 +154,19 @@ def test_random_crop(self): | |
| trans_random_crop1 = transforms.RandomCrop(224) | ||
| trans_random_crop2 = transforms.RandomCrop((140, 160)) | ||
|
|
||
| fake_img = np.random.rand(500, 400, 3).astype('float32') | ||
| fake_img = self.create_image((500, 400, 3)) | ||
| fake_img_crop1 = trans_random_crop1(fake_img) | ||
| fake_img_crop2 = trans_random_crop2(fake_img_crop1) | ||
|
|
||
| np.testing.assert_equal(fake_img_crop1.shape, (224, 224, 3)) | ||
| np.testing.assert_equal(self.get_shape(fake_img_crop1), (224, 224, 3)) | ||
|
|
||
| np.testing.assert_equal(fake_img_crop2.shape, (140, 160, 3)) | ||
| np.testing.assert_equal(self.get_shape(fake_img_crop2), (140, 160, 3)) | ||
|
|
||
| trans_random_crop_same = transforms.RandomCrop((140, 160)) | ||
| img = trans_random_crop_same(fake_img_crop2) | ||
|
|
||
| trans_random_crop_bigger = transforms.RandomCrop((180, 200)) | ||
| trans_random_crop_bigger = transforms.RandomCrop( | ||
| (180, 200), pad_if_needed=True) | ||
| img = trans_random_crop_bigger(img) | ||
|
|
||
| trans_random_crop_pad = transforms.RandomCrop((224, 256), 2, True) | ||
|
|
@@ -165,21 +177,38 @@ def test_grayscale(self): | |
| self.do_transform(trans) | ||
|
|
||
| trans_gray = transforms.Grayscale() | ||
| fake_img = np.random.rand(500, 400, 3).astype('float32') | ||
| fake_img = self.create_image((500, 400, 3)) | ||
| fake_img_gray = trans_gray(fake_img) | ||
|
|
||
| np.testing.assert_equal(len(fake_img_gray.shape), 3) | ||
| np.testing.assert_equal(fake_img_gray.shape[0], 500) | ||
| np.testing.assert_equal(fake_img_gray.shape[1], 400) | ||
| np.testing.assert_equal(self.get_shape(fake_img_gray)[0], 500) | ||
| np.testing.assert_equal(self.get_shape(fake_img_gray)[1], 400) | ||
|
|
||
| trans_gray3 = transforms.Grayscale(3) | ||
| fake_img = np.random.rand(500, 400, 3).astype('float32') | ||
| fake_img = self.create_image((500, 400, 3)) | ||
| fake_img_gray = trans_gray3(fake_img) | ||
|
|
||
| def test_tranpose(self): | ||
| trans = transforms.Compose([transforms.Transpose()]) | ||
| self.do_transform(trans) | ||
|
|
||
| fake_img = self.create_image((50, 100, 3)) | ||
| converted_img = trans(fake_img) | ||
|
|
||
| np.testing.assert_equal(self.get_shape(converted_img), (3, 50, 100)) | ||
|
|
||
| def test_to_tensor(self): | ||
| trans = transforms.Compose([transforms.ToTensor()]) | ||
| fake_img = self.create_image((50, 100, 3)) | ||
|
|
||
| tensor = trans(fake_img) | ||
|
|
||
| assert isinstance(tensor, paddle.Tensor) | ||
| np.testing.assert_equal(tensor.shape, (3, 50, 100)) | ||
|
|
||
| def test_exception(self): | ||
| trans = transforms.Compose([transforms.Resize(-1)]) | ||
|
|
||
| trans_batch = transforms.BatchCompose([transforms.Resize(-1)]) | ||
| trans_batch = transforms.Compose([transforms.Resize(-1)]) | ||
|
|
||
| with self.assertRaises(Exception): | ||
| self.do_transform(trans) | ||
|
|
@@ -203,35 +232,153 @@ def test_exception(self): | |
| transforms.Pad([1.0, 2.0, 3.0]) | ||
|
|
||
| with self.assertRaises(TypeError): | ||
| fake_img = np.random.rand(100, 120, 3).astype('float32') | ||
| fake_img = self.create_image((100, 120, 3)) | ||
| F.pad(fake_img, '1') | ||
|
|
||
| with self.assertRaises(TypeError): | ||
| fake_img = np.random.rand(100, 120, 3).astype('float32') | ||
| fake_img = self.create_image((100, 120, 3)) | ||
| F.pad(fake_img, 1, {}) | ||
|
|
||
| with self.assertRaises(TypeError): | ||
| fake_img = np.random.rand(100, 120, 3).astype('float32') | ||
| fake_img = self.create_image((100, 120, 3)) | ||
| F.pad(fake_img, 1, padding_mode=-1) | ||
|
|
||
| with self.assertRaises(ValueError): | ||
| fake_img = np.random.rand(100, 120, 3).astype('float32') | ||
| fake_img = self.create_image((100, 120, 3)) | ||
| F.pad(fake_img, [1.0, 2.0, 3.0]) | ||
|
|
||
| with self.assertRaises(ValueError): | ||
| transforms.RandomRotate(-2) | ||
| transforms.RandomRotation(-2) | ||
|
|
||
| with self.assertRaises(ValueError): | ||
| transforms.RandomRotate([1, 2, 3]) | ||
| transforms.RandomRotation([1, 2, 3]) | ||
|
|
||
| with self.assertRaises(ValueError): | ||
| trans_gray = transforms.Grayscale(5) | ||
| fake_img = np.random.rand(100, 120, 3).astype('float32') | ||
| fake_img = self.create_image((100, 120, 3)) | ||
| trans_gray(fake_img) | ||
|
|
||
| with self.assertRaises(TypeError): | ||
| transform = transforms.RandomResizedCrop(64) | ||
| transform(1) | ||
|
|
||
| with self.assertRaises(ValueError): | ||
| transform = transforms.BrightnessTransform([-0.1, -0.2]) | ||
|
|
||
| with self.assertRaises(TypeError): | ||
| transform = transforms.BrightnessTransform('0.1') | ||
|
|
||
| with self.assertRaises(ValueError): | ||
| transform = transforms.BrightnessTransform('0.1', keys=1) | ||
|
|
||
| with self.assertRaises(NotImplementedError): | ||
| transform = transforms.BrightnessTransform('0.1', keys='a') | ||
|
|
||
| def test_info(self): | ||
| str(transforms.Compose([transforms.Resize((224, 224))])) | ||
| str(transforms.BatchCompose([transforms.Resize((224, 224))])) | ||
| str(transforms.Compose([transforms.Resize((224, 224))])) | ||
|
|
||
|
|
||
| class TestTransformsPIL(TestTransformsCV2): | ||
| def get_backend(self): | ||
| return 'pil' | ||
|
|
||
|
|
||
| class TestFunctional(unittest.TestCase): | ||
| def test_errors(self): | ||
| with self.assertRaises(TypeError): | ||
| F.to_tensor(1) | ||
|
|
||
| with self.assertRaises(ValueError): | ||
| fake_img = Image.fromarray((np.random.rand(28, 28, 3) * 255).astype( | ||
| 'uint8')) | ||
| F.to_tensor(fake_img, data_format=1) | ||
|
|
||
| with self.assertRaises(TypeError): | ||
| fake_img = Image.fromarray((np.random.rand(28, 28, 3) * 255).astype( | ||
| 'uint8')) | ||
| F.resize(fake_img, '1') | ||
|
|
||
| def test_normalize(self): | ||
| np_img = (np.random.rand(28, 24, 3)).astype('uint8') | ||
| pil_img = Image.fromarray(np_img) | ||
| tensor_img = F.to_tensor(pil_img) | ||
| tensor_img_hwc = F.to_tensor(pil_img, data_format='HWC') | ||
|
|
||
| mean = [0.5, 0.5, 0.5] | ||
| std = [0.5, 0.5, 0.5] | ||
|
|
||
| normalized_img = F.normalize(tensor_img, mean, std) | ||
| normalized_img = F.normalize( | ||
| tensor_img_hwc, mean, std, data_format='HWC') | ||
|
|
||
| normalized_img = F.normalize(pil_img, mean, std, data_format='HWC') | ||
| normalized_img = F.normalize( | ||
| np_img, mean, std, data_format='HWC', to_rgb=True) | ||
|
|
||
| def test_center_crop(self): | ||
| np_img = (np.random.rand(28, 24, 3)).astype('uint8') | ||
| pil_img = Image.fromarray(np_img) | ||
|
|
||
| np_cropped_img = F.center_crop(np_img, 4) | ||
| pil_cropped_img = F.center_crop(pil_img, 4) | ||
|
|
||
| np.testing.assert_almost_equal(np_cropped_img, | ||
| np.array(pil_cropped_img)) | ||
|
|
||
| def test_pad(self): | ||
| np_img = (np.random.rand(28, 24, 3)).astype('uint8') | ||
| pil_img = Image.fromarray(np_img) | ||
|
|
||
| np_padded_img = F.pad(np_img, [1, 2], padding_mode='reflect') | ||
| pil_padded_img = F.pad(pil_img, [1, 2], padding_mode='reflect') | ||
|
|
||
| np.testing.assert_almost_equal(np_padded_img, np.array(pil_padded_img)) | ||
|
|
||
| pil_p_img = pil_img.convert('P') | ||
| pil_padded_img = F.pad(pil_p_img, [1, 2]) | ||
| pil_padded_img = F.pad(pil_p_img, [1, 2], padding_mode='reflect') | ||
|
|
||
| def test_resize(self): | ||
| np_img = (np.zeros([28, 24, 3])).astype('uint8') | ||
| pil_img = Image.fromarray(np_img) | ||
|
|
||
| np_reseized_img = F.resize(np_img, 40) | ||
| pil_reseized_img = F.resize(pil_img, 40) | ||
|
|
||
| np.testing.assert_almost_equal(np_reseized_img, | ||
| np.array(pil_reseized_img)) | ||
|
|
||
| gray_img = (np.zeros([28, 32])).astype('uint8') | ||
| gray_resize_img = F.resize(gray_img, 40) | ||
|
|
||
| def test_to_tensor(self): | ||
| np_img = (np.random.rand(28, 28) * 255).astype('uint8') | ||
| pil_img = Image.fromarray(np_img) | ||
|
|
||
| np_tensor = F.to_tensor(np_img, data_format='HWC') | ||
| pil_tensor = F.to_tensor(pil_img, data_format='HWC') | ||
|
|
||
| np.testing.assert_allclose(np_tensor.numpy(), pil_tensor.numpy()) | ||
|
|
||
| # test float dtype | ||
| float_img = np.random.rand(28, 28) | ||
| float_tensor = F.to_tensor(float_img) | ||
|
|
||
| pil_img = Image.fromarray(np_img).convert('I') | ||
| pil_tensor = F.to_tensor(pil_img) | ||
|
|
||
| pil_img = Image.fromarray(np_img).convert('I;16') | ||
| pil_tensor = F.to_tensor(pil_img) | ||
|
|
||
| pil_img = Image.fromarray(np_img).convert('F') | ||
| pil_tensor = F.to_tensor(pil_img) | ||
|
|
||
| pil_img = Image.fromarray(np_img).convert('1') | ||
| pil_tensor = F.to_tensor(pil_img) | ||
|
|
||
| pil_img = Image.fromarray(np_img).convert('YCbCr') | ||
| pil_tensor = F.to_tensor(pil_img) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lazy import cv2 here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
感谢提醒,在functional中改为了lazy_import,这个是单测,无需lazy_import。