RGB disappears after processing the image - python

I am working with an image (2192 x 2921 x 3) to replace the pixel values equal to 0 with those of its previous nonzero values.
The code finishes without errors, but the output image is no longer RGB.
Is there anything erroneous in my code that causing this?
The function "fill_zeros_with_last" is from StackOverflow.
The code is given below:
import numpy as np
import cv2
from PIL import Image
def fill_zeros_with_last(arr):
prev = np.arange(len(arr))
prev[arr == 0] = 0
prev = np.maximum.accumulate(prev)
return arr[prev]
image = cv2.imread('path\to\image')
image_modified = [] # to store the processed image
for k in range(3):
for j in range(2921):
image1 = fill_zeros_with_last(image[:, j, k]) # replaces 0s with the previous nonzero value.
image_modified.append(image1)
image_modified = np.reshape(image_modified, ((2192, 2921, 3))) # to reshape the image
image_modified = image_modified.astype('uint8') # convert to uint8
img1 = Image.fromarray(image_modified, 'RGB') # convert to RGB
img1.save('image_modified.png') # save image
Here is a sample input image:
Sample output:

It looks like you are confused by the data ordering of NumPy array storing an OpenCV images.
The natural ordering of image in OpenCV (in memory) is "raw major" with b,g,r,b,g,r... data ordering:
Row 0: BGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGR
Row 1: BGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGR
Row 3: BGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGR
The indexing of the image array is: image[r, c, ch] (row, column, color_channel):
image_modified is a list of modified columns, each element in the list applies one color channel:
[
All columns of blue channel of column
Applies image column 0: BBBBBBBBBBBBBBBBBBBBBBBBBBB,
Applies image column 1: BBBBBBBBBBBBBBBBBBBBBBBBBBB,
Applies image column 2: BBBBBBBBBBBBBBBBBBBBBBBBBBB,
All columns of green channel of column
Applies image column 0: GGGGGGGGGGGGGGGGGGGGGGGGGGG,
Applies image column 1: GGGGGGGGGGGGGGGGGGGGGGGGGGG,
Applies image column 2: GGGGGGGGGGGGGGGGGGGGGGGGGGG,
All columns of red channel of column
Applies image column 0: RRRRRRRRRRRRRRRRRRRRRRRRRRR,
Applies image column 1: RRRRRRRRRRRRRRRRRRRRRRRRRRR,
Applies image column 2: RRRRRRRRRRRRRRRRRRRRRRRRRRR,
...
]
For fixing the ordering, we may apply np.reshape followed by np.transpose:
Reshape to 3 columns by <cols> rows by <rows> elements:
image_modified = np.reshape(image_modified, ((3, cols, rows)))
Transpose (permute) to rows by cols by 3:
image_modified = np.transpose(image_modified, (2, 1, 0))
Code sample:
import numpy as np
import cv2
def fill_zeros_with_last(arr):
prev = np.arange(len(arr))
prev[arr == 0] = 0
prev = np.maximum.accumulate(prev)
return arr[prev]
image = cv2.imread('test_image.jpg')
rows, cols = image.shape[0], image.shape[1] # Get height and width of image
image_modified = [] # to store the processed image
for k in range(3):
for j in range(cols):
image1 = fill_zeros_with_last(image[:, j, k]) # replaces 0s with the previous nonzero value.
image_modified.append(image1)
image_modified = np.reshape(image_modified, ((3, cols, rows))) # to reshape the image
image_modified = np.transpose(image_modified, (2, 1, 0)) # Fix the data ordering to match OpenCV convention
cv2.imwrite('image_modified.png', image_modified) # Use cv2.imwrite instead of using PIL because the color ordering is different.
Instead of messing with the ordering, we may use NumPy array for storing image_modified, instead of using a list:
import numpy as np
import cv2
def fill_zeros_with_last(arr):
prev = np.arange(len(arr))
prev[arr == 0] = 0
prev = np.maximum.accumulate(prev)
return arr[prev]
image = cv2.imread('test_image.jpg')
rows, cols = image.shape[0], image.shape[1] # Get height and width of image
#image_modified = [] # to store the processed image
image_modified = np.zeros_like(image) # Initialize image_modified to array of zeros with same size and type of image
for k in range(3):
for j in range(cols):
image1 = fill_zeros_with_last(image[:, j, k]) # replaces 0s with the previous nonzero value.
image_modified[:, j, k] = image1 # Update the column
#image_modified.append(image1)
cv2.imwrite('image_modified.png', image_modified) # Use cv2.imwrite instead of using PIL because the color ordering is different.
Output:

