I have a small image that I would like to find all RGB values that are the same and then compare that against my variable and if the match print match or similar.
Below is a little snippet that I have put together from other sources.
I am able to print that I found the colour but it will print a line for every time that value is found in the image.
Is there a better way to search an image and match that to a single RGB value? Then if found do a thing.
import cv2
path = '3.png'
blue = int(224)
green = int(96)
red = int(32)
img = cv2.imread(path)
x,y,z = img.shape
for i in range(x):
for j in range(y):
if img[i,j,0]==blue & img[i,j,1]==green & img[i,j,1]==red:
print("Found colour at ",i,j)
General Python advice: do not say blue = int(224). Just say blue = 224
Your program expects to find those exact values. In any kind of photo, nothing is exact. You need to find ranges of values.
cv.imread returns data in BGR order. Be aware of that if you access individual values in the numpy array.
Use this:
import numpy as np
import cv2 as cv
img = cv.imread("3.png")
lower_bound = (224-20, 96-20, 32-20) # BGR
upper_bound = (224+20, 96+20, 32+20) # BGR
mask = cv.inRange(img, lower_bound, upper_bound)
count = cv.countNonZero(mask)
print(f"picture contains {count} pixels of that color")
And if you need to know where pixels of that color are, please explain what you need that for. A list of those points is generally useless. There are more useful ways to get these locations but they depend on why you need this information, what for.
I think this may help you with small touching :)
import cv2
import numpy as np
r = int(255)
g = int(255)
b = int(255)
img = 255*np.ones((5,5,3), np.uint8)
[rows, cols] = img.shape[:2]
print(img.shape[:2])
print(img.shape)
for i in range(rows):
for j in range(cols):
if img[i, j][0] == r and img[i, j][1] == g and img[i, j][2] == b:
print(img[i, j])
print(i, j)
Related
I'm pretty new to image processing and python so bear with me
I'm trying to take a big image (5632x2048) which is basically a map of the world with provinces (ripped from Hearts of Iron 4), and each province is colored a different RGB value, and color it with a set of colors, each corresponding to a certain country. I'm currently using this code
import numpy as np
import cv2
import sqlite3
dbPath = 'PATH TO DB'
dirPath = 'PATH TO IMAGE'
con = sqlite3.connect(dbPath)
cur = con.cursor()
im = cv2.imread(dirPath)
cur.execute('SELECT * FROM Provinces ORDER BY id')
provinceTable = cur.fetchall()
for line in provinceTable:
input_rgb = [line[1], line[2], line[3]]
if line[7] == None:
output_rgb = [255,255,255]
else:
output_rgb = line[7].replace('[', '').replace(']','').split(',')
im[np.all(im == (int(input_rgb[0]), int(input_rgb[1]), int(input_rgb[2])), axis=-1)] = (int(output_rgb[0]), int(output_rgb[1]), int(output_rgb[2]))
cv2.imwrite('result.png',im)
The problem I'm running into is that it's painfully slow (50 minutes in and it hasn't finished), due to the fact I'm definitely using numpy wrong by looping through it instead of vectorizing (a concept I'm still new to and have no idea how to do). Google hasn't been very helpful either.
What's the best way to do this?
Edit: forgot to mention that the amount of values I'm replacing is pretty big (~15000)
As I mentioned in the comments, I think you'll want to use np.take(yourImage, LUT) where LUT is a Lookup Table.
So, if you make a dummy image the same shape as yours:
import numpy as np
# Make a dummy image of 5632x2048 RGB values
im = np.random.randint(0,256,(5632,2048,3), np.uint8)
that will be 34MB. Now reshape it to a tall vector of RGB values:
# Make image into a tall vector, as tall as necessary and 3 RGB values wide
v = im.reshape((-1,3))
which will be of shape (11534336, 3) and then flatten that to 24-bit values rather than three 8-bit values with np.dot()
# Make into tall vector of shape 11534336x1 rather than 11534336x3
v24 = np.dot(v.astype(np.uint32),[1,256,65536])
You will now have a 1-D vector of 24-bit pixel values with shape (11534336,)
Now create your RGB lookup table (I am making all 2^24 RGB entries here, you may need less):
RGBLUT = np.zeros((2**24,3),np.uint8)
And set up the LUT. So, supposing you want to map all colours in the original image to mid-grey (128) in the output image:
RGBLUT[:] = 128
Now do the np.dot() thing just the same as we did with the image so we get a LUT with shape (224,1) rather than shape (224,3):
LUT24 = np.dot(RGBLUT.astype(np.uint32), [1,256,65536])
Then do the actual lookup in the table:
result = np.take(LUT24, v24)
On my Mac, that take 334ms for your 5632x2048 image.
Then reshape and convert back to three 8-bit values by shifting and ANDing to undo effect of np.dot().
I am not currently in a position to test the re-assembly, but it will look pretty much like this:
BlueChannel = result & 0xff # Blue channel is bottom 8 bits
GreenChannel = (result>>8) &0 xff # Green channel is middle 8 bits
RedChannel = (result>>16) &0 xff # Red channel is top 8 bits
Now combine those three single channels into a 3-channel image:
RGB = np.dstack(RedChannel, GreenChannel, BlueChannel))
And reshape back from tall vector to dimensions of original image:
RGB = RGB.reshape(im.shape)
As regards setting up the LUT, to something more interesting than mid-grey, if you want to map say orange, i.e. rgb(255,128,0) to magenta, i.e. rgb(255,0,255) you would do something along the lines of:
LUT[np.dot([255,128,0],[1,256,65536])] = [255,0,255] # map orange to magenta
LUT[np.dot([255,255,255],[1,256,65536])] = [0,0,0] # map white to black
LUT[np.dot([0,0,0],[1,256,65536])] = [255,255,255] # map black to white
Keywords: Python, image processing, LUT, RGB LUT 24-bit LUT, lookup table.
Here is one way to do that using Numpy and Python/OpenCV. Here I change red to green.
Input:
import cv2
import numpy as np
# load image
img = cv2.imread('test_red.png')
# change color
result = img.copy()
result[np.where((result==[0,0,255]).all(axis=2))] = [0,255,0]
# save output
cv2.imwrite('test_green.png', result)
# Display various images to see the steps
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
You can create a mask of the image first and use that to replace the colors. There's likely a pure numpy way of doing this that is faster, but I don't know it.
This code takes ~0.5 seconds to run. You should expect it to take about half a second for each color replacement.
import cv2
import numpy as np
import time
# make image
res = (5632, 2048, 3);
img = np.zeros(res, np.uint8);
# change black to white
black = (0,0,0);
white = (255,255,255);
# make a mask
start_time = time.time();
mask = cv2.inRange(img, black, black);
print("Mask Time: " + str(time.time() - start_time));
# replace color
start_time = time.time();
img[mask == 255] = white;
print("Replace Time: " + str(time.time() - start_time));
In terms of your code it'll look like this
for line in provinceTable:
input_rgb = [line[1], line[2], line[3]]
input_rgb = (int(input_rgb[0]), int(input_rgb[1]), int(input_rgb[2]))
if line[7] == None:
output_rgb = (255,255,255)
else:
output_rgb = line[7].replace('[', '').replace(']','').split(',')
output_rgb = (int(output_rgb[0]), int(output_rgb[1]), int(output_rgb[2]))
mask = cv2.inRange(im, input_rgb, input_rgb)
im[mask == 255] = output_rgb
I am making a program where you can chose an image and a color and it will invert only the pixels that match that color. I've been surfing stackoverflow and reddit for solutions but so far no luck.
I tried to do something like this first:
img = Image.open('past.png')
pixels = img.load()
for i in goodpixels:
ImageOps.invert(pixels[i])
AttributeError: 'tuple' object has no attribute 'mode'
No luck with that because ImageOps.invert only inverts full images. Next I tried to use ImageOps.polarize but realized that I couldn't use it because it takes greyscale thresholds not rgb values.
img = Image.open('past.png')
pixels = img.load()
for i in goodpixels:
ImageOps.solarize(img, threshold=pixels[i])
TypeError: '<' not supported between instances of 'int' and 'tuple'
This is my issue, I don't know if this is even possible. If it takes too much work I will probably abandon the project anyways because I'm just keeping myself occupied, and this isn't for marks/job.
Some more code:
def checkpixels():
img = Image.open('past.png')
height, width = img.size
img = img.convert('RGB')
targetcolor = input('What color do you want to search for, you can use RGB format or common names like \'red\', \'black\', e.t.c. Leave this blank to invert all pixels. ')
print('Processing image. This could take several minutes.')
isrgb = re.match(r'\d+, \d+, \d+|\(\d+, \d+, \d+\)', targetcolor)
if type(isrgb) == re.Match:
targetcolor = targetcolor.strip('()')
targetcolor = targetcolor.split(', ')
targetcolor = tuple(map(int, targetcolor))
print(str(targetcolor))
for x in range(width):
for y in range(height):
color = img.getpixel((y-1, x-1))
if color == targetcolor:
goodpixels.append((y-1, x-1))
else:
try:
targetcolor = ImageColor.getcolor(targetcolor.lower(), 'RGB')
print(targetcolor)
for x in range(width):
for y in range(height):
color = img.getpixel((y-1, x-1))
if color == targetcolor:
goodpixels.append((y-1, x-1))
except:
print('Not a valid color smh.')
return goodpixels
goodpixels = []
goodpixels = checkpixels()
Edit: I figured it out! Thank you to Mark Setchell for his incredible brain! I ended up using numpy to convert the image and target color to arrays, making an inverted copy of the image, and using numpy.where() to decide whether or not to switch out the pixels. I also plan on making the target color a range so the chosen color doesn't have to be so specific. All in all my code looks like this:
goodpixels = []
targetcolor = inputcolor()
img = Image.open('past.png')
invertimage = img.copy().convert('RGB')
invertimage = ImageOps.invert(invertimage)
invertimage.save('invert.png')
pastarray = np.array(img)
targetcolorarray = np.array(targetcolor)
pixels = img.load()
inverse = np.array(invertimage)
result = np.where((pastarray==targetcolorarray).all(axis=-1)[...,None], inverse, pastarray)
Image.fromarray(result.astype(np.uint8)).save('result.png')
Of course, inputcolor() is a function offscreen which just decides if the input is a color name or rgb value. Also I used import numpy as np in this example.
A problem that I had was that original my .where method looked like this:
result = np.where((pastarray==[0, 0, 0]).all(axis=-1)[...,None], inverse, pastarray)
This brought up the error: AttributeError: 'bool' object has no attribute 'all'
It turns out all I had to do was convert my color into an array!
Many libraries allow you to import an image into Python as a numpy array. PIL or opencv2 are well documented libraries for working with images:
pip install opencv2
Example numpy.where() selection, meeting a set criteria, in this case inverting all pixel values below THRESHOLD:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# cut off thereshold
THRESHOLD = 230
pixel_data = cv2.imread('filename.png')
pixel_data = np.where(pixel_data < THRESHOLD, 1/pixel_data, pixel_data)
# display the edited image using matplotlib
plt.imshow(pixel_data)
The numpy.where() function applies a condition to your numpy array. More details available on the: numpy official documentation
I am trying to increase the region of interest of an image using the below algorithm.
First, the set of pixels of the exterior border of the ROI is de termined, i.e., pixels that are outside the ROI and are neighbors (using four-neighborhood) to pixels inside it. Then, each pixel value of this set is replaced with the mean value of its neighbors (this time using eight-neighborhood) inside the ROI. Finally, the ROI is expanded by inclusion of this altered set of pixels. This process is repeated and can be seen as artificially increasing the ROI.
The pseudocode is below -
while there are border pixels:
border_pixels = []
# find the border pixels
for each pixel p=(i, j) in image
if p is not in ROI and ((i+1, j) in ROI or (i-1, j) in ROI or (i, j+1) in ROI or (i, j-1) in ROI) or (i-1,j-1) in ROI or (i+1,j+1) in ROI):
add p to border_pixels
# calculate the averages
for each pixel p in border_pixels:
color_sum = 0
count = 0
for each pixel n in 8-neighborhood of p:
if n in ROI:
color_sum += color(n)
count += 1
color(p) = color_sum / count
# update the ROI
for each pixel p=(i, j) in border_pixels:
set p to be in ROI
Below is my code
img = io.imread(path_dir)
newimg = np.zeros((584, 565,3))
mask = img == 0
while(1):
border_pixels = []
for i in range(img.shape[0]):
for j in range(img.shape[1]):
for k in range(0,3):
if(i+1<=583 and j+1<=564 and i-1>=0 and j-1>=0):
if ((mask[i][j][k]) and ((mask[i+1][j][k]== False) or (mask[i-1][j][k]==False) or (mask[i][j+1][k]==False) or (mask[i][j-1][k]==False) or (mask[i-1][j-1][k] == False) or(mask[i+1][j+1][k]==False))):
border_pixels.append([i,j,k])
if len(border_pixels) == 0:
break
for (each_i,each_j,each_k) in border_pixels:
color_sum = 0
count = 0
eight_neighbourhood = [[each_i-1,each_j],[each_i+1,each_j],[each_i,each_j-1],[each_i,each_j+1],[each_i-1,each_j-1],[each_i-1,each_j+1],[each_i+1,each_j-1],[each_i+1,each_j+1]]
for pix_i,pix_j in eight_neighbourhood:
if (mask[pix_i][pix_j][each_k] == False):
color_sum+=img[pix_i,pix_j,each_k]
count+=1
print(color_sum//count)
img[each_i][each_j][each_k]=(color_sum//count)
for (i,j,k) in border_pixels:
mask[i,j,k] = False
border_pixels.remove([i,j,k])
io.imsave("tryout6.png",img)
But it is not doing any change in the image.I am getting the same image as before
so I tried plotting the border pixel on a black image of the same dimension for the first iteration and I am getting the below result-
I really don't have any idea where I am doing wrong here.
Here's a solution that I think works as you have requested (although I agree with #Peter Boone that it will take a while). My implementation has a triple loop, but maybe someone else can make it faster!
First, read in the image. With my method, the pixel values are floats between 0 and 1 (rather than integers between 0 and 255).
import urllib
import matplotlib.pyplot as plt
import numpy as np
from skimage.morphology import binary_dilation, binary_erosion, disk
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu
# create a file-like object from the url
f = urllib.request.urlopen("https://i.stack.imgur.com/JXxJM.png")
# read the image file in a numpy array
# note that all pixel values are between 0 and 1 in this image
a = plt.imread(f)
Second, add some padding around the edges, and threshold the image. I used Otsu's method, but #Peter Boone's answer works well, too.
# add black padding around image 100 px wide
a = np.pad(a, ((100,100), (100,100), (0,0)), mode = "constant")
# convert to greyscale and perform Otsu's thresholding
grayscale = rgb2gray(a)
global_thresh = threshold_otsu(grayscale)
binary_global1 = grayscale > global_thresh
# define number of pixels to expand the image
num_px_to_expand = 50
The image, binary_global1 is a mask that looks like this:
Since the image is three channels (RGB), I process the channels separately. I noticed that I needed to erode the image by ~5 px because the outside of the image has some unusual colors and patterns.
# process each channel (RGB) separately
for channel in range(a.shape[2]):
# select a single channel
one_channel = a[:, :, channel]
# reset binary_global for the each channel
binary_global = binary_global1.copy()
# erode by 5 px to get rid of unusual edges from original image
binary_global = binary_erosion(binary_global, disk(5))
# turn everything less than the threshold to 0
one_channel = one_channel * binary_global
# update pixels one at a time
for jj in range(num_px_to_expand):
# get 1 px ring of to update
px_to_update = np.logical_xor(binary_dilation(binary_global, disk(1)),
binary_global)
# update those pixels with the average of their neighborhood
x, y = np.where(px_to_update == 1)
for x, y in zip(x,y):
# make 3 x 3 px slices
slices = np.s_[(x-1):(x+2), (y-1):(y+2)]
# update a single pixel
one_channel[x, y] = (np.sum(one_channel[slices]*
binary_global[slices]) /
np.sum(binary_global[slices]))
# update original image
a[:,:, channel] = one_channel
# increase binary_global by 1 px dilation
binary_global = binary_dilation(binary_global, disk(1))
When I plot the output, I get something like this:
# plot image
plt.figure(figsize=[10,10])
plt.imshow(a)
This is an interesting idea. You're going to want to use masks and some form of mean ranks to accomplish this. Going pixel by pixel will take you a while, instead you want to use different convolution filters.
If you do something like this:
image = io.imread("roi.jpg")
mask = image[:,:,0] < 30
just_inside = binary_dilation(mask) ^ mask
image[~just_inside] = [0,0,0]
you will have a mask representing just the pixels inside of the ROI. I also set the pixels not in that area to 0,0,0.
Then you can get the pixels just outside of the roi:
just_outside = binary_erosion(mask) ^ mask
Then get the mean bilateral of each channel:
mean_blue = mean_bilateral(image[:,:,0], selem=square(3), s0=1, s1=255)
#etc...
This isn't exactly correct, but I think it should put you in the right direction. I would check out image.sc if you have more general questions about image processing. Let me know if you need more help as this was more general direction than working code.
I want to remove the dark(black strips) and also the white curves in the image, and then align the remained parts connected in a new small-sized image, making the colored parts looks continuously. I hope can get some suggestions for solutions.
I have tried to use PIL to read the image.
I don't know how to set the right threshold and resize the image
I'm not an expert at all in image processing, but let me know if this is enough for you.
Looking at the brightness (sum of the RGB values) distribution, maybe one option is to just filter pixel based on their value:
It looks like the dark parts have a brightness below 100 (or something like that). I filtered it this way:
from PIL import Image
import numpy as np
def filter_image(img,threshold=100):
data = np.array(img.convert('RGB'))
brightness = np.sum(data,axis=2)
filtered_img = data.copy()*0
for i in range(data.shape[0]):
k = 0 # k index the columns that are bright enough
for j in range(data.shape[1]):
if brightness[i,j] > threshold:
filtered_img[i,k,:] = data[i,j,:]
k += 1 # we increment only if it's bright enough
# End of column iterator. The right side of the image is black
return Image.fromarray(filtered_img)
img = Image.open("test.png")
filtered = filter_image(img)
filtered.show()
I get the following result. I'm sure experts can do much better, but it's a start:
The following is only looking for black pixels, as can be seen by the first image, many of the pixels you want out are not black. You will need to find a way to scale up what you will take out.
Also, research will need to be done on collapsing an image, as can be seen by my collapse. Although, this image collapse may work if you are able to get rid of everything but the reddish colors. Or you can reduce by width, which is what the third picture shows.
from PIL import Image, ImageDraw
def main():
picture = Image.open('/Volumes/Flashdrive/Stack_OverFlow/imageprocessing.png', 'r')
# pix_val = list(im.getdata())
# print(pix_val)
# https://code-maven.com/create-images-with-python-pil-pillowimg = Image.new('RGB', (100, 30), color = (73, 109, 137))
blackcount = 0
pix = picture.convert('RGB') # https://stackoverflow.com/questions/11064786/get-pixels-rgb-using-pil
width, height = picture.size
img = Image.new('RGB', (width, height), color=(73, 109, 137))
newpic = []
for i in range(width):
newpictemp = []
for j in range(height):
# https://stackoverflow.com/questions/13167269/changing-pixel-color-python
r, g, b = pix.getpixel((i, j))
if r == 0 and g == 0 and b == 0:
blackcount += 1
else:
img.putpixel((i, j), (r, g, b))
newpictemp.append((r, g, b))
newpic.append(newpictemp)
img.save('pil_text.png')
newheight = int(((width * height) - blackcount) / width)
print(newheight)
img2 = Image.new('RGB', (width, newheight), color=(73, 109, 137))
for i in range(width):
for j in range(newheight):
try:
z = newpic[i][j]
img2.putpixel((i, j), newpic[i][j])
except:
continue
img2.save('pil_text2.png')
if __name__ == "__main__":
main()
No black pixels on left, removed black pixels on right, remove and resize by width (height resize shown in code)
Like the image above suggests, how can I convert the image to the left into an array that represent the darkness of the image between 0 for white and decimals for darker colours closer to 1? as shown in the image usingpython 3`?
Update:
I have tried to work abit more on this. There are good answers below too.
# Load image
filename = tf.constant("one.png")
image_file = tf.read_file(filename)
# Show Image
Image("one.png")
#convert method
def convertRgbToWeight(rgbArray):
arrayWithPixelWeight = []
for i in range(int(rgbArray.size / rgbArray[0].size)):
for j in range(int(rgbArray[0].size / 3)):
lum = 255-((rgbArray[i][j][0]+rgbArray[i][j][1]+rgbArray[i][j][2])/3) # Reversed luminosity
arrayWithPixelWeight.append(lum/255) # Map values from range 0-255 to 0-1
return arrayWithPixelWeight
# Convert image to numbers and print them
image_decoded_png = tf.image.decode_png(image_file,channels=3)
image_as_float32 = tf.cast(image_decoded_png, tf.float32)
numpy.set_printoptions(threshold=numpy.nan)
sess = tf.Session()
squeezedArray = sess.run(image_as_float32)
convertedList = convertRgbToWeight(squeezedArray)
print(convertedList) # This will give me an array of numbers.
I would recommend to read in images with opencv. The biggest advantage of opencv is that it supports multiple image formats and it automatically transforms the image into a numpy array. For example:
import cv2
import numpy as np
img_path = '/YOUR/PATH/IMAGE.png'
img = cv2.imread(img_path, 0) # read image as grayscale. Set second parameter to 1 if rgb is required
Now img is a numpy array with values between 0 - 255. By default 0 equals black and 255 equals white. To change this you can use the opencv built in function bitwise_not:
img_reverted= cv2.bitwise_not(img)
We can now scale the array with:
new_img = img_reverted / 255.0 // now all values are ranging from 0 to 1, where white equlas 0.0 and black equals 1.0
Load the image and then just invert and divide by 255.
Here is the image ('Untitled.png') that I used for this example: https://ufile.io/h8ncw
import numpy as np
import cv2
import matplotlib.pyplot as plt
my_img = cv2.imread('Untitled.png')
inverted_img = (255.0 - my_img)
final = inverted_img / 255.0
# Visualize the result
plt.imshow(final)
plt.show()
print(final.shape)
(661, 667, 3)
Results (final object represented as image):
You can use PIL package to manage images. Here's example how it can be done.
from PIL import Image
image = Image.open('sample.png')
width, height = image.size
pixels = image.load()
# Check if has alpha, to avoid "too many values to unpack" error
has_alpha = len(pixels[0,0]) == 4
# Create empty 2D list
fill = 1
array = [[fill for x in range(width)] for y in range(height)]
for y in range(height):
for x in range(width):
if has_alpha:
r, g, b, a = pixels[x,y]
else:
r, g, b = pixels[x,y]
lum = 255-((r+g+b)/3) # Reversed luminosity
array[y][x] = lum/255 # Map values from range 0-255 to 0-1
I think it works but please note that the only test I did was if values are in desired range:
# Test max and min values
h, l = 0,1
for row in array:
h = max([max(row), h])
l = min([min(row), l])
print(h, l)
You have to load the image from the path and then transform it to a numpy array.
The values of the image will be between 0 and 255. The next step is to standardize the numpy array.
Hope it helps.