Do OpenCV operations impede NumPy arithmetic? - python

I'm attempting to use NumPy and OpenCV to perform object anonymization. The objective is to anonymize faces by isolating them with a mask, blurring said content, and then joining the result to another image. The other image is gotten by inverting the mask and applying it to the source image. The two pieces are combined by addition.
def apply_mask(image, mask):
return np.multiply(image, mask)
Let's test this on a toy example:
image = np.random.randn(256,256)
mask = Mask(256, 256, [64, 64, 192, 192])
Here we've created an object such that mask.inverted_array_label is a 256x256 monochrome image with all pixel intensities zero, except for the rectangle with upper left corner (64,64) and lower right corner (192,192). All pixels in this rectangle have intensity 1.
kernel_size = (121,121)
X = apply_mask(image, mask.inverted_array_label)
X = cv2.GaussianBlur(X, kernel_size, 0)
plt.imshow(X, cmap="gray")
Now we apply a Gaussian blur to the inverted mask, then apply the result to the original image . . .
opp_mask = cv2.GaussianBlur(mask.array_label, kernel_size, 0)
Y = apply_mask(image, opp_mask)
plt.imshow(Y, cmap="gray")
Now I want to add the first image to the second.
Z = np.zeros(X.shape)
Z = np.add(X,Y)
plt.imshow(Z, cmap="gray")
num = 190
print(X[num,num], Y[num,num], Z[num,num])
w1 = X[num, num]
w2 = Y[num, num]
w3 = w1+w2
print(w1,w2,w3)
The arithmetic checks out:
0.006876066498679399 0.7493522694977256 0.756228335996405
0.006876066498679399 0.7493522694977256 0.756228335996405
However, the image does not change as expected:
What am I missing? How do I properly add these two arrays?

Related

How to make a shape larger or smaller without changing the resolution of the image using OpenCV or PIL in Python

I would like to be able to make a certain shape in either a PIL image or an OpenCV image 3 times larger and smaller without changing the resolution of the image or changing the shape of the shape I want to make larger. I have tried using OpenCV's dilation method but that is not it's intended use, plus it changed the shape of the image. For an example:
Thanks.
Here's a way of doing it:
find the interesting shape, i.e. non-white ROI area
extract it
scale it up by a factor
clear the original image to white
paste the scaled ROI back into image with same centre
#!/usr/bin/env python3
import cv2
import numpy as np
if __name__ == "__main__":
# Open image
orig = cv2.imread('image.png',cv2.IMREAD_COLOR)
# Get extent of interesting part, i.e. non-white part
y, x, _ = np.nonzero(~orig)
y0, y1 = np.min(y), np.max(y) # top and bottom rows
x0, x1 = np.min(x), np.max(x) # left and right cols
h, w = y1-y0, x1-x0 # height and width
ROI = orig[y0:y1, x0:x1] # extract ROI
cv2.imwrite('ROI.png', ROI) # DEBUG only
# Upscale ROI
factor = 3
scaledROI = cv2.resize(ROI, (w*factor,h*factor), interpolation=cv2.INTER_NEAREST)
newH, newW = scaledROI.shape[:2]
# Clear original image to white
orig[:] = [255,255,255]
# Get centre of original shape, and position of top-left of ROI in output image
cx, cy = (x0 + x1) //2, (y0 + y1)//2
top = cy - newH//2
left = cx - newW//2
# Paste in rescaled ROI
orig[top:top+newH, left:left+newW] = scaledROI
cv2.imwrite('result.png', orig)
That transforms this:
to this:
Puts me in mind of a pantograph:

How can I extract image segment with specific color in OpenCV?

