Numpy array of a png file is rotated in matplotlib.pyplot - python

I'm trying to scatter all white pixels of this gradient image in matplotlib.pyplot:
import numpy as np
from PIL import Image
import cv2
from matplotlib import pyplot
img = Image.open(
"/root/.../aec.png").convert("L")
img = np.array(img)
kernel = np.ones((2, 2), np.uint8)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
ox, oy = np.where(gradient == 255)
pyplot.plot(ox, oy, ".k")
pyplot.show()
The original picture (...) has a resolution of 2123x1269 and looks like this:
My graph in pyplot shows my gradient picture 270° rotated clockwise and I don't understand why.
I tried pyplot.plot(oy, ox, ".k"), then it's flipped to the x-axis compared to the original image. Rotating the original image with gradient = cv2.rotate(gradient, cv2.cv2.ROTATE_90_CLOCKWISE) gives me coordinates different from the pixel coordinates of my orginal image. (The x-y-pixel-coordinates have to be the ones of the gradient image.) Also the resolution of 2123x1269 should remain and the program should run as fast as possible.
How can I display the pixel coordinates of the gradient image in matplotlib.pyplot correctly?

That is because origin in opencv is at the top-left. Try reverting y axis on pyplot and exchange x and y.
EDIT: Just use plt.imshow(), it is the right function to display image data.

For anyone who ever encounters this problem, this is my final code:
import numpy as np
from PIL import Image
import cv2
from matplotlib import pyplot
img = Image.open(
"/root/.../aec.png").convert("L")
img = np.array(img)
kernel = np.ones((2, 2), np.uint8)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
ox, oy = np.where(gradient == 255)
pyplot.plot(oy, ox, ".k")
pyplot.imshow(gradient)
pyplot.show()
Zoomed in version of the plot: correct plot, until now it servers my purpose.
Thanks #frab

Related

Python: Image preprocessing - Thresholding and binarizing low contrast images for Blob Detection

I am having difficulty thresholding and binarizing low contrast grayscale images that contain white blobs on a black background. Ultimately, I want to count and measure the area of all white blobs in the image. However, Otsu's Thresholding method does not seem to be a good fit because my graylevel histogram lacks two clear peaks. Are there alternate thresholding methods that might be better suited to this type of image?
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import skimage
import skimage.filters
from skimage.io import imread, imshow
from skimage.color import rgb2gray, rgb2hsv
from skimage.measure import label, regionprops, regionprops_table
from skimage.filters import threshold_otsu
from scipy.ndimage import median_filter
from matplotlib.patches import Rectangle
from tqdm import tqdm
Here is my code:
pic = imread('image.jpg')
imshow(pic)
raw grayscale image:
# blur the image to de-noise
blurred_image = skimage.filters.gaussian(pic, sigma=1.0)
# show the histogram of the blurred image
histogram, bin_edges = np.histogram(blurred_image, bins=256, range=(0.0, 1.0))
fig, ax = plt.subplots()
plt.plot(bin_edges[0:-1], histogram)
plt.title("Graylevel histogram")
plt.xlabel("gray value")
plt.ylabel("pixel count")
plt.xlim(0, 1.0)
plt.show()
graylevel histogram:
# perform automatic thresholding
t = skimage.filters.threshold_otsu(blurred_image)
print("Found automatic threshold t = {}.".format(t))
Found automatic threshold t = 0.035040431336474526.
# create a binary mask with the threshold found by Otsu's method
binary_mask = blurred_image > t
fig, ax = plt.subplots()
plt.imshow(binary_mask, cmap="gray")
plt.show()
binary mask:
The white halo in the center of the image is quite problematic. Is there a way to de-noise, threshold, and binarize such that I can isolate the white blobs in the image?
Given your image, the blobs are the outliers.
From the given grayscale image gray, here are some measures:
np.max(gray)
89
np.mean(gray)
7.49876
np.median(gray)
6.0
Although the mean and median are in the range of 6 to 8, the maximum value is at 89, meaning there are some bright pixels in there. To get these outliers, I placed the threshold at mean + (x * standard_deviation) The value x is a value of your choice given the outliers present. For this image, I chose x = 3
Code using OpenCV:
# read image in color BGR
img =cv2.imread('Pattern.jpg')
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# binarize using threshold
thresh = cv2.threshold(gray, int(np.mean(gray) + (np.std(gray) * 3)), 255, cv2.THRESH_BINARY)
The threshold value: thresh[0] -> 31.0
Pixels above this value are white:
cv2.imshow('output', thresh[1])
You can try changing the x value for a better output or run a median filter on the above result

OpenCV: Fit ellipse with most points on contour (instead of least squares)

