Unexpected output when finding variance of an image in OpenCV -Python - python

My program finds the varaince values of an image at each window of a gridded image. The problem is when I print the values they don't match with what is shown in the ouput image. I have included an example image below.
Here is my code:
#import packages
import numpy as np
import cv2
import dateutil
import llist
from matplotlib import pyplot as plt
import argparse
#Read in image as grey-scale
img = cv2.imread('images/0021.jpg', 0)
#Set scale of grid
scale = 6
#Get x and y components of image
y_len,x_len = img.shape
variance = []
for y in range(scale):
for x in range(scale):
#Crop image 9*9 windows
cropped_img=img[(y*y_len)/scale:((y+1)*y_len)/scale,(x*x_len)/scale:((x+1)*x_len)/scale]
(mean,stdv) = cv2.meanStdDev(cropped_img)
var = stdv*stdv
cropped_img[:] = var
#Print mean_values array
variance.append([var])
variance=np.asarray(variance)
np.set_printoptions(suppress=True, precision=3)
print variance.reshape(1,scale,scale)
cv2.imshow('output_var',img)
#cv2.imwrite('images/output_var_300.jpg',img,[int(cv2.IMWRITE_JPEG_QUALITY), 90])
cv2.waitKey(0)
cv2.destroyAllWindows()
Here is the output image of the code above:
From what I can tell the values below don't match the image above. Does anybody have any idea what is happening here?
print variance.reshape(1,scale,scale)
#[[[ 17.208 43.201 215.305 1101.816 1591.606 2453.611]
# [ 46.664 121.162 326.59 809.223 1021.599 5330.989]
# [ 47.754 64.69 705.875 1625.177 3564.494 10148.449]
# [ 19.153 201.864 289.258 632.737 5285.449 4257.597]
# [ 37.621 159.51 271.725 282.291 2239.097 759.007]
# [ 26.108 98.456 32.958 505.609 575.916 70.741]]]
Thank you in advance.
EDIT : Here is a more realistic output image for those who are interested:

Let's take for example, the second row of variance. Since the color values are in range 0-255 per channel, we can try wrapping your values to fit into that range:
>>> row = [46.664, 121.162, 326.59, 809.223, 1021.599, 5330.989]
>>> wrapped = [x % 256 for x in row]
>>> wrapped
[46.66, 121.16, 70.58, 41.22, 253.59, 210.98]
And voila, it makes sense now.

Related

Why are these codes not visually showing the right colors extracted from the image?

