A friend of mine became suddenly aware that her Nikon D80 camera had developed a so called stuck or hot pixel. Repairing a CCD chip in a camera is quite costly and what is more, looking back through the old photos she discovered the defect had been present for over a year. In this article we develop a tiny Python program that masks out a pixel in pretty much the same way that the built-in firmware in de camera does this for pixels that were hot or dead when the camera was constructed.
Correcting stuck or hot pixels in images
Hot or stuck pixels in a camera are annoying and other than replacing the ccd chip, nothing can be done about it with regard to the hardware. More on this in this illuminating article. Sometimes the camera can map out bad pixels but that won't fix old images, so the plan is to create a small program that does this on existing images.
The idea is to replace the offending pixel with a weighted average of the neighboring pixels so it will blend in with the surroundings. Diagonally adjacent pixels have a lower weight then pixels horizontally or vertically adjacent (see picture on the left). Such a program is simple enough to implement using PIL, the Python Imaging Library. There is even a version for Python 3 made available by Christoph Gohlke, although you will have to replace all relative import statements with absolute one if you want that to work. (Simply run the the installer, open all
*.py
files in site-packages/PIL and replace all occurrences of from . import
with a simple import
. A decent editor like notepad++ or ultraedit can manage that in one go).
The program will take the name of an image file and the x,y position of the pixel to fix as its arguments, e.g. python correctpixel.py myimage.jpg 1500,1271
and will produce a corrected image with the same name but with a c_
prefix added, in our example c_myimage.jpg
. It will use a weighted average of the eight neighboring pixels as depicted in this image (the diagonally adjacent pixels have a weight of 1/sqrt(2)
.
The simplest way to find the exact location of the pixel to fix is to open the image in Gimp, locate the pixel and hover over it with the mouse: the location is shown in the status bar at the bottom. Depending on your zoom level these may be fractional numbers, but we need just the integral parts.
The program itself is rather straightforward and is shown in its entirety below:
import sys import Image def correct(im,xy,matrix): pim = im.load() x,y = xy maxx,maxy = im.size n = 0 sumr,sumg,sumb = 0,0,0 for dx,dy,w in matrix: px,py = x+dx,y+dy if px<0 or py<0 or px >= maxx or py >= maxy: break n += w r,g,b = pim[px,py] sumr,sumg,sumb = sumr+r*w,sumg+g*w,sumb+b*w pim[x,y]=(int(sumr/n),int(sumg/n),int(sumb/n)) w=1/(2**0.5) matrixd=((0,1,1),(0,-1,1),(1,0,1),(-1,0,1),(-1,-1,w),(-1,1,w),(1,1,w),(1,-1,w)) im = Image.open(sys.argv[1]) xy = tuple(map(int,sys.argv[2].split(','))) correct(im,xy,matrixd) im.save('c_'+sys.argv[1],quality=97)Given an opened image the function
correct()
will load the data and the loop of the list of pixels offsets and weights given in its matrix
argument. It will check whether a neighbor is within the bounds of the image (line 12; if the stuck pixel is on an edge it might not be) and if so, sum its RGB components according to the weight of this pixel. The pixel is finally replaced by summed values divided by the number of summed pixels. We also take care to convert the RGB components to integers again (line 17Y).
The final lines take care of opening the image (line 22), and converting the pixel position argument to a tuple of integers before calling the correct()
function. The image is saved again with a name prefixed with c_
. The quality
parameter is set to a very high value to more or less save the image with the same jpeg quality a the original image.
Note that at the moment the code shown works for jpeg images only (or to be precise, only for images with three bands, R,G,B.) Png images may have and extra transparency band and the code does not deal with that nor does it play nice with indexed formats (like gif).