I have a binarized image, which I've already used open/close morphology operations on (this is as clean as I can get it, trust me on this) that looks like so:
As you can see, there is an obvious ellipse with some distortion on the top. NOTE: I do not have prior info as to the size of the circle, and this has to run very quickly (HoughCircles is too slow, I've found). I'm trying to figure out how to fit an ellipse to it, such that it maximizes the number of points on the fitted ellipse that correspond to edges on the shape. That is, I want a result like this:
However, I can't seem to find a way in OpenCV to do this. Using the common tools of fitEllipse (blue line) and minAreaRect (green line), I get these results:
Which obviously do not represent the actual ellipse I'm trying to detect. Any thoughts as to how I could accomplish this? Happy to see examples in Python or C++.
Given the shown example image, I was very skeptical of the following statement:
which I've already used open/close morphology operations on (this is as clean as I can get it, trust me on this)
And, after reading your comment,
For precision, I need it to be fit within about 2 pixels accuracy
I was pretty sure, there might be good approximation using morphological operations.
Please have a look at the following code:
import cv2
# Load image (as BGR for later drawing the circle)
image = cv2.imread('images/hvFJF.jpg', cv2.IMREAD_COLOR)
# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Get rid of possible JPG artifacts (when do people learn to use PNG?...)
_, gray = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)
# Downsize image (by factor 4) to speed up morphological operations
gray = cv2.resize(gray, dsize=(0, 0), fx=0.25, fy=0.25)
# Morphological Closing: Get rid of the hole
gray = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))
# Morphological opening: Get rid of the stuff at the top of the circle
gray = cv2.morphologyEx(gray, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (121, 121)))
# Resize image to original size
gray = cv2.resize(gray, dsize=(image.shape[1], image.shape[0]))
# Find contours (only most external)
cnts, _ = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Draw found contour(s) in input image
image = cv2.drawContours(image, cnts, -1, (0, 0, 255), 2)
cv2.imwrite('images/intermediate.png', gray)
cv2.imwrite('images/result.png', image)
The intermediate image looks like this:
And, the final result looks like this:
Since your image is quite large, I think, no harm is done by downsizing it. The following morphological operations are (heavily) sped up, which might be of interest for your setting.
According to your statement:
NOTE: I do not have prior info as to the size of the circle[...]
You can mostly find an appropriate approximation for the above kernel sizes from your inputs. Since there is only one example image given, we can't know the variability on that issue.
Hope that helps!
Hough-Circle is perfect for this. If you know the diameter you can get a better solution. If you only know a range this might fits best:
EDIT: The reason this works better than the fitted ellipse is: If you are looking for a circle you should use a circle as model. The wiki article explains this beautiful idea.
By the way, you could have done this with opening and closing as well. (Given you now exactly how big your circle is)
import skimage
import matplotlib.pyplot as plt
import numpy as np
from skimage import data, color
from skimage.feature import canny
from skimage.draw import circle_perimeter
from skimage.util import img_as_ubyte
from skimage.transform import hough_circle, hough_circle_peaks
image = skimage.io.imread("hvFJF.jpg")
# Load picture and detect edges
edges = canny(image, sigma=3, low_threshold=10, high_threshold=50)
# Detect two radii
hough_radii = np.arange(250, 300, 10)
hough_res = hough_circle(edges, hough_radii)
# Select the most prominent 5 circles
accums, cx, cy, radii = hough_circle_peaks(hough_res, hough_radii,
total_num_peaks=3)
# Draw them
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(10, 4))
image = color.gray2rgb(image)
for center_y, center_x, radius in zip(cy, cx, radii):
circy, circx = circle_perimeter(center_y, center_x, radius)
image[circy, circx] = (220, 20, 20)
ax.imshow(image, cmap=plt.cm.gray)
plt.show()

How to blur an ellipse with ImageDraw

I implemented an algorithm that detects faces and I want to blur faces. I'm using PIL for blurring it.
image = Image.open(path_img)
draw = ImageDraw.Draw(image)
draw.ellipse((top, left, bottom, right), fill = 'white', outline ='white')
I got this with my code
Face to blur
I would like to use :
blurred_image = cropped_image.filter(ImageFilter.GaussianBlur(radius=10 ))
But I can't use it because I'm using an ImageDraw and it works only with Image class. How can I blur with an ellipse (circular) the face?
Thank you
blur the ellipse is the best way
With Pillow, the best way to do this is to use the blurred ellipse as a blending mask for composite.
from PIL import Image, ImageDraw, ImageFilter
def make_ellipse_mask(size, x0, y0, x1, y1, blur_radius):
img = Image.new("L", size, color=0)
draw = ImageDraw.Draw(img)
draw.ellipse((x0, y0, x1, y1), fill=255)
return img.filter(ImageFilter.GaussianBlur(radius=blur_radius))
kitten_image = Image.open("kitten.jpg")
overlay_image = Image.new("RGB", kitten_image.size, color="orange") # This could be a bitmap fill too, but let's just make it orange
mask_image = make_ellipse_mask(kitten_image.size, 150, 70, 350, 250, 5)
masked_image = Image.composite(overlay_image, kitten_image, mask_image)
masked_image.show()
Given this adorable kitten as input, the output is
EDIT: Inspired by Mark Setchell's answer, simply changing the overlay_image line to
overlay_image = kitten_image.filter(ImageFilter.GaussianBlur(radius=15))
gives us this blur variant (with smooth edges for the blur :) )
Not sure if you want to composite something over the image to conceal the contents, or blur it. This is more blurry :-)
Starting with Paddington:
You can go to "Stealth Mode" like this:
#!/usr/bin/env python3
from PIL import Image, ImageDraw, ImageFilter
import numpy as np
# Open image
im = Image.open('paddington.png')
# Make a mask the same size as the image filled with black
mask = Image.new('RGB',im.size)
# Draw a filled white circle onto the black mask
draw = ImageDraw.Draw(mask)
draw.ellipse([90,40,300,250],fill=(255,255,255))
# Blur the entire image
blurred = im.filter(ImageFilter.GaussianBlur(radius=15))
# Select either the original or the blurred image at each pixel, depending on the mask
res = np.where(np.array(mask)>0,np.array(blurred),np.array(im))
# Convert back to PIL Image and save
Image.fromarray(res).save('result.png')
Or, as suggested by #AKX, you can remove the Numpy dependency and make the code a bit smaller too yet still get same result:
#!/usr/bin/env python3
from PIL import Image, ImageDraw, ImageFilter
import numpy as np
# Open image
im = Image.open('paddington.png')
# Make a mask the same size as the image filled with black
mask = Image.new('L',im.size)
# Draw a filled white circle onto the black mask
draw = ImageDraw.Draw(mask)
draw.ellipse([90,40,300,250],fill=255)
# Blur the entire image
blurred = im.filter(ImageFilter.GaussianBlur(radius=15))
# Composite blurred image over sharp one within mask
res = Image.composite(blurred, im, mask)
# Save
res.save('result.png')

How to get the coordinates of the rotated bounding box

With python OpenCV I rotate an image that already has bounding boxes. I do this using the getRotationMatrix2D function. How can I use this to calculate the new coordinates of the bounding box?
Here is my sourcecode:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
def rotateImage(image, angle, bboxes):
center=tuple(np.array(image.shape[0:2])/2)
rot_mat = cv2.getRotationMatrix2D(center,angle,1.0)
return cv2.warpAffine(image, rot_mat, image.shape[0:2],flags=cv2.INTER_LINEAR)
img = cv2.imread('input.jpg')
img = img[:,:,::-1]
img_new = rotateImage(img, 5.0, [(0,0,50,50)])
plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(img_new),plt.title('Output')
plt.show()

Can't display a normal image in matplotlib, it keeps displaying with the jet colormap

I have code that displays the MISER regions of an image:
import numpy as np
import cv2
import sys
import matplotlib.pyplot as plt
imp1 = sys.argv[1]
img1 = cv2.imread(imp1)
mser = cv2.MSER()
gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
vis = img1.copy()
regions = mser.detect(gray, None)
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions]
cv2.polylines(vis, hulls, 1, (0, 255, 0))
def plot_stuff(img1, img1_name, img2, img2_name):
fig,axes = plt.subplots(1,2,figsize=(15,6))
axes[0].imshow(img1, cmap='Greys_r')
axes[0].set_title(img1_name)
axes[1].imshow(img2)
axes[1].set_title(img2_name)
fig.suptitle("All Images")
plt.show()
plot_stuff(img1, 'Original', vis, 'MISER Regions')
And it works fine, except it's blue:
And that's where I got stuck. Because no matter what I do, I can't get it to show the Image as gray, and the MISER lines as green. It keeps returning jet:
Even when I show just the image, it still returns jet. Why isn't there an RGB colormap? Better yet, why does there have to be a colormap at all, why can't it just show the normal image?
You data is stored as 64 bit numpy arrays, from the docs,
For RGB and RGBA images, matplotlib supports float32 and uint8 data types
You either need it in this format or need to specify a colormap. It seems the other issue is that 'cv2.polylines' returns an images, which means you cannot set the colours of the lines and the background separately. A solution to this is to use a blank transparent image of the same size to draw the mser (MISER!?) curves and then plot them both on the same axis,
import numpy as np
import cv2
import sys
import matplotlib.pyplot as plt
imp1 = sys.argv[1]
img1 = cv2.imread(imp1)
mser = cv2.MSER()
gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
vis = np.zeros([img1.shape[0],img1.shape[1],4])
regions = mser.detect(gray, None)
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions]
cv2.polylines(vis, hulls, 1, (0, 255, 0))
vis = np.array(vis, dtype=np.uint8)
#Copy green channel data to (alpha) transparent channel
vis[:,:,3] = vis[:,:,1]
def plot_stuff(img1, img1_name, img2, img2_name):
fig,axes = plt.subplots(1,2,figsize=(15,6))
print(img1.shape)
axes[0].imshow(np.sum(img1,2), cmap='Greys_r')
axes[0].set_title(img1_name)
axes[1].imshow(np.sum(img1,2), cmap='Greys_r')
axes[1].imshow(img2)
axes[1].set_title(img2_name)
fig.suptitle("All Images")
plt.show()
plot_stuff(img1, 'Original', vis, 'MISER Regions')
which for me returns,
using matplotlib.version 1.4.3' and cv2.version'$Rev: 4557 $'
The imshow docs says that cmap is ignored when image has RGB information.
You can consider creating a gray level image
newimg = numpy.sum(img, 2)
Then
ax.imshow(newimg, cmap='gray')

Categories

Resources