convert image (np.array) to binary image - python

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)

Related

Set the values below a certain threshold of a CV2 Colormap to transparent

I'm currently trying to apply an activation heatmap to a photo.
Currently, I have the original photo, as well as a mask of probabilities. I multiply the probabilities by 255 and then round down to the nearest integer. I'm then using cv2.applyColorMap with COLORMAP.JET to apply the colormap to the image with an opacity of 25%.
img_cv2 = cv2.cvtColor(np_img, cv2.COLOR_RGB2BGR)
heatmapshow = np.uint8(np.floor(mask * 255))
colormap = cv2.COLORMAP_JET
heatmapshow = cv2.applyColorMap(np.uint8(heatmapshow - 255), colormap)
heatmap_opacity = 0.25
image_opacity = 1.0 - heatmap_opacity
heatmap_arr = cv2.addWeighted(heatmapshow, heatmap_opacity, img_cv2, image_opacity, 0)
This current code successfully produces a heatmap. However, I'd like to be able to make two changes.
Keep the opacity at 25% For all values above a certain threshold (Likely > 0, but I'd prefer more flexibility), but then when the mask is below that threshold, reduce the opacity to 0% for those cells. In other words, if there is very little activation, I want to preserve the color of the original image.
If possible I'd also like to be able to specify a custom colormap, since the native ones are pretty limited, though I might be able to get away without this if I can do the custom opacity thing.
I read on Stackoverflow that you can possibly trick cv2 into not overlaying any color with NaN values, but also read that only works for floats and not ints, which complicates things since I'm using int8. I'm also concerned that this functionality could change in the future as I don't believe this is intentional design purposefully built into cv2.
Does anyone have a good way of accomplishing these goals? Thanks!
With regard to your second question:
Here is how to create a simple custom two color gradient color map in Python/OpenCV.
Input:
import cv2
import numpy as np
# load image as grayscale
img = cv2.imread('lena_gray.png', cv2.IMREAD_GRAYSCALE)
# convert to 3 equal channels
img = cv2.merge((img, img, img))
# create 1 pixel red image
red = np.full((1, 1, 3), (0,0,255), np.uint8)
# create 1 pixel blue image
blue = np.full((1, 1, 3), (255,0,0), np.uint8)
# append the two images
lut = np.concatenate((red, blue), axis=0)
# resize lut to 256 values
lut = cv2.resize(lut, (1,256), interpolation=cv2.INTER_LINEAR)
# apply lut
result = cv2.LUT(img, lut)
# save result
cv2.imwrite('lena_red_blue_lut_mapped.png', result)
# display result
cv2.imshow('RESULT', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result of colormap applied to image:
With regard to your first question:
You are blending the heat map image with the original image using a constant "opacity" value. You can replace the single opacity value with an image. You just have to do the addWeighted manually as heatmap * opacity_img + original * (1-opacity_img) where your opacity image is float in the range 0 to 1. Then clip and convert back to uint8. If your opacity image is binary, then you can use cv2.bitWiseAnd() in place of multiply.

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 pixelate a square image to 256 big pixels with python?

I need to find an way to reduce a square image to 256 big pixels with python, preferably with the matplotlib and pillow libraries.
Got any ideas ?
Nine months pass and I can now cobble together some Python - per your original request on how to pixellate an image with Python and PIL/Pillow.
#!/usr/local/bin/python3
from PIL import Image
# Open image
img = Image.open("artistic-swirl.jpg")
# Resize smoothly down to 16x16 pixels
imgSmall = img.resize((16,16), resample=Image.Resampling.BILINEAR)
# Scale back up using NEAREST to original size
result = imgSmall.resize(img.size, Image.Resampling.NEAREST)
# Save
result.save('result.png')
Original Image
Result
If you take it down to 32x32 pixels (instead of 16x16) and then resize back up, you get:
Another option is to use PyPXL
Python script to pixelate images and video using K-Means clustering in the Lab colorspace. Video pixelating support multi-processing to achieve better performance.
Using the Paddington image as a source you can run:
python pypxl_image.py -s 16 16 paddington.png paddington_pixelated.png
Which gives this result
Of course if you wanted it to have 256 x 256 pixels rather than just 256 big pixels you could run
python pypxl_image.py -s 256 256 paddington.png paddington_pixelated.png
Which gives this result
Both results have a more retro 8-bit look to them compared to the other solutions, which might suit your needs.
Sorry, I can't give you a Python solution, but I can show you the technique and the result, just using ImageMagick at the command-line:
Starting with this:
First, resize the image down to 16x16 pixels using normal cubic, or bilinear interpolation, then scale the image back up to its original size using "nearest neighbour" interpolation:
magick artistic-swirl.jpg -resize 16x16 -scale 500x500 result.png
Keywords:
Pixelate, pixellate, pixelated, pixellated, ImageMagick, command-line, commandline, image, image processing, nearest neighbour interpolation.
Here is my solution using sliding window
import numpy as np
import matplotlib.pyplot as plt
def pixelate_rgb(img, window):
n, m, _ = img.shape
n, m = n - n % window, m - m % window
img1 = np.zeros((n, m, 3))
for x in range(0, n, window):
for y in range(0, m, window):
img1[x:x+window,y:y+window] = img[x:x+window,y:y+window].mean(axis=(0,1))
return img1
img = plt.imread('test.png')
fig, ax = plt.subplots(1, 4, figsize=(20,10))
ax[0].imshow(pixelate_rgb(img, 5))
ax[1].imshow(pixelate_rgb(img, 10))
ax[2].imshow(pixelate_rgb(img, 20))
ax[3].imshow(pixelate_rgb(img, 30))
# remove frames
[a.set_axis_off() for a in ax.flatten()]
plt.subplots_adjust(wspace=0.03, hspace=0)
The main idea is to slide a window of a certain size through the image and calculate the mean color for that area. Then replace the original pixels in that area with this color.
Another idea is to process grayscale images. Here we calculate the mean color of a grayscale image for a region, but now we replace the original pixels with white or black color depending on whether the mean exceeds a threshold:
def pixelate_bin(img, window, threshold):
n, m = img.shape
n, m = n - n % window, m - m % window
img1 = np.zeros((n,m))
for x in range(0, n, window):
for y in range(0, m, window):
if img[x:x+window,y:y+window].mean() > threshold:
img1[x:x+window,y:y+window] = 1
return img1
# convert image to grayscale
img = np.dot(plt.imread('test.png'), [0.299 , 0.587, 0.114])
fig, ax = plt.subplots(1, 3, figsize=(15,10))
plt.tight_layout()
ax[0].imshow(pixelate_bin(img, 5, .2), cmap='gray')
ax[1].imshow(pixelate_bin(img, 5, .3), cmap='gray')
ax[2].imshow(pixelate_bin(img, 5, .45), cmap='gray')
# remove frames
[a.set_axis_off() for a in ax.flatten()]
plt.subplots_adjust(wspace=0.03, hspace=0)
Live demo
Keep in mind: png has values between 0 and 1, whereas jpg between 0 and 255

Converting 2D Numpy array of grayscale values to a PIL image

Say I have a 2D Numpy array of values on the range 0 to 1, which represents a grayscale image. How do I then convert this into a PIL Image object? All attempts so far have yielded extremely strange scattered pixels or black images.
for x in range(image.shape[0]):
for y in range(image.shape[1]):
image[y][x] = numpy.uint8(255 * (image[x][y] - min) / (max - min))
#Create a PIL image.
img = Image.fromarray(image, 'L')
In the code above, the numpy array image is normalized by (image[x][y] - min) / (max - min) so every value is on the range 0 to 1. Then it is multiplied by 255 and cast to an 8 bit integer. This should, in theory, process through Image.fromarray with mode L into a grayscale image - but the result is a set of scattered white pixels.
I think the answer is wrong. The Image.fromarray( ____ , 'L') function seems to only work properly with an array of integers between 0 and 255. I use the np.uint8 function for this.
You can see this demonstrated if you try to make a gradient.
import numpy as np
from PIL import Image
# gradient between 0 and 1 for 256*256
array = np.linspace(0,1,256*256)
# reshape to 2d
mat = np.reshape(array,(256,256))
# Creates PIL image
img = Image.fromarray(np.uint8(mat * 255) , 'L')
img.show()
Makes a clean gradient
vs
import numpy as np
from PIL import Image
# gradient between 0 and 1 for 256*256
array = np.linspace(0,1,256*256)
# reshape to 2d
mat = np.reshape(array,(256,256))
# Creates PIL image
img = Image.fromarray( mat , 'L')
img.show()
Has the same kind of artifacting.
If I understood you question, you want to get a grayscale image using PIL.
If this is the case, you do not need to multiply each pixels by 255.
The following worked for me
import numpy as np
from PIL import Image
# Creates a random image 100*100 pixels
mat = np.random.random((100,100))
# Creates PIL image
img = Image.fromarray(mat, 'L')
img.show()
im = Image.fromarray(np.uint8(mat), 'L')
or
im = Image.fromarray(np.uint8(mat))
Apparently it accepts type np.uint8(insert array here), also may be able to remove 'L' for conciseness.

count number of black pixels in an image in Python with OpenCV

I have the following test code in Python to read, threshold and display an image:
import cv2
import numpy as np
from matplotlib import pyplot as plt
# read image
img = cv2.imread('slice-309.png',0)
ret,thresh = cv2.threshold(img,0,230, cv2.THRESH_BINARY)
height, width = img.shape
print "height and width : ",height, width
size = img.size
print "size of the image in number of pixels", size
# plot the binary image
imgplot = plt.imshow(img, 'gray')
plt.show()
I would like to count the number of pixels within the image with a certain label, for instance black.
How can I do that ? I looked at tutorials of OpenCV but did not find any help :-(
Thanks!
For black images you get the total number of pixels (rows*cols) and then subtract it from the result you get from cv2.countNonZero(mat).
For other values, you can create a mask using cv2.inRange() to return a binary mask showing all the locations of the color/label/value you want and then use cv2.countNonZero to count how many of them there are.
UPDATE (Per Miki's comment):
When trying to find the count of elements with a particular value, Python allows you to skip the cv2.inRange() call and just do:
cv2.countNonZero(img == scalar_value)
import cv2
image = cv2.imread("pathtoimg", 0)
count = cv2.countNonZero(image)
print(count)

Categories