What's this brightness function - image

The brightness of an image can be measured by the below function as this paper mentioned
In this paper, they didn't talk about Cr, Cg and Cb. Can anyone explain this function?
Thanks in advance.

Cr: Red channel
Cg: Green Channel
Cb: Blue Channel
The coefficients (0.241, 0.691, 0.068) are used to calculate the luminance
For example:
If you have a color (RGB) image and you want to convert to greyscale:
You will extract each channel from the image
greyscale = (0.2126 * Cr) + (0.7152 * Cg) + (0.0722 * Cb)
The coefficients are recommended by ITU-BT709 and are standards for HDTV.
So for calculating the brightness the accepted coefficients are 0.241, 0.691, and 0.068.
UPDATE:
Check more about new coefficients here.
You can print the brightness values:
import cv2
import numpy as np
# img will be BGR image
img = cv2.imread("samurgut3.jpeg")
#When we square the values overflow will occur if we have uint8 type
img = np.float64(img)
# Extract each channel
Cr = img[:, :, 2]
Cg = img[:, :, 1]
Cb = img[:, :, 0]
# Get width and height
Width = img.shape[0]
Height = img.shape[1]
#I don't think so about the height and width will not be here
brightness = np.sqrt((0.241 * (Cr**2)) + (0.691 * (Cg**2)) + (0.068 * (Cb**2))) / (Width * Height)
#We convert float64 to uint8
brightness =np.uint8(np.absolute(brightness))
print(brightness)
Output:
[[4.42336257e-05 4.09825832e-05 4.09825832e-05 ... 3.44907525e-05
4.13226678e-05 4.13226678e-05]
[4.09825832e-05 4.09825832e-05 4.09825832e-05 ... 3.44907525e-05
4.13226678e-05 4.13226678e-05]

the squaring is probably an attempt to convert from gamma-mapped values to linear values.
A somewhat better exponent would be something around 1/0.45 (~2.22) or 2.2 or 2.4.
An even better operation would be to find out the actual gamma curve and apply that. sRGB is defined piecewise and does not match exactly to a simple exponentiation.

Related

How to convert a numpy array to greyscale image?

I am trying to convert an 8-by-8 numpy array of binary format (0 represents black and 1 represents white). This is what I run:
from PIL import Image
data = im.fromarray(array) # array is an 8*8 binary numpy
data.save('dummy_pic.png')
But in the output I get a fully black square. Could anyone give me a hand please?
Black square is probably very dark gray, because you may have np.array with uint8 datatype, which has range of 0-255, not 0-1 like binary array. Try changing array datatype to bool, or scale values to 0-255 range.
Here is code snippet in which binary array is generated and displayed. If you scale by smaller value, circle will become slightly darker.
from PIL import Image
import numpy as np
# Generating binary array which represents circle
radius = 0.9
size = 100
x,y = np.meshgrid(np.linspace(-1,1,size),np.linspace(-1,1,size))
f = np.vectorize(lambda x,y: ( 1.0 if x*x + y*y < radius*radius else 0.0))
array = f(x,y)
# Solution 1:
# convert np.array to bool datatype
array = (array).astype(np.bool)
# Solution 2:
# scale values to uint8 maximum 255
array = ((array) * 255).astype(np.uint8)
img = Image.fromarray(array)
display(img)
Result: White circle

Add Gaussian noise to a binary image knowing noise variance or SNR in python

I am using python open cv for image restoration and in the first step, I have to add gaussian noise to my "binary" image. My image pixel values are integers that can take values 0 or 1. How can I add gaussian noise to my image knowing SNR or noise variance?
I came up with this piece of code which adds 15 percent noise to my image but I don't know if this noise is normal Gaussian and how I can find its variance and SNR.
def add_noise(im):
im_noisy = im.copy()
for i in range(len(im_noisy)):
for j in range(len(im_noisy[0])):
r = np.random.rand()
if r < 0.15:
im_noisy[i][j] = -im_noisy[i][j]+1 #(converts 0 to 1 and vice versa)
return im_noisy
Since the image is binary, the solution is not well defined - we have to select a threshold that above the threshold the noise is 1.
The Gaussian noise supposes to be symmetric, so pixels below the minus threshold are going to be -1.
We may define the threshold by number of sigma's (for example: above 2 sigma, the noise is 1, and below -2 sigma the noise is -1).
When selecting threshold of 2 sigma, statistically we expect that about 2.5% of total pixels are going to be 1 and 2.5% are going to be -1.
Select lower threshold for higher percentage.
Create random normal (Gaussian) distribution image with mean=0 and sigma=1:
sigma = 1
gauss = np.random.normal(0, sigma, im.shape) # Create random normal (Gaussian) distribution image with mean=0 and sigma=1.
Convert to values to -1, 0, 1 - assume pixels value above 2 sigma are "1", below -2 sigma are -1 and other are "0" (2 sigma is an example, we may select other value):
binary_gauss = (gauss > 2*sigma).astype(np.int8)
binary_gauss[gauss < -2*sigma] = -1
After adding binary_gauss to im, clip the result to range [0, 1]:
noisey_im = (im + binary_gauss).clip(0, 1)
Code sample (first part reads a sample image, and convert to binary):
import numpy as np
import cv2 # Used only for testing
im = cv2.imread('chelsea.png', cv2.IMREAD_GRAYSCALE) # Read input image (for testing).
im = cv2.threshold(im, 0, 1, cv2.THRESH_OTSU)[1] # Convert image to binary (for testing).
im = im.astype(np.int8) # Convert to type int8 (8 bits singed)
sigma = 1
gauss = np.random.normal(0, sigma, im.shape) # Create random normal (Gaussian) distribution image with mean=0 and sigma=1.
binary_gauss = (gauss > 2*sigma).astype(np.int8) # Convert to binary - assume pixels with value above 2 sigmas are "1".
binary_gauss[gauss < -2*sigma] = -1 # Set all pixels below 2 sigma to "-1".
noisey_im = (im + binary_gauss).clip(0, 1) # Add noise image, and clip the result ot [0, 1].
noisey_im = noisey_im.astype(np.uint8) # Convert to type uint8
# Show images (for testing).
cv2.imshow('im', im*255)
cv2.imshow('binary_gauss', (binary_gauss+1).astype(np.uint8)*127)
cv2.imshow('noisey_im', noisey_im*255)
cv2.waitKey()
cv2.destroyAllWindows()
im (input image after converting to binary):
binary_gauss (noise image after threshold - values are -1, 0, 1):
noisey_im (im + binary_gauss after threshold):

Why is the whole picture turning red?

We’ve been trying to fix this program for hours, yet nothing seems to work, we just can’t figure out what the problem is. It is supposed to make the whole picture black white, besides the red pixels. (https://imgur.com/a/gxRm3N1 should look like that afterwards)
Here is the result after the using the program, the whole picture is turning red:
https://imgur.com/a/yWYVoIx
How can I fix this?
from image_helper import *
img = load_rgb_image("ara.jpg")
w, h = img.size
pixels = load_rgb_pixels("ara.jpg")
#img.show()
for i in range(w*h):
r,g,b = pixels[i]
new_r = 2*r
new_g = g // 2
new_b = b + 10
pixels[i] = (new_r, new_g, new_b)
new_img = new_rgb_image(w, h, pixels)
new_img.show()
There is an excellent solution implemented in MATLAB.
I was tempting to translate the code from MATLAB to Python (using OpenCV).
Convert the image to HSV color space.
Select "non-red" pixels. In HSV color space, the first channel is the hue - there is a range of hue for pixels that considered to be red.
Set the selected pixel saturation channel to 0. (Pixels with zero saturation are gray).
Convert the image back from HSV to BGR color space.
Here is the Python code (conversation of the original MATLAB code):
import cv2
# The following code is a Python conversion to the following MATLAB code:
# Original MATLAB code: https://stackoverflow.com/questions/4063965/how-can-i-convert-an-rgb-image-to-grayscale-but-keep-one-color
img = cv2.imread('roses.jpg') # Load image.
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # Convert the image to HSV color space.
h = hsv[:, :, 0] # Note: in OpenCV hue range is [0,179]) The original MATLAB code is 360.*hsvImage(:, :, 1), when hue range is [0, 1].
s = hsv[:, :, 1] # Get the saturation plane.
non_red_idx = (h > 20//2) & (h < 340//2) # Select "non-red" pixels (divide the original MATLAB values by 2 due to the range differences).
s[non_red_idx] = 0 # Set the selected pixel saturations to 0.
hsv[:, :, 1] = s # Update the saturation plane.
new_img = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) # Convert the image back to BGR space
# Show images for testing:
cv2.imshow('img', img)
cv2.imshow('new_img', new_img)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.imwrite('new_img.jpg', new_img)
Result:
Notes:
For the method used for selecting the color range, refer to the original post.
The reference post has a simpler solution, using simple for loop (with inferior results), that more resembles your code.
Consider using this code as reference.

When decoding a jpeg how would I reverse chrominance downsampling in matlab?

Hi trying to make a simple jpeg compressor which also decompresses the image. I use the following code to downsample the chrominance of an image for the first step of jpeg compression.
resampler = vision.ChromaResampler;
[Cb, Cr] = resampler(Cb_channel, Cr_channel);
the function is part of the computer vision toolbox for matlab.
for example before downsampling:
Y dimensions = 3024 by 4032;
Cb and Cr dimensions = 3024 by 4032
after downsampling:
Y dimensions = 3024 by 4032;
Cb and Cr dimensions = 3024 by 2016
to display the original RGB image after decompression, the dimensions of all 3 Y, Cb and Cr components need to be the same so that I can merge the channels and convert the image back to RGB. I'm using the following code to achieve this:
Cb_resized = imresize(Cb, [(size(Cb, 1)) (2*size(Cb, 2))]);
Cr_resized = imresize(Cr, [(size(Cr, 1)) (2*size(Cr, 2))]);
When I then merge the 3 channels and use imshow() to see the image it looks fine. So is the above method the correct way of reversing the downsampled chrominance when decoding a jpeg?
Using imresize for up-sampling is "almost correct".
Instead of using imresize, you better use vision.ChromaResampler for up-sampling:
up_resampler = vision.ChromaResampler();
up_resampler.Resampling = '4:2:2 to 4:4:4';
[Cb_resized, Cr_resized] = up_resampler(Cb, Cr);
The module is design such that Resampling = '4:2:2 to 4:4:4' "reverses" the result of Resampling = '4:4:4 to 4:2:2'.
The '4:4:4 to 4:2:2' ChromaResampler uses a convention that displaces the result 0.5 pixels to the right.
(I think shifting by 0.5 pixels supposes to match MPEG-1 codec standard).
The 0.5 displacement is not well documented - I had to build a short test for figuring it out.
As far as I remember, the convention of moving 0.5 a pixel is used by MPEG-1 codec, but not used by MPEG-2 and newer codecs.
I don't think it is used by JPEG, but I am not sure...
Note:
Since the human visual system is not very sensitive to the Chroma resolution, you are probably not going to see the differences if 0.5 displacement is used or not.
For getting the same result as ChromaResampler, you may use imwarp, with displacement of 1 pixel in the horizontal axis.
Understanding imwarp is a little complicated.
I am going to use imwarp for demonstrating that 1 pixel displacement, gives the same result as ChromaResampler:
The following code sample shows the equivalence:
close all
I = imread('peppers.png'); % Read sample image
YUV = rgb2ycbcr(I); % Convert RGB to Y:Cb:Cr
U = YUV(:, :, 2); % Get U color channel
V = YUV(:, :, 3); % Get V color channel
down_resampler = vision.ChromaResampler(); % 4:4:4 to 4:2:2
down_resampler.Resampling = '4:4:4 to 4:2:2';
up_resampler = vision.ChromaResampler(); % 4:2:2 to 4:4:4
up_resampler.Resampling = '4:2:2 to 4:4:4';
% Down-sample U and V using ChromaResampler
[downU, downV] = down_resampler(U, V);
%downU2 = imresize(U, [size(U, 1), size(U, 2)/2]); % Not the same as using imresize
%figure;imshow(downU);figure;imshow(downU2);
% Up-sample downU and downV using ChromaResampler
[upU, upV] = up_resampler(downU, downV);
% Result is not the same as using imresize
%resizedU = imresize(downU, [size(downU, 1), size(downU, 2)*2], 'bilinear');
%resizedV = imresize(downV, [size(downV, 1), size(downV, 2)*2], 'bilinear');
% Use transformation matrix that resize horizontally by x2 and include single pixel horizontal displacement.
tform = affine2d([ 2 0 0
0 1 0
-1 0 1]);
% Use imwarp instead of imresize (the warp includes horizontal displacement of 1 pixel)
warpU = imwarp(downU, tform, 'bilinear', 'OutputView', imref2d([size(downU, 1), size(downU, 2)*2]));
warpU(:, end) = warpU(:, end-1); % Fill the last column by duplication
%figure;imagesc(double(upU) - double(resizedU));impixelinfo
%figure;imshow(upU);figure;imshow(resizedU);
%figure;imshow(upU);figure;imshow(warpU);
% Show the differences:
figure;imagesc(double(upU) - double(warpU));title('Diff');impixelinfo
max_abs_diff = max(imabsdiff(warpU(:), upU(:)));
disp(['max_abs_diff = ', num2str(max_abs_diff)]); % Maximum absolute differenced is 1 (due to rounding).
Note: The imresize usage is kept in comments.
Note:
The default interpolation method of imresize is cubic interpolation, and the default interpolation method of ChromaResampler is linear interpolation.
Cubic interpolation is considered superior, but linear interpolation is commonly used (the visible difference is negligible).

How to add a Gaussian shaped object to an image?

I am interested in adding a single Gaussian shaped object to an existing image, something like in the attached image. The base image that I would like to add the object to is 8-bit unsigned with values ranging from 0-255. The bright object in the attached image is actually a tree represented by normalized difference vegetation index (NDVI) data. The attached script is what I have have so far. How can I add a a Gaussian shaped abject (i.e. a tree) with values ranging from 110-155 to an existing NDVI image?
Sample data available here which can be used with this script to calculate NDVI
file = 'F:\path\to\fourband\image.tif';
[I R] = geotiffread(file);
outputdir = 'F:\path\to\output\directory\'
%% Make NDVI calculations
NIR = im2single(I(:,:,4));
red = im2single(I(:,:,1));
ndvi = (NIR - red) ./ (NIR + red);
ndvi = double(ndvi);
%% Stretch NDVI to 0-255 and convert to 8-bit unsigned integer
ndvi = floor((ndvi + 1) * 128); % [-1 1] -> [0 256]
ndvi(ndvi < 0) = 0; % not really necessary, just in case & for symmetry
ndvi(ndvi > 255) = 255; % in case the original value was exactly 1
ndvi = uint8(ndvi); % change data type from double to uint8
%% Need to add a random tree in the image here
%% Write to geotiff
tiffdata = geotiffinfo(file);
outfilename = [outputdir 'ndvi_' '.tif'];
geotiffwrite(outfilename, ndvi, R, 'GeoKeyDirectoryTag', tiffdata.GeoTIFFTags.GeoKeyDirectoryTag)
Your post is asking how to do three things:
How do we generate a Gaussian shaped object?
How can we do this so that the values range between 110 - 155?
How do we place this in our image?
Let's answer each one separately, where the order of each question builds on the knowledge from the previous questions.
How do we generate a Gaussian shaped object?
You can use fspecial from the Image Processing Toolbox to generate a Gaussian for you:
mask = fspecial('gaussian', hsize, sigma);
hsize specifies the size of your Gaussian. You have not specified it here in your question, so I'm assuming you will want to play around with this yourself. This will produce a hsize x hsize Gaussian matrix. sigma is the standard deviation of your Gaussian distribution. Again, you have also not specified what this is. sigma and hsize go hand-in-hand. Referring to my previous post on how to determine sigma, it is generally a good rule to set the standard deviation of your mask to be set to the 3-sigma rule. As such, once you set hsize, you can calculate sigma to be:
sigma = (hsize-1) / 6;
As such, figure out what hsize is, then calculate your sigma. After, invoke fspecial like I did above. It's generally a good idea to make hsize an odd integer. The reason why is because when we finally place this in your image, the syntax to do this will allow your mask to be symmetrically placed. I'll talk about this when we get to the last question.
How can we do this so that the values range between 110 - 155?
We can do this by adjusting the values within mask so that the minimum is 110 while the maximum is 155. This can be done by:
%// Adjust so that values are between 0 and 1
maskAdjust = (mask - min(mask(:))) / (max(mask(:)) - min(mask(:)));
%//Scale by 45 so the range goes between 0 and 45
%//Cast to uint8 to make this compatible for your image
maskAdjust = uint8(45*maskAdjust);
%// Add 110 to every value to range goes between 110 - 155
maskAdjust = maskAdjust + 110;
In general, if you want to adjust the values within your Gaussian mask so that it goes from [a,b], you would normalize between 0 and 1 first, then do:
maskAdjust = uint8((b-a)*maskAdjust) + a;
You'll notice that we cast this mask to uint8. The reason we do this is to make the mask compatible to be placed in your image.
How do we place this in our image?
All you have to do is figure out the row and column you would like the centre of the Gaussian mask to be placed. Let's assume these variables are stored in row and col. As such, assuming you want to place this in ndvi, all you have to do is the following:
hsizeHalf = floor(hsize/2); %// hsize being odd is important
%// Place Gaussian shape in our image
ndvi(row - hsizeHalf : row + hsizeHalf, col - hsizeHalf : col + hsizeHalf) = maskAdjust;
The reason why hsize should be odd is to allow an even placement of the shape in the image. For example, if the mask size is 5 x 5, then the above syntax for ndvi simplifies to:
ndvi(row-2:row+2, col-2:col+2) = maskAdjust;
From the centre of the mask, it stretches 2 rows above and 2 rows below. The columns stretch from 2 columns to the left to 2 columns to the right. If the mask size was even, then we would have an ambiguous choice on how we should place the mask. If the mask size was 4 x 4 as an example, should we choose the second row, or third row as the centre axis? As such, to simplify things, make sure that the size of your mask is odd, or mod(hsize,2) == 1.
This should hopefully and adequately answer your questions. Good luck!

Resources