From 7162c2e43e3e4846bc2bc6095d29ad7c18da3aa8 Mon Sep 17 00:00:00 2001 From: eptq002345 <1598440105@qq.com> Date: Tue, 5 Sep 2023 17:12:24 +0800 Subject: [PATCH] Update py_transforms_util.py --- .../dataset/vision/py_transforms_util.py | 1125 ++++++++--------- 1 file changed, 551 insertions(+), 574 deletions(-) diff --git a/mindspore/dataset/vision/py_transforms_util.py b/mindspore/dataset/vision/py_transforms_util.py index 3f203e4d31..aca54b031a 100644 --- a/mindspore/dataset/vision/py_transforms_util.py +++ b/mindspore/dataset/vision/py_transforms_util.py @@ -19,7 +19,6 @@ import math import numbers import random import colorsys - import numpy as np from PIL import Image, ImageOps, ImageEnhance, __version__ @@ -28,100 +27,88 @@ from ..core.py_util_helpers import is_numpy augment_error_message = "img should be PIL image. Got {}. Use Decode() for encoded data or ToPIL() for decoded data." - +#检测输入的图像是否为PIL格式 def is_pil(img): - """ - Check if the input image is PIL format. - - Args: - img: Image to be checked. - - Returns: - Bool, True if input is PIL image. - """ + #返回一个bool值:输入的图像是否为Image.Image类型 return isinstance(img, Image.Image) def normalize(img, mean, std, pad_channel=False, dtype="float32"): - """ - Normalize the image between [0, 1] with respect to mean and standard deviation. - - Args: - img (numpy.ndarray): Image array of shape CHW to be normalized. - mean (list): List of mean values for each channel, w.r.t channel order. - std (list): List of standard deviations for each channel, w.r.t. channel order. - pad_channel (bool): Whether to pad a extra channel with value zero. - dtype (str): Output datatype of normalize, only worked when pad_channel is True. (default is "float32") - - Returns: - img (numpy.ndarray), Normalized image. - """ + # 如果参数img不是数组图像,抛出类型错误异常 if not is_numpy(img): raise TypeError("img should be NumPy image. Got {}.".format(type(img))) - + # 如果参数img不是彩色图像,抛出类型错误异常 if img.ndim != 3: raise TypeError('img dimension should be 3. Got {}.'.format(img.ndim)) - + # 如果img的数据类型是整型,则抛出未成功实施某种函数异常 if np.issubdtype(img.dtype, np.integer): raise NotImplementedError("Unsupported image datatype: [{}], pls execute [ToTensor] before [Normalize]." .format(img.dtype)) - + + # 将输入图的通道数num_channels赋值为图像的高度 num_channels = img.shape[0] # shape is (C, H, W) - + # 如果图像每个通道的均值组成列表的长度不等于其标准差组成列表的长度,抛出数值错误异常 if len(mean) != len(std): raise ValueError("Length of mean and std must be equal.") - # if length equal to 1, adjust the mean and std arrays to have the correct - # number of channels (replicate the values) + # 如果长度等于1,调整mean和std数组赋值为正确的输入图通道数num_channels值(复制数值) if len(mean) == 1: + # 将mean赋值为输入图的通道数num_channels的值 mean = [mean[0]] * num_channels + # 将std赋值为输入图的通道数num_channels的值 std = [std[0]] * num_channels + # 如果mean的长度与图像的通道数不等,则抛出数值错误异常 elif len(mean) != num_channels: raise ValueError("Length of mean and std must both be 1 or equal to the number of channels({0})." .format(num_channels)) + + # 将均值转换为numpy数组 mean = np.array(mean, dtype=img.dtype) + # 将标准差转换为numpy数组 std = np.array(std, dtype=img.dtype) + # 将图像减去均值和标准差 image = (img - mean[:, None, None]) / std[:, None, None] if pad_channel: + # 如果pad_channel为True,则将image的第一个维度拼接到image的最后一个维度上 zeros = np.zeros([1, image.shape[1], image.shape[2]], dtype=np.float32) image = np.concatenate((image, zeros), axis=0) + # 如果dtype为float16,则将image的第一个维度转换为float32 if dtype == "float16": + # 将image转换为float16类型 image = image.astype(np.float16) + # 如果pad_channel为True,则将image的第一个维度值为0,并将image和zeros合并 + zeros = np.zeros([1, image.shape[1], image.shape[2]], dtype=np.float32) + image = np.concatenate((image, zeros), axis=0) + # 如果dtype为float16,则将image转换为float16类型 + if dtype == "float16": + image = image.astype(np.float16) + # 返回image return image def decode(img): - """ - Decode the input image to PIL image format in RGB mode. - - Args: - img: Image to be decoded. - - Returns: - img (PIL image), Decoded image in RGB mode. - """ - + ''' + 将图片解码为RGB格式 + :param img: 图片 + :return: 解码后的图片 + ''' try: data = io.BytesIO(img) + # 将图片转换为RGB格式 img = Image.open(data) + # 返回RGB格式的解码图片 return img.convert('RGB') + # 如果打开失败,抛出数值错误异常 except IOError as e: raise ValueError("{0}\n: Failed to decode given image.".format(e)) + + # 如果图片已经被解码,抛出数值错误异常 except AttributeError as e: raise ValueError("{0}\n: Failed to decode, Image might already be decoded.".format(e)) - def hwc_to_chw(img): - """ - Transpose the input image; shape (H, W, C) to shape (C, H, W). - - Args: - img (numpy.ndarray): Image to be converted. - - Returns: - img (numpy.ndarray), Converted image. - """ + # 将输入图像的shape从 转换为 if not is_numpy(img): raise TypeError('img should be NumPy array. Got {}.'.format(type(img))) if img.ndim != 3: @@ -130,42 +117,44 @@ def hwc_to_chw(img): def to_tensor(img, output_type): - """ - Change the input image (PIL image or NumPy image array) to NumPy format. - - Args: - img (Union[PIL image, numpy.ndarray]): Image to be converted. - output_type: The datatype of the NumPy output. e.g. np.float32 - - Returns: - img (numpy.ndarray), Converted image. - """ + '''将图像转换为tensor格式 + + 参数: + img:图像,可以是PIL图像或NumPy数组 + output_type:输出类型 + 返回: + 转换后的tensor格式 + ''' if not (is_pil(img) or is_numpy(img)): raise TypeError("img should be PIL image or NumPy array. Got {}.".format(type(img))) + # 如果图像是PIL图像,则将其转换为NumPy数组 img = np.asarray(img) + # 如果图像的维度不是2或3,则抛出异常 if img.ndim not in (2, 3): raise TypeError("img dimension should be 2 or 3. Got {}.".format(img.ndim)) + # 如果图像的维度是2,则将其转换为3维 if img.ndim == 2: img = img[:, :, None] + # 将图像转换为CHW格式 img = hwc_to_chw(img) + # 将图像转换为输出类型 img = img / 255. return to_type(img, output_type) def to_pil(img): - """ - Convert the input image to PIL format. - - Args: - img: Image to be converted. - - Returns: - img (PIL image), Converted image. - """ + '''将图片转换为PIL格式 + + 参数: + img : 需要转化为PIL格式的图片 + + 返回: + PIL Image: PIL格式的图片. + ''' if not is_pil(img): if not isinstance(img, np.ndarray): raise TypeError("The input of ToPIL should be ndarray. Got {}".format(type(img))) @@ -174,15 +163,11 @@ def to_pil(img): def horizontal_flip(img): - """ - Flip the input image horizontally. - - Args: - img (PIL image): Image to be flipped horizontally. - - Returns: - img (PIL image), Horizontally flipped image. - """ + ''' + 水平翻转图像 + :param img: PIL图像 + :return: 水平翻转后的图像 + ''' if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -190,15 +175,11 @@ def horizontal_flip(img): def vertical_flip(img): - """ - Flip the input image vertically. - - Args: - img (PIL image): Image to be flipped vertically. - - Returns: - img (PIL image), Vertically flipped image. - """ + ''' + 对图像进行垂直翻转 + :param img: PIL图像 + :return: 垂直翻转后的图像 + ''' if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -206,17 +187,13 @@ def vertical_flip(img): def random_horizontal_flip(img, prob): - """ - Randomly flip the input image horizontally. - - Args: - img (PIL image): Image to be flipped. - If the given probability is above the random probability, then the image is flipped. - prob (float): Probability of the image being flipped. - - Returns: - img (PIL image), Converted image. - """ + ''' + 随机水平翻转图片 + :param img: PIL图片 + :param prob: 概率 + :return: 水平翻转后的图片 + ''' + if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -226,17 +203,13 @@ def random_horizontal_flip(img, prob): def random_vertical_flip(img, prob): - """ - Randomly flip the input image vertically. - - Args: - img (PIL image): Image to be flipped. - If the given probability is above the random probability, then the image is flipped. - prob (float): Probability of the image being flipped. - - Returns: - img (PIL image), Converted image. - """ + ''' + 参数: + img:图像 + prob:随机翻转概率 + 返回: + img:随机翻转后的图像 + ''' if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -247,10 +220,10 @@ def random_vertical_flip(img, prob): def crop(img, top, left, height, width): """ - Crop the input PIL image. + Crop the input PIL Image. Args: - img (PIL image): Image to be cropped. (0,0) denotes the top left corner of the image, + img (PIL.Image.Image): Image to be cropped. (0,0) denotes the top left corner of the image, in the directions of (width, height). 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. @@ -258,7 +231,7 @@ def crop(img, top, left, height, width): width (int): Width of the crop box. Returns: - img (PIL image), Cropped image. + PIL.Image.Image, cropped image. """ if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -267,85 +240,72 @@ def crop(img, top, left, height, width): def resize(img, size, interpolation=Inter.BILINEAR): - """ - Resize the input PIL image to desired size. - - Args: - img (PIL image): Image to be resized. - size (Union[int, sequence]): The output size of the resized image. - If size is an integer, smaller edge of the image will be resized to this value with - the same image aspect ratio. - If size is a sequence of (height, width), this will be the desired output size. - interpolation (interpolation mode): Image interpolation mode. Default is Inter.BILINEAR = 2. - - Returns: - img (PIL image), Resized image. - """ + '''对输入图像调整为给定的尺寸大小 + 参数: + img : 被调整的图像 + size : 期望输出大小. + interpolation : 期望的插值 + 返回: + img:调整后的图像 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 判断size是否是int或者list/tuple if not (isinstance(size, int) or (isinstance(size, (list, tuple)) and len(size) == 2)): raise TypeError('Size should be a single number or a list/tuple (h, w) of length 2.' 'Got {}.'.format(size)) + # size是int if isinstance(size, int): + # 获取图片的宽度和高度 img_width, img_height = img.size + # 获取图片的宽高比 aspect_ratio = img_width / img_height # maintain the aspect ratio + # 判断图片的宽度是否小于高度 if (img_width <= img_height and img_width == size) or \ (img_height <= img_width and img_height == size): return img + # 判断图片的宽度是否小于高度的比例 if img_width < img_height: + # 设置输出宽度和高度 out_width = size out_height = int(size / aspect_ratio) + # 返回缩放后的图片 return img.resize((out_width, out_height), interpolation) + # 设置输出高度和宽度 out_height = size out_width = int(size * aspect_ratio) + # 返回缩放后的图片 return img.resize((out_width, out_height), interpolation) + # 返回缩放后的图片 return img.resize(size[::-1], interpolation) -def center_crop(img, size): - """ - Crop the input PIL image at the center to the given size. - - Args: - img (PIL image): Image to be cropped. - size (Union[int, tuple]): The size of the crop box. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, it should be (height, width). - Returns: - img (PIL image), Cropped image. - """ +# 定义一个函数,用于对图像进行中心裁剪 +def center_crop(img, size): + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 如果输入的size是一个整数,则将其转换为元组 if isinstance(size, int): size = (size, size) + # 获取图像的宽度和高度 img_width, img_height = img.size + # 计算裁剪的高度和宽度 crop_height, crop_width = size + # 计算裁剪的上边距 crop_top = int(round((img_height - crop_height) / 2.)) + # 计算裁剪的左边距 crop_left = int(round((img_width - crop_width) / 2.)) + # 返回裁剪后的图像 return crop(img, crop_top, crop_left, crop_height, crop_width) def random_resize_crop(img, size, scale, ratio, interpolation=Inter.BILINEAR, max_attempts=10): - """ - Crop the input PIL image to a random size and aspect ratio. - - Args: - img (PIL image): Image to be randomly cropped and resized. - size (Union[int, sequence]): The size of the output image. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, it should be (height, width). - scale (tuple): Range (min, max) of respective size of the original size to be cropped. - ratio (tuple): Range (min, max) of aspect ratio to be cropped. - interpolation (interpolation mode): Image interpolation mode. Default is Inter.BILINEAR = 2. - max_attempts (int): The maximum number of attempts to propose a valid crop_area. Default 10. - If exceeded, fall back to use center_crop instead. - - Returns: - img (PIL image), Randomly cropped and resized image. - """ + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) if isinstance(size, int): @@ -354,10 +314,11 @@ def random_resize_crop(img, size, scale, ratio, interpolation=Inter.BILINEAR, ma size = size else: raise TypeError("Size should be a single integer or a list/tuple (h, w) of length 2.") - + # 如果缩放范围大于等于放大范围,或者缩放比例大于放大比例 if scale[0] > scale[1] or ratio[0] > ratio[1]: raise ValueError("Range should be in the order of (min, max).") - + + # 输入转因子函数 def _input_to_factor(img, scale, ratio): img_width, img_height = img.size img_area = img_width * img_height @@ -379,90 +340,99 @@ def random_resize_crop(img, size, scale, ratio, interpolation=Inter.BILINEAR, ma # exceeding max_attempts, use center crop img_ratio = img_width / img_height + # 如果图片宽高比小于指定的比例,则宽度等于图片宽度,高度等于图片宽度的指定比例乘以宽度 if img_ratio < ratio[0]: width = img_width height = int(round(width / ratio[0])) + # 如果图片宽高比大于指定的比例,则高度等于图片高度,宽度等于图片高度的指定比例乘以高度 elif img_ratio > ratio[1]: height = img_height width = int(round(height * ratio[1])) + # 如果图片宽高比相等,则宽度等于图片宽度,高度等于图片高度 else: width = img_width height = img_height + # 计算图片的上下左右边界 top = int(round((img_height - height) / 2.)) left = int(round((img_width - width) / 2.)) + # 返回图片的上下左右边界 return top, left, height, width top, left, height, width = _input_to_factor(img, scale, ratio) + # 将图像边界框裁剪 img = crop(img, top, left, height, width) + # 将图像缩放 img = resize(img, size, interpolation) + # 返回缩放后的图像 return img - def random_crop(img, size, padding, pad_if_needed, fill_value, padding_mode): - """ - Crop the input PIL image at a random location. - - Args: - img (PIL image): Image to be randomly cropped. - size (Union[int, sequence]): The output size of the cropped image. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, it should be (height, width). - padding (Union[int, sequence], optional): The number of pixels to pad the image. - If a single number is provided, it pads all borders with this value. - If a tuple or list of 2 values are provided, it pads the (left and top) - with the first value and (right and bottom) with the second value. - If 4 values are provided as a list or tuple, - it pads the left, top, right and bottom respectively. - Default is None. - pad_if_needed (bool): Pad the image if either side is smaller than - the given output size. Default is False. - fill_value (Union[int, tuple]): The pixel intensity of the borders if - the padding_mode is 'constant'. If it is a 3-tuple, it is used to - fill R, G, B channels respectively. - padding_mode (str): The method of padding. Can be any of - ['constant', 'edge', 'reflect', 'symmetric']. - - 'constant', means it fills the border with constant values - - 'edge', means it pads with the last value on the edge - - 'reflect', means it reflects the values on the edge omitting the last - value of edge - - 'symmetric', means it reflects the values on the edge repeating the last - value of edge - - Returns: - PIL image, Cropped image. - """ + ''' + 随机裁剪图片 + :param img: PIL图片 + :param size: 裁剪大小 + :param padding: 填充 + :param pad_if_needed: 是否填充 + :param fill_value: 填充值 + :param padding_mode: 填充模式 + :return: 裁剪后的图片 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 如果size是整形,转化为列表 if isinstance(size, int): size = (size, size) + # 如果size是列表,直接使用 elif isinstance(size, (tuple, list)) and len(size) == 2: size = size + # 如果都不是,抛出类型错误异常 else: raise TypeError("Size should be a single integer or a list/tuple (h, w) of length 2.") def _input_to_factor(img, size): + ''' + 将图片输入到因子中 + :param img: 图片 + :param size: 因子大小 + :return: top, left, height, width + ''' + # 获取图片的长宽 img_width, img_height = img.size + # 把宽高赋值为size,此处宽高为裁剪后宽高 height, width = size + # 如果裁剪后宽高大于图片宽高,抛出数值错误异常 if height > img_height or width > img_width: raise ValueError("Crop size {} is larger than input image size {}.".format(size, (img_height, img_width))) - + # 如果裁剪后宽高等于于图片宽高,返回top, left, height, width if width == img_width and height == img_height: return 0, 0, img_height, img_width - + + # 设置随机生成的top坐标 top = random.randint(0, img_height - height) + # 设置随机生成的left坐标 left = random.randint(0, img_width - width) + # 设置随机生成的height值 + height = random.randint(1, 5) + # 设置随机生成的width值 + width = random.randint(1, 5) return top, left, height, width - + + # 如果padding不为空,则使用padding函数对图片进行填充 if padding is not None: img = pad(img, padding, fill_value, padding_mode) # pad width when needed, img.size (width, height), crop size (height, width) + # 如果pad_if_needed为True,则使用pad函数对图片进行填充,并且计算图片的高度和宽度 if pad_if_needed and img.size[0] < size[1]: img = pad(img, (size[1] - img.size[0], 0), fill_value, padding_mode) # pad height when needed + # 如果pad_if_needed为True,则使用pad函数对图片进行填充,并且计算图片的高度和宽度 if pad_if_needed and img.size[1] < size[0]: img = pad(img, (0, size[0] - img.size[1]), fill_value, padding_mode) + # 计算图片的top, left, height, width top, left, height, width = _input_to_factor(img, size) + # 使用crop函数对图片进行裁剪,并返回裁剪后的图片 return crop(img, top, left, height, width) @@ -471,12 +441,12 @@ def adjust_brightness(img, brightness_factor): Adjust brightness of an image. Args: - img (PIL image): Image to be adjusted. + img (PIL.Image.Image): Image to be adjusted. brightness_factor (float): A non negative number indicated the factor by which the brightness is adjusted. 0 gives a black image, 1 gives the original. Returns: - img (PIL image), Brightness adjusted image. + PIL.Image.Image, brightness adjusted image. """ if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -487,22 +457,20 @@ def adjust_brightness(img, brightness_factor): def adjust_contrast(img, contrast_factor): - """ - Adjust contrast of an image. - - Args: - img (PIL image): PIL image to be adjusted. - contrast_factor (float): A non negative number indicated the factor by which - the contrast is adjusted. 0 gives a solid gray image, 1 gives the original. - - Returns: - img (PIL image), Contrast adjusted image. - """ + ''' + 调整图像的对比度 + :param img: PIL Image + :param contrast_factor: float + :return: PIL Image + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 使用enhancer对图像进行对比度增强 enhancer = ImageEnhance.Contrast(img) img = enhancer.enhance(contrast_factor) + # 返回img PIL格式 return img @@ -511,13 +479,13 @@ def adjust_saturation(img, saturation_factor): Adjust saturation of an image. Args: - img (PIL image): PIL image to be adjusted. + img (PIL.Image.Image): PIL Image to be adjusted. saturation_factor (float): A non negative number indicated the factor by which the saturation is adjusted. 0 will give a black and white image, 1 will give the original. Returns: - img (PIL image), Saturation adjusted image. + PIL.Image.Image, saturation adjusted image. """ if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -532,7 +500,7 @@ def adjust_hue(img, hue_factor): Adjust hue of an image. The Hue is changed by changing the HSV values after image is converted to HSV. Args: - img (PIL image): PIL image to be adjusted. + img (PIL.Image.Image): PIL Image to be adjusted. hue_factor (float): Amount to shift the Hue channel. Value should be in [-0.5, 0.5]. 0.5 and -0.5 give complete reversal of hue channel. This is because Hue wraps around when rotated 360 degrees. @@ -540,7 +508,7 @@ def adjust_hue(img, hue_factor): will give an image with complementary colors . Returns: - img (PIL image), Hue adjusted image. + PIL.Image.Image, hue adjusted image. """ image = img image_hue_factor = hue_factor @@ -567,28 +535,22 @@ def adjust_hue(img, hue_factor): def to_type(img, output_type): - """ - Convert the NumPy image array to desired NumPy dtype. - - Args: - img (numpy): NumPy image to cast to desired NumPy dtype. - output_type (Numpy datatype): NumPy dtype to cast to. - - Returns: - img (numpy.ndarray), Converted image. - """ + '''将img转换为output_type类型''' if not is_numpy(img): raise TypeError("img should be NumPy image. Got {}.".format(type(img))) - return img.astype(output_type) + try: + return img.astype(output_type) + except Exception: + raise RuntimeError("output_type: " + str(output_type) + " is not a valid datatype.") def rotate(img, angle, resample, expand, center, fill_value): """ - Rotate the input PIL image by angle. + Rotate the input PIL Image by angle. Args: - img (PIL image): Image to be rotated. + img (PIL.Image.Image): Image to be rotated. angle (int or float): Rotation angle in degrees, counter-clockwise. resample (Union[Inter.NEAREST, Inter.BILINEAR, Inter.BICUBIC], optional): An optional resampling filter. If omitted, or if the image has mode "1" or "P", it is set to be Inter.NEAREST. @@ -603,7 +565,7 @@ def rotate(img, angle, resample, expand, center, fill_value): If it is an integer, it is used for all RGB channels. Returns: - img (PIL image), Rotated image. + PIL.Image.Image, rotated image. https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.rotate """ @@ -617,171 +579,199 @@ def rotate(img, angle, resample, expand, center, fill_value): def random_color_adjust(img, brightness, contrast, saturation, hue): - """ - Randomly adjust the brightness, contrast, saturation, and hue of an image. - - Args: - img (PIL image): Image to have its color adjusted randomly. - brightness (Union[float, tuple]): Brightness adjustment factor. Cannot be negative. - If it is a float, the factor is uniformly chosen from the range [max(0, 1-brightness), 1+brightness]. - If it is a sequence, it should be [min, max] for the range. - contrast (Union[float, tuple]): Contrast adjustment factor. Cannot be negative. - If it is a float, the factor is uniformly chosen from the range [max(0, 1-contrast), 1+contrast]. - If it is a sequence, it should be [min, max] for the range. - saturation (Union[float, tuple]): Saturation adjustment factor. Cannot be negative. - If it is a float, the factor is uniformly chosen from the range [max(0, 1-saturation), 1+saturation]. - If it is a sequence, it should be [min, max] for the range. - hue (Union[float, tuple]): Hue adjustment factor. - If it is a float, the range will be [-hue, hue]. Value should be 0 <= hue <= 0.5. - If it is a sequence, it should be [min, max] where -0.5 <= min <= max <= 0.5. - - Returns: - img (PIL image), Image after random adjustment of its color. - """ + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) def _input_to_factor(value, input_name, center=1, bound=(0, float('inf')), non_negative=True): + ''' + 将输入值转换为因子 + :param value: 输入值 + :param input_name: 输入名称 + :param center: 因子中心 + :param bound: 因子边界 + :param non_negative: 是否为非负数 + :return: 因子 + ''' if isinstance(value, numbers.Number): + # 如果value是数字,则检查value是否小于0 if value < 0: raise ValueError("The input value of {} cannot be negative.".format(input_name)) # convert value into a range + # 将value转换为范围 value = [center - value, center + value] + # 如果non_negative为True,则将value的最小值设置为0 if non_negative: value[0] = max(0, value[0]) elif isinstance(value, (list, tuple)) and len(value) == 2: + # 如果value是一个列表或元组,且长度为2,则检查value是否在bound范围内 if not bound[0] <= value[0] <= value[1] <= bound[1]: raise ValueError("Please check your value range of {} is valid and " "within the bound {}.".format(input_name, bound)) else: + # 如果value不是数字,则抛出TypeError raise TypeError("Input of {} should be either a single value, or a list/tuple of " "length 2.".format(input_name)) + # 产生一个随机数 factor = random.uniform(value[0], value[1]) + # 返回factor return factor - + + # 将输入的值转换为因子 brightness_factor = _input_to_factor(brightness, 'brightness') contrast_factor = _input_to_factor(contrast, 'contrast') - saturation_factor = _input_to_factor(saturation, 'saturation') + saturation_factor = _input_to_factor(saturation,'saturation') hue_factor = _input_to_factor(hue, 'hue', center=0, bound=(-0.5, 0.5), non_negative=False) + # 创建一个空列表 transforms = [] + # 将brightness_factor添加到transforms列表中 transforms.append(lambda img: adjust_brightness(img, brightness_factor)) + # 将contrast_factor添加到transforms列表中 transforms.append(lambda img: adjust_contrast(img, contrast_factor)) + # 将saturation_factor添加到transforms列表中 transforms.append(lambda img: adjust_saturation(img, saturation_factor)) + # 将hue_factor添加到transforms列表中 transforms.append(lambda img: adjust_hue(img, hue_factor)) # apply color adjustments in a random order + # 随机洗牌 + # 从transforms列表中随机洗牌 random.shuffle(transforms) + # 遍历transforms列表中的每一个元素 for transform in transforms: + # 将transform函数的输出值赋值给img img = transform(img) + # 返回img return img -def random_rotation(img, degrees, resample, expand, center, fill_value): - """ - Rotate the input PIL image by a random angle. - - Args: - img (PIL image): Image to be rotated. - degrees (Union[int, float, sequence]): Range of random rotation degrees. - If degrees is a number, the range will be converted to (-degrees, degrees). - If degrees is a sequence, it should be (min, max). - resample (Union[Inter.NEAREST, Inter.BILINEAR, Inter.BICUBIC], optional): An optional resampling filter. - If omitted, or if the image has mode "1" or "P", it is set to be Inter.NEAREST. - expand (bool, optional): Optional expansion flag. If set to True, expand the output - image to make it large enough to hold the entire rotated image. - If set to False or omitted, make the output image the same size as the input. - Note that the expand flag assumes rotation around the center and no translation. - center (tuple, optional): Optional center of rotation (a 2-tuple). - Origin is the top left corner. - fill_value (Union[int, tuple]): Optional fill color for the area outside the rotated image. - If it is a 3-tuple, it is used for R, G, B channels respectively. - If it is an integer, it is used for all RGB channels. +def random_lighting(img, alpha): + ''' + 随机添加亮度、饱和度、对比度和颜色的效果 + :param img: PIL Image + :param alpha: 添加的亮度 + :return: PIL Image + ''' + if not is_pil(img): + raise TypeError(augment_error_message.format(type(img))) + if img.mode!= 'RGB': + img = img.convert("RGB") + + # 随机生成一个均匀分布的值 + alpha_r = np.random.normal(loc=0.0, scale=alpha) + alpha_g = np.random.normal(loc=0.0, scale=alpha) + alpha_b = np.random.normal(loc=0.0, scale=alpha) + # 将alpha_r, alpha_g, alpha_b分别转换为[0, 1]之间的数 + table = np.array([ + [55.46 * -0.5675, 4.794 * 0.7192, 1.148 * 0.4009], + [55.46 * -0.5808, 4.794 * -0.0045, 1.148 * -0.8140], + [55.46 * -0.5836, 4.794 * -0.6948, 1.148 * 0.4203] + ]) + # 计算pca_r, pca_g, pca_b + pca_r = table[0][0] * alpha_r + table[0][1] * alpha_g + table[0][2] * alpha_b + pca_g = table[1][0] * alpha_r + table[1][1] * alpha_g + table[1][2] * alpha_b + pca_b = table[2][0] * alpha_r + table[2][1] * alpha_g + table[2][2] * alpha_b + # 将pca_r, pca_g, pca_b转换为PIL Image + img_arr = np.array(img).astype(np.float64) + img_arr[:, :, 0] += pca_r + img_arr[:, :, 1] += pca_g + img_arr[:, :, 2] += pca_b + # 将img_arr中的值小于0或者大于255的值赋值为0 + img_arr = np.uint8(np.minimum(np.maximum(img_arr, 0), 255)) + # 将img_arr转换为PIL Image + img = Image.fromarray(img_arr) + return img - Returns: - img (PIL image), Rotated image. - https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.rotate - """ +def random_rotation(img, degrees, resample, expand, center, fill_value): + ''' + 随机旋转图像 + :param img: 图像 + :param degrees: 旋转角度 + :param resample: 是否重采样 + :param expand: 是否展开 + :param center: 坐标 + :param fill_value: 填充值 + :return: 旋转后的图像 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) - + #检测degree格式是否符合标准,转化成可用的列表 if isinstance(degrees, numbers.Number): if degrees < 0: raise ValueError("If degrees is a single number, it cannot be negative.") degrees = (-degrees, degrees) elif isinstance(degrees, (list, tuple)): - if len(degrees) != 2: + if len(degrees)!= 2: raise ValueError("If degrees is a sequence, the length must be 2.") else: raise TypeError("Degrees must be a single non-negative number or a sequence.") - + #随机旋转角度 angle = random.uniform(degrees[0], degrees[1]) return rotate(img, angle, resample, expand, center, fill_value) def five_crop(img, size): - """ - Generate 5 cropped images (one central and four corners). - - Args: - img (PIL image): PIL image to be cropped. - size (Union[int, sequence]): The output size of the crop. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, it should be (height, width). - - Returns: - img_tuple (tuple), a tuple of 5 PIL images - (top_left, top_right, bottom_left, bottom_right, center). - """ + ''' + 裁剪图像,截取图像的五个像素 + :param img: 原始图像 + :param size: 图像的大小 + :return: 图像的五个像素 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 如果size是整数,则将size转换为元组 if isinstance(size, int): size = (size, size) + # 如果size是元组或列表并且size的长度大于2,size不变 elif isinstance(size, (tuple, list)) and len(size) == 2: size = size + #除了以上两种情况,抛出类型错误异常 else: raise TypeError("Size should be a single number or a list/tuple (h, w) of length 2.") - # PIL image.size returns in (width, height) order + # PIL.Image.Image.size returns in (width, height) order img_width, img_height = img.size + # 获取图像的宽度和高度 crop_height, crop_width = size + # 计算裁剪图像的高度和宽度 if crop_height > img_height or crop_width > img_width: + # 如果裁剪图像的高度和宽度大于图像的高度和宽度,抛出数值错误异常 raise ValueError("Crop size {} is larger than input image size {}.".format(size, (img_height, img_width))) + # 计算中心裁剪图像 center = center_crop(img, (crop_height, crop_width)) + # 计算左上角裁剪图像 top_left = img.crop((0, 0, crop_width, crop_height)) + # 计算右上角裁剪图像 top_right = img.crop((img_width - crop_width, 0, img_width, crop_height)) + # 计算左下角裁剪图像 bottom_left = img.crop((0, img_height - crop_height, crop_width, img_height)) + # 计算右下角裁剪图像 bottom_right = img.crop((img_width - crop_width, img_height - crop_height, img_width, img_height)) + # 返回左上角,右上角,左下角,右下角,中心裁剪图像 return top_left, top_right, bottom_left, bottom_right, center - def ten_crop(img, size, use_vertical_flip=False): - """ - Generate 10 cropped images (first 5 from FiveCrop, second 5 from their flipped version). - - The default is horizontal flipping, use_vertical_flip=False. - - Args: - img (PIL image): PIL image to be cropped. - size (Union[int, sequence]): The output size of the crop. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, it should be (height, width). - use_vertical_flip (bool): Flip the image vertically instead of horizontally if set to True. - - Returns: - img_tuple (tuple), a tuple of 10 PIL images - (top_left, top_right, bottom_left, bottom_right, center) of original image + - (top_left, top_right, bottom_left, bottom_right, center) of flipped image. - """ + ''' + 对图片进行10抽取,抽取的每一张图片都是一个5*5的矩形,每一个矩形都是一个图片 + :param img: PIL图片 + :param size: 抽取的图片大小 + :param use_vertical_flip: 是否使用垂直翻转 + :return: 一个包含10张图片的列表 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) - + # 如果size是整数,则将size转换为元组 if isinstance(size, int): size = (size, size) + # 如果size是元组或列表并且size的长度大于2,size不变 elif isinstance(size, (tuple, list)) and len(size) == 2: size = size else: @@ -789,123 +779,126 @@ def ten_crop(img, size, use_vertical_flip=False): first_five_crop = five_crop(img, size) + # 如果使用垂直翻转,则将图片变换为长宽比例不变的图片 if use_vertical_flip: - img = vertical_flip(img) + img = vertical_flip(img) + # 否则,将图片变换为水平翻转的图片 else: img = horizontal_flip(img) + # 将图片进行五折裁剪,并将五折裁剪的结果添加到第一次五折裁剪的结果中 second_five_crop = five_crop(img, size) + # 返回第一次五折裁剪的结果和第二次五折裁剪的结果 return first_five_crop + second_five_crop def grayscale(img, num_output_channels): - """ - Convert the input PIL image to grayscale image. - - Args: - img (PIL image): PIL image to be converted to grayscale. - num_output_channels (int): Number of channels of the output grayscale image (1 or 3). - - Returns: - img (PIL image), grayscaled image. - """ + ''' + 将图像转换为灰度图像 + :param img: 图像 + :param num_output_channels: 灰度图像的通道数 + :return: 灰度图像 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 如果灰度图像的通道数为1,将图像转换为灰度图像 if num_output_channels == 1: img = img.convert('L') + # 如果灰度图像的通道数为3,将图像转换为灰度图像,每个通道都是相同的灰度图像 elif num_output_channels == 3: - # each channel is the same grayscale layer img = img.convert('L') - np_gray = np.array(img, dtype=np.uint8) - np_img = np.dstack([np_gray, np_gray, np_gray]) - img = Image.fromarray(np_img, 'RGB') + np_gray = np.array(img, dtype=np.uint8)# 将图片转化为8位像素灰度图 + np_img = np.dstack([np_gray, np_gray, np_gray]) # 将图像转换为灰度图 + img = Image.fromarray(np_img, 'RGB')# 将灰度图像转换为RGB图像 + # 如果灰度图像的通道数不为1或3,抛出数值错误异常 else: raise ValueError('num_output_channels should be either 1 or 3. Got {}.'.format(num_output_channels)) - + # 返回转化完的灰度图 return img def pad(img, padding, fill_value, padding_mode): - """ - Pad the image according to padding parameters. - - Args: - img (PIL image): Image to be padded. - padding (Union[int, sequence], optional): The number of pixels to pad the image. - If a single number is provided, it pads all borders with this value. - If a tuple or list of 2 values are provided, it pads the (left and top) - with the first value and (right and bottom) with the second value. - If 4 values are provided as a list or tuple, - it pads the left, top, right and bottom respectively. - Default is None. - fill_value (Union[int, tuple]): The pixel intensity of the borders if - the padding_mode is "constant". If it is a 3-tuple, it is used to - fill R, G, B channels respectively. - padding_mode (str): The method of padding. Can be any of - ['constant', 'edge', 'reflect', 'symmetric']. - - 'constant', means it fills the border with constant values - - 'edge', means it pads with the last value on the edge - - 'reflect', means it reflects the values on the edge omitting the last - value of edge - - 'symmetric', means it reflects the values on the edge repeating the last - value of edge - - Returns: - img (PIL image), Padded image. - """ + ''' + 对图像进行补全处理 + :param img: 图像 + :param padding: 填充长度 + :param fill_value: 填充值 + :param padding_mode: 填充模式 + :return: 填充后的图像 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) - + # 如果填充长度为一个数字,则图片填充的顶部底部左边右边全为此padding值 if isinstance(padding, numbers.Number): top = bottom = left = right = padding - + # 如果填充长度为一个列表 elif isinstance(padding, (tuple, list)): + # 列表长度为2,顶部底部填充长度为第一个值,左边右边填充长度为第二个值 if len(padding) == 2: left = top = padding[0] right = bottom = padding[1] + # 列表长度为4,则四个值分别为左边,顶部,右边,底部 elif len(padding) == 4: left = padding[0] top = padding[1] right = padding[2] bottom = padding[3] + # 其他情况,抛出数值错误异常 else: raise ValueError("The size of the padding list or tuple should be 2 or 4.") else: raise TypeError("Padding can be any of: a number, a tuple or list of size 2 or 4.") - + + #判断填充值是否正确,填充值是否是单个数值或列表 if not isinstance(fill_value, (numbers.Number, str, tuple)): raise TypeError("fill_value can be any of: an integer, a string or a tuple.") - + # 判断填充模式是否正确,填充模式是否为恒定、边缘、映射、对称 if padding_mode not in ['constant', 'edge', 'reflect', 'symmetric']: raise ValueError("Padding mode should be 'constant', 'edge', 'reflect', or 'symmetric'.") + # 如果填充模式为'constant' if padding_mode == 'constant': + + # 如果图像模式为P,则获取图像的调色板 if img.mode == 'P': palette = img.getpalette() + # 将图像填充为左上角,右下角,填充值 image = ImageOps.expand(img, border=(left, top, right, bottom), fill=fill_value) + # 将调色板替换图像 image.putpalette(palette) return image + # 否则,将图像填充为左上角,右下角,填充值,直接返回图像 return ImageOps.expand(img, border=(left, top, right, bottom), fill=fill_value) if img.mode == 'P': + # 获取图像的调色板 palette = img.getpalette() + # 将图像转换为numpy数组 img = np.asarray(img) + # 将图像填充到指定的位置 img = np.pad(img, ((top, bottom), (left, right)), padding_mode) + # 将图像转换为PIL图像 img = Image.fromarray(img) + # 将调色板替换图像的调色板 img.putpalette(palette) + # 返回替换后的图像 return img img = np.asarray(img) + # 如果img的维度为3,则在图像的左上角填充0 if len(img.shape) == 3: img = np.pad(img, ((top, bottom), (left, right), (0, 0)), padding_mode) + # 如果img的维度为2,则在图像的左上角填充0 if len(img.shape) == 2: img = np.pad(img, ((top, bottom), (left, right)), padding_mode) + # 将图像转换为Image对象 return Image.fromarray(img) - def get_perspective_params(img, distortion_scale): """Helper function to get parameters for RandomPerspective. """ @@ -927,19 +920,14 @@ def get_perspective_params(img, distortion_scale): def perspective(img, start_points, end_points, interpolation=Inter.BICUBIC): - """ - Apply perspective transformation to the input PIL image. - - Args: - img (PIL image): PIL image to be applied perspective transformation. - start_points (list): List of [top_left, top_right, bottom_right, bottom_left] of the original image. - end_points: List of [top_left, top_right, bottom_right, bottom_left] of the transformed image. - interpolation (interpolation mode): Image interpolation mode, Default is Inter.BICUBIC = 3. - - Returns: - img (PIL image), Image after being perspectively transformed. - """ - + ''' + 使用插值投影将图像转换为投影矩阵 + :param img: PIL Image + :param start_points: 图像起始点 + :param end_points: 图像结束点 + :param interpolation: 插值方法 + :return: PIL Image + ''' def _input_to_coeffs(original_points, transformed_points): # Get the coefficients (a, b, c, d, e, f, g, h) for the perspective transforms. # According to "Using Projective Geometry to Correct a Camera" from AMS. @@ -947,12 +935,17 @@ def perspective(img, start_points, end_points, interpolation=Inter.BICUBIC): # https://github.com/python-pillow/Pillow/blob/master/src/libImaging/Geometry.c#L377 matrix = [] + # 将转换后的点和原始点拼接起来 for pt1, pt2 in zip(transformed_points, original_points): matrix.append([pt1[0], pt1[1], 1, 0, 0, 0, -pt2[0] * pt1[0], -pt2[0] * pt1[1]]) matrix.append([0, 0, 0, pt1[0], pt1[1], 1, -pt2[1] * pt1[0], -pt2[1] * pt1[1]]) + # 将拼接后的矩阵转换为数组 matrix_a = np.array(matrix, dtype=np.float) + # 将原始点按照8个点拼接起来 matrix_b = np.array(original_points, dtype=np.float).reshape(8) + # 使用numpy的linalg.lstsq函数求解矩阵 res = np.linalg.lstsq(matrix_a, matrix_b, rcond=None)[0] + # 返回矩阵的拟合结果 return res.tolist() if not is_pil(img): @@ -961,10 +954,8 @@ def perspective(img, start_points, end_points, interpolation=Inter.BICUBIC): coeffs = _input_to_coeffs(start_points, end_points) return img.transform(img.size, Image.PERSPECTIVE, coeffs, interpolation) - +# 获取擦除参数,关于系数的计算 def get_erase_params(np_img, scale, ratio, value, bounded, max_attempts): - """Helper function to get parameters for RandomErasing/ Cutout. - """ if not is_numpy(np_img): raise TypeError('img should be NumPy array. Got {}.'.format(type(np_img))) @@ -1013,46 +1004,38 @@ def get_erase_params(np_img, scale, ratio, value, bounded, max_attempts): def erase(np_img, i, j, height, width, erase_value, inplace=False): - """ - Erase the pixels, within a selected rectangle region, to the given value. Applied on the input NumPy image array. - - Args: - np_img (numpy.ndarray): NumPy image array of shape (C, H, W) to be erased. - i (int): The height component of the top left corner (height, width). - j (int): The width component of the top left corner (height, width). - height (int): Height of the erased region. - width (int): Width of the erased region. - erase_value: Erase value return from helper function get_erase_params(). - inplace (bool, optional): Apply this transform inplace. Default is False. - Returns: - np_img (numpy.ndarray), Erased NumPy image array. - """ + ''' + 擦除图像中指定位置的像素 + :param np_img: NumPy array + :param i: 行号 + :param j: 列号 + :param height: 高度 + :param width: 宽度 + :param erase_value: 擦除像素的值 + :param inplace: 是否擦除原图像 + :return: 擦除后的图像 + ''' + # 如果参数img不是数组图像,抛出类型错误异常 if not is_numpy(np_img): raise TypeError('img should be NumPy array. Got {}.'.format(type(np_img))) + # 如果np_img不是擦除原图像,复制np_img if not inplace: np_img = np_img.copy() - # (i, j) here are the coordinates of axes (height, width) as in CHW + # 将np_img中第i到i+height行,第j到j+width列的值替换为erase_value np_img[:, i:i + height, j:j + width] = erase_value + # 返回新的np_img PIL格式 return np_img - def linear_transform(np_img, transformation_matrix, mean_vector): - """ - Apply linear transformation to the input NumPy image array, given a square transformation matrix and a mean_vector. - - The transformation first flattens the input array and subtract mean_vector from it, then computes the - dot product with the transformation matrix, and reshapes it back to its original shape. - - Args: - np_img (numpy.ndarray): NumPy image array of shape (C, H, W) to be linear transformed. - transformation_matrix (numpy.ndarray): a square transformation matrix of shape (D, D), D = C x H x W. - mean_vector (numpy.ndarray): a NumPy ndarray of shape (D,) where D = C x H x W. - - Returns: - np_img (numpy.ndarray), Linear transformed image. - """ + ''' + 线性变换 + :param np_img: NumPy数组 + :param transformation_matrix: 矩阵 + :param mean_vector: 均值向量 + :return: 线性变换后的图像 + ''' if not is_numpy(np_img): raise TypeError('img should be NumPy array. Got {}'.format(type(np_img))) if transformation_matrix.shape[0] != transformation_matrix.shape[1]: @@ -1064,87 +1047,87 @@ def linear_transform(np_img, transformation_matrix, mean_vector): if mean_vector.shape[0] != transformation_matrix.shape[0]: raise ValueError("mean_vector length {0} should match either one dimension of the square " "transformation_matrix {1}.".format(mean_vector.shape[0], transformation_matrix.shape)) + # 将图像均值向量和矩阵拼接 zero_centered_img = np_img.reshape(1, -1) - mean_vector + # 将图像均值向量和矩阵拼接后的结果乘以矩阵 transformed_img = np.dot(zero_centered_img, transformation_matrix) + # 如果结果的长度不等于原图像长度,则抛出数值错误异常 if transformed_img.size != np_img.size: raise ValueError("Linear transform failed, input shape should match with transformation_matrix.") + # 将结果reshape成原图像的形状 transformed_img = transformed_img.reshape(np_img.shape) return transformed_img def random_affine(img, angle, translations, scale, shear, resample, fill_value=0): - """ - Applies a random Affine transformation on the input PIL image. - - Args: - img (PIL image): Image to be applied affine transformation. - angle (Union[int, float]): Rotation angle in degrees, clockwise. - translations (sequence): Translations in horizontal and vertical axis. - scale (float): Scale parameter, a single number. - shear (Union[float, sequence]): Shear amount parallel to X axis and Y axis. - resample (Union[Inter.NEAREST, Inter.BILINEAR, Inter.BICUBIC], optional): An optional resampling filter. - fill_value (Union[tuple int], optional): Optional fill_value to fill the area outside the transform - in the output image. Used only in Pillow versions > 5.0.0. - If None, no filling is performed. - - Returns: - img (PIL image), Randomly affine transformed image. - - """ + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise ValueError("Input image should be a Pillow image.") - # rotation + # angle angle = random.uniform(angle[0], angle[1]) # translation if translations is not None: + # 计算最大转换倍数 max_dx = translations[0] * img.size[0] max_dy = translations[1] * img.size[1] + # 计算新的转换倍数 translations = (np.round(random.uniform(-max_dx, max_dx)), - np.round(random.uniform(-max_dy, max_dy))) + np.round(random.uniform(-max_dy, max_dy))) else: translations = (0, 0) # scale if scale is not None: + # 计算新的缩放比例 scale = random.uniform(scale[0], scale[1]) else: scale = 1.0 # shear if shear is not None: + # 计算新的旋转角度 if len(shear) == 2: shear = [random.uniform(shear[0], shear[1]), 0.] elif len(shear) == 4: shear = [random.uniform(shear[0], shear[1]), - random.uniform(shear[2], shear[3])] + random.uniform(shear[2], shear[3])] else: shear = 0.0 - output_size = img.size - center = (img.size[0] * 0.5 + 0.5, img.size[1] * 0.5 + 0.5) + output_size = img.size + # 计算图片的中心点 + center = (img.size[0] * 0.5 + 0.5, img.size[1] * 0.5 + 0.5) + # 将角度转换为弧度 angle = math.radians(angle) + # 如果shear是元组或列表,且元素个数为2 if isinstance(shear, (tuple, list)) and len(shear) == 2: shear = [math.radians(s) for s in shear] + # 如果shear是数字 elif isinstance(shear, numbers.Number): + # 将shear转换为弧度 shear = math.radians(shear) shear = [shear, 0] + # 如果shear不是元组或列表,且元素个数不为2 else: raise ValueError( "Shear should be a single value or a tuple/list containing " + "two values. Got {}.".format(shear)) +# 将scale转换为浮点数 scale = 1.0 / scale # Inverted rotation matrix with scale and shear d = math.cos(angle + shear[0]) * math.cos(angle + shear[1]) + \ math.sin(angle + shear[0]) * math.sin(angle + shear[1]) + # 计算矩阵 matrix = [ math.cos(angle + shear[0]), math.sin(angle + shear[0]), 0, -math.sin(angle + shear[1]), math.cos(angle + shear[1]), 0 ] + # 计算缩放比例 matrix = [scale / d * m for m in matrix] # Apply inverse of translation and of center translation: RSS^-1 * C^-1 * T^-1 @@ -1155,6 +1138,7 @@ def random_affine(img, angle, translations, scale, shear, resample, fill_value=0 matrix[2] += center[0] matrix[5] += center[1] + # Apply center translation: C * RSS^-1 * C^-1 * T^-1 if __version__ >= '5': kwargs = {"fillcolor": fill_value} else: @@ -1163,77 +1147,112 @@ def random_affine(img, angle, translations, scale, shear, resample, fill_value=0 def mix_up_single(batch_size, img, label, alpha=0.2): - """ - Apply mix up transformation to image and label in single batch internal, One hot encoding should done before this. - - Args: - batch_size (int): the batch size of dataset. - img (numpy.ndarray): NumPy image to be applied mix up transformation. - label (numpy.ndarray): NumPy label to be applied mix up transformation. - alpha (float): the mix up rate. - - Returns: - mix_img (numpy.ndarray): NumPy image after being applied mix up transformation. - mix_label (numpy.ndarray): NumPy label after being applied mix up transformation. - """ - + ''' + 混合掉一个图像和标签 + :param batch_size: 批次大小 + :param img: 图像 + :param label: 标签 + :param alpha: 权重 + :return: 混合后的图像和标签 + ''' def cir_shift(data): + ''' + 对图像进行编码 + :param data: 图像 + :return: 编码后的图像 + ''' index = list(range(1, batch_size)) + [0] data = data[index, ...] + data = data[index,...] return data + # 创建一个batch_size大小的随机系数,其中alpha为概率的参数 lam = np.random.beta(alpha, alpha, batch_size) + # 将lam的值转换为batch_size维的数组 lam_img = lam.reshape((batch_size, 1, 1, 1)) + # 将lam的值转换为batch_size维的数组,并将其reshape为(batch_size, 1, 1, 1) mix_img = lam_img * img + (1 - lam_img) * cir_shift(img) - + # 将lam的值转换为batch_size维的数组,并将其reshape为(batch_size, 1) lam_label = lam.reshape((batch_size, 1)) + # 将lam的值乘以img和cir_shift(img)的值,并将结果赋值给mix_img + mix_label = lam_label * label + (1 - lam_label) * cir_shift(label) return mix_img, mix_label def mix_up_muti(tmp, batch_size, img, label, alpha=0.2): - """ - Apply mix up transformation to image and label in continuous batch, one hot encoding should done before this. - - Args: - tmp (class object): mainly for saving the tmp parameter. - batch_size (int): the batch size of dataset. - img (numpy.ndarray): NumPy image to be applied mix up transformation. - label (numpy.ndarray): NumPy label to be applied mix up transformation. - alpha (float): refer to the mix up rate. - - Returns: - mix_img (numpy.ndarray): NumPy image after being applied mix up transformation. - mix_label (numpy.ndarray): NumPy label after being applied mix up transformation. - """ + ''' + 混合混合概率 + :param tmp: 原始数据 + :param batch_size: 批量大小 + :param img: 图片 + :param label: 标签 + :param alpha: 混合概率 + :return: 混合后的图片和标签 + ''' + # 创建一个batch_size大小的随机系数,其中alpha为概率的参数 lam = np.random.beta(alpha, alpha, batch_size) + # 如果tmp.is_first为True,则将lam的值设置为1 if tmp.is_first: lam = np.ones(batch_size) tmp.is_first = False - + # 将lam的值转换为batch_size*1*1*1的形式 lam_img = lam.reshape((batch_size, 1, 1, 1)) mix_img = lam_img * img + (1 - lam_img) * tmp.image - + # 将lam的值转换为batch_size*1的形式 lam_label = lam.reshape(batch_size, 1) mix_label = lam_label * label + (1 - lam_label) * tmp.label + # 将mix_img和mix_label混合到tmp.image和tmp.label中 tmp.image = mix_img tmp.label = mix_label return mix_img, mix_label +# RGB格式转换为BGR格式 +def rgb_to_bgr(np_rgb_img, is_hwc): + + if is_hwc: + np_bgr_img = np_rgb_img[:, :, ::-1] + else: + np_bgr_img = np_rgb_img[::-1, :, :] + return np_bgr_img -def rgb_to_hsv(np_rgb_img, is_hwc): - """ - Convert RGB img to HSV img. +# RGB格式转换为BGR格式 +def rgb_to_bgrs(np_rgb_imgs, is_hwc): + if not is_numpy(np_rgb_imgs): + raise TypeError("img should be NumPy image. Got {}".format(type(np_rgb_imgs))) - Args: - np_rgb_img (numpy.ndarray): NumPy RGB image array of shape (H, W, C) or (C, H, W) to be converted. - is_hwc (Bool): If True, the shape of np_hsv_img is (H, W, C), otherwise must be (C, H, W). + if not isinstance(is_hwc, bool): + raise TypeError("is_hwc should be bool type. Got {}.".format(type(is_hwc))) - Returns: - np_hsv_img (numpy.ndarray), NumPy HSV image with same type of np_rgb_img. - """ + shape_size = len(np_rgb_imgs.shape) + + if not shape_size in (3, 4): + raise TypeError("img shape should be (H, W, C)/(N, H, W, C)/(C ,H, W)/(N, C, H, W). " + "Got {}.".format(np_rgb_imgs.shape)) + + if shape_size == 3: + batch_size = 0 + if is_hwc: + num_channels = np_rgb_imgs.shape[2] + else: + num_channels = np_rgb_imgs.shape[0] + else: + batch_size = np_rgb_imgs.shape[0] + if is_hwc: + num_channels = np_rgb_imgs.shape[3] + else: + num_channels = np_rgb_imgs.shape[1] + + if num_channels != 3: + raise TypeError("img should be 3 channels RGB img. Got {} channels.".format(num_channels)) + if batch_size == 0: + return rgb_to_bgr(np_rgb_imgs, is_hwc) + return np.array([rgb_to_bgr(img, is_hwc) for img in np_rgb_imgs]) + +# RGB格式转化为HSV格式 +def rgb_to_hsv(np_rgb_img, is_hwc): if is_hwc: r, g, b = np_rgb_img[:, :, 0], np_rgb_img[:, :, 1], np_rgb_img[:, :, 2] else: @@ -1247,20 +1266,8 @@ def rgb_to_hsv(np_rgb_img, is_hwc): np_hsv_img = np.stack((h, s, v), axis=axis) return np_hsv_img - +# RGB格式转化为HSV格式 def rgb_to_hsvs(np_rgb_imgs, is_hwc): - """ - Convert RGB imgs to HSV imgs. - - Args: - np_rgb_imgs (numpy.ndarray): NumPy RGB images array of shape (H, W, C) or (N, H, W, C), - or (C, H, W) or (N, C, H, W) to be converted. - is_hwc (Bool): If True, the shape of np_rgb_imgs is (H, W, C) or (N, H, W, C); - If False, the shape of np_rgb_imgs is (C, H, W) or (N, C, H, W). - - Returns: - np_hsv_imgs (numpy.ndarray), NumPy HSV images with same type of np_rgb_imgs. - """ if not is_numpy(np_rgb_imgs): raise TypeError("img should be NumPy image. Got {}".format(type(np_rgb_imgs))) @@ -1292,18 +1299,8 @@ def rgb_to_hsvs(np_rgb_imgs, is_hwc): return rgb_to_hsv(np_rgb_imgs, is_hwc) return np.array([rgb_to_hsv(img, is_hwc) for img in np_rgb_imgs]) - +# HSV转化为RGB格式 def hsv_to_rgb(np_hsv_img, is_hwc): - """ - Convert HSV img to RGB img. - - Args: - np_hsv_img (numpy.ndarray): NumPy HSV image array of shape (H, W, C) or (C, H, W) to be converted. - is_hwc (Bool): If True, the shape of np_hsv_img is (H, W, C), otherwise must be (C, H, W). - - Returns: - np_rgb_img (numpy.ndarray), NumPy HSV image with same shape of np_hsv_img. - """ if is_hwc: h, s, v = np_hsv_img[:, :, 0], np_hsv_img[:, :, 1], np_hsv_img[:, :, 2] else: @@ -1320,36 +1317,36 @@ def hsv_to_rgb(np_hsv_img, is_hwc): def hsv_to_rgbs(np_hsv_imgs, is_hwc): - """ - Convert HSV imgs to RGB imgs. - - Args: - np_hsv_imgs (numpy.ndarray): NumPy HSV images array of shape (H, W, C) or (N, H, W, C), - or (C, H, W) or (N, C, H, W) to be converted. - is_hwc (Bool): If True, the shape of np_hsv_imgs is (H, W, C) or (N, H, W, C); - If False, the shape of np_hsv_imgs is (C, H, W) or (N, C, H, W). - - Returns: - np_rgb_imgs (numpy.ndarray), NumPy RGB images with same type of np_hsv_imgs. - """ + ''' + 将HSV图像转换为RGB图像 + :param np_hsv_imgs: 原始的HSV图像 + :param is_hwc: 是否为HWC格式 + :return: 转换后的RGB图像 + ''' + # 如果参数img不是数组图像,抛出类型错误异常 if not is_numpy(np_hsv_imgs): raise TypeError("img should be NumPy image. Got {}.".format(type(np_hsv_imgs))) - + # 如果is_hwc不是bool 值,抛出类型错误异常 if not isinstance(is_hwc, bool): raise TypeError("is_hwc should be bool type. Got {}.".format(type(is_hwc))) + # 把shape_size赋值为np_hsv_imgs的形状长度信息 shape_size = len(np_hsv_imgs.shape) - + # 如果shape_size的值不在3,4之间,抛出类型错误异常 if not shape_size in (3, 4): raise TypeError("img shape should be (H, W, C)/(N, H, W, C)/(C, H, W)/(N, C, H, W). " "Got {}.".format(np_hsv_imgs.shape)) - + + # 如果图像的形状为3,则batch_size为0 if shape_size == 3: batch_size = 0 + # 如果图像是HWC格式,则num_channels为图像的通道数 if is_hwc: num_channels = np_hsv_imgs.shape[2] + # 否则num_channels为图像的行数 else: num_channels = np_hsv_imgs.shape[0] + # 如果图像形状不为3,则batch_size赋值为np_hsv_imgs矩阵第一维度 else: batch_size = np_hsv_imgs.shape[0] if is_hwc: @@ -1357,130 +1354,110 @@ def hsv_to_rgbs(np_hsv_imgs, is_hwc): else: num_channels = np_hsv_imgs.shape[1] + # 如果num_channels不是3,则抛出类型错误异常 if num_channels != 3: raise TypeError("img should be 3 channels RGB img. Got {} channels.".format(num_channels)) + # 如果batch_size为0,则返回hsv_to_rgb函数的结果 if batch_size == 0: return hsv_to_rgb(np_hsv_imgs, is_hwc) + # 否则,返回一个batch_size大小的数组,每个元素为hsv_to_rgb函数的结果 return np.array([hsv_to_rgb(img, is_hwc) for img in np_hsv_imgs]) def random_color(img, degrees): """ - Adjust the color of the input PIL image by a random degree. - - Args: - img (PIL image): Image to be color adjusted. - degrees (sequence): Range of random color adjustment degrees. - It should be in (min, max) format (default=(0.1,1.9)). - - Returns: - img (PIL image), Color adjusted image. + 随机颜色增强 + :param img: 输入图像 + :param degrees: 随机颜色增强的参数 + :return: 增强后的图像 """ - + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): - raise TypeError("img should be PIL image. Got {}.".format(type(img))) + raise TypeError(augment_error_message.format(type(img))) + # 计算随机颜色增强的参数 v = (degrees[1] - degrees[0]) * random.random() + degrees[0] + # 返回增强后的图像 return ImageEnhance.Color(img).enhance(v) def random_sharpness(img, degrees): - """ - Adjust the sharpness of the input PIL image by a random degree. - - Args: - img (PIL image): Image to be sharpness adjusted. - degrees (sequence): Range of random sharpness adjustment degrees. - It should be in (min, max) format (default=(0.1,1.9)). - - Returns: - img (PIL image), Sharpness adjusted image. - """ - + ''' + 随机着色调 + :param img: PIL格式的图像 + :param degrees: 图像的角度 + :return: 图像的着色调增强 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): - raise TypeError("img should be PIL image. Got {}.".format(type(img))) + raise TypeError(augment_error_message.format(type(img))) + # 计算角度的随机值 v = (degrees[1] - degrees[0]) * random.random() + degrees[0] + # 返回图像的着色调增强 return ImageEnhance.Sharpness(img).enhance(v) +# 调整图像的gamma值,并返回调整后的图像 +def adjust_gamma(img, gamma, gain): + # 如果输入的图像不是PIL格式,抛出类型错误异常 + if not is_pil(img): + raise TypeError("img should be PIL image. Got {}.".format(type(img))) + + # 计算gamma值的表 + gamma_table = [(255 + 1 - 1e-3) * gain * pow(x / 255., gamma) for x in range(256)] + # 如果图像是三通道,则将gamma值乘以3再进行图像映射 + if len(img.split()) == 3: + gamma_table = gamma_table * 3 + img = img.point(gamma_table) + # 如果图像是一通道,则以gamma值进行图像映射 + elif len(img.split()) == 1: + img = img.point(gamma_table) + # 返回img PIL格式 + return img def auto_contrast(img, cutoff, ignore): """ - Automatically maximize the contrast of the input PIL image. + Automatically maximize the contrast of the input PIL Image. Args: - img (PIL image): Image to be augmented with AutoContrast. + img (PIL.Image): Image to be augmented with AutoContrast. cutoff (float, optional): Percent of pixels to cut off from the histogram (default=0.0). - ignore (Union[int, sequence], optional): Pixel values to ignore (default=None). + ignore (Union[int, Sequence[int]], optional): Pixel values to ignore (default=None). Returns: - img (PIL image), Augmented image. - + PIL.Image, augmented image. """ if not is_pil(img): - raise TypeError("img should be PIL image. Got {}.".format(type(img))) + raise TypeError(augment_error_message.format(type(img))) return ImageOps.autocontrast(img, cutoff, ignore) - +# 在 RGB 模式下对输入图像应用像素反转。 def invert_color(img): - """ - Invert colors of input PIL image. - - Args: - img (PIL image): Image to be color inverted. - - Returns: - img (PIL image), Color inverted image. - - """ - if not is_pil(img): - raise TypeError("img should be PIL image. Got {}.".format(type(img))) + raise TypeError(augment_error_message.format(type(img))) return ImageOps.invert(img) def equalize(img): - """ - Equalize the histogram of input PIL image. - - Args: - img (PIL image): Image to be equalized - - Returns: - img (PIL image), Equalized image. - - """ - + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): - raise TypeError("img should be PIL image. Got {}.".format(type(img))) + raise TypeError(augment_error_message.format(type(img))) + # 返回图像均衡图像直方图 return ImageOps.equalize(img) def uniform_augment(img, transforms, num_ops): - """ - Uniformly select and apply a number of transforms sequentially from - a list of transforms. Randomly assigns a probability to each transform for - each image to decide whether apply it or not. - All the transforms in transform list must have the same input/output data type. - - Args: - img: Image to be applied transformation. - transforms (list): List of transformations to be chosen from to apply. - num_ops (int): number of transforms to sequentially aaply. - - Returns: - img, Transformed image. - - """ - + # 从transforms中随机选择num_ops个运算,并将其作为参数传入AugmentOp op_idx = np.random.choice(len(transforms), size=num_ops, replace=False) for idx in op_idx: AugmentOp = transforms[idx] + # 从random.random()中随机选择一个小于等于pr的数,并将其赋值给pr pr = random.random() + # 如果random.random()小于pr,则执行AugmentOp if random.random() < pr: img = AugmentOp(img.copy())