I'm trying to blurr an image by mapping each pixel to the average of the N pixels to the right of it (in the same row). My iterative solution produces good output, but my linear-algebra solution is producing bad output.
From testing, I believe my kernel-matrix is correct; and, I know the last N rows don't get blurred, but that's fine for now. I'd appreciate any hints or solutions.
iterative-solution output (good), linear-algebra output (bad)
original image; and here is the failing linear-algebra code:
def blur(orig_img):
# turn image-mat into a vector
flattened_img = orig_img.flatten()
L = flattened_img.shape[0]
N = 3
# kernel
kernel = np.zeros((L, L))
for r, row in enumerate(kernel[0:-N]):
row[r:r+N] = [round(1/N, 3)]*N
print(kernel)
# blurr the img
print('starting blurring')
blurred_img = np.matmul(kernel, flattened_img)
blurred_img = blurred_img.reshape(orig_img.shape)
return blurred_img
The equation I'm modelling is this:
One option might be to just use a kernel and a convolution?
For example if we load a gray scale image like so:
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from scipy import ndimage
# load a hackinsh grayscale image
image = np.asarray(Image.open('cup.jpg')).mean(axis=2)
plt.imshow(image)
plt.title('Gray scale image')
plt.show()
Now one can use a kernel and convolution. For example to create a filter that filters just one rows and compute the value of the center pixel as the difference between the pixels to the right and left one can do the following:
# Create a kernel that takes the difference between neighbors horizontal pixes
k = np.array([[-1,0,1]])
plt.subplot(121)
plt.title('Kernel')
plt.imshow(k)
plt.subplot(122)
plt.title('Output')
plt.imshow(ndimage.convolve(image, k, mode='constant', cval=0.0))
plt.show()
Therefore, one can blurr an image by mapping each pixel to the average of the N pixels to the right of it by creating the appropiate kernel.
# Create a kernel that takes the average of N pixels to the right
n=10
k = np.zeros(n*2);k[n:]=1/n
k = k[np.newaxis,...]
plt.subplot(121)
plt.title('Kernel')
plt.imshow(k)
plt.subplot(122)
plt.title('Output')
plt.imshow(ndimage.convolve(image, k, mode='constant', cval=0.0))
plt.show()
The issue was incorrect usage of cv2.imshow() in displaying the output image. It expects floating-point pixel values to be in [0, 1]; which, is done in the below code (near bottom):
def blur(orig_img):
flattened_img = orig_img.flatten()
L = flattened_img.shape[0]
N = int(round(0.1 * orig_img.shape[0], 0))
# mask (A)
mask = np.zeros((L, L))
for r, row in enumerate(mask[0:-N]):
row[r:r+N] = [round(1/N, 2)]*N
# blurred img = A * flattened_img
print('starting blurring')
blurred_img = np.matmul(mask, flattened_img)
blurred_img = blurred_img.reshape(orig_img.shape)
cv2.imwrite('blurred_img.png', blurred_img)
# normalize img to [0,1]
blurred_img = (
blurred_img - blurred_img.min()) / (blurred_img.max()-blurred_img.min())
return blurred_img
Ammended output
Thank you to #CrisLuengo for identifying the issue.
Related
I'm facing an issue, and would like some inputs from the community on how to improve the disparity map. I'm following this tutorial for calculating the disparity map between 2 images. The code I have is as follows:
import cv2
import numpy as np
import sys
from matplotlib import pyplot as plt
num_disparities = 64 # number of disparities to check
block = 9 # block size to match
def preprocess_frame(path):
image = cv2.imread(path, 0)
image = cv2.equalizeHist(image)
image = cv2.GaussianBlur(image, (5, 5), 0)
return image
def calculate_disparity_matrix(args):
left_image = preprocess_frame(args[1])
right_image = preprocess_frame(args[2])
rows, cols = left_image.shape
kernel = np.ones([block, block]) / block
disparity_maps = np.zeros(
[left_image.shape[0], left_image.shape[1], num_disparities])
for d in range(0, num_disparities):
# shift image
translation_matrix = np.float32([[1, 0, d], [0, 1, 0]])
shifted_image = cv2.warpAffine(
right_image, translation_matrix,
(right_image.shape[1], right_image.shape[0]))
# calculate squared differences
SAD = abs(np.float32(left_image) - np.float32(shifted_image))
# convolve with kernel and find SAD at each point
filtered_image = cv2.filter2D(SAD, -1, kernel)
disparity_maps[:, :, d] = filtered_image
disparity = np.argmin(disparity_maps, axis=2)
disparity = np.uint8(disparity * 255 / num_disparities)
disparity = cv2.equalizeHist(disparity)
plt.imshow(disparity, cmap='gray', vmin=0, vmax=255)
plt.show()
def calculate_disparity_inbuilt(args):
left_image = preprocess_frame(args[1])
right_image = preprocess_frame(args[2])
rows, cols = left_image.shape
stereo = cv2.StereoBM_create(numDisparities=num_disparities,
blockSize=block)
disparity = stereo.compute(left_image, right_image)
plt.imshow(disparity, cmap='gray', vmin=0, vmax=255)
plt.show()
The problem is that the output that I get from the inbuilt function in OpenCV is hardly similar to the one I've implemented. I was expecting at least a slight similarity between the 2. Is this expected? or am I doing something wrong here?
Implemented Algorithm
OpenCV Algorithm
I tried to implement the Sobel_X filter in scipy with convolve2d function.
I compared with the results from this function:
from scipy.signal import convolve2d
from scipy import misc
from skimage.exposure import rescale_intensity
import cv2
import numpy as np
#https://www.pyimagesearch.com/2016/07/25/convolutions-with-opencv-and-python/
def convolve(image, kernel):
# grab the spatial dimensions of the image, along with
# the spatial dimensions of the kernel
(iH, iW) = image.shape[:2]
(kH, kW) = kernel.shape[:2]
# print("Kh,Kw", kernel.shape[:2])
# allocate memory for the output image, taking care to
# "pad" the borders of the input image so the spatial
# size (i.e., width and height) are not reduced
pad = (kW - 1) // 2
# print("pad", pad)
image = cv2.copyMakeBorder(image, pad, pad, pad, pad,
cv2.BORDER_REPLICATE)
# self.imshow(image, "padded image")
output = np.zeros((iH, iW), dtype="float32")
# loop over the input image, "sliding" the kernel across
# each (x, y)-coordinate from left-to-right and top to
# bottom
for y in np.arange(pad, iH + pad):
for x in np.arange(pad, iW + pad):
# extract the ROI of the image by extracting the
# *center* region of the current (x, y)-coordinates
# dimensions
roi = image[y - pad:y + pad + 1, x - pad:x + pad + 1]
# perform the actual convolution by taking the
# element-wise multiplicate between the ROI and
# the kernel, then summing the matrix
k = (roi * kernel).sum()
# store the convolved value in the output (x,y)-
# coordinate of the output image
output[y - pad, x - pad] = k
# self.imshow(output, "padded image")
# rescale the output image to be in the range [0, 255]
output = rescale_intensity(output, in_range=(0, 255))
output = (output * 255).astype("uint8")
# return the output image
return output
Here are the Sobel_X Kernel and code to compare.
sobelX = np.array((
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]), dtype="int")]
testim=misc.face(gray=True)
convolved_func=convolve(testim, sobelX)
convolved_np=convolve2d(testim, sobelX, boundary='symm', mode='same')
cv2.imshow("Face", np.hstack((convolved_func,np.array(convolved_np, dtype="uint8"))))
cv2.waitKey(0)
cv2.destroyAllWindows()
As you can see here the results are entirely different
I can't get how to implement these filters to get the same results.
Should I somehow change the filter function or maybe there some special things in numpy to implement it, wright?
I tried to make the function for scipy as in this and that examples, but the results the same or worth (I've got black image).
You will get results slightly different.
Do thresholding to remove all numbers which are less than 0.
convolved_np[convolved_np<0]=0
That will give you something similar, still not the same. Some artifacts appeared.
I think these functions differ, that's why I have got a bit different results. Maybe there are some mistakes, so if you can add some to this answer, I will appreciate it.
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
I'm trying to find the 3 dominant colours of an several images using K-means clustering. The problem I'm facing is that K-means also clusters the background of the image. I am using Python 2.7 and OpenCV 3
All images have the same grey background of the following RGB colour: 150,150,150. To avoid that K-means also clusters the background color, I created a masked array which masks all '150' pixel values from the original image array, theoretically leaving only the non-background pixels in the array for K-Means to work with. However, when I run my script, it still returns the grey as one of the dominant colours.
My question: is a masked array the way to go (and did I do something wrong) or are there better alternatives to somehow exclude pixels from K-means clustering?
Please find my code below:
from sklearn.cluster import KMeans
from sklearn import metrics
import cv2
import numpy as np
def centroid_histogram(clt):
numLabels = np.arange(0, len(np.unique(clt.labels_)) + 1)
(hist, _) = np.histogram(clt.labels_, bins=numLabels)
hist = hist.astype("float")
hist /= hist.sum()
return hist
image = cv2.imread("test1.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
h, w, _ = image.shape
w_new = int(100 * w / max(w, h))
h_new = int(100 * h / max(w, h))
image = cv2.resize(image, (w_new, h_new))
image_array = image.reshape((image.shape[0] * image.shape[1], 3))
image_array = np.ma.masked_values(image_array,150)
clt = KMeans(n_clusters=3)
clt.fit(image_array)
hist = centroid_histogram(clt)
zipped = zip(hist, clt.cluster_centers_)
zipped.sort(reverse=True, key=lambda x: x[0])
hist, clt.cluster_centers = zip(*zipped)
print(clt.cluster_centers_)
If you want to extract the values of pixels other than your background, you can use numpy indexation :
img2=image_array[image_array!=[150,150,150]]
img2=img2.reshape((len(img2)/3,3))
This will yield the list of pixels which are not [150,150,150].
However, it does not preserve the structure of the image, just gives you the list of pixels values. I can't really remember, but maybe for K-means you need to give the whole image, i.e. you also need to feed it the position of the pixels ? But in that case, no masking will ever help because masking is just replacing values of certain pixels by another, not getting rid of pixels all together.
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)