Estimating fake PRNU on noise-free images #25

Open
opened 2024-03-28 15:59:48 +01:00 by Benjamin_Loison · 16 comments

prnu

Related to #19, #27 and #9.

![prnu](/attachments/061aba3a-9a62-4789-ac66-9367359a61c4) Related to #19, #27 and #9.
11 KiB
Benjamin_Loison added the
high priority
epic
labels 2024-03-28 15:59:48 +01:00
Benjamin_Loison pinned this 2024-03-28 16:00:41 +01:00
Author
Owner

As Context-Adaptive Interpolator is slow and possibly incorrectly implemented, let us try a good known working denoiser:

Figure_1

import matplotlib.pyplot as plt

from skimage.restoration import (denoise_tv_chambolle, denoise_bilateral,
                                 denoise_wavelet, estimate_sigma)
from skimage import data, img_as_float
from skimage.util import random_noise


original = img_as_float(data.chelsea()[100:250, 50:300])

sigma = 0.155
noisy = random_noise(original, var=sigma**2)

fig, ax = plt.subplots(nrows=2, ncols=4, figsize=(8, 5),
                       sharex=True, sharey=True)

plt.gray()

# Estimate the average noise standard deviation across color channels.
sigma_est = estimate_sigma(noisy, channel_axis=-1, average_sigmas=True)
# Due to clipping in random_noise, the estimate will be a bit smaller than the
# specified sigma.
print(f'Estimated Gaussian noise standard deviation = {sigma_est}')

tiles = [
    {
        'title': 'Original',
        'image': original,
    },
    {
        'title': 'Noisy',
        'image': noisy,
    },
    {
        'title': 'TV',
        'image': denoise_tv_chambolle(noisy, weight=0.1, channel_axis=-1),
    },
    {
        'title': '(more) TV',
        'image': denoise_tv_chambolle(noisy, weight=0.2, channel_axis=-1),
    },
    {
        'title': 'Bilateral',
        'image': denoise_bilateral(noisy, sigma_color=0.05, sigma_spatial=15, channel_axis=-1),
    },
    {
        'title': '(more) Bilateral',
        'image': denoise_bilateral(noisy, sigma_color=0.1, sigma_spatial=15, channel_axis=-1),
    },
    {
        'title': 'Wavelet denoising',
        'image': denoise_wavelet(noisy, channel_axis=-1, rescale_sigma=True),
    },
    {
        'title': 'Wavelet denoising\nin YCbCr colorspace',
        'image': denoise_wavelet(noisy, channel_axis=-1, convert2ycbcr=True, rescale_sigma=True),
    },
]

for y in range(2):
    for x in range(4):
        axis = ax[y, x]
        tile = tiles[y * 4 + x]
        axis.imshow(tile['image'])
        axis.axis('off')
        rms = rmsDiffNumpy(tile['image'], original)
        axis.set_title(tile['title'] + f'\nRMS with original: {round(rms, 4)}')

fig.tight_layout()

plt.show()

Based on https://scikit-image.org/docs/0.22.x/auto_examples/filters/plot_denoise.html

As Context-Adaptive Interpolator is slow and possibly incorrectly implemented, let us try a good known working denoiser: ![Figure_1](/attachments/75bc5de3-00e5-4e34-8aae-de668515ca8b) ```py import matplotlib.pyplot as plt from skimage.restoration import (denoise_tv_chambolle, denoise_bilateral, denoise_wavelet, estimate_sigma) from skimage import data, img_as_float from skimage.util import random_noise original = img_as_float(data.chelsea()[100:250, 50:300]) sigma = 0.155 noisy = random_noise(original, var=sigma**2) fig, ax = plt.subplots(nrows=2, ncols=4, figsize=(8, 5), sharex=True, sharey=True) plt.gray() # Estimate the average noise standard deviation across color channels. sigma_est = estimate_sigma(noisy, channel_axis=-1, average_sigmas=True) # Due to clipping in random_noise, the estimate will be a bit smaller than the # specified sigma. print(f'Estimated Gaussian noise standard deviation = {sigma_est}') tiles = [ { 'title': 'Original', 'image': original, }, { 'title': 'Noisy', 'image': noisy, }, { 'title': 'TV', 'image': denoise_tv_chambolle(noisy, weight=0.1, channel_axis=-1), }, { 'title': '(more) TV', 'image': denoise_tv_chambolle(noisy, weight=0.2, channel_axis=-1), }, { 'title': 'Bilateral', 'image': denoise_bilateral(noisy, sigma_color=0.05, sigma_spatial=15, channel_axis=-1), }, { 'title': '(more) Bilateral', 'image': denoise_bilateral(noisy, sigma_color=0.1, sigma_spatial=15, channel_axis=-1), }, { 'title': 'Wavelet denoising', 'image': denoise_wavelet(noisy, channel_axis=-1, rescale_sigma=True), }, { 'title': 'Wavelet denoising\nin YCbCr colorspace', 'image': denoise_wavelet(noisy, channel_axis=-1, convert2ycbcr=True, rescale_sigma=True), }, ] for y in range(2): for x in range(4): axis = ax[y, x] tile = tiles[y * 4 + x] axis.imshow(tile['image']) axis.axis('off') rms = rmsDiffNumpy(tile['image'], original) axis.set_title(tile['title'] + f'\nRMS with original: {round(rms, 4)}') fig.tight_layout() plt.show() ``` Based on https://scikit-image.org/docs/0.22.x/auto_examples/filters/plot_denoise.html
Author
Owner

