Yesterday I decided to implement the retinex algorithm described by John McCann in 1999 as a gimp plugin. I am using Python (in particular numpy for the main calculations) and consequently chose to put in some modifications to the algorithm to make it more efficient, but it makes generally the same effect as that described in Brian Funt, Florian Ciurea, and John McCann "Retinex in Matlab," Proceedings of the IS&T/SID Eighth Color Imaging Conference: Color Science, Systems and Applications, 2000, pp 112-121. http://www.cs.sfu.ca/~colour/publications/IST-2000/index.html As this is my first foray into gimp plugging in, I'd appreciate if someone could look over the code. Is there a more efficient way of getting out one colour channel of the image at a time? At the moment I read in the whole image which takes a lot of memory. Searching the archives today I notice that Pedro Paf was suggesting implementing it as a Summer of Code project - as the algorithm is very simple (a day to make even starting no numpy or Gimp python knowledge), what enhancements where being contemplated?
#! /usr/bin/env python # Implementation of a retinex algorithm similar to that described by # John McCann in 1999 # For more information about the algorithm see http://www.cs.sfu.ca/~colour/publications/IST-2000/index.html # Brian Funt, Florian Ciurea, and John McCann "Retinex in Matlab," Proceedings of the IS&T/SID Eighth Color Imaging Conference: Color Science, Systems and Applications, 2000, pp 112-121. # Copyright (C) 2007 John Fremlin # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import gimp from gimpfu import * import numpy import scipy import scipy.ndimage import struct import Image gettext.install("gimp20-python", gimp.locale_directory, unicode=True) small_amount = 1/1024.0 difference_from_neighbours_kernel = numpy.array([ [-1, -1, -1], [-1,8,-1], [-1,-1,-1]],"d") global_logscale = True def shrink(chan,scale): return scipy.ndimage.zoom(chan,1/float(scale),prefilter=False,order=5) def image_clip(chan): if global_logscale: return chan.clip(-numpy.inf,0.0) else: return chan.clip(0.0,1.0) def retinex_at_scale(retinex,orig,scale): assert(orig.size == retinex.size) working = orig diff = scipy.ndimage.convolve(working,difference_from_neighbours_kernel) result = (retinex + diff)/2 working = (retinex + image_clip(result))/2 return working def resize(chan,new_size): orig = chan.shape zoom = [((new+0.9)/float(old)) for old, new in zip(orig, new_size)] ret = scipy.ndimage.zoom(chan,zoom,prefilter=False,order=5) assert(new_size == ret.shape) return ret def process_one_channel(chan): retinex = numpy.array([[chan.mean()]],"d") for logscale in range(int(numpy.log2(min(*chan.shape))),-1,-1): scale = 1 << logscale orig = shrink(chan,scale) retinex = retinex_at_scale(resize(retinex,orig.shape),orig,scale) return retinex # return numpy.abs(chan-retinex) def retinex(image): if global_logscale: image = numpy.log(image+small_amount) [ width, height, channels ] = image.shape for c in range(channels): gimp.progress_update(c/channels) image[:,:,c] = process_one_channel(image[:,:,c]) if global_logscale: image = numpy.exp(image)-small_amount return image def progress_update(stage,proportion_done): gimp.progress_update(proportion_done*0.10) def read_in(drawable): width = drawable.width height = drawable.height bpp = drawable.bpp pr = drawable.get_pixel_rgn(0, 0, width, height, False) # image = numpy.zeros((height,width,3),"d") # for y in range(height): # for x in range(width): # image[y,x,:] = struct.unpack('BBB',pr[x,y][0:3]) # progress_update("read-in", (y / float(height))) a = numpy.fromstring(pr[:,:],"B") assert(a.size == width * height * bpp) image = numpy.array(a.reshape(height,width,bpp),"d")[:,:,0:min(bpp,3)] return image/256.0 def write_out(drawable,image): byte_image = numpy.array((image*256).round(0),"B") width = drawable.width height = drawable.height bpp = drawable.bpp pr = drawable.get_pixel_rgn(0, 0, width, height, True) assert(byte_image.size == width * height * bpp) pr[:,:] = byte_image.tostring() # for y in range(height): # for x in range(width): # pr[x,y] = struct.pack('BBB',*byte_image[y,x,:]) # progress_update("write-out", (y / float(height))) def retinex_plugin(img, drawable, new_layer_name, opacity, logscale, flatten): img.undo_group_start() try: global_logscale = logscale image = read_in(drawable) image = retinex(image) new_layer = gimp.Layer(img, new_layer_name, drawable.width, drawable.height, RGB_IMAGE, opacity, NORMAL_MODE) write_out(new_layer,image) img.add_layer(new_layer,0) if flatten: img.flatten() finally: img.undo_group_end() register( "mccann99-retinex", N_("Lighten the dark areas of an image while preserving detail"), "Performs a retinex algorithm similar to that described by John McCann in 1999", "John Fremlin", "John Fremlin", "2007", N_("McCann99 Retinex..."), "RGB*", [ (PF_IMAGE, "image", "Input image", None), (PF_DRAWABLE, "drawable", "Input drawable", None), (PF_STRING, "name", _("New _layer name"), _("Retinex")), (PF_SLIDER, "opacity", _("Op_acity"), 25, (0, 100, 1)), (PF_BOOL, "logscale", _("Lo_garithmic brightness manipulation"), True), (PF_BOOL, "flatten", _("_Flatten layers together after processing"), True) ], [], retinex_plugin, menu="<Image>/Filters/Enhance", domain=("gimp20-python", gimp.locale_directory) ) main()
_______________________________________________ Gimp-developer mailing list Gimp-developer@xxxxxxxxxxxxxxxxxxxxxx https://lists.XCF.Berkeley.EDU/mailman/listinfo/gimp-developer