Skip to content

Instantly share code, notes, and snippets.

@mildmojo
Created February 4, 2020 02:18
Show Gist options
  • Save mildmojo/f745f7eaee44ba8a4776a62a0c72a260 to your computer and use it in GitHub Desktop.
Save mildmojo/f745f7eaee44ba8a4776a62a0c72a260 to your computer and use it in GitHub Desktop.
Dark frame subtraction script for removing digital camera sensor noise from long-exposure photos
#!/usr/bin/env node
/*
darksub.js
Implements the dark frame subtraction pipeline using ImageMagick described here:
https://www.imagemagick.org/discourse-server/viewtopic.php?p=62840&sid=c6a5c35cc2805a51e37ae5f18895b609#p62840
It's very slow.
This is really just a batch script; you'll need to have ImageMagick's
command-line utility `convert` installed (try `apt install imagemagick`
or `brew install imagemagick`).
Usage: $ darksub.js <darkframe.jpg> <pic1.jpg...picN.jpg>
The first argument should be the dark frame to be subtracted.
The remaining arguments are the photos from which to remove the dark frame noise.
Files are written to the directory named in OUTPUT_DIR.
*/
const fs = require('fs');
const path = require('path');
const execSync = require('child_process').execSync;
const BLUR_RADIUS = 20;
const OUTPUT_DIR = 'darksub-out';
if (process.argv.length < 4) {
console.log(`Usage: ${path.basename(__filename)} <darkframe.jpg> <target0.jpg...targetN.jpg>`);
process.exit(1);
}
files = Array.from(process.argv.slice(2));
darkFrame = files.shift();
if (!fs.existsSync(OUTPUT_DIR))
fs.mkdirSync(OUTPUT_DIR);
for (file of files) {
// Don't de-noise the dark frame if it's among the targets.
if (file === darkFrame) continue;
outfile = path.join(OUTPUT_DIR, file);
darkProduct = path.join(OUTPUT_DIR, 'darkProduct.png');
oneMinusDarkProduct = path.join(OUTPUT_DIR, 'oneMinusDarkProduct.png');
corrections = path.join(OUTPUT_DIR, 'corrections.png');
blurFile = path.join(OUTPUT_DIR, 'blurFile.png');
process.stdout.write(`Processing ${file}....`);
// the product of the dark frame and the starting image is now in startbydark
execSync(`convert -compose multiply -composite ${darkFrame} ${file} ${darkProduct}`);
// I multiply by 2.54, as the math has some weird 255=100 thing, I'm sure it's documented, but...
execSync(`convert -evaluate Multiply 2.54 ${darkProduct} ${darkProduct}`);
//start - dark
// oneMinusDarkProduct = start - start*dark = start (1-dark)
execSync(`convert -compose minus -composite ${darkProduct} ${file} ${oneMinusDarkProduct}`);
//blurs
// so I can easily get the local color
execSync(`convert -gaussian-blur 20x${BLUR_RADIUS} ${file} ${blurFile}`);
//local * dark
// corrections = dark * local_color
execSync(`convert -compose multiply -composite ${darkFrame} ${blurFile} ${corrections}`);
// fixing the math again, yes I realize this should be 2.55 and not 2.54
execSync(`convert -evaluate Multiply 2.54 ${corrections} ${corrections}`);
// final = start - start*dark + dark*local_color = (1-dark) * start + dark * local
execSync(`convert -compose plus -composite ${oneMinusDarkProduct} ${corrections} ${outfile}`);
[
darkProduct,
oneMinusDarkProduct,
blurFile,
corrections,
].forEach(fs.unlinkSync);
process.stdout.write('done\n');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment