Check the parity of the Green and Blue values into an image - python

I have an array ver of shape (9,8,2) which I extracted from an image.
What I'm searching to do is to iterate over the pixels of this array, check if Green and Blue values have a different parity, if so, put the result True in a list, and do the same for each lines.
ver = im_arr[:9,:8,1:3]
Here I took the first 9 lines of the image, the 8 first pixels, and I've extracted the columns to have only the Green and Blue values.
I now know how to with a 2D array like that :
cf = cf[:,1:3]
jou = (cf.sum(1)%2).astype(bool)
But in this case, with a 3D array, I really don't know how to do, I tried with three for loop, but it doesn't work :
for i in range(ver.shape[0]):
for y in range(ver.shape[1]):
for z in range(ver.shape[2]):
juju[i,y,z] = (ver.sum(1)%2).astype(bool)

import numpy as np
image = np.random.randint(0, 255, (100, 100, 3))
subset = image[:9, :8, 1:]
mask = ~np.all(np.remainder(subset, 2) == 0, axis=-1)
This code generates a random image and extracts the subset. Then it checks that the remainder of all numbers in the array are divisible by 2. The np.all call checks if across the last dimension, all values are True and returns True if so, False otherwise. To get the mask you want I just negate the result with ~
Edit to add a solution that checks whether both values across dimensions are either equal or odd.
import numpy as np
image = np.random.randint(0, 255, (100, 100, 3))
subset = image[:9, :8, 1:]
even = np.remainder(subset, 2) == 0
mask = ~np.equal(even[..., 0], even[..., 1])

If you mask the LSB of the Green and Blue channels and add them, you can only get:
0 + 0 = 0
0 + 1 = 1 <--- THIS
1 + 0 = 1 <--- THIS
1 + 1 = 2
So, you want to identify where they add up to 1, i.e. they are different:
mask = np.sum((ver & 1), axis=-1) == 1

Related

How to filter a 3D RGB numpy array

I want to get a 2d mask array of green color.
Code:
W = 100; H = 100
Map = np.zeros((W,H,3), dtype=int)
Map[1,1] = [0,255,0]
MResult = (Map[:,:] == [0,255,0])
A = np.zeros((W,H))
A[MResult] = 1
Not works.
I think there is also a conceptual mistake in your code, the height should be the first dimension, so Map should be np.zeros((H,W,3), dtype='int'). Also note that dtype is a string, not the python built-in int.
Now returning to the problem, you need to use numpy's smart indexing to better manipulate the arrays. It seems that you want to set the green channel to 255 in Map, you would do so by doing Map[:,:,1] = 255 note that we use : to say "all the elements in the row and column, and in channel 1 should be set to 255".
Then we get a binary mask for A, we should do MResult = Map[:,:,1] == 255, and in the end we simply do the A[MResult] = 1 as you did.
To check only green channel is not good idea,
we get white color too.
But code:
import numpy as np
W = 100; H = 100
Map = np.zeros((W,H,3), dtype=int)
Map[1,1] = [0,255,0]
A = np.zeros((W,H))
A[(0 == Map[:,:,0]) & (255 == Map[:,:,1]) & (0 == Map[:,:,2])] = 1
now to works.

Scanning lists more efficiently in python

