How to mask outside or inside an arbitrary shape in Python? - python

from scipy import interpolate
import matplotlib.pyplot as plt
import numpy as np
import cv2
a=np.arange(1,9)
filename = 'image_file_0.tiff'
img = cv2.imread(filename)
x = np.array([389, 392, 325, 211, 92,103,194,310])
y = np.array([184,281,365,401,333,188,127,126])
x = np.r_[x, x[0]]
y = np.r_[y, y[0]]
tck, u = interpolate.splprep([x, y], s=0, per=True)
xi, yi = interpolate.splev(np.linspace(0, 1, 1000), tck)
fig, ax = plt.subplots(1, 1)
ax.plot(xi, yi, '-b')
plt.imshow(img)
plt.show()
I want to transform the pixel values of the outer or inner region after forming an arbitrary closed curve in the image. How do I do it?

You can do it using the cv2.fillpoly function.
Using this picture for example:
We can get the shape we want to mask using:
from scipy import interpolate
import matplotlib.pyplot as plt
import numpy as np
import cv2
a=np.arange(1,9)
filename = 'image_file_0.tiff'
img = cv2.imread('image_left.png', cv2.IMREAD_COLOR)
x = np.array([289, 292, 125, 111, 40, 80, 94,210])
y = np.array([84 , 181, 265, 241,133, 88, 27, 40])
x = np.r_[x, x[0]]
y = np.r_[y, y[0]]
tck, u = interpolate.splprep([x, y], s=0, per=True)
xi, yi = interpolate.splev(np.linspace(0, 1, 1000), tck)
plt.imshow(img[:,:,[2,1,0]])
plt.scatter(xi, yi)
plt.show()
This results in:
Now the masking can be done using:
contour = np.array([[xii, yii] for xii, yii in zip(xi.astype(int), yi.astype(int))])
mask = np.zeros_like(img)
cv2.fillPoly(mask, pts=[contour], color=(255, 255, 255))
masked_img = cv2.bitwise_and(img, mask)
And the result:
Using the inverted mask you manipulate the outer pixels as you wish:
mask = np.ones_like(img)*255
cv2.fillPoly(mask, pts=[contour], color=(0,0,0))
masked_img = cv2.bitwise_and(img, mask)
This results in:

Here's another way to do it using cv2.drawContours(). The idea is to take your rough contour points, smooth them out, draw this contour onto a mask, then cv2.bitwise_and() or inverse bitwise-and to extract the desired regions.
Input image -> Generated mask
Result -> Inverted result
import numpy as np
import cv2
from scipy import interpolate
# Load image, make blank mask, define rough contour points
image = cv2.imread('1.jpg')
mask = np.zeros(image.shape, dtype=np.uint8)
x = np.array([192, 225, 531, 900, 500])
y = np.array([154, 281, 665, 821, 37])
x = np.r_[x, x[0]]
y = np.r_[y, y[0]]
# Smooth contours
tck, u = interpolate.splprep([x, y], s=0, per=True)
x_new, y_new = interpolate.splev(np.linspace(0, 1, 1000), tck)
smooth_contour = np.array([[[int(i[0]), int(i[1])]] for i in zip(x_new, y_new)])
# Draw contour onto blank mask in white
cv2.drawContours(mask, [smooth_contour], 0, (255,255,255), -1)
result1 = cv2.bitwise_and(image, mask)
result2 = cv2.bitwise_and(image, 255 - mask)
cv2.imshow('image', image)
cv2.imshow('mask', mask)
cv2.imshow('result1', result1)
cv2.imshow('result2', result2)
cv2.waitKey()

Related

OpenCV transform image shape transformation into a given contour

