【问题标题】:Scipy rotate and zoom an image without changing its dimensionsScipy 旋转和缩放图像而不改变其尺寸
【发布时间】:2016-09-04 06:47:50
【问题描述】:

对于我的神经网络,我想通过向我的图像添加小的随机旋转和缩放来增强我的训练数据。我遇到的问题是 scipy 在应用旋转和缩放时会改变我的图像大小。如果图像的一部分超出范围,我只需要剪裁边缘。我所有的图片都必须是相同的尺寸。

def loadImageData(img, distort = False):
    c, fn = img
    img = scipy.ndimage.imread(fn, True)

    if distort:
        img = scipy.ndimage.zoom(img, 1 + 0.05 * rnd(), mode = 'constant')
        img = scipy.ndimage.rotate(img, 10 * rnd(), mode = 'constant')
        print(img.shape)

    img = img - np.min(img)
    img = img / np.max(img)
    img = np.reshape(img, (1, *img.shape))

    y = np.zeros(ncats)
    y[c] = 1
    return (img, y)

【问题讨论】:

    标签: python image numpy scipy


    【解决方案1】:

    scipy.ndimage.rotate 接受 reshape= 参数:

    重塑 : 布尔型,可选

    如果reshape 为真,则调整输出形状,以便输入 数组完全包含在输出中。默认为真。

    所以要“剪裁”边缘,您只需调用 scipy.ndimage.rotate(img, ..., reshape=False)

    from scipy.ndimage import rotate
    from scipy.misc import face
    from matplotlib import pyplot as plt
    
    img = face()
    rot = rotate(img, 30, reshape=False)
    
    fig, ax = plt.subplots(1, 2)
    ax[0].imshow(img)
    ax[1].imshow(rot)
    

    scipy.ndimage.zoom 的情况更复杂。

    一个简单的方法是 zoom 整个输入数组,然后使用切片索引和/或零填充使输出与输入大小相同。但是,在您增加图像大小的情况下,插入无论如何只会在边缘被剪掉的像素是很浪费的。

    相反,您可以在应用 zoom 之前仅索引输入中将落在输出数组范围内的部分:

    import numpy as np
    from scipy.ndimage import zoom
    
    
    def clipped_zoom(img, zoom_factor, **kwargs):
    
        h, w = img.shape[:2]
    
        # For multichannel images we don't want to apply the zoom factor to the RGB
        # dimension, so instead we create a tuple of zoom factors, one per array
        # dimension, with 1's for any trailing dimensions after the width and height.
        zoom_tuple = (zoom_factor,) * 2 + (1,) * (img.ndim - 2)
    
        # Zooming out
        if zoom_factor < 1:
    
            # Bounding box of the zoomed-out image within the output array
            zh = int(np.round(h * zoom_factor))
            zw = int(np.round(w * zoom_factor))
            top = (h - zh) // 2
            left = (w - zw) // 2
    
            # Zero-padding
            out = np.zeros_like(img)
            out[top:top+zh, left:left+zw] = zoom(img, zoom_tuple, **kwargs)
    
        # Zooming in
        elif zoom_factor > 1:
    
            # Bounding box of the zoomed-in region within the input array
            zh = int(np.round(h / zoom_factor))
            zw = int(np.round(w / zoom_factor))
            top = (h - zh) // 2
            left = (w - zw) // 2
    
            out = zoom(img[top:top+zh, left:left+zw], zoom_tuple, **kwargs)
    
            # `out` might still be slightly larger than `img` due to rounding, so
            # trim off any extra pixels at the edges
            trim_top = ((out.shape[0] - h) // 2)
            trim_left = ((out.shape[1] - w) // 2)
            out = out[trim_top:trim_top+h, trim_left:trim_left+w]
    
        # If zoom_factor == 1, just return the input array
        else:
            out = img
        return out
    

    例如:

    zm1 = clipped_zoom(img, 0.5)
    zm2 = clipped_zoom(img, 1.5)
    
    fig, ax = plt.subplots(1, 3)
    ax[0].imshow(img)
    ax[1].imshow(zm1)
    ax[2].imshow(zm2)
    

    【讨论】:

    • scipy.ndimage.zoom 太慢了(不知道为什么),所以你的函数需要大约 500 毫秒来处理 (480,640) 图像
    • 可能样条插值导致速度变慢。此外,缩放中心是放大的右下角,而缩小的中心是令人困惑的。我会改用 cv2.resize
    • @MohamedEzz “此外,缩放中心位于右下角用于放大,而用于缩小则令人困惑。” - 缩放应该在图像,但是在zoom_factor &gt; 1 时计算缩放区域的边界框的方式存在错误,我现在已经修复了。 clipped_zoom 将关键字参数传播到 scipy.ndimage.zoom,因此如果三次样条插值太慢,您可以传递 order=0。我不怀疑cv2.resize 更快,但 OpenCV 是一个严重的依赖项,OP 要求基于 scipy 的解决方案。
    • 感谢您的澄清。回复:运行时,在我下面的回答中,我快速对 order=3 和 order=0 进行了基准测试,但它仍然慢得多。同意这个问题主要是关于 Scipy,但它认为不使用更好的 cv2 是没有限制的。
    • int(np.round(h * zoom_factor)) 中的舍入有时会导致生成的图像比目标图像小 1 个像素。然后计算得到 -1 作为 diff 并且你得到图像像素大小 1 用于输出。更改为 np.ceil() 而不是 np.round() 似乎可以解决它。
    【解决方案2】:

    我推荐使用cv2.resize,因为它比scipy.ndimage.zoom 快得多,可能是因为支持更简单的插值方法。

    对于 480x640 图像:

    • cv2.resize 大约需要 2 毫秒
    • scipy.ndimage.zoom 大约需要 500 毫秒
    • scipy.ndimage.zoom(...,order=0) 大约需要 175 毫秒

    如果您正在动态地进行数据扩充,那么这种加速是非常宝贵的,因为这意味着在更短的时间内进行更多的实验。

    这是使用cv2.resizeclipped_zoom 版本

    def cv2_clipped_zoom(img, zoom_factor=0):
    
        """
        Center zoom in/out of the given image and returning an enlarged/shrinked view of 
        the image without changing dimensions
        ------
        Args:
            img : ndarray
                Image array
            zoom_factor : float
                amount of zoom as a ratio [0 to Inf). Default 0.
        ------
        Returns:
            result: ndarray
               numpy ndarray of the same shape of the input img zoomed by the specified factor.          
        """
        if zoom_factor == 0:
            return img
    
    
        height, width = img.shape[:2] # It's also the final desired shape
        new_height, new_width = int(height * zoom_factor), int(width * zoom_factor)
        
        ### Crop only the part that will remain in the result (more efficient)
        # Centered bbox of the final desired size in resized (larger/smaller) image coordinates
        y1, x1 = max(0, new_height - height) // 2, max(0, new_width - width) // 2
        y2, x2 = y1 + height, x1 + width
        bbox = np.array([y1,x1,y2,x2])
        # Map back to original image coordinates
        bbox = (bbox / zoom_factor).astype(np.int)
        y1, x1, y2, x2 = bbox
        cropped_img = img[y1:y2, x1:x2]
        
        # Handle padding when downscaling
        resize_height, resize_width = min(new_height, height), min(new_width, width)
        pad_height1, pad_width1 = (height - resize_height) // 2, (width - resize_width) //2
        pad_height2, pad_width2 = (height - resize_height) - pad_height1, (width - resize_width) - pad_width1
        pad_spec = [(pad_height1, pad_height2), (pad_width1, pad_width2)] + [(0,0)] * (img.ndim - 2)
        
        result = cv2.resize(cropped_img, (resize_width, resize_height))
        result = np.pad(result, pad_spec, mode='constant')
        assert result.shape[0] == height and result.shape[1] == width
        return result
    

    【讨论】:

    • 这是一个很好的功能。我认为如果它还可以处理返回原始 img 的 0 缩放因子可能会更好。这可以很容易地在开始时在缩放参数上添加控制语句来完成。如果您同意,我建议进行修改。
    猜你喜欢
    • 2018-06-15
    • 2013-11-22
    • 1970-01-01
    • 2019-08-10
    • 2019-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-09
    相关资源
    最近更新 更多