homer512 icon

python convolve

homer512 | PRO | 12/05/15 11:32:05 AM UTC | 0 ⭐ | 591 👁️ | Never ⏰ | []
Python |

4.14 KB

|

None

|

0 👍

/

0 👎

#!/usr/bin/python2
 
 
"""Demonstration of image convolution in Python
 
Yes, scipy.ndimage does a better job at it.
This is just a demonstration of proper optimization of numpy routines
"""
 
 
import numpy
 
 
def apply_filter_mask(image, mask):
    """Applies some kind of convolution filter
 
    Arguments:
    image -- Array-like object (preferably numpy.array). First and second
             dimension: Height and width. Third dimension: Color channels
    mask -- Array-like object of the filter coefficients. Two-dimensional.
            For example ((1, 2, 1), (2, 4, 2), (1, 2, 1)) for a Gauss filter
 
    Return value:
    A new image object of the same shape and type as image. Borders are
    initialized to 0
    """
    # First, ensure we don't get any surprises with the argument types
    image, mask = (numpy.asarray(arr) for arr in (image, mask))
    # Alright. Let's assume we have the mask mentioned in the description.
    # Over the course of this function we will need every pixel in image
    # multiplied by 1, 2, and 4. So let's do this only once in the beginning.
    #
    # Also, because we are multiplying a huge array with a scalar, it is worth
    # checking whether we actually need to do a multiplication. For 0 we skip
    # processing altogether.
    # This can be extended to deal with cases where a bit shift is sufficient
    # but you get the idea.
    parts = {factor: image if factor == 1 else image * factor for factor
             in set(mask.flat) if factor}
    # We don't need to care about the number of color channels.
    # Numpy's broadcast and vector operation rules will do it automatically
    height, width = image.shape[0:2]
    # For the iteration through the mask, we need the factor and the relative
    # offsets compared to the center pixel in the mask
    maskheight, maskwidth = mask.shape
    verticalborder, horizontalborder = (length / 2 for length
                                        in (maskheight, maskwidth))
    # Now build the output array. As an optimization, we could copy the first
    # parts entry, saving the zero initialization and an addition but let's
    # keep it simple for now
    result = numpy.zeros_like(image)
    # Here we just leave the borders at 0 but in general, when we need special
    # treatment for border pixels, it is faster to do it separately outside
    # the loop for the center frame
    center_frame = result[verticalborder:-verticalborder,
                          horizontalborder:-horizontalborder]
    # Let's skip being fancy and use a simple nested for loop for the rest.
    # It's only a few iterations, anyway
    for verticaloff, maskline in enumerate(mask):
        vertical_end = height - maskheight + verticaloff + 1
        for horizontaloff, factor in enumerate(maskline):
            if not factor:
                continue
            part = parts[factor]
            horizontal_end = width - maskwidth + horizontaloff + 1
            center_frame += part[verticaloff:vertical_end,
                                 horizontaloff:horizontal_end]
    # Alright. Nearly done. Now we just need to normalize the result. This is
    # an integer division and therefore extremely slow, especially on ARM CPUs.
    # So, let's go the extra mile and replace it with a bit shift if we can
    normfactor = mask.sum()
    if normfactor == 1: # maybe we are dealing with a normalized mask of floats?
        return result
    # In Python3 we could use math.log2. Oh well, let's do it the ugly way
    try:
        binnorm = bin(normfactor)
    except TypeError: # again, probably a mask of floats
        center_frame /= normfactor
    else:
        if binnorm.count('1') > 1: # no power of two
            center_frame /= normfactor
        else:
            normshift = binnorm.count('0') - 1
            center_frame >>= normshift
    return result
 
 
def main():
    image = numpy.random.random_integers(0, 10, 25).reshape(5, 5)
    mask = numpy.array(((1, 2, 1), (2, 4, 2), (1, 2, 1)))
    filtered = apply_filter_mask(image, mask)
    print "%s\n*\n%s\n=\n%s\n" % (image, mask, filtered)
 
 
if __name__ == '__main__':
    main()

Comments