Does anyone know whether it's possible to transform image A into image B contour if their shapes are random, using OpenCV or any other python libraries that work with images?
Here is what I have so far with 2 images:
I've been able to find draw contours of the bulb and insert a fox in it using bitwise_and method, but what it does is it crops the second image, whereas I need it to transform its shape into the bulb contour.
import cv2
src1 = cv2.imread('fox.png')
src2 = cv2.imread('bulb-contour-filled.png')
src2 = cv2.resize(src2, src1.shape[1::-1])
dst = cv2.bitwise_and(src1, src2)
cv2.imwrite('img_fin.jpg', dst)
Bulb original image:
Fox original image:
Bulb contour:
The Concept
For this we are going to need to slice the into triangles, and warp each triangle individually. The starting points of the image should be along the outline of the original image, and the ending points should be along the outline of the destination shape.
Although below I have hard-coded the 2 sets of points, you'll just need to figure out the optimal processing to retrieve the contours of the 2 images (each needs have the same number of points and in the same order). Also, I have programmed an interactive OpenCV program that will allow us to easily retrieve the coordinates.
The Code
import cv2
import numpy as np
def triangles(points):
points = np.where(points, points, 1)
subdiv = cv2.Subdiv2D((*points.min(0), *points.max(0)))
for pt in points:
subdiv.insert(tuple(map(int, pt)))
for pts in subdiv.getTriangleList().reshape(-1, 3, 2):
yield [np.where(np.all(points == pt, 1))[0][0] for pt in pts]
def crop(img, pts):
x, y, w, h = cv2.boundingRect(pts)
img_cropped = img[y: y + h, x: x + w]
pts[:, 0] -= x
pts[:, 1] -= y
return img_cropped, pts
def warp(img1, img2, pts1, pts2):
img2 = img2.copy()
for indices in triangles(pts1):
img1_cropped, triangle1 = crop(img1, pts1[indices])
img2_cropped, triangle2 = crop(img2, pts2[indices])
transform = cv2.getAffineTransform(np.float32(triangle1), np.float32(triangle2))
img2_warped = cv2.warpAffine(img1_cropped, transform, img2_cropped.shape[:2][::-1], None, cv2.INTER_LINEAR, cv2.BORDER_REFLECT_101)
mask = np.zeros_like(img2_cropped)
cv2.fillConvexPoly(mask, np.int32(triangle2), (1, 1, 1), 16, 0)
img2_cropped *= 1 - mask
img2_cropped += img2_warped * mask
return img2
def resize(img, size):
h, w = img.shape[:2]
return cv2.resize(img, (int(w * size), int(h * size)))
img1 = resize(cv2.imread("dog.png"), 0.8)
img2 = resize(cv2.imread("bulb.png"), 0.8)
pts1 = np.array([[322, 508], [390, 475], [413, 425], [440, 367], [453, 305], [458, 289], [446, 202], [434, 139], [392, 104], [324, 94], [246, 97], [194, 101], [111, 127], [98, 185], [88, 240], [95, 306], [90, 363], [123, 431], [160, 487], [223, 508]])
pts2 = np.array([[459, 793], [513, 715], [541, 580], [552, 470], [583, 398], [633, 323], [643, 233], [616, 144], [557, 71], [470, 28], [354, 27], [264, 72], [206, 138], [179, 225], [178, 302], [236, 401], [266, 480], [278, 564], [297, 707], [357, 792]])
cv2.imshow("result", warp(img1, img2, pts1, pts2))
cv2.waitKey(0)
cv2.destroyAllWindows()
The Output
The Explanation
Import the necessary libraries:
import cv2
import numpy as np
Define a function, triangles, that will take in an array of coordinates, points, and yield lists of 3 indices of the array for triangles that will cover the area of the original array of coordinates:
def triangles(points):
points = np.where(points, points, 1)
subdiv = cv2.Subdiv2D((*points.min(0), *points.max(0)))
for pt in points:
subdiv.insert(tuple(map(int, pt)))
for pts in subdiv.getTriangleList().reshape(-1, 3, 2):
yield [np.where(np.all(points == pt, 1))[0][0] for pt in pts]
Define a function, crop, that will take in an image array, img, and an array of three coordinates, pts. It will return a rectangular segment of the image just large enough to fit the triangle formed by the three point, and return the array of three coordinates transferred to the top-left corner of image:
def crop(img, pts):
x, y, w, h = cv2.boundingRect(pts)
img_cropped = img[y: y + h, x: x + w]
pts[:, 0] -= x
pts[:, 1] -= y
return img_cropped, pts
Define a function, warp, that will take in 2 image arrays, img1 and img2, and 2 arrays of coordinates, pts1 and pts2. It will utilize the triangles function defined before iterate through the triangles from the first array of coordinates, the crop function defined before to crop both images at coordinates corresponding to the triangle indices and use the cv2.warpAffine() method to warp the image at the current triangle of the iterations:
def warp(img1, img2, pts1, pts2):
img2 = img2.copy()
for indices in triangles(pts1):
img1_cropped, triangle1 = crop(img1, pts1[indices])
img2_cropped, triangle2 = crop(img2, pts2[indices])
transform = cv2.getAffineTransform(np.float32(triangle1), np.float32(triangle2))
img2_warped = cv2.warpAffine(img1_cropped, transform, img2_cropped.shape[:2][::-1], None, cv2.INTER_LINEAR, cv2.BORDER_REFLECT_101)
mask = np.zeros_like(img2_cropped)
cv2.fillConvexPoly(mask, np.int32(triangle2), (1, 1, 1), 16, 0)
img2_cropped *= 1 - mask
img2_cropped += img2_warped * mask
return img2
Read in your images. Note that I have resized the images to better fit my screen. If you remove the resizing part, you'll need to use the program below to re-adjust the points and get the corrected sets of points:
def resize(img, size):
h, w = img.shape[:2]
return cv2.resize(img, (int(w * size), int(h * size)))
img1 = resize(cv2.imread("dog.png"), 0.8)
img2 = resize(cv2.imread("bulb.png"), 0.8)
Finally, define the 2 sets of points; the first set outlining the first image, and the second one outlining the second. Use the warp function defined before to warp img1 to have its keypoints overlap with the kewpoints of img2 and show the resulting image:
pts1 = np.array([[0, 0], [286, 0], [286, 198], [174, 198], [158, 116], [0, 97]])
pts2 = np.array([[80, 37], [409, 42], [416, 390], [331, 384], [291, 119], [111, 311]])
cv2.imshow("result", warp(img1, img2, pts1, pts2))
cv2.waitKey(0)
cv2.destroyAllWindows()
Tools
Use the program below to manually drag the point onto each image, and see the warp effect in real-time. Of course, rather than manually doing this, you can detect the contours of the two images (make sure that they have the same number of points and are in the same order):
import cv2
import numpy as np
def triangles(points):
points = np.where(points, points, 1)
subdiv = cv2.Subdiv2D((*points.min(0), *points.max(0)))
for pt in points:
subdiv.insert(tuple(map(int, pt)))
for pts in subdiv.getTriangleList().reshape(-1, 3, 2):
yield [np.where(np.all(points == pt, 1))[0][0] for pt in pts]
def crop(img, pts):
x, y, w, h = cv2.boundingRect(pts)
img_cropped = img[y: y + h, x: x + w]
pts[:, 0] -= x
pts[:, 1] -= y
return img_cropped, pts
def warp(img1, img2, pts1, pts2):
img2 = img2.copy()
for indices in triangles(pts1):
img1_cropped, triangle1 = crop(img1, pts1[indices])
img2_cropped, triangle2 = crop(img2, pts2[indices])
transform = cv2.getAffineTransform(np.float32(triangle1), np.float32(triangle2))
img2_warped = cv2.warpAffine(img1_cropped, transform, img2_cropped.shape[:2][::-1], None, cv2.INTER_LINEAR, cv2.BORDER_REFLECT_101)
mask = np.zeros_like(img2_cropped)
cv2.fillConvexPoly(mask, np.int32(triangle2), (1, 1, 1), 16, 0)
img2_cropped *= 1 - mask
img2_cropped += img2_warped * mask
return img2
def draw_circle(event, x, y, flags, param):
pts = param
if event == cv2.EVENT_LBUTTONDOWN:
for pt in pts:
dist = (pt[0] - x) ** 2 + (pt[1] - y) ** 2
if dist < 225:
active_pt[:] = pt
elif event == cv2.EVENT_LBUTTONUP:
active_pt[:] = 0
elif event == cv2.EVENT_MOUSEMOVE:
if np.any(active_pt):
for pt in pts:
if np.all(active_pt == pt):
pt[:] = active_pt[:] = x, y
def draw_circles(img, pts):
img = img.copy()
for i, (x, y) in enumerate(pts):
cv2.circle(img, (x, y), 15, (0, 0, 255), -1)
cv2.putText(img, str(i), (x - 10, y + 10), cv2.FONT_HERSHEY_COMPLEX, 0.8, (0, 0, 0), 2)
return img
def resize(img, size):
h, w = img.shape[:2]
return cv2.resize(img, (int(w * size), int(h * size)))
img1 = resize(cv2.imread("dog.png"), 0.8)
img2 = resize(cv2.imread("bulb.png"), 0.8)
pts_count = 20
pts1 = np.arange(pts_count * 2).reshape((pts_count, 2))
pts2 = np.arange(pts_count * 2).reshape((pts_count, 2))
active_pt = np.array([0, 0])
cv2.namedWindow("image 1")
cv2.setMouseCallback('image 1', draw_circle, pts1)
cv2.namedWindow("image 2")
cv2.setMouseCallback('image 2', draw_circle, pts2)
pause = False
while True:
cv2.imshow('image 1', draw_circles(img1, pts1))
cv2.imshow('image 2', draw_circles(img2, pts2))
if not pause:
try:
cv2.imshow("result", warp(img1, img2, pts1, pts2))
except:
pass
key = cv2.waitKey(20)
if key & 0xFF == ord("q"):
break
if key & 0xFF == ord("p"):
pause = not pause
cv2.waitKey(0)
cv2.destroyAllWindows()
Here is a rough demonstration of how it works (sped up x4):
If the real-time warping is too slow on your computer, simply press the p key to pause the warp updating, and press it again to resume it.

