Remove Dust from 1000 Photos with Gimp and Script-Fu

I think many photographers had to clean the captured photos from the dust particles on the matrix. Without full Photoshop-a or LightRoom-a, it’s extremely difficult to quickly process a large number of photos.
But we have Gimp and the desire to write a script for it.
On Habré there were already quite a few articles about the possibility of writing scripts in Gimp.
Here is the most detailed overview of the Script-fu language itself and the possibility of writing extensions to Gimp on it.
Here is an article about batch processing.
In theory, after reading 2 of these articles, you can do anything. But only when solving the problem indicated in the title, I came across many nuances, overcoming of which took quite a lot of time, and which are described in few places. Even in English Tutorial and Help. About them and will be discussed.


DSLR cameras, in addition to all its advantages, are endowed with a slight drawback. Dust on the matrix. There are a lot of physical devices for eliminating it, and some models even have special internal systems for shaking off dust from the matrices (here's a great article about all the technologies in this direction).

But in ordinary life there are situations when you go on vacation without taking a single “mop” to clean the matrix, and even a pear to blow off dust. Well, or shot 1000 shots, and only at home during the analysis I noticed a huge speck of dust on all frames, which is striking.

In this case, most specialists use Adobe Photoshop Light Room or the latest editions of the full Adobe Photoshop .
The LR using the rich capabilities of batch processing of photos .
In full Photoshop, the ability to create Actions .

But ordinary people, as a rule, have neither LightRoom nor even Photoshop.
But there is Gimp, the ability to write scripts for it, and a little time and zeal.

Writing a script

To do this, open Gimp and in it the Script-Fu console.

The appearance of the console on the right.

It is better to write the script in some text editor that displays parentheses. I used Notepad ++ for this. After writing, we will insert the script into the console and check how it works.

To implement the idea, we will use some clone functionality which essentially should do the same as the copy tool in the GUI of Gimp-a itself. We will enter the coordinates of our specks of dust in the photo and somehow set the area from where to clone the area to close this speck of dust.

To test the idea, I did not immediately write a “function” according to all the rules, as described here .
To get started, I found the functionality I needed in the procedure browser.

And he began to pick up the parameters.
Here the most interesting and undocumented began. The devil is hiding in the details.
As you can see from the function description, 7 parameters are fed to its input, the first two of them are DRAWABLE objects. With a little googling, you can easily find examples of scripts that get these drawabale from the path to the picture.

Here is the code
( let* 
      (image (car (gimp-file-load RUN-NONINTERACTIVE "E:/test.png" "E:/test.png")))
      (drawable (car (gimp-image-get-active-layer image)))

The first 2 parameters are (since both source and target we have our own picture)
The third parameter is set in IMAGE-CLONE, because we want to clone from the same picture and not from some previously created pattern there. 4th and 5th, too, everything is clear - we write here the coordinates of our specks of dust.
But the 6th and 7th parameters are not so obvious.

By trial and error, I found out that this tool works with a brush. And just the 6th and 7th parameters set the path along which this brush will move. I will describe below how to set the brush correctly.
Most of the time was spent on understanding the 6th parameter, and a lot of confusion made a comment to it
Number of stroke control points (count each coordinate as 2 points) (num-strokes> = 2)
I persistently entered here the number of coordinates, not numbers. And got unexpected results. It turns out that this is just the number of numbers in the next parameter (array). Those. let's say if we want to start cloning at x = 50 y = 50 and make a brush with a route through 3 points from point 0, 0 to point 10, 10 and then to point 20, 30 (the points are connected by lines, but not looped, i.e. the latter does not connect with the first) then we need to write something like this
  (gimp-clone drawable drawable IMAGE-CLONE 50 50 6 #(0 0 10 10 20 30) ) 

6 numbers in the array, but they describe only 3 coordinates. If you need 4 coordinates, you would write 8 and then an array of 8 numbers. etc.

Now about the brush. Alas, in this version of Gimp (2.8.4) there is a certain glitch when working with brushes. You can’t select a brush from existing ones and set its size (this bug ). The size will be taken either standard or the one that you have in the GUI is now set for it.
That's why we have to make our own brush and set parameters for it (which is actually more convenient)

Here is the code that opens the test picture, creates the brush and clones the left side of the picture - on the right side along the given route (draw a triangle).
( let* 
    ; create variables for get DRAWABLE
      (image (car (gimp-file-load RUN-NONINTERACTIVE "E:/test.png" "E:/test.png")))
      (drawable (car (gimp-image-get-active-layer image)))
    ; create and set brush	
    (gimp-brush-new "MyBrush")
    (gimp-brush-set-radius "MyBrush" 4)
    (gimp-brush-set-shape "MyBrush" BRUSH-GENERATED-CIRCLE)
    (gimp-brush-set-hardness "MyBrush" 0.50)
    (gimp-brush-set-spacing "MyBrush" 0)
    (gimp-brush-set-spikes "MyBrush" 0)
    (gimp-brushes-set-brush "MyBrush")
    ; clones
    (gimp-clone drawable drawable IMAGE-CLONE 100 10 8 #(450 10 360 150 540 150 450 10) )
    ; save result
    (gimp-file-save RUN-NONINTERACTIVE image drawable "E:/test_e.png" "E:/test_e.png")
    (gimp-image-delete image)

Copy the script to the console and press Enter. If everything succeeds, we see the following in the console.

In case of errors, we will see a description of the error.

And here are the pictures, before the script and after.

You can play with your brush options as you wish.

Apply the result to thousands of files

Using the code from the article described above , we write a function that we will apply to the folder, it will apply cloning to each photo, and save the result in a new file in a new folder.
The path to the folders (input and output), the point that we will close (i.e. our speck of dust) and its size will be passed as a parameter. We will clone from the area to the right of the speck of dust.

We get such a script
(define (script-fu-batch-dust-remove inputFolder outputFolder dustX dustY dustRadius)
  (let* ((filelist (cadr (file-glob (string-append inputFolder DIR-SEPARATOR "*") 1))))
    ; create and set brush	
    (gimp-brush-new "DustBrush")
    (gimp-brush-set-radius "DustBrush" dustRadius)
    (gimp-brush-set-shape "DustBrush" BRUSH-GENERATED-CIRCLE)
    (gimp-brush-set-hardness "DustBrush" 0.70)
    (gimp-brush-set-spacing "DustBrush" 0)
    (gimp-brush-set-spikes "DustBrush" 0)
    (gimp-brushes-set-brush "DustBrush")
    ; go throw all files in inputFolder
    (while (not (null? filelist))
      (let* ((filename (car filelist))
          (image (car (gimp-file-load RUN-NONINTERACTIVE filename filename)))
          (drawable (car (gimp-image-get-active-layer image)))
		  (dustCoordinates (vector dustX dustY))
        ; clone
        (gimp-clone drawable drawable IMAGE-CLONE (+ dustX (* dustRadius 2)) dustY 2 dustCoordinates)
        ; save result to outputFolder
        (set! filename (string-append outputFolder DIR-SEPARATOR (car (gimp-image-get-name image))))
        (gimp-file-save RUN-NONINTERACTIVE image drawable filename  filename)
        (gimp-image-delete image)
      (set! filelist (cdr filelist))
    ; remove just created Brush for not spam brush list
    (gimp-brush-delete "DustBrush")

We insert it into the console. Now we have the script-fu-batch-dust-remove function in the console.
Now we open our photos with dust, find the size of the dust with a brush (point the mouse at the dust and select the size of the brush so that it completely covers the dust). The coordinates of our cursor on the photo are written from the bottom left.
We write out the received coordinates and radius.
Copy all the files that we want to fix in the folder. Copy everything in a row is not worth it, I only copied those where the speck of dust is clearly striking, i.e. for example, against a background of clear sky or sea or other uniform texture. If you copy everything - even those where the specks of dust are not visible due to the heterogeneity of the image in this place - then these photographs will not be cloned in order.

Run the script in the console
(script-fu-batch-dust-remove "E:/toEdit/in" "E:/toEdit/out" 3186 682 15)

And in the E: / toEdit / out folder we get all the files with the same names - but already without dust!
The result is achieved.

After a series of experiments, I came to the conclusion that it is better to use the function not gimp-clone but gimp-heal. With it, the results are better, regardless of the dust surrounding the picture. The parameters are exactly the same, only there is no IMAGE-CLONE
So the call in my script will be something like this
(gimp-heal drawable drawable (+ dustX2 (* dustRadius2 2)) dustY2 2 dustCoordinates2)

Examples of the script working with gimp-heal
Successful processing (aperture 22)


Not quite successful processing (aperture 11)

After (half the window is now instead of a speck of dust)

The result is when the aperture is 7.0

After (it’s clear that a little aura still remains, I would probably have to increase the radius for this photo)

In most cases, the result as in the first or third picture is good, because Dust particles are much more striking when surrounded by a monotonous background, and on it they perfectly close. The script copes with its task - it removes dust particles that are clearly conspicuous, and for posting to a web album, such processing is enough. If you want to print something in high resolution, the photos will naturally be modified individually.

In light of the fact that in the script I used the gimp-file-save method to save the results, the output files are smaller than the original ones. Those. it turns out gimp-file-save presses JPEG in a different quality than the source files. If this is critical for anyone, they can read the procedures about file-jpeg-save in the browser and use it instead of the simpler solution with gimp-file-save that I proposed.

Also popular now: