Reorder/reshape NP array as image - python-3.x

I can grab the colours of an image with
import re
from PIL import Image
import numpy as np
docName = "pal.png"
img = Image.open(docName).convert("RGB")
# Make into Numpy array
npArr = np.array(img)
# Arrange all pixels into a tall column of 3 RGB values and find unique rows (colours)
colours, counts = np.unique(npArr.reshape(-1,3), axis=0, return_counts=1)
# Change to string
npStr = np.array2string(colours, separator = ", ")
pal = re.sub(r"\s?\[|\]\,|]]", "", npStr)
print(pal)
Using a small 4 colour sample image
We have four colours:
51, 51, 51
179, 198, 15
255, 204, 0
255, 255, 255
Trouble is NP re-orders them in order of brightness. I want to preserve the order as reading it from top left to bottom right.
I need them in this order:
51, 51, 51 # near black
255, 255, 255 # white
255, 204, 0 # yellow
179, 198, 15 # green
Can that be easily done with NumPy?

I don't know exactly what the image is, but you could use the return_index=True parameter in np.unique. This way you get the indices of the first occurrences for corresponding colours in colours. If you then sort these indices, you can index colours from your image to get the unique colours while preserving the order.
colours, idx, counts = np.unique(
npArr.reshape(-1,3), axis=0, return_index=True, return_counts=True
)
print(npArr.reshape(-1,3)[np.sort(idx)])

Related

How To Grab Parts of Specific Color from one Image and Draw Them into Another Image Using OpenCV Python?

I have two pictures called pic1.jpg and pic2.jpg, and these two pictures are of the same size (same width, same height).
I want to take those parts whose color is yellow (rgb=255,255,0) from pic1, and then draw them to pic2 at the same position.
How can I do this via opencv-python? I googled and tried below code, but it doesn't work.
image1 = cv2.imread('pic1.jpg')
image2 = cv2.imread('pic2.jpg')
hsv = cv2.cvtColor(image1, cv2.COLOR_BGR2HSV)
# only want the yellow parts
lower_color = np.array([0, 255, 255])
upper_color = np.array([0, 255, 255])
#
mask = cv2.inRange(hsv, lower_color, upper_color)
# add them to image2
result = cv2.bitwise_and(image2, image2, mask=mask)
cv2.imwrite('final.jpg', result)
We can't use cv2.bitwise_and for replacing masked pixels in image2 with pixels from image1.
In C++ we may use mat::copyTo with mask for doing that, but in Python, we can't use copyTo, because it cannot be used with NumPy arrays.
We may solve it using something like result = cv2.bitwise_or(cv2.bitwise_and(image1, mask), cv2.bitwise_and(image2, cv2.bitwise_not(mask))).
But using NumPy logical indexing seems more elegant.
Note:
As commented, [0, 255, 255] is red in HSV.
We don't have to convert to HSV for finding yellow pixels.
If we do, the yellow value is [30, 255, 255] in HSV.
For applying logical indexing or bitwise operations we have to make mask the same dimensions as the images.
Using OpenCV: mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR) replicates the mask to 3 axes.
Code sample:
import cv2
import numpy as np
image1 = cv2.imread('pic1.jpg')
image2 = cv2.imread('pic2.jpg')
hsv = cv2.cvtColor(image1, cv2.COLOR_BGR2HSV)
cv2.imwrite('hsv.png', hsv)
# Only want the yellow parts. Yellow in HSV equls [30, 255, 255]
lower_color = np.array([28, 250, 250])
upper_color = np.array([32, 255, 255])
mask = cv2.inRange(hsv, lower_color, upper_color)
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR) # Convert maks to 3D array - as np.concatenate((mask,mask,mask))
#result = cv2.bitwise_or(cv2.bitwise_and(image1, mask), cv2.bitwise_and(image2, cv2.bitwise_not(mask))) # Pure OpenCV solution.
result = image2
result[mask==255] = image1[mask==255] # Use logical indexing for replacing the masked pixels in image2 with pixels from image1.
cv2.imwrite('final.jpg', result)
# Write mask for testing
cv2.imwrite('mask.jpg', mask)
The following images were used for testing:
image1:
image2:
result:
mask:
Update:
Code sample without converting to HSV color space:
Conversion to HSV is useful for finding larger range of yellow (yellowish) colored pixels.
Example: Searching pixels with Hue=30, Saturation in range [100, 255] and Value in range [30, 255] returns large range of yellow pixels (dark yellow, bright yellow...).
When looking for pure bright yellow, we may apply cv2.inRange to BGR color format, and search for pixels with Blue=0, Green=255, Red=255.
The example uses a bit wider range [0, 250, 250] to [5, 255, 255] (mainly because the JPEG compression modifies the original values).
Code sample, without converting to HSV:
import cv2
import numpy as np
image1 = cv2.imread('pic1.jpg')
image2 = cv2.imread('pic2.jpg')
# Only want the yellow parts. Yellow in BGR equls [0, 255, 255]
lower_color = np.array([0, 250, 250])
upper_color = np.array([5, 255, 255])
mask = cv2.inRange(image1, lower_color, upper_color) # Apply inRange to image1 in BGR color format
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR) # Convert maks to 3D array - as np.concatenate((mask,mask,mask))
result = image2
result[mask==255] = image1[mask==255] # Use logical indexing for replacing the masked pixels in image2 with pixels from image1.
cv2.imwrite('final.jpg', result)

