Padding an image in Python - python

I am trying to add columns and rows on all sides.
padded_array = np.zeros([img.shape[0] + (size//2) + (size//2), img.shape[1] + (size//2) + (size//2)])
padded_array[size//2 : padded_array.shape[0]-(size//2), size//2 : padded_array.shape[1]-(size//2)] = gray
Here, img is the original image and gray is the gray-scaled image and shape of both of them is same.
Now, I am trying to create a padded_array by adding (size//2) rows on top and below and
(size//2) columns left and right.
size is always odd.
When I try to pad, I don't understand why the gray image is not broadcasted into the padded array.
Instead, what it is doing is broadcasting value 255 on all pixels in that range of gray image and padded rows and columns are left blank.
I am adding the screenshots of both the images, please have a look.
Gray Scale Image is :
Padded Image after broadcasting gray is :

You can divide image width / image height and multiply with a constant.
import matplotlib.pyplot as plt
def pad(image, h=2):
w = (image.shape[0]/image.shape[1]) * h
plt.figure(figsize=(w, h))
plt.imshow(im)
plt.axis('off')
plt.show()
im = plt.imread('blur.png')
pad(im)
Output:

The mistake here was while defining the padded_array I didn't define the data type of array to be int, it was float by default and that was the reason for white image, as soon as I defined the data in padded_array are int, everything turned out fine.

Related

Remove CMYK colors to keep only black from a PNG

I'm trying to remove the colors from a PNG there is a way to do it ? My goal is to import my image in a PDF using Python, I tryed first with an SVG file but impossible to import, nothing appears with no error. So I wanted to try with a PNG but still hard to import.
Now I have an image with these percentage of colors :
And my final result would be this :
I already tried with openCV but no result from it, I'm looking for a solution since few days.
file = "app\\static\\img\\Picto CE_MAROC_H_6mm.png"
src = cv2.imread(file, cv2.IMREAD_UNCHANGED)
src[:,:,2] = np.zeros([src.shape[0], src.shape[1]])
cv2.imwrite(file,src)
Thanks in advance for your help ! :)
What does it mean to only have a K channel?
Most applications use RGB or RGBA, whereas the CMYK color space is typically for printed material. We should translate what does it mean for an image to only use the K channel.
First, let's look the formulas to convert the CMYK colorspace to RGB. We will assume that C, M, K are on a 0-100 integer scale:
R = 255 * (1 - C/100) * (1 - K/100)
G = 255 * (1 - M/100) * (1 - K/100)
B = 255 * (1 - Y/100) * (1 - K/100)
Since we only care for the K channel, we will set C, Y, and M to 0. This simplifies the formulas to:
R = 255 * (1 - K/100)
G = 255 * (1 - K/100)
B = 255 * (1 - K/100)
Notice that R = G = B when only the K channel is set. This produces a gray monochrome throughout the image, effectively making it grayscale. As such, the goal would be to produce a grayscale image given a RGBA image input.
Converting color to grayscale
Converting a color to its grayscale component is simply done by preserving the luminance of the original image in a gray monochrome palette. To do so, a formula must be defined which takes in a RGB input and returns a single value Y, creating a YYY color on the gray monochrome scale. This can simply be done by assigning each color a coefficient to scale how much an effect each has on luminance. Since the human eye is most sensitive to G, R, then B, we would want to assign a high coefficient to G and a low coefficient to B. The most common grayscale calculation used is luma coding for color TV and video systems:
Y = round(0.229 * R + 0.587 * G + 0.114 * B)
Converting an image to only use the K channel in Python
Now knowing the above information, we can convert an image to only use the K channel. For this, we can use imageio which can provide pixel information in RGB format. Since image data is given as an n dimensional array, we can also use numpy to abstract any loops needed to apply a grayscale to every pixel.
I will be using the imageio.v3 module as that is the most recent API as of this post. Loading in the image can be done by calling imageio.v3.imread and passing in the location of the image.
First, we want to get a luminance value for each pixel in the image. This can be done by taking the dot product of the image and the coefficients of the luminance formula. This will produce a 2D array as (height, width, RGB) x (RGB) = (height, width). We also need to round the values and cast each to a unsigned 8-bit integer to get our values into the 0-255 integer color range.
import numpy as np
# For some image `im` loaded by `#imread`
# The coefficients for converting an RGB color to its luminance value
grayscale_coef = [0.299, 0.587, 0.114]
# Create a 2D array where any pixel (height, width) translates to a single luminance value
grayscale = np.dot(im, grayscale_coef)
# Round the each luminance value and convert to a 0-255 range
grayscale = np.round(grayscale).astype(np.uint8)
Saving as CMYK
Now that we have the value to put into the K channel, we need to reconstruct the 3D array setting the CMY channels to 0 and then outputting to an image format that supports CMYK (JPG, TIFF, etc.). For this, we can use pillow.
from PIL import Image
# Create the CMY channels initialized to 0
cmy = np.zeros(grayscale.shape + (3,))
# Stack the CMY and K channels together
# Cast type to unsigned byte to avoid channel turning completely black
cmyk = np.dstack((cmy, grayscale)).astype(np.uint8)
# Read image from CMYK array buffer
result = Image.fromarray(cmyk, mode="CMYK")
# Save image in a supported format
result.save("<filename_here>.jpg")

Offsetting a tiled shape inside the image frame

I have an image that only contains a tiled shape in it with everywhere else black. However, this tiled pattern can be shifted/offset anywhere in the image particularly over the image borders. Knowing that this shape can be fit inside the image after offsetting it and leaving the borders black, how can I calculate how many pixels in x and y coordinates it needs to get offset for that to happen in an optimized way?
Input image
Desired output after offset/shiftimg
My thought was getting connected components in the image, check which labels are on the border, calculate the longest distance between each axis shapes that are on the border and offsetting in the axis' with those values. It can work but I feel like there should be smarter ways.
So here is the details of what I put in my comment for doing that with Python/OpenCV/Numpy. Is this what you want?
Read the input
Convert to gray
Threshold to binary
Count the number of white pixels in each column and store in array
Find the first and last black (zero count) element in the array
Get the center x values
Crop the image into left and right parts at the center x
Stack them together horizontally in the opposite order
Save the result
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('black_white.jpg')
hh, ww = img.shape[:2]
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)[1]
# count number of white pixels in columns as new array
count = np.count_nonzero(thresh, axis=0)
# get first and last x coordinate where black (count==0)
first_black = np.where(count==0)[0][0]
last_black = np.where(count==0)[0][-1]
# compute x center
black_center = (first_black + last_black) // 2
print(black_center)
# crop into two parts
left = img[0:hh, 0:black_center]
right = img[0:hh, black_center:ww]
# combine them horizontally after swapping
result = np.hstack([right, left])
# write result to disk
cv2.imwrite("black_white_rolled.jpg", result)
# display it
cv2.imshow("RESULT", result)
cv2.waitKey(0)

Gradual conversion of image to greyscale with numpy in python

Say I have an image, and I want to have it fade out to greyscale over a distance.
I already know that to entirely convert an image to greyscale with Numpy, I'd do something like
import numpy as np
import cv2
myImage = cv2.imread("myImage.jpg")
grey = np.dot(an_image[...,:3], [0.2989, 0.5870, 0.1140])
This is not what I'm looking for. I already can get that to work.
I have a NxMx3 matrix (where N and M are the dimensions of the image), and this matrix is a dimension with the red transform, green transform, and blue transform.
So, for a given origin and radius of "keep this colored", I have
greyscaleWeights = np.array([0.2989, 0.5870, 0.1140])
# We flip this so we can weight down the transformation
greyscaleWeightOffsets = np.ones(3) - greyscaleWeights
from scipy.spatial.distance import cdist as getDistances
transformWeighter = list()
for rowNumber in np.arange(rowCount, dtype= 'int'):
# Create a row of tuples containing the coordinate we are at in the picture
row = [(x, rowNumber) for x in np.arange(columnCount, dtype= 'int')]
# Transform this into a row of distances from our in-color center
rowDistances = getDistances(row, [self.focusOrigin]).T[0]
# Get the transformation weights: inside of the focus radius we have no transform,
# outside of the pixelDistanceToFullTransform we have a weight of 1, and an even
# gradation in-between
rowWeights = [np.clip((x - self.focusRadius) / pixelDistanceToFullTransform, 0, 1) for x in rowDistances]
transformWeighter.append(rowWeights)
# Convert this into an numpy array
transformWeighter = np.array(transformWeighter)
# Change this 1-D set of weights into 3-D weights (for each color channel)
transformRGB = np.repeat(transformWeighter[:, :, None],3, axis=1).reshape(self.image.shape)
# Change the weight offsets back into greyscale weights
greyscaleTransform = 1 - greyscaleWeightOffsets * transformRGB
greyscaleishImage = self.image * greyscaleTransform
I do get the fade behaviour I was hoping for, but it just fades into the green channel while nuking the red and blue, so far as I can tell.
So, for example:
transforms into
which is the correct transformation behaviour, but fading to green instead of greyscale...
Well, the answer was both easy and hard.
The premise of my question was fundamentally flawed. To quote this answer on answers.opencv.org:
First, you must understand that a MxNx3 in greyscale doesn't exist. I mean, the concept of greyscale is that you have one channel describing the intensity on a gradual scale between black and white. So, it is not clear why would you need a 3 channels greyscale image, but if you do, I suggest that you take the value of each pixel of your 1 channel greyscale image and that you copy it three times, one on each channel of a BGR image. When a BGR image has the same value on each channel, it appears to be grey.
The correct answer then was to change the color space then desaturate the image, so
imageHSV = cv2.cvtColor(self.image, cv2.COLOR_RGB2HSV)
newSaturationChannel = saturationWeighter * imageHSV[:,:,1]
imageHSV[:,:,1] = newSaturationChannel
greyscaleishImage = cv2.cvtColor(imageHSV, cv2.COLOR_HSV2RGB)

How to analyze only a part of an image?

I want to analyse a specific part of an image, as an example I'd like to focus on the bottom right 200x200 section and count all the black pixels, so far I have:
im1 = Image.open(path)
rgb_im1 = im1.convert('RGB')
for pixel in rgb_im1.getdata():
Whilst you could do this with cropping and a pair of for loops, that is really slow and not ideal.
I would suggest you use Numpy as it is very commonly available, very powerful and very fast.
Here's a 400x300 black rectangle with a 1-pixel red border:
#!/usr/bin/env python3
import numpy as np
from PIL import Image
# Open the image and make into Numpy array
im = Image.open('image.png')
ni = np.array(im)
# Declare an ROI - Region of Interest as the bottom-right 200x200 pixels
# This is called "Numpy slicing" and is near-instantaneous https://www.tutorialspoint.com/numpy/numpy_indexing_and_slicing.htm
ROI = ni[-200:,-200:]
# Calculate total area of ROI and subtract non-zero pixels to get number of zero pixels
# Numpy.count_nonzero() is highly optimised and extremely fast
black = 200*200 - np.count_nonzero(ROI)
print(f'Black pixel total: {black}')
Sample Output
Black pixel total: 39601
Yes, you can make it shorter, for example:
h, w = 200,200
im = np.array(Image.open('image.png'))
black = h*w - np.count_nonzero(ni[-h:,-w:])
If you want to debug it, you can take the ROI and make it into a PIL Image which you can then display. So just use this line anywhere after you make the ROI:
# Display image to check
Image.fromarray(ROI).show()
You can try cropping the Image to the specific part that you want:-
img = Image.open(r"Image_location")
x,y = img.size
img = img.crop((x-200, y-200, x, y))
The above code takes an input image, and crops it to its bottom right 200x200 pixels. (make sure the image dimensions are more then 200x200, otherwise an error will occur)
Original Image:-
Image after Cropping:-
You can then use this cropped image, to count the number of black pixels, where it depends on your use case what you consider as a BLACK pixel (a discrete value like (0, 0, 0) or a range/threshold (0-15, 0-15, 0-15)).
P.S.:- The final Image will always have a dimension of 200x200 pixels.
from PIL import Image
img = Image.open("ImageName.jpg")
crop_area = (a,b,c,d)
cropped_img = img.crop(crop_area)

convert image (np.array) to binary image

Thank you for reading my question.
I am new to python and became interested in scipy. I am trying to figure out how I can make the image of the Racoon (in scipy misc) to a binary one (black, white). This is not taught in the scipy-lecture tutorial.
This is so far my code:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from scipy import misc #here is how you get the racoon image
face = misc.face()
image = misc.face(gray=True)
plt.imshow(image, cmap=plt.cm.gray)
print image.shape
def binary_racoon(image, lowerthreshold, upperthreshold):
img = image.copy()
shape = np.shape(img)
for i in range(shape[1]):
for j in range(shape[0]):
if img[i,j] < lowerthreshold and img[i,j] > upperthreshold:
#then assign black to the pixel
else:
#then assign white to the pixel
return img
convertedpicture = binary_racoon(image, 80, 100)
plt.imshow(convertedpicture, cmap=plt.cm.gist_gray)
I have seen other people using OpenCV to make a picture binary, but I am wondering how I can do it in this way by looping over the pixels? I have no idea what value to give to the upper and lower threshold, so I made a guess of 80 and 100. Is there also a way to determine this?
In case anyone else is looking for a quick minimal example to experiment with, here's what I used to binarize an image:
from scipy.misc import imread, imsave
# read in image as 8 bit grayscale
img = imread('cat.jpg', mode='L')
# specify a threshold 0-255
threshold = 150
# make all pixels < threshold black
binarized = 1.0 * (img > threshold)
# save the binarized image
imsave('binarized.jpg', binarized)
Input:
Output:
You're overthinking this:
def to_binary(img, lower, upper):
return (lower < img) & (img < upper)
In numpy, the comparison operators apply over the whole array elementwise. Note that you have to use & instead of and to combine the booleans, since python does not allow numpy to overload and
You don't need to iterate over the x and y positions of the image array. Use the numpy array to check if the array is above of below the threshold of interest. Here is some code that produces a boolean (true/false) array as the black and white image.
# use 4 different thresholds
thresholds = [50,100,150,200]
# create a 2x2 image array
fig, ax_arr = plt.subplots(2,2)
# iterate over the thresholds and image axes
for ax, th in zip(ax_arr.ravel(), thresholds):
# bw is the black and white array with the same size and shape
# as the original array. the color map will interpret the 0.0 to 1.0
# float array as being either black or white.
bw = 1.0*(image > th)
ax.imshow(bw, cmap=plt.cm.gray)
ax.axis('off')
# remove some of the extra white space
fig.tight_layout(h_pad=-1.5, w_pad=-6.5)

Categories