Compute angle between two objects in an image

I have some specific images of two objects (a phone and a TV remote) and I want to calculate the angle between two edges that intersect of these. I used Canny to detect the edges and Hough line for the angle, but the hough_line() function found too many angles that doesnt match the requirement.
Original image:
This is the requirement:
And this is which I made:
My code:
import cv2
from skimage.transform import hough_line, hough_line_peaks
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
def edge_detection(img, blur_ksize=5, threshold1=100, threshold2=200):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_gaussian = cv2.GaussianBlur(gray, (blur_ksize, blur_ksize), 0)
img_canny = cv2.Canny(img_gaussian, threshold1, threshold2)
return img_canny
image = edge_detection(cv2.imread('img1.png'))
h, theta, d = hough_line(image)
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
ax = axes.ravel()
ax[0].imshow(image)
ax[0].set_title('Input image')
ax[0].set_axis_off()
ax[1].imshow(image, cmap=cm.gray)
for _, angle, dist in zip(*hough_line_peaks(h, theta, d)):
y0 = (dist - 0 * np.cos(angle)) / np.sin(angle)
y1 = (dist - image.shape[1] * np.cos(angle)) / np.sin(angle)
ax[1].plot((0, image.shape[1]), (y0, y1), '-r')
ax[1].set_xlim((0, image.shape[1]))
ax[1].set_ylim((image.shape[0], 0))
ax[1].set_axis_off()
ax[1].set_title('Detected lines')
plt.tight_layout()
plt.show()
angle = []
dist = []
for _, a , d in zip(*hough_line_peaks(h, theta, d)):
angle.append(a)
dist.append(d)
angle = [a*180/np.pi for a in angle]
print(angle)
Are there any ways to detect and calculate exactly one angle I need in opencv? Thanks a lot
Update
I tried different values of blur_ksize, threshold1 and threshold2 in Canny detection, it's seem like I could remove redundant lines, but now the angles those hough_line_peaks() return are negative. Can anyone explain this for me? And I also want to put the angle values to the peaks in plot, to see which angle has which value
here is a sample solution, but I don't know whether it works for all images. You have to tune the hough transform parameters.
import cv2
import numpy as np
import matplotlib.pyplot as plt
def edge_detection(img, blur_ksize=5, threshold1=70, threshold2=200):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_gaussian = cv2.GaussianBlur(gray, (blur_ksize, blur_ksize), 0)
img_canny = cv2.Canny(img_gaussian, threshold1, threshold2)
return img_canny
img = cv2.imread('stack.png')
image = edge_detection(img)
minLineLength = 300
maxLineGap = 80
lines = cv2.HoughLinesP(image,1,np.pi/180,50,minLineLength,maxLineGap)
equations = []
for line in lines:
x1,y1,x2,y2 = line[0]
equations.append(np.cross([x1,y1,1],[x2,y2,1]))
cv2.line(img,(x1,y1),(x2,y2),(255,0,0),2)
font = cv2.FONT_HERSHEY_SIMPLEX
thetas = []
N = len(equations)
for ii in range(1,N):
a1,b1,c1 = equations[0]
a2,b2,c2 = equations[ii]
# intersection point
pt = np.cross([a1,b1,c1],[a2,b2,c2])
pt = np.int16(pt/pt[-1])
# angle between two lines
num = a1*b2 - b1*a2
den = a1*a2 + b1*b2
if den != 0:
theta = abs(np.arctan(num/den))*180/3.1416
# show angle and intersection point
cv2.circle(img, (pt[0],pt[1]), 5, (255,0,0), -1)
cv2.putText(img, str(round(theta, 1)), (pt[0]-20,pt[1]-20), font, 0.8, (255,0,0), 2, 0)
thetas.append(theta)
plt.imshow(img)
plt.show()

