Related
I have a numpy array where each element has 3 values (RGB) from 0 to 255, and it spans from [0, 0, 0] to [255, 255, 255] with 256 elements evenly spaced. I want to plot it as a 16 by 16 grid but have no idea how to map the colors (as the numpy array) to the data to create the grid.
import numpy as np
# create an evenly spaced RGB representation as integers
all_colors_int = np.linspace(0, (255 << 16) + (255 << 8) + 255, dtype=int)
# convert the evenly spaced integers to RGB representation
rgb_colors = np.array(tuple(((((255<<16)&k)>>16), ((255<<8)&k)>>8, (255)&k) for k in all_colors_int))
# data to fit the rgb_colors as colors into a plot as a 16 by 16 numpy array
data = np.array(tuple((k,p) for k in range(16) for p in range(16)))
So, how to map the rgb_colors as colors to the data data into a grid plot?
There's quite a bit going on here, and I think it's valuable to talk about it.
linspace
I suggest you read the linspace documentation.
https://numpy.org/doc/stable/reference/generated/numpy.linspace.html
If you want a 16x16 grid, then you should start by generating 16x16=256 values, however if you inspect the shape of the all_colors_int array, you'll notice that it's only generated 50 values, which is the default value of the linspace num argument.
all_colors_int = np.linspace(0, (255 << 16) + (255 << 8) + 255, dtype=int)
print(all_colors_int.shape) # (50,)
Make sure you specify this third 'num' argument to generate the correct quantity of RGB pixels.
As a further side note, (255 << 16) + (255 << 8) + 255 is equivalent to (2^24)-1. The 2^N-1 formula is usually what's used to fill the first N bits of an integer with 1's.
numpy is faster
On your next line, your for loop manually iterates over all of the elements in python.
rgb_colors = np.array(tuple(((((255<<16)&k)>>16), ((255<<8)&k)>>8, (255)&k) for k in all_colors_int))
While this might work, this isn't considered the correct way to use numpy arrays.
You can directly perform bitwise operations to the entire numpy array without the python for loop. For example, to extract bits [16, 24) (which is usually the red channel in an RGB integer):
# Shift over so the 16th bit is now bit 0, then select only the first 8 bits.
RedChannel = (all_colors_int >> 16) & 255
Building the grid
There are many ways to do this in numpy, however I would suggest this approach.
Images are usually represented with a 3-dimensional numpy array, usually of the form
(HEIGHT, WIDTH, CHANNELS)
First, reshape your numpy int array into the 16x16 grid that you want.
reshaped = all_colors_int.reshape((16, 16))
Again, the numpy documentation is really great, give it a read:
https://numpy.org/doc/stable/reference/generated/numpy.reshape.html
Now, extract the red, green and blue channels, as described above, from this reshaped array. If you operate directly on the numpy array, you won't need a nested for-loop to iterate over the 16x16 grid, numpy will handle this for you.
RedChannel = (reshaped >> 16) & 255
GreenChannel = ... # TODO
BlueChannel = ... # TODO
And then finally, we can convert our 3, 16x16 grids, into a 16x16x3 grid, using the numpy stack function
https://numpy.org/doc/stable/reference/generated/numpy.stack.html
grid_rgb = np.stack((
RedChannel,
GreenChannel,
BlueChannel
), axis=2).astype(np.uint8)
Notice two things here
When we 'stack' arrays, we create a new dimension. The axis=2 argument tells numpy to add this new dimension at index 2 (e.g. the third axis). Without this, the shape of our grid would be (3, 16, 16) instead of (16, 16, 3)
The .astype(np.uint8) casts all of the values in this numpy array into a uint8 data type. This is so the grid is compatible with other image manipulation libraries, such as openCV, and PIL.
Show the image
We can use PIL for this.
If you want to use OpenCV, then remember that OpenCV interprets images as BGR not RGB and so your channels will be inverted.
# Show Image
from PIL import Image
Image.fromarray(grid_rgb).show()
If you've done everything right, you'll see an image... And it's all gray.
Why is it gray?
There are over 16 million possible colours. Selecting only 256 of them just so happens to select only pixels with the same R, G and B values which results in an image without any color.
If you want to see some colours, you'll need to either show a bigger image (e.g. 256x256), or alternatively, you can use a dimension that's not a power of two. For example, try a prime number, as this will add a small amount of pseudo-randomness to the RGB selection, e.g. try 17.
Best of luck.
Based solely on the title 'How to plot a normalized RGB map' rather than the approach you've provided, it appears that you'd like to plot a colour spectrum in RGB.
The following approach can be taken to manually construct this.
import cv2
import matplotlib.pyplot as plt
import numpy as np
h = np.repeat(np.arange(0, 180), 180).reshape(180, 180)
s = np.ones((180, 180))*255
v = np.ones((180, 180))*255
hsv = np.stack((h, s, v), axis=2).astype('uint8')
rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
plt.imshow(rgb)
Explanation:
It's generally easier to construct (and decompose) a colour palette using the HSV (hue, saturation, value) colour scale; where hue is the colour itself, saturation can be thought of as the intensity and value as the distance from black. Therefore, there's really only one value to worry about, hue. Saturation and value can be set to 255, for 'full intensity'.
cv2 is used here to simply convert the constructed HSV colourscale to RGB and matplotlib is used to plot the image. (I didn't use cv2 for plotting as it doesn't play nicely with Jupyter.)
The actual spectrum values are constructed in numpy.
Breakdown:
Create the colour spectrum of hue and plug 255 in for the saturation and value. Why is 180 used?
h = np.repeat(np.arange(0, 180), 180).reshape(180, 180)
s = np.ones((180, 180))*255
v = np.ones((180, 180))*255
Stack the three channels H+S+V into a 3-dimensional array, convert the array values to unsigned 8-bit integers, and have cv2 convert from HSV to RGB for us, to be lazy and save us working out the math.
hsv = np.stack((h, s, v), axis=2).astype('uint8')
rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
Plot the RGB image.
plt.imshow(rgb)
I have a large set of also large images (5000,10000,3 channels, RGB) from a semantic segmentation process. I am trying to create a new image with the most "common" value for each pixel, the mode of each pixel for the complete set. Those images have some particularities. First of all, they have the same size, but sometimes contains black pixels that represent no information and must be excluded from the mode calculation. Merging together all image set, I will be able to define which pixel colour tuple (r,g,b) is the most common and store this information as a new image without black pixels.
I have tried using scipy stats.mode to analyse a list of np.array from the images, but this method does not count the (0,0,0) tuple as a nan_policy='omit', so after the calculation, it returns a black image. (0,0,0) is the most frequent pixel colour after all.
I tried also replacing the (0,0,0) tuple by a 'nan' value but the ram usage goes up really fast and is not efficient.
Could anyone give me a hint of some vectorised method to implement this stat calculation?
Thanks!
some sample images: img1img2img3img4
It sounds like you stored mixed tuples and nan values in a numpy array. This is not very effficient, because that would be an object array that needs to handle memory allocation separately for each pixel.
It is better to convert the each RGB tuple to a (integer) floating-point value. A single-precision float can store integers up to 2**24-1 without loss of precision; that is just enough for storing 24-bit RGB values.
Here is how to do it with 5 images of 50x100 pixels.
from scipy.stats import mode as stats_mode
ny, nx = 50, 100
imgs = np.random.randint(255, size=(5, ny, nx, 3), dtype=np.uint8)
imgs[:3, ny//2, nx//2, :] = 0 # ignore thsee
imgs[3:, ny//2, nx//2, :] = [255, 255, 254] # find this
my = 10 # slice size - must divide ny
mode_img = np.zeros((ny, nx, 3), dtype=np.uint8)
flt_imgs = np.zeros((5, my, nx), dtype=np.float32)
for iy in range(0, ny, my):
yslice = slice(iy, iy+my)
flt_imgs[:] = imgs[:, yslice, :, 0]*(256*256)
flt_imgs += imgs[:, yslice, :, 1]*256
flt_imgs += imgs[:, yslice, :, 2]
flt_imgs[flt_imgs == 0] = np.nan
mode_result = stats_mode(flt_imgs, axis=0, nan_policy='omit')
imode = mode_result.mode[0].astype(np.int32)
mode_img[yslice, :, 0] = (imode >> 16) & 0xff
mode_img[yslice, :, 1] = (imode >> 8) & 0xff
mode_img[yslice, :, 2] = imode & 0xff
print(f'Found mode: {mode_img[ny//2, nx//2]}')
Output:
Found mode: [255 255 254]
I have two images ( of the same size): A and B
A is the mask, it contains regions that have zero value and others that have RGB values.
B is the RGB image that i want to change the values of some of its pixels to their correspondent A's pixels (pixels that have the same position and that are different from zero).
I think it would be something like this:
if A(i,j) <>0 then B(i,j)=A(i,j)
except that i don't know how to write it in python...
can anyone help?
If you read the images with opencv:
h = b.shape[0]
w = b.shape[1]
for y in range(0, h):
for x in range(0, w):
if a[y,x] > 0:
b[y,x] = a[y,x]
Or better, as points #Dan MaĊĦek in the comment
import numpy as np
def apply_mask(img, mask):
img = np.where(mask > 0, mask, img)
return img
Notice that in numpy arrays, the height comes first in shape. Opencv loads the image into numpy arrays.
To apply the mask for src, you can use cv2.bitwise_and:
cv2.bitwise_and(src, src, mask=mask)
I am new to OpenCV and Python. I want to perform both Gaussian filter and median filter by first adding noise to the image. I have got successful output for the Gaussian filter but I could not get median filter.Can anyone please explain how to perform median filtering in OpenCV with Python for noise image. Following is my code:
import numpy as np
import cv2
img = cv2.imread('lizard.jpg').astype(np.float32)
gaussian_blur = np.array([[1,2,1],[2,4,2],[1,2,1]],dtype=np.float32)
gaussian_blur = gaussian_blur/np.sum(gaussian_blur)
img_noise = img + np.random.uniform(-20,20,size=np.shape(img))
cv2.imwrite('gt3_plus_noise.jpg',img_noise)
median = cv2.medianBlur(img_noise.astype(np.float32),(3),0)
cv2.imshow('Median Blur',median)
cv2.waitKey()
cv2.destroyAllWindows()
img_blur_g = cv2.filter2D(img_noise.astype(np.float32), -1,gaussian_blur)
cv2.imwrite('gt3_noise_filtered_gaussian.jpg',img_blur_g)
Output:
noise filtered gaussian
noise image
median filter image
OpneCV has function medianBlur in Python and C++ to perform median filtering. You can get details from here: http://docs.opencv.org/2.4/modules/imgproc/doc/filtering.html#medianblur
To use this function, follow following code snippet:
n=3; #where n*n is the size of filter
output_image = cv2.medianBlur(input_image, n)
When OpenCV has a float image, it assumes that the range is between 0 and 1. However, your image still has values between 0 and 255 (and maybe a little above and below that). This is fine for manipulation, but in order to view your image you'll either need to normalize it to the range 0 and 1, or, you'll have to convert back to a uint8 image and saturate the values. Currently your image is just overflowing past 1, which is the assumed max value for a float image. The colors are only showing in the darker regions of the image since the values are very small; specifically, less than 1.
Saturating the values for a uint8 image means anything below 0 is fixed at 0 and anything above 255 is fixed at 255. Normal numpy operations do not saturate values, they overflow and roll over (so np.array(-1).astype(np.uint8) ==> 255, meaning any dark values that have some bit subtracted off will turn bright). See here for more about saturation.
This problem isn't too hard to solve, and there are a number of solutions. An explicit way is to simply fix the values greater than 255 at 255 and fix the values less than 0 to 0 and convert to a uint8 image:
>>> img = np.array([[150, 0], [255, 150]], dtype=np.float32)
>>> noise = np.array([[20, -20], [20, -20]], dtype=np.float32)
>>> noisy_img = img+noise
>>> noisy_img
array([[ 170., -20.],
[ 275., 130.]], dtype=float32)
>>> noisy_img[noisy_img>255] = 255
>>> noisy_img[noisy_img<0] = 0
>>> noisy_img = np.uint8(noisy_img)
>>> noisy_img
array([[170, 0],
[255, 130]], dtype=uint8)
You can also use cv2.convertScaleAbs() to cast with saturation, which is simpler but less explicit:
>>> img = np.array([[150, 0], [255, 150]], dtype=np.float32)
>>> noise = np.array([[20, -20], [20, -20]], dtype=np.float32)
>>> noisy_img = img+noise
>>> cv2.convertScaleAbs(noisy_img)
array([[170, 20],
[255, 130]], dtype=uint8)
I'm playing around with a camera for a microscope using Micro-Manager 1.4. Using the Python interface, I've managed to access the camera, change exposure time etc, and I can capture individual images.
However, each image is returned as NumPy arrays where each pixel is represented as a single integer, e.g. "7765869". As far as I can find online, this is known as a "BufferedImage" in Java, and it means that the RGB values are encoded as:
BufferedImage = R * 2^16 + G * 2^8 + B
My question is: How can I, using e.g. Numpy or OpenCV, convert this kind of array into a more handy array where each pixel is a RGB triplet of uint8 values? Needless to say, the conversion should be as efficient as possible.
The easiest is to let numpy do the conversion for you. Your numpy array will probably be of type np.uint32. If you view it as an array of np.uint8, you will have an RGB0 format image, i.e. the values of R, G, and B for each pixel, plus an empty np.uint8 following. It's easy to reshape and discard that zero value:
>>> img = np.array([7765869, 16777215], dtype=np.uint32)
>>> img.view(np.uint8)
array([109, 127, 118, 0, 255, 255, 255, 0], dtype=uint8)
>>> img.view(np.uint8).reshape(img.shape+(4,))[..., :3]
array([[109, 127, 118],
[255, 255, 255]], dtype=uint8)
Best thing is there is no calculation or copying of data, just a reinterpretation of the contents of your original image: I don't think you can get much more efficient than that!
I recall that for some operations OpenCV requires a contiguous array, so you may have to add a .copy() to the end of that expression to really get rid of the column of zeros, not simply ignore it, although this would of course trigger the copy of data that the code above had avoided.
One way is
Red = BufferedImage / 2**16
Green = (BufferedImage % 2**16) / 2**8
Blue = (BufferedImage % 2**8)
However, I doubt it's the most elegant (Pythonic?) or the fastest way.
rgbs = [((x&0xff0000)>>16,(x&0xff00)>>8,x&0xff) for x in values]
at least I think ...
afaik the formula above can also be written as
BufferedRGB = RED<<16 + GREEN << 8 + BLUE
red,green,blue = 0xFF,0x99,0xAA
red<<16 + green << 8 + blue #= 0xFF99AA (buffered into one value)
#apply a bitmask to get colors back
red = (0xFF99AA & 0xFF0000) >> 16 # = 0xFF
green = (0xFF99AA & 0xFF00) >> 8 # = 0x99
blue = 0xFF99AA & 0xFF # = 0xAA
which is somewhat more readable to me and clear what is going on
The fastest approach would probably be to keep this in numpy:
from numpy import *
x = array([211*2**16 + 11*2**8 + 7]) # test data
b, g, r = bitwise_and(x, 255), bitwise_and(right_shift(x, 8), 255), bitwise_and(right_shift, 16), 255)
print r, g, b
(array([211]), array([11]), array([7]))