I have some code, which works as intended, however takes about 4 and a half hours to run, I understand that there are about 50 billion calculations my poor pc needs to do but I thought it would be worth asking!
This code gets an image, and wants to find every possible region of 331*331 pixels in the given image, and find how many black pixels there are in each, I will use this data to create a heatmap of black pixel density, and also a list of all of the values found:
image = Image.open(self.selectedFile)
pixels = list(image.getdata())
width, height = image.size
pixels = [pixels[i * width:(i+1) * width] for i in range(height)]
#print(pixels)
rightShifts = width - 331
downShifts = height - 331
self.totalRegionsLabel['text'] = f'Total Regions: {rightShifts * downShifts}'
self.blackList = [0 for i in range(0, rightShifts*downShifts)]
self.heatMap = [[] for i in range(0, downShifts)]
for x in range(len(self.heatMap)):
self.heatMap[x] = [0 for i in range(0, rightShifts)]
for x in range(rightShifts):
for y in range(downShifts):
blackCount = 0
for z in range(x + 331):
for w in range(y + 331):
if pixels[z][w] == 0:
blackCount += 1
self.blackList[x+1*y] = blackCount
self.heatMap[x][y] = blackCount
print(self.blackList)
You have several problems here, as I pointed out. Your z/w loops are always starting at the upper left, so by the time you get towards the end, you're summing the entire image, not just a 331x331 subset. You also have much confusion in your axes. In an image, [y] is first, [x] is second. An image is rows of columns. You need to remember that.
Here's an implementation as I suggested above. For each column, I do a full sum on the top 331x331 block. Then, for every row below, I just subtract the top row and add the next row below.
self.heatMap = [[0]*rightShifts for i in range(downShifts)]
for x in range(rightShifts):
# Sum up the block at the top.
blackCount = 0
for row in range(331):
for col in range(331):
if pixels[row][x+col] == 0:
blackCount += 1
self.heatMap[0][x] = blackCount
for y in range(1,downShifts):
# To do the next block down, we subtract the top row and
# add the bottom.
for col in range(331):
blackCount += pixels[y+330][x+col] - pixels[y-1][x+col]
self.heatMap[y][x] = blackCount
You could tweak this even more by alternating the columns. So, at the bottom of the first column, scoot to the right by subtracting the first column and adding the next new column. then scoot back up to the top. That's a lot more trouble.
The two innermost for-loops seem to be transformable to some numpy code if using this package is not an issue. It would give something like:
pixels = image.get_data() # it is probably already a numpy array
# Get an array filled with either True or False, with True whenever pixel is black:
pixel_is_black = (pixels[x:(x+331), y:(y+331)] == 0)
pixel_is_black *= 1 # Transform True and False to respectively 1 and 0. Maybe not needed
self.blackList[x+y] = pixel_is_black.sum() # self explanatory
This is the simplest optimization I can think of, you probably can do much better with clever numpy tricks.
I would recommend using some efficient vector computations through the numpy and opencv libraries.
First, binarize your image so that black pixels are set to zero, and any other color pixels (gray to white) are set to 1. Then, apply a 2D filter to the image of shape 331 x 331 where each value in the filter kernel is (1 / (331 x 331) - this will take the average of all the values in each 331x331 area and assign it to the center pixel.
This gives you a heatmap, where each pixel value is the proportion of non-black pixels in the surrounding 331 x 331 region. A darker pixel (value closer to zero) means more pixels in that region are black.
For some background, this approach uses image processing techniques called image binarization and box blur
Example code:
import cv2
import numpy as np
# setting up a fake image, with some white spaces, gray spaces, and black spaces
img_dim = 10000
fake_img = np.full(shape=(img_dim, img_dim), fill_value=255, dtype=np.uint8) # white
fake_img[: img_dim // 3, : img_dim // 3] = 0 # top left black
fake_img[2 * img_dim // 3 :, 2 * img_dim // 3 :] = 0 # bottom right black
fake_img[img_dim // 3 : 2 * img_dim // 3, img_dim // 3 : 2 * img_dim // 3] = 127 # center gray
# show the fake image
cv2.imshow("", fake_img)
cv2.waitKey()
cv2.destroyAllWindows()
# solution to your problem
binarized = np.where(fake_img == 0, 0, 1) # have 0 values where black, 1 values else
my_filter = np.full(shape=(331, 331), fill_value=(1 / (331 * 331))) # set up filter
heatmap = cv2.filter2D(fake_img, 1, my_filter) # apply filter, which takes average of values in 331x331 block
# show the heatmap
cv2.imshow("", heatmap)
cv2.waitKey()
cv2.destroyAllWindows()
I ran this on my laptop, with a huge (fake) image of 10000 x 10000 pixels, almost instantly.
Sorry I should have deleted this post before you all put the effort in, however, some of these workarounds are really smart and interesting, I ended up coming up with a solution independently that is the same as what Tim Robbers first suggested, I used the array I had and built a second one on which every item in a row is the number of black cells preceding it, and then for each row in a region instead of scanning every item, just scan the preceding value and the final value and you are good:
image = Image.open(self.selectedFile).convert('L') #convert to luminance mode as RGB information is irrelevant
pixels = list(image.getdata()) #get the value of every pixel in the image
width, height = image.size
pixels = [pixels[i * width:(i+1) * width] for i in range(height)] #split the pixels array into a two dimensional array with the dimensions to match the image
#This program scans every possible 331*331 square starting from the top left, so it will move right width - 331 pixels and down height - 331 pixels
rightShifts = width - 331
downShifts = height - 331
self.totalRegionsLabel['text'] = f'Total Regions: {rightShifts * downShifts}' #This wont update till the function has completed running
#The process of asigning new values to values in an array is faster than appending them so this is why I prefilled the arrays:
self.heatMap = [[] for i in range(0, downShifts)]
for x in range(len(self.heatMap)):
self.heatMap[x] = [0 for i in range(0, rightShifts)]
cumulativeMatrix = [] #The cumulative matrix replaces each value in each row with how many zeros precede it
for y in range(len(pixels)):
cumulativeMatrix.append([])
cumulativeMatrix[y].append(0)
count = 0
for x in range(len(pixels[y])):
if pixels[y][x] == 0:
count += 1
cumulativeMatrix[y].append(count)
regionCount = 0
maxValue = 0 #this is the lowest possible maximum value
minValue = 109561 #this is the largest possible minimum value
self.blackList = []
#loop through all possible regions
for y in range(downShifts):
for x in range(rightShifts):
blackPixels = 0
for regionY in range(y, y + 331):
lowerLimit = cumulativeMatrix[regionY][x]
upperLimit = cumulativeMatrix[regionY][x+332]
blackPixels += (upperLimit - lowerLimit)
if blackPixels > maxValue:
maxValue = blackPixels
if blackPixels < minValue:
minValue = blackPixels
self.blackList.append(blackPixels)
self.heatMap[y][x] = blackPixels
regionCount += 1
This brought run time to under a minute and thus solved my problem, however, thank you for your contributions I have learned a lot from reading them!
Try to look into the map() function. It uses C to streamline iterations.
You can speed up your for loops like this:
pixels = list(map(lambda i: x[i*width:(i+1)*width], range(height)))

Python element-wise vectorised boolean operations to classify image pixels based on their colour

I have an RGB image which I am loading into a 2D array using PIL
img = Image.open(path)
imgData = numpy.array(img)
I need to efficiently translate this into a 2D array of RGB tuples (in some sense a 3D array) the same size containing a rough 'classification' of each pixel - 'red', 'green', 'white' or 'other' - at each index based on which 'colour region' they lie within. This is for purposes of image recognition.
My current implementation uses a element-wise for loop but is very slow (an 8MP image takes 1+ minutes):
for i in range(cols): # for every col
for j in range(rows): # for every row
r,g,b = imgData[i,j]
if b > 220: # white
n = 3
elif r > 230: # red
n = 2
else: # green
n = 1
mapData[i,j] = n
(I realise that the order of the if statements here affects the precedence of the classifications - this is not a major issue for now although I would prefer to define the colour spaces exclusively)
I am running Python 3.6.4 and happy to use NumPy or not. Having done a bunch of research, it seems like there are a number of faster and more 'pythonic' and vectorised ways to do this but I have not been able to get any working.
Any help would be much appreciated
Thanks!
Using np.where makes this pretty fast.
mapData = np.where(imgData[:,:,2] > 220, 3, np.where(imgData[:,:,0]>230, 2, 1))
But when applying this to a picture the only results where ones. Did I miss anything or should the cases be made in a different way?
Your algorithm as of the moment can be captured like this:
r, g, b = imgData[...,0], imgData[...,1], imgData[...,2]
mapData = np.ones_like(r, dtype=int)
mapData[r > 230] = 2
mapData[b > 220] = 3
Note the order of operations in assigning these numbers.
Colour classification is usually done by treating RGB colours as vectors. Normalize each one to the magnitude, then find the distance to your target colour.
For example, the skin detector in smartcrop.js works like this (using pyvips):
def pythag(im):
return sum([x ** 2 for x in im]) ** 0.5
skin = [0.78, 0.57, 0.44]
score = 1 - pythag(img / pythag(img) - skin)
Now score is a float image with values in 0 - 1 which is 1 for pixels most likely to be skin-coloured. Note that it ignores brightness: you'll need another rule to chop off very dark areas.
In your case I guess you'd need an array set of target vectors, then compute all the colour probabilities, and finally label the output pixel with the index of the highest-scoring vector. Something like:
import sys
import pyvips
def pythag(im):
return sum([x ** 2 for x in im]) ** 0.5
def classify(img, target):
return 1 - pythag(img / pythag(img) - target)
# find [index, max] of an array of pyvips images
def argmax(ar):
if len(ar) == 1:
return [0, ar[0]]
else:
index, mx = argmax(ar[:-1])
return [(ar[-1] > mx).ifthenelse(len(ar) - 1, index),
(ar[-1] > mx).ifthenelse(ar[-1], mx)]
skin = [0.78, 0.57, 0.44]
red = [1, 0, 0]
green = [0, 1, 0]
targets = [red, green, skin]
# we're not doing any coordinate transformations, so we can stream the image
img = pyvips.Image.new_from_file(sys.argv[1], access="sequential")
scores = [classify(img, x) for x in targets]
index, mx = argmax(scores)
index.write_to_file(sys.argv[2])
(plug: pyvips is typically 2x or 3x faster than numpy and needs much less memory)

How can i speed up operations with NumPy array in Python 2.7

I try to process many images which represented as NumPy array, but it takes too long. that's what im trying to do
# image is a list with images
max = np.amax(image[k])# k is current image index in loop
# here i try to normalize SHORT color to BYTE color and make it fill all range from 0 to 255
# in images max color value is like 30000 min is usually 0
i = 0
while i < len(image[k]):
j = 0
while j < len(image[k][i]):
image[k][i][j] = float(image[k][i][j]) / (max) * 255
j += 1
i += 1
if i only read images (170 in total (images is 512x512)) without it takes about 7 secs, if i do this normalization it takes 20 mins. And it's all over in code. Here i try to make my image colored
maskLoot1=np.zeros([len(mask1), 3*len(mask1[0])])
for i in range(len(mask1)):
for j in range(len(mask1[0])):
maskLoot1[i][j*3]=mask1[i][j]
maskLoot1[i][j*3+1]=mask1[i][j]
maskLoot1[i][j*3+2]=mask1[i][j]
Next i try to replace selected region pixels with colored ones, for example 120 (grey) -> (255 40 0) in rgb model.
for i in range(len(mask1)):
for j in range(len(mask1[0])):
#mask is NumPy array with selected pixel painted in white (255)
if (mask[i][j] > 250):
maskLoot1[i][j * 3] = lootScheme[mask1[i][j]][1] #red chanel
maskLoot1[i][j * 3+1] = lootScheme[mask1[i][j]][2] #green chanel
maskLoot1[i][j * 3+2] = lootScheme[mask1[i][j]][3] #bluechanel
And it also takes much time, not 20 min but long enouch to make my script lag. consider it's just 2 of many my operations on arrays, and if for second case we can use some bultin function for others is very unlikely. So is there a way to speed up my sode?
For your mask-making code try this replacement to loops:
maskLoot1 = np.dstack(3*[mask1]).reshape((mask1.shape[0],3*mask1.shape[1]))
There are many other ways/variations of achieving the above, e.g.,
maskLoot1 = np.tile(mask1[:,:,None], 3).reshape((mask1.shape[0],3*mask1.shape[1]))
As for the first part of your question the best answer is in the first comment to your question by #furas
First thing, consider moving to Python 3.*. Numpy is dropping support for Python Numpy is dropping support for Python 2.7 from 2020.
For your code questions. You are missing the point of using Numpy below. Numpy is compiled from lower level libraries and it runs very fast, you should not loop over indices in Python, you should throw matrices to Numpy.
Question 1
Normalization is very fast using a listcomp and an np.array
import numpy as np
import time
# create dummy image structure (k, i, j, c) or (k, i, j)
# k is image index, i is row, j is columns, c is channel RGB
images = np.random.uniform(0, 30000, size=(170, 512, 512))
t_start = time.time()
norm_images = np.array([(255*images[k, :, :]/images[k, :, :].max()).astype(int) for k in range(170)])
t_end = time.time()
print("Processing time = {} seconds".format(t_end-t_start))
print("Input shape = {}".format(images.shape))
print("Output shape = {}".format(norm_images.shape))
print("Maximum input value = {}".format(images.max()))
print("Maximum output value = {}".format(norm_images.max()))
That creates the following output
Processing time = 0.2568979263305664 seconds
Input shape = (170, 512, 512)
Output shape = (170, 512, 512)
Maximum input value = 29999.999956185838
Maximum output value = 255
It takes 0.25 seconds!
Question 2
Not sure what you meant here but if you want to clone the values of a monochromatic image to RGB values you can do it like this
# coloring (by copying value and keeping your structure)
color_img = np.array([np.tile(images[k], 3) for k in range(170)])
print("Output shape = {}".format(color_img.shape))
Which produces
Output shape = (170, 512, 1536)
If you instead would like to keep a (c, i, j, k) structure
color_img = np.array([[images[k]]*3 for k in range(170)]) # that creates (170, 3, 512, 512)
color_img = np.swapaxes(np.swapaxes(color_img, 1,2), 2, 3) # that creates (170, 512, 512, 3)
All this takes 0.26 seconds!
Question 3
Coloring certain regions, I would use a function again and a listcomp. Since this is an example I have used a default colouring of (255, 40, 0) but you can use anything, including a LUT.
# create mask of zeros and ones
mask = np.floor(np.random.uniform(0,256, size=(512,512)))
default_scheme = (255, 40, 0)
def substitute(cimg, mask, scheme):
ind = mask > 250
cimg[ind, :] = scheme
return cimg
new_cimg = np.array([substitute(color_img[k], mask, default_scheme) for k in range(170)])
In general for-loops are significantly faster than while-loops. Also using a function for
maskLoot1[i][j*3]=mask1[i][j]
maskLoot1[i][j*3+1]=mask1[i][j]
maskLoot1[i][j*3+2]=mask1[i][j]
and calling the function in the loop should speed up the process significantly.

Iterating a function over the RGB values of individual pixels

I've already opened my image and can access the individual pixels' RGB values, but what I'm trying to do now is apply a function to the RGB values of each of the pixels individually. That is, I don't want to apply it the same way across all the pixels in the entire image; I want to apply it differently depending on whether, for each individual pixel, the blue value is > red > green (rather than green > red > blue, etc, etc).
So my question is, how do I access the individual RGB elements within each pixel (as opposed to accessing all of the red, green, and blue values across the entire image at once)? Eventually my question will be "what's the fastest way to do this?" since it's obviously going to take a while to apply a function across each pixel individually but for now I'd be happy just to have any solution at all.
Thanks for any suggestions.
EDIT for clarity/more specificity:
I'm actually trying to apply a different set of instructions depending the ordering of 127.5 - abs(127.5 - red/green/blue)), not simply on the order of red>green>blue (as stated initially above, bc I was trying to simplify). Once that ordering is determined for a given pixel, then the appropriate set of instructions is applied. Again, this is pixel-by-pixel -- I'm not ordering things based on ALL red values across the image, just the rgbs of the individual pixels. So what I'm trying to do would look something like this (here I'm playing out just one of the six possible orders; I've omitted the five other possibilities for brevity):
def rgb_manip(red,green,blue):
r_max = int(127.5 - abs(127.5 - red))
g_max = int(127.5 - abs(127.5 - green))
b_max = int(127.5 - abs(127.5 - blue))
if r_max >= g_max >= b_max:
if r_max >= g_max + b_max:
new_red = red + g_max + b_max
new_green = green - g_max
new_blue = blue - b_max
else:
new_red = red + r_max
new_green = green - r_max + b_max
new_blue = blue - b_max
# elif... And so on, with a different set of instructions for each of the 6 possibilities depending on the order of the r_max, g_max, b_max values (e.g., r_max >= b_max >= g_max or g_max >= r_max >= b_max, etc, etc)
If you convert your image into an array, you can access the RGB values for one pixel, or one of the R, G, or B values for all pixels:
from __future__ import division
import numpy as np
from PIL import Image
im = Image.open(imfile)
arr = np.asarray(im)
arr[..., 0] # All Red values
arr[..., 1] # All Green values
arr[..., 2] # All Blue values
arr[0, 0] # RGB for first corner pixel
arr[m, n] # RGB for pixel at [m, n]
arr[m, n, 0] # R value for pixel [m, n]
arr[m, n, c] # value for color c at pixel [m, n]
You can get a ranking for each pixel using argsort, as in:
def transform(a, c=255/2):
return c - np.abs(c - a)
ranking = transform(arr).argsort(axis=-1)
which ranks the criterion values from smallest to largest value along the last (color) axis. So this gives a new array where each 'color' array instead of being the RGB values are the sorting of the transformed R, B, and G values (call them "R', B', G'"), so if the corner pixel had G' > B' > R', then ranking[0, 0] would be [0, 2, 1] because R' (0) is smallest, then next is B' (2), finally the largest is G' (1).
The advantage of doing the above is that you have an array that says which method to use on which pixel. It can have at most six orderings of the transformed channels. I suggest defining a separate function like so for each of orderings. Then, only one decision must be made within the function (the second nested if/else in your example), and it can be done with np.where which applies one thing to parts of an array where a condition is met, and another thing to the rest. This only works for two options, but if there are multiple options (if/elif/else), other techniques can work equally well.
def bgr(a):
""" for when B' < G' < R'
"""
t = transform(a)
red, green, blue = a.transpose([2,0,1])
# same as: red, green, blue = a[..., 0], a[..., 1], a[..., 2]
r_max, g_max, b_max = t.transpose([2,0,1])
assert np.all((b_max <= g_max) & (g_max <= r_max)), "doesn't match rank"
condition = r_max >= g_max + b_max
new_red = np.where(condition, red + g_max + b_max, red + r_max)
new_green = np.where(condition, green - g_max, green - r_max + b_max)
new_blue = blue - b_max
return np.dstack([new_red, new_green, new_blue])
this function only works for the first if in yours. I would make a new function for each of those six things, and fill them into a dict like so:
functions = {
(0, 1, 2) : rgb, # for R'<G'<B'
(0, 2, 1) : rbg, # for R'<B'<G'
#etc...
}
If your output has RGB values too:
out = np.empty_like(arr)
Then loop through all six rankings/functions:
for rank, func in functions.items():
mask = np.all(transform(arr).argsort(-1) == rank, -1)
out[mask] = func(arr[mask])

Categories

Resources