I work with logos and other simple graphics, in which there are no gradients or complex patterns. My task is to extract from the logo segments with letters and other elements.
To do this, I define the background color, and then I go through the picture in order to segment the images. Here is my code for more understanding:
MAXIMUM_COLOR_TRANSITION_DELTA = 100 # 0 - 765
def expand_segment_recursive(image, unexplored_foreground, segment, point, color):
height, width, _ = image.shape
# Unpack coordinates from point
py, px = point
# Create list of pixels to check
neighbourhood_pixels = [(py, px + 1), (py, px - 1), (py + 1, px), (py - 1, px)]
allowed_zone = unexplored_foreground & np.invert(segment)
for y, x in neighbourhood_pixels:
# Add pixel to segment if its coordinates within the image shape and its color differs from segment color no
# more than MAXIMUM_COLOR_TRANSITION_DELTA
if y in range(height) and x in range(width) and allowed_zone[y, x]:
color_delta = np.sum(np.abs(image[y, x].astype(np.int) - color.astype(np.int)))
print(color_delta)
if color_delta <= MAXIMUM_COLOR_TRANSITION_DELTA:
segment[y, x] = True
segment = expand_segment_recursive(image, unexplored_foreground, segment, (y, x), color)
allowed_zone = unexplored_foreground & np.invert(segment)
return segment
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Pass image as the argument to use the tool")
exit(-1)
IMAGE_FILENAME = sys.argv[1]
print(IMAGE_FILENAME)
image = cv.imread(IMAGE_FILENAME)
height, width, _ = image.shape
# To filter the background I use median value of the image, as background in most cases takes > 50% of image area.
background_color = np.median(image, axis=(0, 1))
print("Background color: ", background_color)
# Create foreground mask to find segments in it (TODO: Optimize this part)
foreground = np.zeros(shape=(height, width, 1), dtype=np.bool)
for y in range(height):
for x in range(width):
if not np.array_equal(image[y, x], background_color):
foreground[y, x] = True
unexplored_foreground = foreground
for y in range(height):
for x in range(width):
if unexplored_foreground[y, x]:
segment = np.zeros(foreground.shape, foreground.dtype)
segment[y, x] = True
segment = expand_segment_recursive(image, unexplored_foreground, segment, (y, x), image[y, x])
cv.imshow("segment", segment.astype(np.uint8) * 255)
while cv.waitKey(0) != 27:
continue
Here is the desired result:
In the end of run-time I expect 13 extracted separated segments (for this particular image). But instead I got RecursionError: maximum recursion depth exceeded, which is not surprising as expand_segment_recursive() can be called for every pixel of the image. And since even with small image resolution of 600x500 i got at maximum 300K calls.
My question is how can I get rid of recursion in this case and possibly optimize the algorithm with Numpy or OpenCV algorithms?
You can actually use a thresholded image (binary) and connectedComponents to do this job in a couple of steps. Also, you may use findContours or other methods.
Here is the code:
import numpy as np
import cv2
# load image as greyscale
img = cv2.imread("hp.png", 0)
# puts 0 to the white (background) and 255 in other places (greyscale value < 250)
_, thresholded = cv2.threshold(img, 250, 255, cv2.THRESH_BINARY_INV)
# gets the labels and the amount of labels, label 0 is the background
amount, labels = cv2.connectedComponents(thresholded)
# lets draw it for visualization purposes
preview = np.zeros((img.shape[0], img.shape[2], 3), dtype=np.uint8)
print (amount) #should be 3 -> two components + background
# draw label 1 blue and label 2 green
preview[labels == 1] = (255, 0, 0)
preview[labels == 2] = (0, 255, 0)
cv2.imshow("frame", preview)
cv2.waitKey(0)
At the end, the thresholded image will look like this:
and the preview image (the one with the colored segments) will look like this:
With the mask you can always use numpy functions to get things like, coordinates of the segments you want or to color them (like I did with preview)
UPDATE
To get different colored segments, you may try to create a "border" between the segments. Since they are plain colors and not gradients, you can try to do an edge detector like canny and then put it black in the image....
import numpy as np
import cv2
img = cv2.imread("total.png", 0)
# background to black
img[img>=200] = 0
# get edges
canny = cv2.Canny(img, 60, 180)
# make them thicker
kernel = np.ones((3,3),np.uint8)
canny = cv2.morphologyEx(canny, cv2.MORPH_DILATE, kernel)
# apply edges as border in the image
img[canny==255] = 0
# same as before
amount, labels = cv2.connectedComponents(img)
preview = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
print (amount) #should be 14 -> 13 components + background
# color them randomly
for i in range(1, amount):
preview[labels == i] = np.random.randint(0,255, size=3, dtype=np.uint8)
cv2.imshow("frame", preview )
cv2.waitKey(0)
The result is:

Convert 2D Shape to 1D Space (Shape Classification. )