With PRNU_FACTOR = 0.1:

PRNU quite guessable on a given image. Now need to reduce its factor and mean on several images to make it clear.

Figure_1

Figure_1

With PRNU_FACTOR = 0.01 it is more subtile but still visible on both images. For the image with PRNU see the U. For the PRNU estimate look at the top of R and bottom of U for instance.

Figure_1

Figure_1

Figure_1

PRNU estimate taking into account all images:

Figure_1

Manipulating brightness and contrast on GIMP does not enable a clear result as the following one. However, the not significant RMS decreasing reason is unclear. Even with normalizing it does not seem to make sense.

With `PRNU_FACTOR = 0.1`: `PRNU` quite guessable on a given image. Now need to reduce its factor and mean on several images to make it clear. ![Figure_1](/attachments/cbc9c1d0-0fbd-4c09-994b-1550ca8b70bf) ![Figure_1](/attachments/74e53c7e-56f7-4476-ac37-97b3fc4ac00e) With `PRNU_FACTOR = 0.01` it is more subtile but still visible on both images. For the image with PRNU see the `U`. For the PRNU estimate look at the top of `R` and bottom of `U` for instance. ![Figure_1](/attachments/9e27873d-7b69-4ae6-bac0-c3fc0a27a83d) ![Figure_1](/attachments/f0e37357-ee49-4e6f-977d-c17935a87a7e) ![Figure_1](/attachments/a3d9c1f8-d0d7-4f45-8312-05c4fe8b172d) PRNU estimate taking into account all images: ![Figure_1](/attachments/961d2993-ffd2-4700-846d-f13540a86db6) Manipulating brightness and contrast on GIMP does not enable a clear result as the following one. However, the not significant RMS decreasing reason is unclear. Even with normalizing it does not seem to make sense.
Author
Owner

prnu_4x4

Split images in 4x4 to increase PRNU estimation accuracy:

Figure_1

![prnu_4x4](/attachments/694f0141-1500-4ab1-899d-7b508a4fd137) Split images in 4x4 to increase PRNU estimation accuracy: ![Figure_1](/attachments/28fddccb-341f-419c-9a1a-24e25ecbceba)
Author
Owner

Let us not consider this RMS inconsistency but admit that the visual clear PRNU is fine, so let us add Gaussian noise.

Let us not consider this RMS inconsistency but admit that the visual clear PRNU is fine, so let us add Gaussian noise.
Author
Owner

In a first time we show an example of Gaussian noise and PRNU on an image, then we consider the PRNU estimate on the mean of the extracted noise for the considered images: in a first time only full images and in a second time split each images in 4x4.

With NOISE_FACTOR = 0.25:

Figure_2

Figure_1

Figure_1

With NOISE_FACTOR = 0.1:

Figure_1

Figure_1

Figure_1

In a first time we show an example of Gaussian noise and PRNU on an image, then we consider the PRNU estimate on the mean of the extracted noise for the considered images: in a first time only full images and in a second time split each images in 4x4. With `NOISE_FACTOR = 0.25`: ![Figure_2](/attachments/3be0a027-b636-46ab-a369-b716a7a6f83b) ![Figure_1](/attachments/5e6d590c-f5ad-4604-994f-1c2149791cee) ![Figure_1](/attachments/752afc5f-2aca-4930-bb79-4c31e70a065e) With `NOISE_FACTOR = 0.1`: ![Figure_1](/attachments/02086e14-6d0c-4ad2-9b7c-67345bc0dd4c) ![Figure_1](/attachments/cfc257ca-e634-461c-952f-46ffdc416b36) ![Figure_1](/attachments/40b64532-cc85-4eaf-a864-8bab38291fe2)
Author
Owner

If want to move on Gaussian noise as PRNU, then have to understand RMS unexpected results. May also be surprised, so can try anyway. To have recognizable PRNU can use GIMP > Filters > Noise > RGB Noise... selecting white and setting {Red,Blue,Green} to 1.

image

prnu_4x4_noise

Figure_1

Figure_1

If want to move on Gaussian noise as PRNU, then have to understand RMS unexpected results. May also be surprised, so can try anyway. To have recognizable PRNU can use `GIMP` > `Filters` > `Noise` > `RGB Noise...` selecting white and setting `{Red,Blue,Green}` to `1`. ![image](/attachments/72b83e32-eda3-45b8-ab39-183be7a59d1e) ![prnu_4x4_noise](/attachments/f247c66e-de1f-427c-985e-8fedc944549c) ![Figure_1](/attachments/866c14b7-821b-484c-850a-7cee63854ede) ![Figure_1](/attachments/c0f0b660-2c99-4ce5-84e8-5f6421a9f828)
Author
Owner

Concerning the RMS, maybe it is due to residual scene and with more images we would have a many pixels, hence important, less significant as nearer to 0.

Concerning the RMS, maybe it is due to residual scene and with more images we would have a many pixels, hence important, less significant as nearer to 0.
Author
Owner

How to ease visualizing the values of this rendering of image of floats? Could render as a greyscale image to make sure of my rough understanding of image values.

How to ease visualizing the values of this rendering of image of floats? Could render as a greyscale image to make sure of my rough understanding of image values.
Author
Owner

Figure_1

Figure_1

![Figure_1](/attachments/6442226e-dbe4-4cfc-8701-522193f1d1e9) ![Figure_1](/attachments/763a6ef4-1315-49b4-bb7e-801c45aae4e0)
Author
Owner

I start having the feeling that the RMS inconsistency is because of the PRNU estimation background which can not converge to 0 as it is just adding residual scene.

I start having the feeling that the RMS inconsistency is because of the PRNU estimation background which can not converge to 0 as it is just adding residual scene.
Author
Owner
![Figure_1](/attachments/5d7ca2a8-4fe2-4ed0-84e0-de45ba2e4e2f) The associated code is at https://gitea.lemnoslife.com/Benjamin_Loison/Robust_image_source_identification_on_modern_smartphones/src/commit/dd7a6892e50a6fb4ef9142028d9b25ab835e6598/datasets/noise_free_test_images/estimate_prnu.py.
Author
Owner

Next step:

Try on known working old cameras, i.e. with actual noise and PRNU? See #29.

Next step: Try on known working old cameras, i.e. with actual noise and PRNU? See #29.
Benjamin_Loison added the
enhancement
label 2024-03-30 00:08:52 +01:00
Benjamin_Loison unpinned this 2024-04-23 04:50:43 +02:00
Author
Owner

Add a single or multiple color scales?

Related to Benjamin_Loison/matplotlib/issues/32.

Based on the Stack Overflow question 23876588:

Using:

axis[0].set_title('First image without noise')
axis0 = axis[0].imshow(imageWithoutPrnuNpArrayTile)
plt.colorbar(axis0, label = 'Intensity', ax = axis[0])

With tight_layout:

with_tight_layout

Without tight_layout:

without_tight_layout

With cax instead of ax:

cax_with_tight_layout

The Stack Overflow answer 49037495 code leads to:

