0

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:

enter image description here

Sample output:
enter image description here

Rotem
  • 30,366
  • 4
  • 32
  • 65
Nanda
  • 361
  • 1
  • 5
  • 14
  • 1
    You've posted 15 lines of code, without a comment, without `import` statements and without any images. Please take the [tour] and read about [mcve] then come back and [edit] your question if you'd like some help. Thank you. – Mark Setchell Dec 05 '22 at 21:11
  • My apologies. I have edited the code and supplied a sample image for reproduction. – Nanda Dec 05 '22 at 21:26

1 Answers1

2

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:
enter image description here

Rotem
  • 30,366
  • 4
  • 32
  • 65