1

I am trying to add noise to images in psychopy. I was using imread but do not appear able to read images in the format needed to add noise to individual pixels throughout the image. I'm hoping someone may have ideas of how I might be able to do this.

Specifically, my task presents stimuli to participants at their own perceptual threshold. I am using an adaptive staircasing procedure (the quest handler) to titrate between trials the amount of noise added to an image to reach this threshold. i.e., the participant identifies the image correctly, the next image has more noise; they identify an image incorrectly, the next trial has less noise. We do this over repeated trials to get the amount of noise needed for participants to answer a certain percentage of trials correctly.

I'm trying to add an amount of noise at each trial equal to the percent of noise passed from the quest handler by altering individual pixels to add gaussian noise. I do not wish to alter the original image. I envision this working by reading the image in as a matrix of pixels, copying it, adding noise to pixels in that matrix, and presenting that new stimulus for that trial. The imread function I was using to read in my images does not appear able to do this - does anyone have any suggestions?

Kayla
  • 11
  • 1
  • `imread` should return an object which allows you to directly access pixels by their coordinates: see https://docs.opencv.org/3.4/d5/d98/tutorial_mat_operations.html, remembering to push the "Python" buttons to see the correct syntax. – Michael MacAskill Mar 02 '20 at 03:17
  • This is helpful, thank you. I think where I'm stuck in using this is that I am hoping to avoid searching pixel by pixel using coordinates to add noise - the script I have is instead adding a random distribution of noise at a certain percentage. Am I correct that this method would require searching for each pixel I wanted to alter using their coordinates? – Kayla Mar 02 '20 at 17:58
  • No, it's a matrix, so (using the `numpy` library for example), you could take your noise distribution matrix and add all the corresponding elements (i.e. pixels) of the image and noise in one step (i.e. a single matrix addition). e.g. https://stackoverflow.com/a/30624520/3592211 – Michael MacAskill Mar 02 '20 at 20:03
  • Okay. The code you provided is the code I've adapted to add noise to my images. Errors we got trying to implement it are what lead us to believe our images weren't being read in properly. I will try to edit this using information from your first comment and will update with whether that was successful! – Kayla Mar 03 '20 at 01:16
  • OK, good luck. If problems persist, To get usefully specific answers on SO (rather than a stream of comments like this), you should probably edit the post to show the code you used (ideally in an extract that can actually be run) and the exact output of any error messages you get (and/or a description of how it otherwise fails to what is intended). – Michael MacAskill Mar 03 '20 at 09:25

1 Answers1

0

I have demonstrated how one can read an image with cv2 and apply varying levels of noise to it. The noise addition does not modify the original image. As @Michael MacAskill wrote in his comments, you can apply noise to an image with a single vector operation. In my answer, I create a Gaussian with mean 1 and the same shape as the image, and I multiply it against the image. The level of noise can be increased by increasing the standard deviation of the Gaussian noise distribution.

import cv2
import matplotlib.pyplot as plt
import numpy as np


def apply_noise(image, scale):
    """Return image with noise.

    Parameters
    ----------
    image : ndarray, image to which noise is applied.
    scale : positive float, standard deviation of Gaussian 
        noise distribution.
    """
    image = np.asarray(image)
    # Create a Gaussian noise array.
    noise = np.random.normal(loc=1.0, scale=scale, size=image.shape)
    # Apply the noise array.
    noisy = image * noise
    # Tranform to integer type.
    noisy = noisy.astype(np.int32)
    # Clip the values to RGB bounds.
    noisy = noisy.clip(0, 255)
    return noisy

I downloaded a sample image using

wget -qO "astronaut.jpg" https://live.staticflickr.com/8674/16504233985_9f1060624e_q_d.jpg

and here are sample results. The original image:

img = cv2.imread("astronaut.jpg")
# Transform from BGR to RGB
img = img[..., ::-1]
plt.imshow(img)

astronaut

The image with some noise applied:

img_a = apply_noise(image=img, scale=0.1)
plt.imshow(img_a)
plt.title("Gaussian std. dev. = 0.1")

astronaut with some noise applied

The image with more noise applied:

img_b = apply_noise(image=img, scale=0.5)
plt.imshow(img_b)
plt.title("Gaussian std. dev. = 0.5")

astronaut with more noise applied

jkr
  • 17,119
  • 2
  • 42
  • 68