Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/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()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement