Difference between revisions of "Pattern Noise"
BAndiT1983 (talk | contribs) |
|||
(33 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
=Intro= | |||
General info about pattern noise: https://web.archive.org/web/20210202034649/http://theory.uchicago.edu/~ejm/pix/20d/tests/noise/ and http://isl.stanford.edu/~abbas/ee392b/lect07.pdf | |||
The CMV12000 sensor suffers from dynamic row noise. | The CMV12000 sensor suffers from dynamic row noise. | ||
Line 4: | Line 7: | ||
One can observe this noise by looking at the difference between two images taken at identical settings. There are two main components that appear obvious in such a difference frame: random noise (per pixel, increases on brighter pixels) and row noise (per line). | One can observe this noise by looking at the difference between two images taken at identical settings. There are two main components that appear obvious in such a difference frame: random noise (per pixel, increases on brighter pixels) and row noise (per line). | ||
http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/twoframe-diff/it8-gainx1-offset2047-20ms-01.jpg http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/twoframe-diff/it8-gainx1-offset2047-20ms-01-minus-02-small.jpg | |||
=Correction methods= | |||
There are two ways to deal with this noise, after performing [[Black Calibration]]: | There are two ways to deal with this noise, after performing [[Black Calibration]]: | ||
Line 27: | Line 34: | ||
Here, let's say R = x1 is row noise (stdev = 1.6 at gain=x1) and x2 is black column noise. | Here, let's say R = x1 is row noise (stdev = 1.6 at gain=x1) and x2 is black column noise. | ||
<pre style="white-space: pre-wrap"> | |||
R = x1 | R = x1 | ||
B = mean(black_col') = R + x2 => x2 = B - R | B = mean(black_col') = R + x2 => x2 = B - R | ||
x2 can be estimated as mean(black_col') - mean(active_area') | x2 can be estimated as mean(black_col') - mean(active_area') | ||
stdev(x2) = 1.3. | stdev(x2) = 1.3. | ||
</pre> | |||
We want to find k that minimizes var(R - k*B). | We want to find k that minimizes var(R - k*B). | ||
<pre style="white-space: pre-wrap"> | |||
var(R - k*B) = var(x1 * (1-k) - x2 * k), | var(R - k*B) = var(x1 * (1-k) - x2 * k), | ||
=> k = var(x1)) / (var(x1) + var(x2). | => k = var(x1)) / (var(x1) + var(x2). | ||
</pre> | |||
In particular, for gain = x1, k = 1.6^2 / (1.6^2 + 1.3^2) = 0.6. | In particular, for gain = x1, k = 1.6^2 / (1.6^2 + 1.3^2) = 0.6. | ||
Line 43: | Line 50: | ||
Things get a little more complex because the static offset is different on odd and even rows, and it also appears to change from the left side to right side of the frame. More details on the [[Raw preprocessing]] page. | Things get a little more complex because the static offset is different on odd and even rows, and it also appears to change from the left side to right side of the frame. More details on the [[Raw preprocessing]] page. | ||
====Fixed frequency perturbation in black columns==== | |||
A closer look at the frequency spectrum of the black columns, compared to the spectrum of the row noise from a dark frame, revealed a strong fixed-frequency component present only in the black columns. Attempting to fix row noise with the above procedure would introduce some of this fixed frequency component in the main image as well. | |||
In the example image from below, this component has a frequency of 1/41.27 pixels<sup>-1</sup>, with an amplitude of 1.14 DN. The value is different in other test images, and appears to be consistent in the images taken during the same experiment. It doesn't change with exposure time. Cause is unknown. | |||
TODO: detailed analysis, FFT graphs... | |||
We'll attempt to filter out this perturbation from the black columns before using them for reducing row noise. | |||
Good news: after removing this perturbation, the optimal black columns multiplier increases to about 0.8 :) | |||
====Using nearby rows to reduce the row noise even more==== | |||
Maybe some nearby rows could offer some hints about the row offset on the line being analyzed? | |||
Looks like yes. Trying to write the row noise as a linear combination of row averages from black columns, using linear regression, not just the ones from the same row, but also from nearby rows, and discarding very small coefficients, gives the following filter: | |||
<pre style="white-space: pre-wrap"> | |||
(y % 2) | |||
? | |||
black_col[y-1] * 0.24 + | |||
black_col[y] * 0.63 | |||
: | |||
black_col[y] * 0.38 + | |||
black_col[y+1] * 0.43 | |||
</pre> | |||
What's interesting: on even rows, row noise (in the active area) depends on the black column average from the same row, but also on the black column average from the previous row. But on odd rows, it depends on the black column average from the same row and the next row. This probably means they are processed in pairs, and there is some common perturbation that affects both of them. | |||
This gives a minor improvement (0.6 -> 0.5). | |||
====Exploiting differences in green channels==== | |||
The two green channels are expected to be nearly identical, except for high-frequency details like sharp edges. That means, if we take the median difference on each row, we expect to get zero. In practice, we get some nonzero values. We could try to see if there is any correlation between those green channel differences and our row noise. | |||
Let's extract only the green channels from an image: the result will be W x H/2. Let's define green_delta(lag) = row_median(green - circshift(green,-lag)). That means, from each green row, subtract some nearby green row, and take the median difference. We'll compute this at lags -2, -1, 1, 2, mapped to array indices 0, 1, 2, 3. | |||
Linear regression gives an ugly FIR filter that looks like this: | |||
<pre style="white-space: pre-wrap">(y % 2) | |||
? | |||
black_col[y-2] * 0.17 + | |||
black_col[y-1] * 0.14 + | |||
black_col[y+0] * 0.17 + | |||
black_col[y+1] * 0.16 + | |||
black_col[y+2] * 0.14 + | |||
green_delta[0][y] * 0.22 + | |||
green_delta[1][y] * 0.31 + | |||
green_delta[2][y] * 0.38 | |||
: | |||
black_col[y-2] * 0.12 + | |||
black_col[y-1] * 0.13 + | |||
black_col[y+0] * 0.14 + | |||
black_col[y+1] * 0.14 + | |||
black_col[y+2] * 0.12 + | |||
black_col[y+3] * 0.12 + | |||
green_delta[0][y] * 0.33 + | |||
green_delta[3][y] * 0.32 | |||
</pre> | |||
As weird as it looks, it seems to work! | |||
Improvement (over the "Kalman" scaling of black columns): 0.6 -> 0.3. | |||
====Reducing the remaining row noise by image filtering==== | ====Reducing the remaining row noise by image filtering==== | ||
Line 48: | Line 116: | ||
Basic algorithm: | Basic algorithm: | ||
# Filter the image with an edge-aware vertical blur (bilateral filter on pixels from the same column) | # Filter the image with an edge-aware vertical blur (bilateral filter on pixels from the same column) | ||
# Subtract the blurred image; the residuals will reveal the row noise | # Subtract the blurred image; the residuals will reveal the row noise (see example) | ||
# Mask out highlights and strong edges | # Mask out highlights and strong edges | ||
# Take the median value from each row of the residuals image | # Take the median value from each row of the residuals image | ||
Line 55: | Line 123: | ||
Source: [http://github.com/apertus-open-source-cinema/misc-tools-utilities/blob/master/raw2dng/patternnoise.c patternnoise.c] | Source: [http://github.com/apertus-open-source-cinema/misc-tools-utilities/blob/master/raw2dng/patternnoise.c patternnoise.c] | ||
=Usage= | |||
The methods discussed here are implemented in [http://github.com/apertus-open-source-cinema/misc-tools-utilities/tree/master/raw2dng raw2dng]. | The methods discussed here are implemented in [http://github.com/apertus-open-source-cinema/misc-tools-utilities/tree/master/raw2dng raw2dng]. | ||
Line 63: | Line 131: | ||
* if the image also suffers from column noise, use raw2dng --fixpn | * if the image also suffers from column noise, use raw2dng --fixpn | ||
Tip: the algorithm for filtering row noise is also available in | Troubleshooting or checking the effectiveness of each step: | ||
* disable row noise reduction from black columns, but use the static offsets: --no-blackcol-rn | |||
* disable fixed frequency correction for black columns: --no-blackcol-ff | |||
* disable black reference columns completely: --no-blackcol (you need to compute new darkframes if you use this) | |||
Tip: the algorithm for filtering row noise is also available in [http://www.magiclantern.fm/forum/index.php?topic=13152 MLVFS], so you can use it on MLV videos (recorded with Magic Lantern) as well. | |||
=Tests on a dark frame= | |||
Checking a single 10ms dark frame, corrected using 256 exposures between 1.2ms and 77ms, various settings. Showing only noise measurements. | |||
# raw data without any processing | |||
<pre style="white-space: pre-wrap">raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --no-darkframe --check-darkframe | |||
Average : 190.35 | |||
Pixel noise : 7.14 | |||
Row noise : 5.29 (74.1%) | |||
Col noise : 5.41 (75.7%) | |||
</pre> | |||
# disable black columns, simple darkframe | |||
<pre style="white-space: pre-wrap">raw2dng *x1*.raw12 --calc-darkframe --no-blackcol --swap-lines | |||
raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --no-blackcol --check-darkframe | |||
Average : 140.33 | |||
Pixel noise : 3.08 | |||
Row noise : 1.33 (43.0%) | |||
Col noise : 0.09 (3.0%) | |||
</pre> | |||
# disable black columns, enable dark current frame | |||
<pre style="white-space: pre-wrap">raw2dng *x1*.raw12 --calc-dcnuframe --no-blackcol --swap-lines | |||
raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --no-blackcol --check-darkframe | |||
Average : 142.46 | |||
Pixel noise : 3.06 | |||
Row noise : 1.32 (43.1%) | |||
Col noise : 0.08 (2.5%) | |||
</pre> | |||
# dark current frame, use only static offsets from black columns | |||
<pre style="white-space: pre-wrap">raw2dng *x1*.raw12 --calc-dcnuframe --swap-lines | |||
raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --no-blackcol-rn --check-darkframe | |||
Average : 127.46 | |||
Pixel noise : 3.06 | |||
Row noise : 1.39 (45.4%) | |||
Col noise : 0.10 (3.2%) | |||
</pre> | |||
# dark current frame, enable black columns, don't remove the fixed frequency component from black columns | |||
<pre style="white-space: pre-wrap">raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --no-blackcol-ff --check-darkframe | |||
Average : 127.46 | |||
Pixel noise : 2.90 | |||
Row noise : 0.82 (28.2%) | |||
Col noise : 0.10 (3.3%) | |||
</pre> | |||
# dark current frame, enable black columns, remove the fixed frequency component (default setting) | |||
<pre style="white-space: pre-wrap">raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --check-darkframe | |||
Average : 127.46 | |||
Pixel noise : 2.85 | |||
Row noise : 0.61 (21.4%) | |||
Col noise : 0.10 (3.4%) | |||
</pre> | |||
# also enable --fixrn (aggressive correction) | |||
<pre style="white-space: pre-wrap">raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --fixrn --check-darkframe | |||
Average : 127.42 | |||
Pixel noise : 2.79 | |||
Row noise : 0.12 (4.1%) | |||
Col noise : 0.10 (3.5%) | |||
</pre> | |||
So, row noise was reduced from 1.33 (after dark frame correction) to 0.61 after using the black reference columns. | |||
If it's still noticeable, --fixrn should bring it to very low levels, unless you have strong horizontal lines in the image, which might trick the algorithm. | |||
==Example | Some experimental row noise filters: | ||
% 2-tap FIR, separate for odd/even rows | |||
<pre style="white-space: pre-wrap">raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --rnfilter=1 --check-darkframe | |||
Average : 127.44 | |||
Pixel noise : 2.84 | |||
Row noise : 0.51 (18.1%) | |||
Col noise : 0.10 (3.4%) | |||
</pre> | |||
% that ugly FIR that also looks at green channel differences | |||
<pre style="white-space: pre-wrap">raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --rnfilter=2 --check-darkframe | |||
Average : 127.47 | |||
Pixel noise : 2.81 | |||
Row noise : 0.31 (11.1%) | |||
Col noise : 0.10 (3.4%) | |||
</pre> | |||
=Example= | |||
Showing half-res image crops pushed by 4 stops (ufraw-batch --wb=auto --exposure=4 --shrink=2). | Showing half-res image crops pushed by 4 stops (ufraw-batch --wb=auto --exposure=4 --shrink=2). | ||
Line 76: | Line 225: | ||
http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-totally-raw-crop.jpg http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-no-blackcol-crop.jpg | http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-totally-raw-crop.jpg http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-no-blackcol-crop.jpg | ||
----- | |||
* Left: after dark frame, dark current and static offsets from black reference columns | |||
* Right: after dark frame, dark current and black reference columns correction, without removing the fixed frequency component | |||
http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-no-blackcol-rn-crop.jpg http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-no-blackcol-ff-crop.jpg | |||
Line 86: | Line 243: | ||
http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-crop.jpg http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-fixrn-crop.jpg | http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-crop.jpg http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-fixrn-crop.jpg | ||
----- | |||
* Left: --rnfilter=1 | |||
* Right: --rnfilter=2 | |||
http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-rnfilter1-crop.jpg http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-rnfilter2-crop.jpg | |||
Line 91: | Line 255: | ||
Algorithm internals: | Algorithm internals for --fixrn: | ||
* Left: filtered image (vertical blur using a bilateral filter) | * Left: filtered image (vertical blur using a bilateral filter) | ||
Line 98: | Line 262: | ||
http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-fixrn-dbg-denoised-crop.jpg http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-fixrn-dbg-noise-crop.jpg | http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-fixrn-dbg-denoised-crop.jpg http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-fixrn-dbg-noise-crop.jpg | ||
----- | |||
Downsized images: | |||
Corrected with dark frame and dark current only: | |||
http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-no-blackcol-small.jpg | |||
Also corrected with black columns: | |||
http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-small.jpg | |||
Also corrected with --fixrn: | |||
http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-fixrn-small.jpg | |||
Corrected with --rnfilter=2: | |||
http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-rnfilter2-small.jpg | |||
Larger images (half-res): | |||
* [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-no-blackcol.jpg 10ms+4-no-blackcol.jpg] | |||
* [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4.jpg 10ms+4.jpg] | |||
* [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-fixrn.jpg 10ms+4-fixrn.jpg] | |||
* [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms+4-rnfilter2.jpg 10ms+4-rnfilter2.jpg] | |||
----- | |||
All files used for this test, including scripts, calibration frames and uncompressed images, can be found here: | All files used for this test, including scripts, calibration frames and uncompressed images, can be found here: | ||
Line 105: | Line 298: | ||
* Raw12 image: [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms.raw12 10ms.raw12] | * Raw12 image: [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms.raw12 10ms.raw12] | ||
* Calibration frames: [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/darkframe-x1.pgm darkframe-x1.pgm] and [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/dcnuframe-x1.pgm dcnuframe-x1.pgm] | * Calibration frames: [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/darkframe-x1.pgm darkframe-x1.pgm] and [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/dcnuframe-x1.pgm dcnuframe-x1.pgm] | ||
* Final image: [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms.DNG 10ms.DNG] | * Final image (--rnfilter=2): [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/10ms.DNG 10ms.DNG] | ||
* Testing script: [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/fpntest.sh fpntest.sh] | * Testing script: [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/fpntest.sh fpntest.sh] | ||
* Script output: [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/fpntest.log fpntest.log] | * Script output: [http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/fpntest.log fpntest.log] |
Latest revision as of 08:54, 4 October 2021
1 Intro
General info about pattern noise: https://web.archive.org/web/20210202034649/http://theory.uchicago.edu/~ejm/pix/20d/tests/noise/ and http://isl.stanford.edu/~abbas/ee392b/lect07.pdf
The CMV12000 sensor suffers from dynamic row noise.
That means, a scalar offset gets added to each row. The offset is not correlated between different frames, so we can't remove it using a calibration frame (dark frame or whatever).
One can observe this noise by looking at the difference between two images taken at identical settings. There are two main components that appear obvious in such a difference frame: random noise (per pixel, increases on brighter pixels) and row noise (per line).
2 Correction methods
There are two ways to deal with this noise, after performing Black Calibration:
- use info from black reference columns to reduce dynamic row noise without guessing anything (fast, can be implemented in real-time, see Raw_preprocessing)
- use denoising techniques to reduce the remaining row noise (the guesswork part, slow)
2.1 Reducing row noise using black reference columns
The application note AN01 from CMOSIS says:
"The noise is also present in the black reference columns (8 left and 8 right), so when enabled (reg 89[15] = 1), these can be used for row noise correction by for example making a relative row profile of these black columns and subtract this from the image."
However, simply subtracting each row average of the black columns from our image is not going to work. Here's why:
Kalman filter theory: http://robocup.mi.fu-berlin.de/buch/kalman.pdf
From page 3, if we know how noisy our estimations are, the optimal weights are inversely proportional with the noise variances:
x_optimal = (x1 * var(x2) + x2 * var(x1)) / (var(x1) + var(x2))
Here, let's say R = x1 is row noise (stdev = 1.6 at gain=x1) and x2 is black column noise.
R = x1 B = mean(black_col') = R + x2 => x2 = B - R x2 can be estimated as mean(black_col') - mean(active_area') stdev(x2) = 1.3.
We want to find k that minimizes var(R - k*B).
var(R - k*B) = var(x1 * (1-k) - x2 * k), => k = var(x1)) / (var(x1) + var(x2).
In particular, for gain = x1, k = 1.6^2 / (1.6^2 + 1.3^2) = 0.6.
So, we don't have to simply subtract the black columns. Rather, we'll subtract the static offset (median value) first, and then, we'll subtract the remaining variations multiplied by 0.6 at gain=x1.
Things get a little more complex because the static offset is different on odd and even rows, and it also appears to change from the left side to right side of the frame. More details on the Raw preprocessing page.
2.2 Fixed frequency perturbation in black columns
A closer look at the frequency spectrum of the black columns, compared to the spectrum of the row noise from a dark frame, revealed a strong fixed-frequency component present only in the black columns. Attempting to fix row noise with the above procedure would introduce some of this fixed frequency component in the main image as well.
In the example image from below, this component has a frequency of 1/41.27 pixels-1, with an amplitude of 1.14 DN. The value is different in other test images, and appears to be consistent in the images taken during the same experiment. It doesn't change with exposure time. Cause is unknown.
TODO: detailed analysis, FFT graphs...
We'll attempt to filter out this perturbation from the black columns before using them for reducing row noise.
Good news: after removing this perturbation, the optimal black columns multiplier increases to about 0.8 :)
2.3 Using nearby rows to reduce the row noise even more
Maybe some nearby rows could offer some hints about the row offset on the line being analyzed?
Looks like yes. Trying to write the row noise as a linear combination of row averages from black columns, using linear regression, not just the ones from the same row, but also from nearby rows, and discarding very small coefficients, gives the following filter:
(y % 2) ? black_col[y-1] * 0.24 + black_col[y] * 0.63 : black_col[y] * 0.38 + black_col[y+1] * 0.43
What's interesting: on even rows, row noise (in the active area) depends on the black column average from the same row, but also on the black column average from the previous row. But on odd rows, it depends on the black column average from the same row and the next row. This probably means they are processed in pairs, and there is some common perturbation that affects both of them.
This gives a minor improvement (0.6 -> 0.5).
2.4 Exploiting differences in green channels
The two green channels are expected to be nearly identical, except for high-frequency details like sharp edges. That means, if we take the median difference on each row, we expect to get zero. In practice, we get some nonzero values. We could try to see if there is any correlation between those green channel differences and our row noise.
Let's extract only the green channels from an image: the result will be W x H/2. Let's define green_delta(lag) = row_median(green - circshift(green,-lag)). That means, from each green row, subtract some nearby green row, and take the median difference. We'll compute this at lags -2, -1, 1, 2, mapped to array indices 0, 1, 2, 3.
Linear regression gives an ugly FIR filter that looks like this:
(y % 2) ? black_col[y-2] * 0.17 + black_col[y-1] * 0.14 + black_col[y+0] * 0.17 + black_col[y+1] * 0.16 + black_col[y+2] * 0.14 + green_delta[0][y] * 0.22 + green_delta[1][y] * 0.31 + green_delta[2][y] * 0.38 : black_col[y-2] * 0.12 + black_col[y-1] * 0.13 + black_col[y+0] * 0.14 + black_col[y+1] * 0.14 + black_col[y+2] * 0.12 + black_col[y+3] * 0.12 + green_delta[0][y] * 0.33 + green_delta[3][y] * 0.32
As weird as it looks, it seems to work!
Improvement (over the "Kalman" scaling of black columns): 0.6 -> 0.3.
2.5 Reducing the remaining row noise by image filtering
Basic algorithm:
- Filter the image with an edge-aware vertical blur (bilateral filter on pixels from the same column)
- Subtract the blurred image; the residuals will reveal the row noise (see example)
- Mask out highlights and strong edges
- Take the median value from each row of the residuals image
- Subtract these values from each row of the original image
Source: patternnoise.c
3 Usage
The methods discussed here are implemented in raw2dng.
- black reference columns are used by default, as long as you use a dark frame (since this method is fast and has no side effects)
- to reduce the remaining row noise, use raw2dng --fixrn
- if the image also suffers from column noise, use raw2dng --fixpn
Troubleshooting or checking the effectiveness of each step:
- disable row noise reduction from black columns, but use the static offsets: --no-blackcol-rn
- disable fixed frequency correction for black columns: --no-blackcol-ff
- disable black reference columns completely: --no-blackcol (you need to compute new darkframes if you use this)
Tip: the algorithm for filtering row noise is also available in MLVFS, so you can use it on MLV videos (recorded with Magic Lantern) as well.
4 Tests on a dark frame
Checking a single 10ms dark frame, corrected using 256 exposures between 1.2ms and 77ms, various settings. Showing only noise measurements.
# raw data without any processing
raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --no-darkframe --check-darkframe Average : 190.35 Pixel noise : 7.14 Row noise : 5.29 (74.1%) Col noise : 5.41 (75.7%)
# disable black columns, simple darkframe
raw2dng *x1*.raw12 --calc-darkframe --no-blackcol --swap-lines raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --no-blackcol --check-darkframe Average : 140.33 Pixel noise : 3.08 Row noise : 1.33 (43.0%) Col noise : 0.09 (3.0%)
# disable black columns, enable dark current frame
raw2dng *x1*.raw12 --calc-dcnuframe --no-blackcol --swap-lines raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --no-blackcol --check-darkframe Average : 142.46 Pixel noise : 3.06 Row noise : 1.32 (43.1%) Col noise : 0.08 (2.5%)
# dark current frame, use only static offsets from black columns
raw2dng *x1*.raw12 --calc-dcnuframe --swap-lines raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --no-blackcol-rn --check-darkframe Average : 127.46 Pixel noise : 3.06 Row noise : 1.39 (45.4%) Col noise : 0.10 (3.2%)
# dark current frame, enable black columns, don't remove the fixed frequency component from black columns
raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --no-blackcol-ff --check-darkframe Average : 127.46 Pixel noise : 2.90 Row noise : 0.82 (28.2%) Col noise : 0.10 (3.3%)
# dark current frame, enable black columns, remove the fixed frequency component (default setting)
raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --check-darkframe Average : 127.46 Pixel noise : 2.85 Row noise : 0.61 (21.4%) Col noise : 0.10 (3.4%)
# also enable --fixrn (aggressive correction)
raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --fixrn --check-darkframe Average : 127.42 Pixel noise : 2.79 Row noise : 0.12 (4.1%) Col noise : 0.10 (3.5%)
So, row noise was reduced from 1.33 (after dark frame correction) to 0.61 after using the black reference columns.
If it's still noticeable, --fixrn should bring it to very low levels, unless you have strong horizontal lines in the image, which might trick the algorithm.
Some experimental row noise filters:
% 2-tap FIR, separate for odd/even rows
raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --rnfilter=1 --check-darkframe Average : 127.44 Pixel noise : 2.84 Row noise : 0.51 (18.1%) Col noise : 0.10 (3.4%)
% that ugly FIR that also looks at green channel differences
raw2dng blackframes-gainx1-offset2047-10ms-01.raw12 --swap-lines --rnfilter=2 --check-darkframe Average : 127.47 Pixel noise : 2.81 Row noise : 0.31 (11.1%) Col noise : 0.10 (3.4%)
5 Example
Showing half-res image crops pushed by 4 stops (ufraw-batch --wb=auto --exposure=4 --shrink=2).
- Left: raw sensor data (adjusted black level manually).
- Right: after dark frame and dark current subtraction, but without correction from black columns.
- Note: in the raw data, even and odd rows have different black offsets; that's why we have wrong colors.
- Left: after dark frame, dark current and static offsets from black reference columns
- Right: after dark frame, dark current and black reference columns correction, without removing the fixed frequency component
- Left: after dark frame, dark current and black reference columns correction.
- Right: after row noise reduction (--fixrn).
- Left: --rnfilter=1
- Right: --rnfilter=2
Algorithm internals for --fixrn:
- Left: filtered image (vertical blur using a bilateral filter)
- Right: noise image, revealing row noise. Black regions are from edges that were masked.
Downsized images:
Corrected with dark frame and dark current only:
Also corrected with black columns:
Also corrected with --fixrn:
Corrected with --rnfilter=2:
Larger images (half-res):
All files used for this test, including scripts, calibration frames and uncompressed images, can be found here: http://files.apertus.org/AXIOM-Beta/snapshots/pattern-noise/
In particular, you might be interested in:
- Raw12 image: 10ms.raw12
- Calibration frames: darkframe-x1.pgm and dcnuframe-x1.pgm
- Final image (--rnfilter=2): 10ms.DNG
- Testing script: fpntest.sh
- Script output: fpntest.log