How to use rgb value to add color in pandas.scatter_matrix?

I want to add color in pd.scatter_matrix, the rgb value like that,
val_rgb = [[127 80 34]
[130 89 34]
[170 133 75]
...]
I once use them in scatter3D them like that,
for i in range(0, len(df)):
ax1.scatter3D(
df[i,0],
df[i,1],
df[i,2],
s = 2,
marker='o',
c = '#%02x%02x%02x' % tuple(val_rgb[i])
)
However, in scatter_matrix, I find it only can add c='red' , Is there more accurate to adjust the color of each point?
PS(I also find adding color by label sns.pairplot(df), but also didn't find how to add color accurately...)
You can specify a color using RGB format using a tuple of float values between 0 and 1. Thus simply divide the RGB values by 255:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
N = 500
data = pd.DataFrame(np.random.randn(N, 4), columns=['A','B','C','D'])
colors = np.random.randint(256, size=(N, 3)) # random colors
pd.plotting.scatter_matrix(data, alpha=.2, color=colors / 255)

Change Dimensions of ndarray and Multiply Contents

I have an MxN ndarray that contains True and False values inside those arrays and want to draw those as an image.
The goal is to convert the array to a pillow image with each True value as a constant color. I was able to get it working by looping through each pixel and changing them individually by a comparison and drawing the pixel on a blank image, but that method is way too slow.
# img is a PIL image result
# image is the MxN ndarray
pix = img.load()
for x in range(image.shape[0]):
for y in range(image.shape[1]):
if image[x, y]:
pix[y, x] = (255, 0, 0)
Is there a way to change the ndarray to a MxNx3 by replacing the tuples directly to the True values?
If you have your True/False 2D array and the label for the color, for example [255,255,255], the following will work:
colored = np.expand_dims(bool_array_2d,axis=-1)*np.array([255,255,255])
To illustrate it with a dummy example: in the following code I have created a random matrix of 0s and 1s and then have turned the 1s to white ([255,255,255]).
import numpy as np
import matplotlib.pyplot as plt
array = np.random.randint(0,2, (100,100))
colors = np.array([255,255,255])
colored = np.expand_dims(array, axis=-1)*colors
plt.imshow(colored)
Hope this has helped
Did find another solution, converted to an image first, then converted to RGB, then converted back to separate to 3 channels. When I was trying to combine multiple boolean arrays together, this way was a lot faster.
img = Image.fromarray(image * 1, 'L').convert('RGB')
data = np.array(img)
red, green, blue = data.T
area = (red == 1)
data[...][area.T] = (255, 255, 255)
img = Image.fromarray(data)
I think you can do this quite simply and fast like this:
# Make a 2 row by 3 column image of True/False values
im = np.random.choice((True,False),(2,3))
Mine looks like this:
array([[False, False, True],
[ True, True, True]])
Now add a new axis it make it 3 channel and multiply the truth values by your new "colour":
result = im[..., np.newaxis]*[255,255,255]
which gives you this:
array([[[ 0, 0, 0],
[ 0, 0, 0],
[255, 255, 255]],
[[255, 255, 255],
[255, 255, 255],
[255, 255, 255]]])

Why do assigned RGB values get changed automatically?

First, consider this code:
from PIL import Image
im = Image.open("best_tt.jpg")
im2 = Image.new("RGB", im.size, (255,255,255))
b = 200
for i in range(im.size[0]):
for j in range(im.size[1]):
rgb = im.getpixel((i,j))
if rgb[0] <= b and rgb[1] <= b and rgb[2] <= b:
im2.putpixel((i,j), (0,0,0))
else:
im2.putpixel((i,j), (0, rgb[1], rgb[2]))
im2.save("tmp.jpg")
What I am doing is simply removing the RED component from each pixel (other than black pixels: the if statement checks for pixels that look black). In other words, I'm converting the given image to a yellow scale (since G+B = Y).
In that way, every pixel should have an RGB value like (0, G, B).
However, certain pixels of the new image returned values like:
(1, 255, 203)
(3, 205, 243)
(16, 242, 47)
though some had the red component as 0.
What causes this arbitrary adjustment of the RGB values?
The save() function will determine the type as a jpeg, which has a default compression quality of 75. The way the file is encoded and compressed can end up changing values after the fact.
See the PIL documentation for save() below:
https://pillow.readthedocs.io/en/3.1.x/handbook/image-file-formats.html

Getting Rid of Lines in OpenCV-Python

Like I converted my original input to HSV color space image & applied the INRANGE function and found the green & blue lines & now i want to get rid of them and I want the image to look like in output....how shall i now get rid of the lines & replace them by the background color??
Code Snippet:
import cv2 as cv
import numpy as np
img= cv.imread('C:\input.png',1)
hsv=cv.cvtColor(img,cv.COLOR_BGR2HSV)
lower_green = np.array([30,70,20])
upper_green = np.array([70,255,255])
lower_blue = np.array([95, 110, 20])
upper_blue = np.array([135, 255, 255])
mask = cv.inRange(hsv, lower_green , upper_blue)
res = cv.bitwise_and(img,img, mask= mask)
cv.imwrite("out2.jpg", res)
Here is a quick and dirty solution.
Create a mask from manually threshold image containing the lines (mask 1)
Also create a binary inverted image of this mask (mask 2)
Mask the image of the shirt with mask 1
Inpaint the image above using mask 2
The solution definitely can be improved by performing morphological operations on the mask to remove the lines. Share your thoughts as well
By doing something like #jeru-luke said, the result will be like this:
import cv2 as cv
import numpy as np
img = cv.imread('z12.png', 1)
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
lower_green = np.array([30, 70, 20])
upper_green = np.array([70, 255, 255])
lower_blue = np.array([95, 110, 20])
upper_blue = np.array([135, 255, 255])
mask = cv.inRange(hsv, lower_green, upper_blue)
mask = cv.bitwise_not(mask)
bk = np.full(img.shape, 255, dtype=np.uint8) # white bk
fg_masked = cv.bitwise_and(img, img, mask=mask)
# get masked background, mask must be inverted
mask = cv.bitwise_not(mask)
bk_masked = cv.bitwise_and(bk, bk, mask=mask)
# combine masked foreground and masked background
final = cv.bitwise_or(fg_masked, bk_masked)
cv.imwrite('out_put.png', final)
cv.imshow('final', final), cv.waitKey(0)
import cv2 as cv
import numpy as np
img= cv.imread(r'input.png',1)
hsv=cv.cvtColor(img,cv.COLOR_BGR2HSV)
h,s,v = cv.split(hsv)
th, threshed = cv.threshold(s, 100, 255, cv.THRESH_OTSU|cv.THRESH_BINARY) #black background
mask_w = cv.bitwise_not(threshed) #white background
fg_masked = cv.bitwise_and(v, v, mask=mask_w) #masking the image of shirt with mask_w
dst = cv.inpaint(fg_masked,threshed,3, cv.INPAINT_NS) #inpainting
#Dilation & Erosion.
kernel = np.ones((4, 4),np.uint8)
dilation = cv.dilate(dst,kernel,iterations = 2)
erosion = cv.erode(dilation, kernel, iterations=1)
dilation2= cv.dilate(erosion,kernel,iterations = 1)
dilation3= cv.dilate(dilation2,kernel,iterations = 1)
erosion_final = cv.erode(dilation3, kernel, iterations=3)
cv.imwrite("output_2 [improved].png", erosion_final)

Resources