so I am working on a program to extract up to 4 of the most common colors, from a picture. Right now, I'm working on it visually showing the most common colors, however, after reading the image, I am:
unable to get an output of the correct rgb codes (it's not outputting it for me)
and
the chart that pops up either shows all black, or shows 3 random colors that are not in the picture.
Any tips or help? I've tried anything that I can, I am not sure why it cannot read the colors well. Thank you.
The code:
import matplotlib.image as img
import matplotlib.pyplot as plt
from scipy.cluster.vq import whiten
from scipy.cluster.vq import kmeans
import pandas as pd
import numpy as np
bimage = img.imread('Images/build2.jpg') #read image (this part works)
print(bimage.shape)
r = []
g = []
b = []
for row in bimage:
for temp_r, temp_g, temp_b in row:
r.append(temp_r)
g.append(temp_g)
b.append(temp_b)
bimage_df = pd.DataFrame({'red': r,
'green': g,
'blue': b})
bimage_df['scaled_color_red'] = whiten(bimage_df['red']) #supposed to give color codes
bimage_df['scaled_color_blue'] = whiten(bimage_df['blue'])
bimage_df['scaled_color_green'] = whiten(bimage_df['green'])
cluster_centers, _ = kmeans(bimage_df[['scaled_color_red', #to find most common colors
'scaled_color_blue',
'scaled_color_green']], 3)
dominant_colors = []
red_std, green_std, blue_std = bimage_df[['red',
'green',
'blue']].std()
for cluster_center in cluster_centers:
red_scaled, green_scaled, blue_scaled = cluster_center
dominant_colors.append((
red_scaled * red_std / 255,
green_scaled * green_std / 255,
blue_scaled * blue_std / 255
))
plt.imshow([dominant_colors])
plt.show()
The image I used:
I have tried using this method for an output and another type of chart too, but that gave me all black or purple, unrelated colors. I had referred to geeks4geeks for this, could not troubleshoot either. Any help would be greatly appreciated.
The major issue is the usage of whiten method that is not adequate for the sample image:
whiten documentation:
Before running k-means, it is beneficial to rescale each feature dimension of the observation set by its standard deviation (i.e. “whiten” it - as in “white noise” where each frequency has equal power). Each feature is divided by its standard deviation across all observations to give it unit variance.
The normalization method assumes normal distribution of the noise.
The sample image is not a natural image (has no noise), and the normalization procedure does not feat the given image.
Instead of normalization, it is recommended to convert the image to LAB color space, where color distances better match the perceptual distances.
Keeping the colors in RGB format may work good enough...
Swapping the green and the blue channels is another issue.
Instead of using a for loop, we may use NumPy array operations (it's not a bug, just faster):
fimage = bimage.astype(float) # Convert image from uint8 to float (kmeans requires floats).
r = fimage[:, :, 0].flatten().tolist() # Convert red elements to list
g = fimage[:, :, 1].flatten().tolist() # Convert grenn elements to list
b = fimage[:, :, 2].flatten().tolist() # Convert blue elements to list
bimage_df = pd.DataFrame({'red': r,
'green': g,
'blue': b})
Apply kmeans with 100 iterations (the default is 20, and may not be enough):
cluster_centers, _ = kmeans(bimage_df[['red', #Find rhe 4 most common colors
'green',
'blue']], 4, iter=100) # The default is 20 iterations, use 100 iterations for better convergence
Before using plt.imshow we have to convert the colors to uint8 type (we may also convert to range [0, 1]), otherwize the displayed colors are going to be white (saturated).
dominant_colors = np.round(cluster_centers).astype(np.uint8) # Round and convert to uint8
plt.imshow([dominant_colors])
plt.show()
Code sample:
import matplotlib.image as img
import matplotlib.pyplot as plt
#from scipy.cluster.vq import whiten
from scipy.cluster.vq import kmeans
import pandas as pd
import numpy as np
bimage = img.imread('Images/build2.jpg') #read image (this part works)
print(bimage.shape)
#r = []
#g = []
#b = []
#for row in bimage:
# for temp_r, temp_g, temp_b in row:
# r.append(temp_r)
# g.append(temp_g)
# b.append(temp_b)
# Use NumPy array operations, instead of using a for loop.
fimage = bimage.astype(float) # Convert image from uint8 to float (kmeans requires floats).
r = fimage[:, :, 0].flatten().tolist() # Convert red elements to list
g = fimage[:, :, 1].flatten().tolist() # Convert grenn elements to list
b = fimage[:, :, 2].flatten().tolist() # Convert blue elements to list
bimage_df = pd.DataFrame({'red': r,
'green': g,
'blue': b})
# Don't use whiten
#bimage_df['scaled_color_red'] = whiten(bimage_df['red']) #supposed to give color codes
#bimage_df['scaled_color_blue'] = whiten(bimage_df['blue'])
#bimage_df['scaled_color_green'] = whiten(bimage_df['green'])
#cluster_centers, _ = kmeans(bimage_df[['scaled_color_red', #to find most common colors
# 'scaled_color_blue',
# 'scaled_color_green']], 3)
cluster_centers, _ = kmeans(bimage_df[['red', #Find the 4 most common colors
'green',
'blue']], 4, iter=100) # The default is 20 iterations, use 100 iterations for better convergence
dominant_colors = np.round(cluster_centers).astype(np.uint8) # Round and convert to uint8
print(dominant_colors)
# Since whiten is not used, we don't need the STD
#red_std, green_std, blue_std = bimage_df[['red',
# 'green',
# 'blue']].std()
#for cluster_center in cluster_centers:
# red_scaled, green_scaled, blue_scaled = cluster_center
# dominant_colors.append((
# red_scaled * red_std / 255,
# green_scaled * green_std / 255,
# blue_scaled * blue_std / 255
# ))
plt.imshow([dominant_colors])
plt.show()
Result:

NameError: name 'IMG_H' is not defined

I am a new programming Interface. I am using the PIL and Matplotlib libraries for the contract streaching.When I am using the Histogram Equalizer I am getting the error as name 'IMG_H' is not defined.I am also Converting my image to numpy array, calculate the histogram, cumulative sum, mapping and then apply the mapping to create a new image.
You can see my code below -
# HISTOGRAM EQUALIZATION
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
def make_histogram(img):
""" Take an image and create a historgram from it's luma values """
y_vals = img[:,:,0].flatten()
histogram = np.zeros(256, dtype=int)
for y_index in range(y_vals.size):
histogram[y_vals[y_index]] += 1
return histogram
def make_cumsum(histogram):
""" Create an array that represents the cumulative sum of the histogram """
cumsum = np.zeros(256, dtype=int)
cumsum[0] = histogram[0]
for i in range(1, histogram.size):
cumsum[i] = cumsum[i-1] + histogram[i]
return cumsum
def make_mapping(histogram, cumsum):
mapping = np.zeros(256, dtype=int)
luma_levels = 256
for i in range(histogram.size):
mapping[i] = max(0, round((luma_levels*cumsum[i])/(IMG_H*IMG_W))-1)
return mapping
def apply_mapping(img, mapping):
""" Apply the mapping to our image """
new_image = img.copy()
new_image[:,:,0] = list(map(lambda a : mapping[a], img[:,:,0]))
return new_image
# Load image
pillow_img = Image.open('pout.jpg')
# Convert our image to numpy array, calculate the histogram, cumulative sum,
# mapping and then apply the mapping to create a new image
img = np.array(pillow_img)
histogram = make_histogram(img)
cumsum = make_cumsum(histogram)
mapping = make_mapping(histogram, cumsum)
new_image = apply_mapping(img, mapping)
output_image = Image.fromarray(np.uint8(new_image))
imshow(output_image, cmap='gray')
# Display the old (black) and new (red) histograms next to eachother
x_axis = np.arange(256)
fig = plt.figure()
fig.add_subplot(1,2,1)
plt.bar(x_axis , histogram, color = "black")
fig.add_subplot(1,2,2)
plt.bar(x_axis , make_histogram(new_image), color = "red")
plt.show()
You have this variable here:
mapping[i] = max(0, round((luma_levels*cumsum[i])/(IMG_H*IMG_W))-1)
But you didn't define it (or import) before, therefore you get this error.
for i in range(histogram.size):
mapping[i] = max(0, round((luma_levels*cumsum[i])/(IMG_H*IMG_W))-1)
In above stated line. you are using 2 Variables, IMG_H and IMG_W.
where you defined these variables?
EDITED PART
for i in range(histogram.size):
mapping[i] = max(0, round((luma_levels*cumsum[i])/(IMG_H*IMG_W))-1)
in the above stated line you are using 2 variables try to do multiplication (IMG_H*IMG_W) but you did not define and import these variables in the whole code.
You can do like this.
you can define these variables on the top of the code.
your code shows that these variables are defined for Image width and height
IMG_W = 120 #Any value in integer for Image Width
IMG_H = 124 #Any value in integer for Image Height

Sort image as NP array

I'm trying to sort an image by luminosity using NumPy, which I'm new to. I've managed to create a random image and sort it.
def create_image(output, width, height, arr):
array = np.zeros([height, width, 3], dtype=np.uint8)
numOfSwatches = len(arr)
swatchWidth = int(width/ numOfSwatches)
for i in range (0, numOfSwatches):
m = i * swatchWidth
r = (i+1) * swatchWidth
array[:, m:r] = arr[i]
img = Image.fromarray(array)
img.save(output)
Which creates this image:
So far so good. Only now I want to switch from creating random images to loading them and then sorting them.
#!/usr/bin/python3
import numpy as np
from PIL import Image
# --------------------------------------------------------------
def load_image( infilename ) :
img = Image.open( infilename )
img.load()
data = np.asarray( img, dtype = "int32" )
return data
# --------------------------------------------------------------
def lum (r,g,b):
return math.sqrt( .241 * r + .691 * g + .068 * b )
myImageFile = "random_colours.png"
imageNP = load_image(myImageFile)
imageNP.sort(key=lambda rgb: lum(*rgb) )
The image should look like this:
The error I get is TypeError: 'key' is an invalid keyword argument for this function I may have created the NP array incorrectly as it worked when it was a random NP array.
Have not ever used PIL, but the following approach hopefully works (I'm not sure as I can't reproduce your exact examples), and of course there might be more efficient ways to do so.
I'm using your functions, having changed the math.sqrt function to np.sqrt in the lum function - as it is better for vector calculations. By the way, I believe this won't work with an int32 type array (as in your load_image function).
The key part is Numpy's argsort function (last line), which gives the indices that would sort the given array; this is applied to a row of the luminosity array (exploiting simmetry) and later used as indexer of img_array.
# Create random image
np.random.seed(4)
img = create_image('test.png', 75, 75, np.random.random((25,3))*255)
# Convert to Numpy array and calculate luminosity
img_array = np.array(img, dtype = np.uint8)
luminosity = lum(img_array[...,0], img_array[...,1], img_array[...,2])
# Sort by luminosity and convert to image again
img_sorted = Image.fromarray(img_array[:,luminosity[0].argsort()])
The original picture:
And the luminosity-sorted one:

Extract N number of patches from an image

I have an image of dimension 155 x 240. Like the following:
I want to extract certain shape of patchs (25 x 25).
I don't want to patch from the whole image.
I want to extract N number of patch from non-zero (not background) area of the image. How can I do that? Any idea or suggestion or implementation will be appreciated. You can try with either Matlab or Python.
Note:
I have generated a random image so that you can process it for patching. image_process variable is that image in this code.
import numpy as np
from scipy.ndimage.filters import convolve
import matplotlib.pyplot as plt
background = np.ones((155,240))
background[78,120] = 2
n_d = 50
y,x = np.ogrid[-n_d: n_d+1, -n_d: n_d+1]
mask = x**2+y**2 <= n_d**2
mask = 254*mask.astype(float)
image_process = convolve(background, mask)-sum(sum(mask))+1
image_process[image_process==1] = 0
image_process[image_process==255] = 1
plt.imshow(image_process)
Lets assume that the pixels values you want to omit is 0.
In this case what you could do, is first find the indices of the non-zero values, then slice the image in the min/max position to get only the desired area, and then simply apply extract_patches_2d with the desired window size and number of patches.
For example, given the dummy image you supplied:
import numpy as np
from scipy.ndimage.filters import convolve
import matplotlib.pyplot as plt
background = np.ones((155,240))
background[78,120] = 2
n_d = 50
y,x = np.ogrid[-n_d: n_d+1, -n_d: n_d+1]
mask = x**2+y**2 <= n_d**2
mask = 254*mask.astype(float)
image_process = convolve(background, mask)-sum(sum(mask))+1
image_process[image_process==1] = 0
image_process[image_process==255] = 1
plt.figure()
plt.imshow(image_process)
plt.show()
from sklearn.feature_extraction.image import extract_patches_2d
x, y = np.nonzero(image_process)
xl,xr = x.min(),x.max()
yl,yr = y.min(),y.max()
only_desired_area = image_process[xl:xr+1, yl:yr+1]
window_shape = (25, 25)
B = extract_patches_2d(only_desired_area, window_shape, max_patches=100) # B shape will be (100, 25, 25)
If you plot the only_desired_area you will get the following image:
This is the main logic if you wish an even tighter bound you should adjust the slicing properly.

Sliding window over an image OpenCV

I am trying to define a window that scans across an image, I want to find the average RGB values in each window and output them.
I have managed to get the average RGB values for the entire image like this:
img = cv2.imread('images/0021.jpg')
mean = cv2.mean(img)
print mean[0]
print mean[1]
print mean[2]
Gives:
#Output
51.0028081597
63.1069849537
123.663025174
How could I apply this mean function to a moving window and output the values for each window?
EDIT:
Here is what I have now:
img = cv2.imread('images/0021.jpg')
def new(img):
rows,cols = img.shape
final = np.zeros((rows, cols, 3, 3))
for x in (0,1,2):
for y in (0,1,2):
img1 = np.vstack((img[x:],img[:x]))
img1 = np.column_stack((img1[:,y:],img1[:,:y]))
final[x::3,y::3] = np.swapaxes(img1.reshape(rows/3,3,cols/3,-1),1,2)
b,g,r = cv2.split(final)
rgb_img = cv2.merge([r,g,b])
mean = cv2.mean(rgb_img)
print mean[0]
print mean[1]
print mean[2]
But now I am getting zero output.
I wrote a script similar to the given links. It basically divides your img to 3*3 parts and then computes mean (and standard deviation) of each part. With a little array optimization I think you can use it real time/on video.
PS: Divisions should be integer division
EDIT: now the script gives 9 outputs each represent a mean of its own region.
import numpy as np
import cv2
img=cv2.imread('aerial_me.jpg')
scale=3
y_len,x_len,_=img.shape
mean_values=[]
for y in range(scale):
for x in range(scale):
cropped_image=img[(y*y_len)/scale:((y+1)*y_len)/scale,
(x*x_len)/scale:((x+1)*x_len)/scale]
mean_val,std_dev=cv2.meanStdDev(cropped_image)
mean_val=mean_val[:3]
mean_values.append([mean_val])
mean_values=np.asarray(mean_values)
print mean_values.reshape(3,3,3)
The output is bgr mean values of each window:
[[[ 69.63661573 66.75843063 65.02066449]
[ 118.39233345 114.72655391 116.14441964]
[ 159.26887164 143.40760348 144.63208436]]
[[ 75.50831044 107.45708276 103.0781851 ]
[ 108.46450034 141.52005495 139.84878949]
[ 122.67583265 154.86071992 153.67907072]]
[[ 83.67678571 131.45284169 128.27706902]
[ 86.57919815 129.09968235 128.64439389]
[ 90.1102402 135.33173999 132.86622807]]]
[Finished in 0.5s]
Filter with a kernel of shape equal to your window, and values all equal to 1/window_areas. The result is local average you seek (also known as a "box blur" operation).

Categories