I'm looking for a example of code/library using Python to convert a 2D shape to 1D space based on following steps:
Find the centroid of the shape.
By choosing the centroid as a reference origin, unwrap the outer contour counterclockwise to turn it into a distance signal that is composed of all between each boundary pixel and the centroid (like the image)
Thank you!
I did something like this a while back for fun, inspired by a Kaggle competition on leaf classification. I used opencv for finding the contours of the images. Below is the code for python 2.7. See here for the orientation of the returned contour. You may have to adapt it for your needs, specifically the thresholding part. Hope this helps.
import cv2
import numpy as np
import matplotlib.pyplot as plt
def shape_desc(im):
# threshold image
_, bw = cv2.threshold(im, 128, 255, cv2.THRESH_BINARY)
# find contours
contours, hierarchy = cv2.findContours(im.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# extract largest contour
largest_idx = np.argmax([len(contours[i]) for i in range(0, len(contours))])
# get (x,y) coordinates
x = np.array([contours[largest_idx][i][0][0] for i in range(0, len(contours[largest_idx]))], dtype = np.float).reshape((len(contours[largest_idx]), 1))
y = np.array([contours[largest_idx][i][0][1] for i in range(0, len(contours[largest_idx]))], dtype = np.float).reshape((len(contours[largest_idx]), 1))
# find the centroid
m = cv2.moments(np.array([[x[i][0], y[i][0]] for i in range(0, len(x))]).reshape((-1, 1 ,2)).astype(np.int32))
x_bar = m['m10']/m['m00']
y_bar = m['m01']/m['m00']
x_1 = np.array([i[0] for i in x])
y_1 = np.array([i[0] for i in y])
# take the centroid as the reference
x = x_1 - x_bar
y = y_1 - y_bar
return np.sqrt(x*x + y*y)
Here are the results of applying this for the following images that are similar in shape. Note that the images and plots have been rescaled.
filename = '19.jpg'
im = cv2.imread(filename, 0)
desc = shape_desc(im)
plt.stem(desc)

Using masks to apply different thresholds to different parts of an image

I have an image, in which I want to threshold part of the image within a circular region, and then the remainder of the image outside of this region.
Unfortunately my attempts seem to be thresholding the image as a whole, ignoring the masks. How can this be properly achieved? See code attempt below.
def circular_mask(h, w, centre=None, radius=None):
if centre is None: # use the middle of the image
centre = [int(w / 2), int(h / 2)]
if radius is None: # use the smallest distance between the centre and image walls
radius = min(centre[0], centre[1], w - centre[0], h - centre[1])
Y, X = np.ogrid[:h, :w]
dist_from_centre = np.sqrt((X - centre[0]) ** 2 + (Y - centre[1]) ** 2)
mask = dist_from_centre <= radius
return mask
img = cv2.imread('image.png', 0) #read image
h,w = img.shape[:2]
mask = circular_mask(h,w, centre=(135,140),radius=75) #create a boolean circle mask
mask_img = img.copy()
inside = np.ma.array(mask_img, mask=~mask)
t1 = inside < 50 #threshold part of image within the circle, ignore rest of image
plt.imshow(inside)
plt.imshow(t1, alpha=.25)
plt.show()
outside = np.ma.array(mask_img, mask=mask)
t2 = outside < 20 #threshold image outside circle region, ignoring image in circle
plt.imshow(outside)
plt.imshow(t2, alpha=.25)
plt.show()
fin = np.logical_or(t1, t2) #combine the results from both thresholds together
plt.imshow(fin)
plt.show()
Working solution:
img = cv2.imread('image.png', 0)
h,w = img.shape[:2]
mask = circular_mask(h,w, centre=(135,140),radius=75)
inside = img.copy()*mask
t1 = inside < 50#get_threshold(inside, 1)
plt.imshow(inside)
plt.show()
outside = img.copy()*~mask
t2 = outside < 70
plt.imshow(outside)
plt.show()
plt.imshow(t1)
plt.show()
plt.imshow(t2)
plt.show()
plt.imshow(np.logical_and(t1,t2))
plt.show()
I assume your image is single layered (e.g. Grey Scale).
You can make 2 copies of the image. Multiply (or Logical AND) your mask with one of them and invert of that mask with the other one. Now apply your desired threshold to each of them. In the end merge both images using Logical OR operation.

Filling holes in image with OpenCV or Skimage

I m trying to fill holes for a chessboard for stereo application. The chessboard is at micro scale thus it is complicated to avoid dust... as you can see :
Thus, the corners detection is impossible. I tried with SciPy's binary_fill_holes or similar approaches but i have a full black image, i dont understand.
Here is a function that replaces the color of each pixel with the color that majority of its neighbor pixels have.
import numpy as np
import cv2
def remove_noise(gray, num):
Y, X = gray.shape
nearest_neigbours = [[
np.argmax(
np.bincount(
gray[max(i - num, 0):min(i + num, Y), max(j - num, 0):min(j + num, X)].ravel()))
for j in range(X)] for i in range(Y)]
result = np.array(nearest_neigbours, dtype=np.uint8)
cv2.imwrite('result2.jpg', result)
return result
Demo:
img = cv2.imread('mCOFl.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
remove_noise(gray, 10)
Input image:
Out put:
Note: Since this function replace the color of corner pixels too, you can sue cv2.goodFeaturesToTrack function to find the corners and restrict the denoising for that pixels
corners = cv2.goodFeaturesToTrack(gray, 100, 0.01, 30)
corners = np.squeeze(np.int0(corners))
You can use morphology: dilate, and then erode with same kernel size.
A faster, more accurate way is to use skimage.morphology.remove_small_objects docs
im = imread('a.png',cv2.IMREAD_GRAYSCALE)
im = im ==255
from skimage import morphology
cleaned = morphology.remove_small_objects(im, 200)

Categories