/home/benjamin/.local/lib/python3.10/site-packages/matplotlib/projections/__init__.py:63: UserWarning: Unable to import Axes3D. This may be due to multiple versions of Matplotlib being installed (e.g. as a system package and as a pip package). As a result, the 3D projection is not available.
  warnings.warn("Unable to import Axes3D. This may be due to multiple versions of "
Traceback (most recent call last):
  File "<tmp 1>", line 14, in <module>
    cax = divider.append_axes('right', size='5%', pad=0.05)
  File "/home/benjamin/.local/lib/python3.10/site-packages/matplotlib/_api/deprecation.py", line 387, in wrapper
    return func(*inner_args, **inner_kwargs)
  File "/usr/lib/python3/dist-packages/mpl_toolkits/axes_grid1/axes_divider.py", line 552, in append_axes
    ax = self.new_horizontal(size, pad, pack_start=False, **kwargs)
  File "/usr/lib/python3/dist-packages/mpl_toolkits/axes_grid1/axes_divider.py", line 486, in new_horizontal
    ax = self._get_new_axes(**kwargs)
  File "/usr/lib/python3/dist-packages/mpl_toolkits/axes_grid1/axes_divider.py", line 437, in _get_new_axes
    axes_class = axes._axes_class
AttributeError: 'Axes' object has no attribute '_axes_class'

The Stack Overflow answer 23877042 code leads to:

Traceback (most recent call last):
  File "<tmp 2>", line 162, in <module>
    plt.register_cmap(cmap=blue_red2)
AttributeError: module 'matplotlib.pyplot' has no attribute 'register_cmap'

The other answers to the mentioned Stack Overflow question do not seem interesting.

orientation = 'horizontal' makes the figure ok.

Add a single or multiple color scales? Related to [Benjamin_Loison/matplotlib/issues/32](https://codeberg.org/Benjamin_Loison/matplotlib/issues/32). Based on [the Stack Overflow question 23876588](https://stackoverflow.com/q/23876588): Using: ```python axis[0].set_title('First image without noise') axis0 = axis[0].imshow(imageWithoutPrnuNpArrayTile) plt.colorbar(axis0, label = 'Intensity', ax = axis[0]) ``` With `tight_layout`: ![with_tight_layout](/attachments/55d18935-fdb6-4231-8e14-87b329912124) Without `tight_layout`: ![without_tight_layout](/attachments/46b07fc8-6ea3-471c-b043-05df8b53aec5) With `cax` instead of `ax`: ![cax_with_tight_layout](/attachments/ff9efe42-e9f0-40ad-b640-26c94c34fc10) [The Stack Overflow answer 49037495](https://stackoverflow.com/a/49037495) code leads to: ``` /home/benjamin/.local/lib/python3.10/site-packages/matplotlib/projections/__init__.py:63: UserWarning: Unable to import Axes3D. This may be due to multiple versions of Matplotlib being installed (e.g. as a system package and as a pip package). As a result, the 3D projection is not available. warnings.warn("Unable to import Axes3D. This may be due to multiple versions of " Traceback (most recent call last): File "<tmp 1>", line 14, in <module> cax = divider.append_axes('right', size='5%', pad=0.05) File "/home/benjamin/.local/lib/python3.10/site-packages/matplotlib/_api/deprecation.py", line 387, in wrapper return func(*inner_args, **inner_kwargs) File "/usr/lib/python3/dist-packages/mpl_toolkits/axes_grid1/axes_divider.py", line 552, in append_axes ax = self.new_horizontal(size, pad, pack_start=False, **kwargs) File "/usr/lib/python3/dist-packages/mpl_toolkits/axes_grid1/axes_divider.py", line 486, in new_horizontal ax = self._get_new_axes(**kwargs) File "/usr/lib/python3/dist-packages/mpl_toolkits/axes_grid1/axes_divider.py", line 437, in _get_new_axes axes_class = axes._axes_class AttributeError: 'Axes' object has no attribute '_axes_class' ``` [The Stack Overflow answer 23877042](https://stackoverflow.com/a/23877042) code leads to: ``` Traceback (most recent call last): File "<tmp 2>", line 162, in <module> plt.register_cmap(cmap=blue_red2) AttributeError: module 'matplotlib.pyplot' has no attribute 'register_cmap' ``` The other answers to the mentioned Stack Overflow question do not seem interesting. `orientation = 'horizontal'` makes the figure ok.
Author
Owner
Related to [Benjamin_Loison/matplotlib/issues/35](https://codeberg.org/Benjamin_Loison/matplotlib/issues/35).
Author
Owner

Should check the denoiser range. There is no denoiser here.
Could use the scikit util view as block/window.

Should check the denoiser range. There is no denoiser here. Could use the scikit util view as block/window.
Benjamin_Loison changed title from Estimating fake PRNU on noise-less images to Estimating fake PRNU on noise-free images 2024-07-23 22:43:16 +02:00
Author
Owner
Related to [PRNU_extraction/issues/34](https://codeberg.org/Benjamin_Loison/PRNU_extraction/issues/34).
Sign in to join this conversation.
No description provided.