Related

Image Array "ValueError: setting an array element with a sequence"

I want to do some operation in numpy array. Actually I'm trying to zoom an image using the nearest neighbour rule. I have facing that above titled issue.
import cv2
import numpy as np
from numpy import ndarray
img = cv2.imread('abc.jpg')
rows = img.shape[0]*2
cols = img.shape[1]*2
zoomed = np.zeros((rows, cols), dtype=img.dtype)
for i in range(0, rows):
for j in range(0, cols):
zoomed[i][j] = img[int(i/2)][int(j/2)]
cv2.imshow('Input Image', img)
cv2.imshow('Zoomed Image', zoomed)
cv2.waitKey(0)
Try:
zoomed = np.zeros((rows, cols, 3), dtype=img.dtype)
The error you're getting is happening because img[int(i/2)][int(j/2)] is actually three RGB values and zoomed[i][j] can only hold integers. Creating zoomed to have shape (rows, cols, 3) allows zoomed to hold 3 integers at every row, column location.

Different array dimensions causing failure to merge two images into one

When trying to join two images to create one:
img3 = imread('image_home.png')
img4 = imread('image_away.png')
result = np.hstack((img3,img4))
imwrite('Home_vs_Away.png', result)
This error sometimes appears:
all the input array dimensions for the concatenation axis must match exactly, but along dimension 0, the array at index 0 has size 192 and the array at index 1 has size 191
How should I proceed to generate the image when there is this difference in array size when np.hstack does not work?
Note:
I use several images, so not always the largest image is the first and not always the largest is the second, it can be quite random which is the smallest or largest between the two.
You can manually add a row/column with a color of your choice to match the shapes. Or you can simply let cv2.resize handle the resizing for you. In this code I show how to use both methods.
import numpy as np
import cv2
img1 = cv2.imread("image_home.png")
img2 = cv2.imread("image_away.png")
# Method 1 (add a column and a row to the smallest image)
padded_img = np.ones(img1.shape, dtype="uint8")
color = np.array(img2[-1, -1]) # take the border color
padded_img[:-1, :-1, :] = img2
padded_img[-1, :, :] = color
padded_img[:, -1, :] = color
# Method 2 (let OpenCV handle the resizing)
padded_img = cv2.resize(img2, img1.shape[:2][::-1])
result = np.hstack((img1, padded_img))
cv2.imwrite("Home_vs_Away.png", result)

Unique Color Detection and Storing images dynamically