How to label numbers on image with matrix position? (paint by numbers)

First Process I did this
import numpy as np
import cv2
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
import numpy as np
img = cv2.imread("img.jpg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = np.array(img, dtype=np.float64) / 255
plt.imshow(img)
Then I took the image data to train to find the mean color by k-mean.
w, h, d = original_shape = tuple(img.shape)
print(w, h, d) # 627 783 3
img = np.reshape(img, (w * h, d))
img.shape # (490941, 3)
bit_of_color = 32
kmeans = KMeans(n_clusters=bit_of_color, random_state=0).fit(img)
labels = kmeans.predict(img)
kmeans.labels_ # array([16, 16, 16, ..., 28, 28, 28], dtype=int32)
After that, I created an image to display the color obtained from the model.
image = np.zeros((w, h, d))
mean_ = kmeans.cluster_centers_
d = mean_.shape[1]
mean_[1]
def adjust_image(mean_color_from_model, labels, w, h):
d = mean_color_from_model.shape[1]
print(mean_color_from_model.shape)
image = np.zeros((w, h, d))
label_idx = 0
for i in range(w):
for j in range(h):
image[i][j] = mean_color_from_model[labels[label_idx]]
# print(image[i][j])
label_idx += 1
print(label_idx)
return image
plt.axis('off')
img_kmean = adjust_image(kmeans.cluster_centers_, labels, w, h)
plt.imshow(img_kmean)
And get the following result.
enter image description here
Next, I'll remove the color from the image and still the only the line of the object.
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('img2.png',0)
edges = cv.Canny(img,100,200)
y, x = edges.shape
for i in range(y):
for j in range(x):
if (edges[i][j] == 0):
edges[i][j] = 255
elif (edges[i][j] == 255):
edges[i][j] = 0
plt.imshow(edges,cmap = 'gray')
plt.show()
This is a result.
enter image description here
Then, if I want to label a number into an image using the position of the matrix how to do that?
Desired result. (right image)
enter image description here
Now, My process follows this. But it's still a mistake.
How to make it better?
Follow this Code
def check_bool(x, y, size_x, size_y):
for j in range(y,y + size_y):
for k in range(x,x + size_x):
try:
if (copy_edge[k][j] == 0): #255 W 0 B
return False # ถ้าในกรอบเจอสีดำจะ set เป็น False และนำไป Plot ไม่ได้
break
except:
pass
return True
def put_text(img_plt, text, x, y):
cv2.putText(
img_plt, #numpy array on which text is written
str(text), #text
(x,y), # x y
cv2.FONT_HERSHEY_SIMPLEX, #font family
0.5, #font size
(0, 0, 0, 0), #font color
2) #font stroke
size_x = 20
size_y = 20
copy_edge = edges.copy()
color_edge = img_kmean.copy()
y, x, d = color_edge.shape
for round in range(len(mean_color)):
for y_ in range(0, y, size_y):
for x_ in range(0, x, size_x):
status = check_bool(x_, y_, size_x, size_y) # ถ้า Plot ได้
# print(status)
if status == True: # Putting text
c = sum(color_edge[y_][x_])
c0 = sum(mean_color[round])
if c == c0:
put_text(copy_edge, round, x_, y_)
plt.figure(figsize = (17,10))
plt.imshow(copy_edge,cmap = 'gray')
plt.axis('off')
To label the pixels which are not 0 you can use opencv's cv2connectedComponentsWithStats() function
import numpy as np
import cv2
from skimage.color import label2rgb
# read the image as gray channel
I = cv2.imread("imgPath", 0)
# apply canny
edges = cv2.Canny(img,100,200)
# Invert the canny image
edges = 255 - edges
#dilating the mask to merge some edges(You can skip this step)
#edges = cv2.dilate(edges, np.ones((2,2)))
# label the bw mask from canny (white pixels are labelled automatically)
n, labels, _, _ = cv2.connectedComponentsWithStats(edges)
# convert the labels to RGB for visualization purpose
labels_rgb = np.uint8(255*label2rgb(labels, bg_label=0))
#save it
cv2.imwrite("./Pictures/bw.png", labels_rgb)

How to blur a face in opencv with round borders - Python?

I blurred the face in OpenCV like this:
I used this code:
face = cv2.medianBlur(face, 100)
img[top:bottom,left:right] = face
But I want to make the face border round like this (does not need to be perfect)
First, create a mask image. To do so, draw a white circle at the face location on a black image.
Second, blur the whole image.
Third, copy blurred content to the original image only where your mask is > 0.
p1 = (65, 65)
w, h = 100, 100
p2 = (p1[0] + w, p1[1] + h)
circle_center = ((p1[0] + p2[0])// 2, (p1[1] + p2[1]) // 2)
circle_radius = int(math.sqrt(w * w + h * h) // 2)
mask_img = np.zeros(img.shape, dtype='uint8')
cv2.circle(mask_img, circle_center, circle_radius, (255, 255, 255), -1)
img_all_blurred = cv2.medianBlur(img, 99)
img_face_blurred = np.where(mask_img > 0, img_all_blurred, img)
Output:
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread('image.jpg')
h, w, c = img.shape
plt.imshow(img)
plt.show()
c_mask = np.zeros((h,w), np.uint8)
cv2.circle(c_mask,(w//2,h//2),100,1,thickness=-1)
mask = cv2.bitwise_and(img, img, mask=c_mask)
plt.imshow(mask)
plt.show()
img_mask = img - mask
plt.imshow(img_mask)
plt.show()
blur = cv2.blur(img,(17, 17))
plt.imshow(blur)
plt.show()
mask2 = cv2.bitwise_and(blur, blur, mask=c_mask) # mask
plt.imshow(mask2)
plt.show()
final_img = img_mask + mask2
print(np.max(final_img))
plt.imshow(final_img)
plt.show()
You can blur whole image then copy the result to source using any mask you like.

Histogram of a region of an image

I want to get the histogrm of a region in a numpy image in python. I found a solution on how to use a mask here.
this solution didnt help me because if i use it I will loose the real number of black pixels. Also, the region that i want to get is not necessarly rectangular.
To compute histogram use np.histogram function. It returns a histogram and bins. So you can store the results and work with it:
hist, bins = np.histogram(arr, bins=bins, range=range)
If you want to plot the results, you can use plt.bar after applying np.histogramsimply passing bins and hist:
plt.bar(bins, hist)
Another option is using matplotlib plt.hist it computes the histogram and plots it from a raw data:
plt.hist(arr, bins=bins)
Here is the complete example for the histogram of image region of any shape:
Code:
import numpy as np
import matplotlib.pyplot as plt
from scipy.misc import face
from PIL import Image, ImageDraw
# Let's create test image with different colors
img = np.zeros((300, 300, 3), dtype=np.uint8)
img[0:150, 0:150] = [255, 0, 0]
img[0:150, 150:] = [0, 255, 0]
img[150:, :150] = [0, 0, 255]
img[150:, 150:] = [255, 255, 255]
# define our function for preparing mask
def prepare_mask(polygon, image):
"""Returns binary mask based on input polygon presented as list of coordinates of vertices
Params:
polygon (list) - coordinates of polygon's vertices. Ex: [(x1,y1),(x2,y2),...] or [x1,y1,x2,y2,...]
image (numpy array) - original image. Will be used to create mask of the same size. Shape (H, W, C).
Output:
mask (numpy array) - boolean mask. Shape (H, W).
"""
# create an "empty" pre-mask with the same size as original image
width = image.shape[1]
height = image.shape[0]
mask = Image.new('L', (width, height), 0)
# Draw your mask based on polygon
ImageDraw.Draw(mask).polygon(polygon, outline=1, fill=1)
# Covert to np array
mask = np.array(mask).astype(bool)
return mask
def compute_histogram(mask, image):
"""Returns histogram for image region defined by mask for each channel
Params:
image (numpy array) - original image. Shape (H, W, C).
mask (numpy array) - boolean mask. Shape (H, W).
Output:
list of tuples, each tuple (each channel) contains 2 arrays: first - computed histogram, the second - bins.
"""
# Apply binary mask to your array, you will get array with shape (N, C)
region = image[mask]
red = np.histogram(region[..., 0].ravel(), bins=256, range=[0, 256])
green = np.histogram(region[..., 1].ravel(), bins=256, range=[0, 256])
blue = np.histogram(region[..., 2].ravel(), bins=256, range=[0, 256])
return [red, green, blue]
def plot_histogram(histograms):
"""Plots histogram computed for each channel.
Params:
histogram (list of tuples) - [(red_ch_hist, bins), (green_ch_hist, bins), (green_ch_hist, bins)]
"""
colors = ['r', 'g', 'b']
for hist, ch in zip(histograms, colors):
plt.bar(hist[1][:256], hist[0], color=ch)
# Create some test masks
red_polygon = [(50, 100), (50, 50), (100, 75)]
green_polygon = [(200, 100), (200, 50), (250, 75)]
blue_polygon = [(50, 250), (50, 200), (100, 225)]
white_polygon = [(200, 250), (200, 200), (250, 225)]
polygons = [red_polygon, green_polygon, blue_polygon, white_polygon]
for polygon in polygons:
mask = prepare_mask(polygon, img)
histograms = compute_histogram(mask, img)
# Let's plot our test results
plt.figure(figsize=(10, 10))
plt.subplot(221)
plt.imshow(img)
plt.title('Image')
plt.subplot(222)
plt.imshow(mask, cmap='gray')
plt.title('Mask')
plt.subplot(223)
plot_histogram(histograms)
plt.title('Histogram')
plt.show()
Output:
The final test on raccoon:
Code:
raccoon = face()
polygon = [(200, 700), (150, 600), (300, 500), (300, 400), (400, 500)]
mask = prepare_mask(polygon, raccoon)
histograms = compute_histogram(mask, raccoon)
plt.figure(figsize=(10, 10))
plt.subplot(221)
plt.imshow(raccoon)
plt.title('Image')
plt.subplot(222)
plt.imshow(mask, cmap='gray')
plt.title('Mask')
plt.subplot(223)
plot_histogram(histograms)
plt.title('Histogram')
plt.show()
Output:
Let's define (x,y)coordinates of 5 points (p0, p1, p2, p3, p4) as the corners of the region. We can make a mask using opencv fillPoly function after concatinating the points as np array. The pixel values of image can be filtered with this mask. I use matplotlib's histogram. Opencv and numpy have also histogram functions.
import cv2
import numpy as np
from matplotlib import pyplot as plt
img_x = img.copy()
pts = np.concatenate((p0, p1, p2, p3, p4)).reshape((-1, 1, 2))
cv2.fillPoly(img_x , [pts], (255, 255, 255))
n, bins, patches = plt.hist(img[img_x == (255, 255, 255)], 256, [0, 256])

Categories

Resources