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.
Related
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:
I'm trying to test which points in a list (numpy array or pandas) are inside a given boolean mask (or labelled image).
I have found way to compare with polygons but not with a mask
From this dataset example how can I test which coords are inside the mask? (best would be to add a column in pandas saying which label they are inside of - that, or add a new column in the "coords" variable saying which label it belongs to).
Masks/labels won't be rectangles in my implementation (basically cell shapes), I'm just doing so here because it's easier.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# import numpy as np
coords = np.random.rand(40, 2) *1024
mask = np.zeros((1024,1024))
mask[300:600,50:125] = 1
mask[700:800,400:650] = 2
plt.imshow(mask)
plt.scatter(coords[:,0],coords[:,1],color='red')
You can use NumPy indexing of the mask with the coordinates after a bit of massaging.
coords_int = np.round(coords).astype(int) # or np.floor, depends
values_at_coords = mask[tuple(coords_int.T)]
points_per_value = np.bincount(values_at_coords)
Now points_per_value contains an array such that points_per_value[i] contains the number of coordinates that landed in mask label i. (docs for np.bincount)
For more about the second line, you can read about NumPy integer array indexing in the NumPy docs.
from collections import defaultdict
import matplotlib.pyplot as plt
coords = np.random.rand(40, 2) *1024
mask = np.zeros((1024,1024))
mask[300:600,50:125] = 1
mask[700:800,400:650] = 2
plt.imshow(mask, origin='lower')
plt.scatter(coords[:,1],coords[:,0],color='red')
res = defaultdict(list)
for i in np.unique(mask)[1:]:
temp = coords[(coords[:, 0] >= (mask == i).nonzero()[0][0]) & (coords[:, 0] <= (mask == i).nonzero()[0][-1])]
res[i] = temp[(temp[:, 1] >= (mask == i).nonzero()[1][0]) & (temp[:, 1] <= (mask == i).nonzero()[1][-1])]
print(res)
You can use shapely to assert whether a point is inside a polygon or not.
from shapely.geometry import Point
from shapely.geometry.polygon import Polygon
Your first mask is defined by the following polygon.
polygon = Polygon([(300, 50), (300, 125), (600, 50), (600, 125)])
Test for two sample points.
point1 = Point(315, 1200)
point2 = Point(315, 75)
print(polygon.contains(point1)) #False
print(polygon.contains(point2)) #True
I have 100 images of 10 x 10. I want to put them in a single array of shape 100 x 10 x 10 and then compute the center of mass of the 100 images in one go (without a loop for).
Currently, I am using the function center_of_mass from scipy as below:
import numpy as np
from scipy.ndimage.measurements import center_of_mass
# Example data
image = np.arange(100).reshape(10,10)
images = np.repeat([image],100, axis=0)
result = []
for i in range(images.shape[0]):
result.append( center_of_mass(images[i,:]) )
Is there a way to remove that for loop?
You can use the labels and index arguments to the center_of_mass function (one label per image). The downside is that the memory usage is roughly doubled.
labels = np.ones_like(images).cumsum(0)
result2 = [tup[1:] for tup in
center_of_mass(images, labels, index=np.arange(1, images.shape[0]+1))
]
assert result2 == result
Use reshape matrix and dot product.
By example:
import numpy as np
# Example data
image = np.arange(80).reshape(8,10)
images = np.repeat([image],90, axis=0)
images_row=images.reshape((90, 8*10))
S=np.sum(images_row, axis=1)
Y_mat,X_mat = np.meshgrid(np.arange(10),np.arange(8))
Y_mats = np.repeat([Y_mat],90, axis=0)
Y_mats = Y_mats.reshape((90, 8*10))
X_mats= np.repeat([X_mat],90, axis=0)
X_mats = X_mats.reshape((90, 8*10))
#center of mass:
X_c=np.dot(images_row, X_mats.T)/S
Y_c=np.dot(images_row, Y_mats.T)/S
Gamut I want to plot in CIE1931 space: https://www.google.co.uk/search?biw=1337&bih=1257&tbm=isch&sa=1&ei=9x3kW7rqBo3ygQb-8aWYBw&q=viewpixx+gamut&oq=viewpixx+gamut&gs_l=img.3...2319.2828.0.3036.5.5.0.0.0.0.76.270.5.5.0....0...1c.1.64.img..0.0.0....0.KT8w80tcZik#imgrc=77Ufw31S6UVlYM
I want to create a triangle plot of the ciexyY colours within the these coordinates: (.119,.113),(.162,.723),(.695,.304) as in the image - with a set luminance Y at 30.0.
I have created a 3d array of xy values between 0-1.
I then created a matrix with 1s inside the triangle and 0s outside the triangle.
I multiplied the triangle matrix by the xyY ndarray.
Then I looped through the xyY ndarray and converted xyY values to rgb, and displayed them.
The result is somewhat close but not correct. I think the error is in the last section when I convert to rgb, but I'm not sure why. This is the current image: https://imgur.com/a/7cWY0FI. Any recommendations would be really appreciated.
from __future__ import division
import numpy as np
from colormath.color_objects import sRGBColor, xyYColor
from colormath.color_conversions import convert_color
import matplotlib.pyplot as plt
def frange(x,y,jump):
while x < y:
yield x
x += jump
def onSameSide(p1,p2, A,B):
cp1 = np.cross(B-A, p1-A)
cp2 = np.cross(B-A, p2-A)
if(np.dot(cp1, cp2) >= 0):
return True
else:
return False
def isPointInTriangle(p,A,B,C):
if(onSameSide(p,A,B,C) and onSameSide(p,B,A,C) and onSameSide(p,C,A,B)):
return True
else:
return False
xlen = 400
ylen = 400
#CIExyY colour space
#Make an array (1,1,3) with each plane representing how x,y,Y vary in the coordinate space
ciexyY = np.zeros((3,xlen,ylen))
ciexyY[2,:,:]=30.0
for x in frange(0,1,1/xlen):
ciexyY[0,:,int(xlen*x)]=x
for y in frange(0,1,1/xlen):
ciexyY[1,int(ylen*y),:]=y
#coordinates from Viewpixx gamut, scaled up to 100
blue=np.array((.119,.113,30.0))
green=np.array((.162,.723,30.0))
red=np.array((.695,.304,30.0))
#scale up to size of image
blue = np.multiply(blue,xlen)
green = np.multiply(green,xlen)
red = np.multiply(red,xlen)
#make an array of zeros and ones to plot the shape of Viewpixx triangle
triangleZeros = np.zeros((xlen,ylen))
for x in frange(0,xlen,1):
for y in frange(0,ylen,1):
if(isPointInTriangle((x,y,0),blue,green,red)):
triangleZeros[x,y]=1
else:
triangleZeros[x,y]=0
#cieTriangle
cieTriangle = np.multiply(ciexyY,triangleZeros)
#convert cieTriangle xyY to rgb
rgbTriangle = np.zeros((3,xlen,ylen))
for x in frange(0,xlen,1):
for y in range(0,ylen,1):
xyYcolour = xyYColor(cieTriangle[0,x,y],cieTriangle[1,x,y],cieTriangle[2,x,y])
rgbColour = convert_color(xyYcolour,sRGBColor)
rgbTriangle[0,x,y] = rgbColour.rgb_r
rgbTriangle[1,x,y] = rgbColour.rgb_g
rgbTriangle[2,x,y] = rgbColour.rgb_b
rgbTriangle = np.transpose(rgbTriangle)
plt.imshow(rgbTriangle)
plt.show()
We have all the common Chromaticity Diagrams in Colour, I would recommend it over python-colormath because Colour is vectorised and thus much faster.
Do you have a render of your current image to share though?
from colour.plotting import plot_chromaticity_diagram_CIE1931
plot_chromaticity_diagram_CIE1931()
My goal is to trace drawings that have a lot of separate shapes in them and to split these shapes into individual images. It is black on white. I'm quite new to numpy,opencv&co - but here is my current thought:
scan for black pixels
black pixel found -> watershed
find watershed boundary (as polygon path)
continue searching, but ignore points within the already found boundaries
I'm not very good at these kind of things, is there a better way?
First I tried to find the rectangular bounding box of the watershed results (this is more or less a collage of examples):
from numpy import *
import numpy as np
from scipy import ndimage
np.set_printoptions(threshold=np.nan)
a = np.zeros((512, 512)).astype(np.uint8) #unsigned integer type needed by watershed
y, x = np.ogrid[0:512, 0:512]
m1 = ((y-200)**2 + (x-100)**2 < 30**2)
m2 = ((y-350)**2 + (x-400)**2 < 20**2)
m3 = ((y-260)**2 + (x-200)**2 < 20**2)
a[m1+m2+m3]=1
markers = np.zeros_like(a).astype(int16)
markers[0, 0] = 1
markers[200, 100] = 2
markers[350, 400] = 3
markers[260, 200] = 4
res = ndimage.watershed_ift(a.astype(uint8), markers)
unique(res)
B = argwhere(res.astype(uint8))
(ystart, xstart), (ystop, xstop) = B.min(0), B.max(0) + 1
tr = a[ystart:ystop, xstart:xstop]
print tr
Somehow, when I use the original array (a) then argwhere seems to work, but after the watershed (res) it just outputs the complete array again.
The next step could be to find the polygon path around the shape, but the bounding box would be great for now!
Please help!
#Hooked has already answered most of your question, but I was in the middle of writing this up when he answered, so I'll post it in the hopes that it's still useful...
You're trying to jump through a few too many hoops. You don't need watershed_ift.
You use scipy.ndimage.label to differentiate separate objects in a boolean array and scipy.ndimage.find_objects to find the bounding box of each object.
Let's break things down a bit.
import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
def draw_circle(grid, x0, y0, radius):
ny, nx = grid.shape
y, x = np.ogrid[:ny, :nx]
dist = np.hypot(x - x0, y - y0)
grid[dist < radius] = True
return grid
# Generate 3 circles...
a = np.zeros((512, 512), dtype=np.bool)
draw_circle(a, 100, 200, 30)
draw_circle(a, 400, 350, 20)
draw_circle(a, 200, 260, 20)
# Label the objects in the array.
labels, numobjects = ndimage.label(a)
# Now find their bounding boxes (This will be a tuple of slice objects)
# You can use each one to directly index your data.
# E.g. a[slices[0]] gives you the original data within the bounding box of the
# first object.
slices = ndimage.find_objects(labels)
#-- Plotting... -------------------------------------
fig, ax = plt.subplots()
ax.imshow(a)
ax.set_title('Original Data')
fig, ax = plt.subplots()
ax.imshow(labels)
ax.set_title('Labeled objects')
fig, axes = plt.subplots(ncols=numobjects)
for ax, sli in zip(axes.flat, slices):
ax.imshow(labels[sli], vmin=0, vmax=numobjects)
tpl = 'BBox:\nymin:{0.start}, ymax:{0.stop}\nxmin:{1.start}, xmax:{1.stop}'
ax.set_title(tpl.format(*sli))
fig.suptitle('Individual Objects')
plt.show()
Hopefully that makes it a bit clearer how to find the bounding boxes of the objects.
Use the ndimage library from scipy. The function label places a unique tag on each block of pixels that are within a threshold. This identifies the unique clusters (shapes). Starting with your definition of a:
from scipy import ndimage
image_threshold = .5
label_array, n_features = ndimage.label(a>image_threshold)
# Plot the resulting shapes
import pylab as plt
plt.subplot(121)
plt.imshow(a)
plt.subplot(122)
plt.imshow(label_array)
plt.show()