If an image is given , find out the unique colors in that image and write output images corresponding to each unique color.
In that all other pixels which don't have that unique color should me marked white.
for eg , if an image has 3 colors - in the output folder there should be three images where each color is separated. Using Open CV & Python.
I've created the unique color list using my methods. What I want is to give a count of all those unique colors in the sample.png image and give the corresponding images output as per the question.
I believe the code below (with comments) should help you with this!
Feel free to follow up if any of the code is unclear!
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
from copy import deepcopy
# Load image and convert it from BGR (opencv default) to RGB
fpath = "dog.png" # TODO: replace with your path
IMG = cv.cvtColor(cv.imread(fpath), cv.COLOR_BGR2RGB)
# Get dimensions and reshape into (H * W, C) vector - i.e. a long vector, where each element is a tuple corresponding to a color!
H, W, C = IMG.shape
IMG_FLATTENED = np.vstack([IMG[:, w, :] for w in range(W)])
# Get unique colors using np.unique function, and their counts
colors, counts = np.unique(IMG_FLATTENED, axis=0, return_counts = True)
# Jointly loop through colors and counts
for color, count in zip(colors, counts):
print("COLOR: {}, COUNT: {}".format(color, count))
# Create placeholder image and mark all pixels as white
SINGLE_COLOR = (255 * np.ones(IMG.shape)).astype(np.uint8) # Make sure casted to uint8
# Compute binary mask of pixel locations where color is, and set color in new image
color_idx = np.all(IMG[..., :] == color, axis=-1)
SINGLE_COLOR[color_idx, :] = color
# Write file to output with color and counts specified
cv.imwrite("color={}_count={}.png".format(color, count), SINGLE_COLOR)
Ack, he beat me to it. Well, here's what I've got.
Oh no, I don't think the line
blank[img == color] = img[img == color]
behaves how I think it does. I think it just coincidentally works for this case. I'll edit the code with a solution I'm more confident works for all cases.
Original Image
import cv2
import numpy as np
# load image
img = cv2.imread("circles.png");
# get uniques
unique_colors, counts = np.unique(img.reshape(-1, img.shape[-1]), axis=0, return_counts=True);
# split off each color
splits = [];
for a in range(len(unique_colors)):
# get the color
color = unique_colors[a];
blank = np.zeros_like(img);
mask = cv2.inRange(img, color, color); # edited line 1
blank[mask == 255] = img[mask == color]; # edited line 2
# show
cv2.imshow("Blank", blank);
cv2.waitKey(0);
# save each color with its count
file_str = "";
for b in range(3):
file_str += str(color[b]) + "_";
file_str += str(counts[a]) + ".png";
cv2.imwrite(file_str, blank);

Broadcasting in numpy with multiple dimensions

I have an image with a black background that contains different shapes in different colors. I want to generate an image per shape, in which the shape is white and the background is black. I have been able to do this with numpy, but I would like to optimize my code using vectorization. This is what I have so far:
import numpy as np
import cv2
image = cv2.imread('mask.png')
image.shape
# (720, 1280, 3)
# Get all colors that are not black
colors = np.unique(image.reshape(-1,3), axis=0)
colors = np.delete(colors, [0,0,0], axis=0)
colors.shape
# (5, 3)
# Example for one color. I could do a for-loop, but I want to vectorize instead
c = colors[0]
query = (image == c).all(axis=2)
# Make the image all black, except for the pixels that match the shape
image[query] = [255,255,255]
image[np.logical_not(query)] = [0,0,0]
Approach #1
You can save a lot on intermediate array data with extension of unique colors into higher dim and then comparing against original data array and then using the mask directly to get the final output -
# Get unique colors (remove black)
colors = np.unique(image.reshape(-1,3), axis=0)
colors = np.delete(colors, [0,0,0], axis=0)
mask = (colors[:,None,None,:]==image).all(-1)
out = mask[...,None]*np.array([255,255,255])
Approach #2
A better/memory-efficient way to get that mask would be with something like this -
u,ids = np.unique(image.reshape(-1,3), axis=0, return_inverse=1)
m,n = image.shape[:-1]
ids = ids.reshape(m,n)-1
mask = np.zeros((ids.max()+1,m,n),dtype=bool)
mask[ids,np.arange(m)[:,None],np.arange(n)] = ids>=0
and hence, a better way to get the final output, like so -
out = np.zeros(mask.shape + (3,), dtype=np.uint8)
out[mask] = [255,255,255]
and probably a better way to get ids would be with matrix-multiplication. Hence :
u,ids = np.unique(image.reshape(-1,3), axis=0, return_inverse=1)
could be replaced by :
image2D = np.tensordot(image,256**np.arange(3),axes=(-1,-1))
ids = np.unique(image2D,return_inverse=1)[1]
I was able to solve it the following way:
import numpy as np
import cv2
# Read the image
image = cv2.imread('0-mask.png')
# Get unique colors (remove black)
colors = np.unique(image.reshape(-1,3), axis=0)
colors = np.delete(colors, [0,0,0], axis=0)
# Get number of unique colors
instances = colors.shape[0]
# Reshape colors and image for broadcasting
colors = colors.reshape(instances,1,1,3)
image = image[np.newaxis]
# Generate multiple images, one per instance
mask = np.ones((instances, 1, 1, 1))
images = (image * mask)
# Run query with the original image
query = (image == colors).all(axis=3)
# For every image, color the shape white, everything else black
images[query] = [255,255,255]
images[np.logical_not(query)] = [0,0,0]

Creating a chessboard in PIL

Background
I have been trying to create a chessboard in the PIL module and I have got the general pattern for the first two rows, but can't figure out how to apply this to the entire board. As you can see, I have created an image:
from PIL import Image
img = Image.new("RGB", (15,15), "white") # create a new 15x15 image
pixels = img.load() # create the pixel map
My solution for the first two rows
Note - I am still learning Python so this code may seem very inefficient, but feel free to suggest improvements.
The second row:
Code:
black_2 = []
for i in range(img.size[0]):
if i % 2 == 0:
black_2.append(i)
This gives me all the horizontal index positions on where to put a black pixel. Therefore, for the 15x15 board I created, it returns [0, 2, 4, 6, 8, 10, 12, 14]
The first row:
Code:
I then use the second row to work out the horizontal index positions for the first row
black_1 = [i-1 for i in black_2 if i > 0]
if img.size[0] % 2 == 0: # 'that' if statement
black_1.append(img.size[0]-1)
For the 15x15 pixel board I created, it returns [1, 3, 5, 7, 9, 11, 13]. I created that if statement because I realised that the last black pixel was not showing if the board had an even length, and that seemed to fix it.
Changing the pixels to black:
# hardcoded to check patterns
for i in black_1:
pixels[i,0] = (0,0,0)
for k in black_2:
pixels[k,1] = (0,0,0)
img.show()
How can I apply both patterns to the rest of the board, regardless of its size?
I would suspect a for var in range() loop is needed, but I am not sure how it would change depending on if the height(img.size[1]) of the board is odd or even.
Overall pattern so far:
black_1 applies to first row
black_2 applies to second row
A chess board has 64 squares instead of 256. Firstly you need (8,8) and then you can use double for loops to assign the color to all the 8 rows.
General example for any size
from PIL import Image
size = 16
img = Image.new("RGB", (size,size), "white") # create a new 15x15 image
pixels = img.load() # create the pixel map
black_2 = []
for i in range(img.size[0]):
if i % 2 == 0:
black_2.append(i)
black_1 = [i-1 for i in black_2 if i > 0]
if img.size[0] % 2 == 0: # 'that' if statement
black_1.append(img.size[0]-1)
for i in black_1:
for j in range(0, size, 2):
pixels[i,j] = (0,0,0)
for k in black_2:
for l in range(1, size+1, 2):
pixels[k,l] = (0,0,0)
img.show()
This is a simple way. In case it confuses you, one pixel in the image corresponds to a whole square of the chessboard and you can scale it up at the end if you want to.
#!/usr/bin/env python3
from PIL import Image
# Create new black image of entire board
w, h = 12, 6
img = Image.new("RGB", (w,h))
pixels = img.load()
# Make pixels white where (row+col) is odd
for i in range(w):
for j in range(h):
if (i+j)%2:
pixels[i,j] = (255,255,255)
img.save('result.png')
If you want it to be a larger image, just resize at the end. So, say you want each square of the board to be 15px x 15px:
img = img.resize((15*w,15*h), Image.NEAREST)
Solution:
Using numpy modul, its very fast and you have only 11 loops(also not looping through every pixel, but rather through patterns). It doesnt matter how big is the chessboard. Processing should be similary fast
timing: 2400 x 2400 pixel chessboard -> 0.17s
import numpy as np
from PIL import Image
n = 50 # size of one element, row = 8*n, chessboard = 8*n x 8*n
segment_black = np.zeros(shape = [n,n])
segment_white = np.ones(shape = [n,n])*255
chessboard = np.hstack((segment_black,segment_white))
for i in range(4):
chessboard = np.hstack((chessboard,segment_black))
chessboard = np.hstack((chessboard,segment_white))
temp = chessboard
for i in range(7):
chessboard = np.concatenate((np.fliplr(chessboard),temp))
img = Image.fromarray(chessboard.astype(np.uint8))
img.save('chess.jpg')
img.show()
You could use PIL.Image.paste to paste img on itself.
for n in range(15/2):
img.paste(img, (0, n*2))
Using all your existing code, just add this at the bottom.

Categories