how to extract self-defined ROI with dlib facelandmarks? - python

I don't know how to extract the irregular area surrounded by green lines. i.e., the left cheek and the right cheek of a face.
from collections import OrderedDict
import numpy as np
import cv2
import dlib
import imutils
CHEEK_IDXS = OrderedDict([("left_cheek", (1, 2, 3, 4, 5, 48, 31)),
("right_cheek", (11, 12, 13, 14, 15, 35, 54))
])
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
img = cv2.imread('Tom_Cruise.jpg')
img = imutils.resize(img, width=600)
overlay = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
detections = detector(gray, 0)
for k, d in enumerate(detections):
shape = predictor(gray, d)
for (_, name) in enumerate(CHEEK_IDXS.keys()):
pts = np.zeros((len(CHEEK_IDXS[name]), 2), np.int32)
for i, j in enumerate(CHEEK_IDXS[name]):
pts[i] = [shape.part(j).x, shape.part(j).y]
pts = pts.reshape((-1, 1, 2))
cv2.polylines(overlay, [pts], True, (0, 255, 0), thickness=2)
cv2.imshow("Image", overlay)
cv2.waitKey(0)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
I know if just simply extract a rectangular area from the face as cheeks, the code can be like this
ROI1 = img[shape[29][1]:shape[33][1], shape[54][0]:shape[12][0]] #right cheeks
ROI1 = img[shape[29][1]:shape[33][1], shape[4][0]:shape[48][0]] #left cheek
but I want to extract the irregular area for subsequent processing, how can i do it ?

You can accomplish this by two simple steps:
Create a mask using the point coordinates you have
Execute bitwise_and operation (crop)
Code:
cv2.drawContours(mask, [pts], -1, (255, 255, 255), -1, cv2.LINE_AA)
output = cv2.bitwise_and(img, img, mask=mask)
Output:
Additionally, if you want to focus on the cropped polygons, you can create a bounding rectangle to the polygons then crop from the output frame like tihs:
# Create a bounding rects list at global level
bounding_rects = []
# Calculate Bounding Rects for each pts array inside the for loop
bounding_rects.append(cv2.boundingRect(pts))
# Assign geometrical values to variables to crop (Use a range(len(bounding_boxes)) for loop here)
enter code here
x1,y1,w1,h1 = bounding_rects[0]
x2,y2,w2,h2, = bounding_rects[1]
# At the end of the program, crop the bounding boxes from output
cropped1= output[y1:y1+h1, x1:x1+w1]
cropped2= output[y2:y2+h2, x2:x2+w2]
Output:

Related

Using mask of image to find contours openCV

I have the task of find the contours of a red boundary drawn on a site location map. From the contours detected, I need to find the coordinates and save these to an array. I am able to filter for the red boundary and draw the contours, however I don't know how use this new image in my coordinate extraction. As a temporary solution I have screenshotted the mask generated, saved this, then in a new program have used this screenshot to find the coordinates. Is there a way to join all this code together?
This is the code for drawing the contours:
import cv2
img = cv2.imread(r'C:\Users\abbys\OneDrive\Pictures\agileapp\cornwall_cropped.png')
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Gen lower mask (0-5) and upper mask (175-180) of RED
mask1 = cv2.inRange(img_hsv, (0,50,20), (5,255,255))
mask2 = cv2.inRange(img_hsv, (175,50,20), (180,255,255))
# Merge the mask and crop the red regions
mask = cv2.bitwise_or(mask1, mask2 )
cropped = cv2.bitwise_and(img, img, mask=mask)
## Display
cv2.imshow("mask", mask)
cv2.imshow("cropped", cropped)
cv2.waitKey()
This is the code used to extract the coordinates - the image I read in is a screen shot of the 'cropped' image from the above code
# Reading image
font = cv2.FONT_HERSHEY_COMPLEX
img2 = cv2.imread(r'C:\Users\abbys\OneDrive\Pictures\agileapp\cropped.png', cv2.IMREAD_COLOR)
# Reading same image in another
# variable and converting to gray scale.
img = cv2.imread(r'C:\Users\abbys\OneDrive\Pictures\agileapp\cropped.png', cv2.IMREAD_GRAYSCALE)
# Converting image to a binary image
# ( black and white only image).
_, threshold = cv2.threshold(img, 110, 255, cv2.THRESH_BINARY)
# Detecting contours in image.
contours, _= cv2.findContours(threshold, cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
# Going through every contour found in the image.
for cnt in contours :
approx = cv2.approxPolyDP(cnt, 0.009 * cv2.arcLength(cnt, True), True)
# draws boundary of contours.
cv2.drawContours(img2, [approx], 0, (0, 0, 255), 5)
# Used to flatted the array containing
# the co-ordinates of the vertices.
n = approx.ravel()
i = 0
for j in n :
if(i % 2 == 0):
x = n[i]
y = n[i + 1]
# String containing the co-ordinates.
string = str(x) + " " + str(y)
if(i == 0):
# text on topmost co-ordinate.
cv2.putText(img2, "Arrow tip", (x, y),
font, 0.5, (255, 0, 0))
else:
# text on remaining co-ordinates.
cv2.putText(img2, string, (x, y),
font, 0.5, (0, 255, 0))
i = i + 1
# Showing the final image.
cv2.imshow('image2', img2)
# Exiting the window if 'q' is pressed on the keyboard.
if cv2.waitKey(0) & 0xFF == ord('q'):
cv2.destroyAllWindows()

How to deal with DICOM by OpenCV FindContours with ambiguous edge

I want to segment the left auricle DICOM image for the calculation of cv2.minAreaRect.
I use cv2.GaussianBlur to filtter noise first, then use cv2.kmeans to segment the image as mask.
Afterwards, I use cv2.Canny and cv2.findContours to find the edge I want.
But after I apply cv2.minAreaRect to the contours, I get many small rectangles.
This isn't what I expect, I want to find the whole minimum bounding rectangle as below.
The following is my code.
import cv2
import numpy as np
# read input and convert to range 0-1
image = cv2.imread('1.jpg')
image = cv2.GaussianBlur(image, (15, 15), 0)
h, w, c = image.shape
# reshape to 1D array
image_2d = image.reshape(h*w, c).astype(np.float32)
# set number of colors
numcolors = 2
numiters = 10
epsilon = 1
attempts = 10
# do kmeans processing
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, numiters, epsilon)
ret, labels, centers = cv2.kmeans(image_2d, numcolors, None, criteria, attempts, cv2.KMEANS_RANDOM_CENTERS)
# reconstitute 2D image of results
centers = np.uint8(centers)
newimage = centers[labels.flatten()]
newimage = newimage.reshape(image.shape)
cv2.imshow('new image', newimage)
cv2.waitKey(0)
k = 0
for center in centers:
# select color and create mask
layer = newimage.copy()
mask = cv2.inRange(layer, center, center)
#print(mask[203][130])
# apply mask to layer
layer[mask == 0] = [0,0,100]
#cv2.imshow('layer', layer)
#cv2.waitKey(0)
num = 0
# save kmeans clustered image and layer
if(k == 0):
edges = cv2.Canny(mask, 1, 10)
contours, hierarchy = cv2.findContours(edges,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)[-2:]
raw_img3 = cv2.drawContours(image.copy(), contours, -1, (0, 0, 255), 3)
cnts = contours
cv2.imshow('Canny', raw_img3)
cv2.waitKey(0)
for cnt in cnts:
# find minimum bounding rectangle
rect = cv2.minAreaRect(cnt)
box2 = cv2.boxPoints(rect)
box2 = np.int0(box2)
aa = cv2.drawContours(image.copy(), [box2], 0, (255, 0, 0), 4)
cv2.imshow('Canny', aa)
cv2.waitKey(0)
k = k + 1

Open CV Watershed not segmenting oval objects properly

Attemping to create a way to process images to count different types of tablets. The following code has been working well for circular objects, however oval shapes are creating issues that I cant find a workaround for.
kernel = np.ones((5,5),np.uint8)
image = cv2.imread('sample.jpg')
shifted = cv2.GaussianBlur(image, (15, 15), 1)
shifted = cv2.pyrMeanShiftFiltering(shifted, 21, 51)
shifted = cv2.erode(shifted,kernel,iterations=1)
shifted = cv2.dilate(shifted,kernel,iterations=1)
cv2.imwrite("step1.jpg", shifted)
gray = cv2.cvtColor(shifted, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv2.imwrite("step2.jpg", thresh)
thresh = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
cv2.imwrite("step3.jpg", thresh)
thresh = cv2.bitwise_not(thresh)
thresh = cv2.erode(thresh,kernel,iterations=1)
cv2.imwrite("step4.jpg", thresh)
D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, indices=False, min_distance=10,
labels=thresh)
markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]
labels = watershed(-D, markers, mask=thresh)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1))
for label in np.unique(labels):
if label == 0:
continue
mask = np.zeros(gray.shape, dtype="uint8")
mask[labels == label] = 255
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
c = max(cnts, key=cv2.contourArea)
((x, y), r) = cv2.minEnclosingCircle(c)
cv2.circle(image, (int(x), int(y)), int(r), (0, 255, 0), 2)
cv2.putText(image, "#{}".format(label), (int(x) - 10, int(y)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.imwrite("step5.jpg", image)
cv2.waitKey(0)
Image that is being used is :
https://imgur.com/a/1U49DeT
Output after filtering yields :
https://imgur.com/a/vXwrWlG
Any teaching points as to how to fix this would be greatly appreciated.
I think there is a better way to use the watershed operator.
It relies on having a good gradient, but if the images are similar to this one, you should be able to do this effectively. Also, there are very powerful edge detectors today, much better than the one I used on this demo.
import cv2
import numpy as np
import higra as hg
from skimage.segmentation import relabel_sequential
import matplotlib.pyplot as plt
def main():
img_path = "pills.jpg"
img = cv2.imread(img_path)
img = cv2.resize(img, (256, 256))
img = cv2.GaussianBlur(img, (9, 9), 0)
edges = cv2.Canny(img, 100, 100)
size = img.shape[:2]
graph = hg.get_4_adjacency_graph(size)
edge_weights = hg.weight_graph(graph, edges, hg.WeightFunction.mean)
tree, altitudes = hg.watershed_hierarchy_by_area(graph, edge_weights)
segments = hg.labelisation_horizontal_cut_from_threshold(tree, altitudes, 500)
segments, _, _ = relabel_sequential(segments)
print('The number of pills is ', segments.max() - 1)
plt.imshow(segments)
plt.show()
if __name__ == "__main__":
main()
Initially, I resize the image to speed up the computation and apply a blur to reduce the background gradient. I detect its edges (gradient) and create a graph with it as edge weights; then I compute the watershed hierarchy ordered by area and threshold it obtaining the connected component at that level, from this you can count the number of segments.

Python OpenCV - Extrapolating the largest rectangle off of a set of contour points

I'm trying to make an OpenCV detect a bed in the image. I am running the usual Grayscale, Blur, Canny, and I've tried Convex Hull. However, since there's quite a number of "noise" which gives extra contours and messes up the object detection. Because of this, I am unable to detect the bed properly.
Here is the input image as well as the Canny Edge Detection result:
As you can see, it's almost there. I have the outline of the bed already, albeit, that the upper right corner has a gap - which is preventing me from detecting a closed rectangle.
Here's the code I'm running:
import cv2
import numpy as np
def contoursConvexHull(contours):
print("contours length = ", len(contours))
print("contours length of first item = ", len(contours[1]))
pts = []
for i in range(0, len(contours)):
for j in range(0, len(contours[i])):
pts.append(contours[i][j])
pts = np.array(pts)
result = cv2.convexHull(pts)
print(len(result))
return result
def auto_canny(image, sigma = 0.35):
# compute the mediam of the single channel pixel intensities
v = np.median(image)
# apply automatic Canny edge detection using the computed median
lower = int(max(0, (1.0 - sigma) * v))
upper = int(min(255, (1.0 + sigma) *v))
edged = cv2.Canny(image, lower, upper)
# return edged image
return edged
# Get our image in color mode (1)
src = cv2.imread("bed_cv.jpg", 1)
# Convert the color from BGR to Gray
srcGray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
# Use Gaussian Blur
srcBlur = cv2.GaussianBlur(srcGray, (3, 3), 0)
# ret is the returned value, otsu is an image
##ret, otsu = cv2.threshold(srcBlur, 0, 255,
## cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# Use canny
##srcCanny = cv2.Canny(srcBlur, ret, ret*2, 3)
srcCanny1 = auto_canny(srcBlur, 0.70)
# im is the output image
# contours is the contour list
# I forgot what hierarchy was
im, contours, hierarchy = cv2.findContours(srcCanny1,
cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
##cv2.drawContours(src, contours, -1, (0, 255, 0), 3)
ConvexHullPoints = contoursConvexHull(contours)
##cv2.polylines(src, [ConvexHullPoints], True, (0, 0, 255), 3)
cv2.imshow("Source", src)
cv2.imshow("Canny1", srcCanny1)
cv2.waitKey(0)
Since the contour of the bed isn't closed, I can't fit a rectangle nor detect the contour with the largest area.
The solution I can think of is to extrapolate the largest possible rectangle using the contour points in the hopes of bridging that small gap, but I'm not too sure how to proceed since the rectangle is incomplete.
Since you haven't provided any other examples, I provide an algorithm working with this case. But bare in mind that you will have to find ways of adapting it to however the light and background changes on other samples.
Since there is a lot of noise and a relatively high dynamic range, I suggest not to use Canny and instead use Adaptive Thresholding and Find Contours on that (it doesn't need edges as an input), that helps with choosing different threshold values for different parts of the image.
My result:
Code:
import cv2
import numpy as np
def clahe(img, clip_limit=2.0, grid_size=(8,8)):
clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=grid_size)
return clahe.apply(img)
src = cv2.imread("bed.png")
# HSV thresholding to get rid of as much background as possible
hsv = cv2.cvtColor(src.copy(), cv2.COLOR_BGR2HSV)
lower_blue = np.array([0, 0, 120])
upper_blue = np.array([180, 38, 255])
mask = cv2.inRange(hsv, lower_blue, upper_blue)
result = cv2.bitwise_and(src, src, mask=mask)
b, g, r = cv2.split(result)
g = clahe(g, 5, (3, 3))
# Adaptive Thresholding to isolate the bed
img_blur = cv2.blur(g, (9, 9))
img_th = cv2.adaptiveThreshold(img_blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 51, 2)
im, contours, hierarchy = cv2.findContours(img_th,
cv2.RETR_CCOMP,
cv2.CHAIN_APPROX_SIMPLE)
# Filter the rectangle by choosing only the big ones
# and choose the brightest rectangle as the bed
max_brightness = 0
canvas = src.copy()
for cnt in contours:
rect = cv2.boundingRect(cnt)
x, y, w, h = rect
if w*h > 40000:
mask = np.zeros(src.shape, np.uint8)
mask[y:y+h, x:x+w] = src[y:y+h, x:x+w]
brightness = np.sum(mask)
if brightness > max_brightness:
brightest_rectangle = rect
max_brightness = brightness
cv2.imshow("mask", mask)
cv2.waitKey(0)
x, y, w, h = brightest_rectangle
cv2.rectangle(canvas, (x, y), (x+w, y+h), (0, 255, 0), 1)
cv2.imshow("canvas", canvas)
cv2.imwrite("result.jpg", canvas)
cv2.waitKey(0)

Skin detection from hue-saturation histogram - OpenCV Python

I'm working on a little program in python to estimate the direction of pointing gestures with 2D picture from a monocular camera and I'm using OpenCV 2.3.
I know it's a bit tricky but I'm motivated! :)
My approach is fisrt to use the face detection to detect an area into which I'm sure there is a lot of skin:
img = cv2.imread("/home/max/recordings/cameras/imageTEST.jpg",1)
img_hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
hc1 = cv2.CascadeClassifier("/home/max/haarcascade_frontalface_alt.xml")
faces1 = hc1.detectMultiScale(img)
for (x,y,w,h) in faces1:
cv2.rectangle(img, (x,y), (x+w,y+h), 255)
crop_img = img[y+2:y+w, x+2:x+h]
I really want to use this method because I want my detection to be robust to light variation. Then I compute the hue-saturation histogram of the picture of the detected face to make a back projection:
roihist = cv2.calcHist([crop_img],[0,1], None, [180, 256], [0, 180, 0, 256] )
dst = cv2.calcBackProject([img],[0,1],roihist,[0,180,0,256],1)
And finally I would be able to binarize the picture with a threshold and track the head and hands blobs to estimate the direction of pointing.
I've no problem with my code but the skin is not detected...
What am I doing wrong?
Thx for your help!
Max
Have you tried using the Cr channel from the YCbCr format? I had some luck with Cr when I had previously worked on hand detection using skin colour. Also, there is this paper, which uses a nice method for detecting hands. But keep in mind that as long as you use skin colour, the detection will not work for all hands, but can be tuned for a given user or a bunch of users.
I've been working through the available opencv examples on the web lately (just the basic stuff for fun). I've moved of from the face recognition (interesting, but too black box for my liking) to manually selecting the roi in the HSV space, then using 'camshift' to track. I was still getting variable results I didn't understand so I also plot all the intermediate processing windows such as the hsv image and the backproject image, also graph the histogram across the windows. Suddenly all is clear - you can see exactly what the computer is trying to work with.
Here is my working code for python3.4, opencv3. You can manually select the skin. Credit mostly to other examples I've found on the web.
The 'cv2.calcBAckProject' function thresholds out the skin features nicely.
import numpy as np
import cv2
roiPts = []
track_mode = False
termination = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
roiBox = None
kernel = np.ones((5, 5), np.uint8)
frame_width_in_px = 640
number_of_histogram_elements=16
def selectROI(event, x,y,flags,param):
global track_mode, roiPts
if (event == cv2.EVENT_LBUTTONDOWN) and (len(roiPts)==4): #reselecting ROI points so take out of tracking mode and empty current roipoints
roiPts=[]
track_mode = False
if (event==cv2.EVENT_LBUTTONDOWN) and (len(roiPts) < 4): #ROI point selection
roiPts.append([x, y])
cap = cv2.VideoCapture(0)
cv2.namedWindow("frame")
cv2.setMouseCallback("frame", selectROI)
while True:
ret, frame = cap.read()
if len(roiPts)<=4 and len(roiPts)>0:
for x,y in roiPts:
cv2.circle(frame, (x,y), 4, (0, 255, 0), 1) # draw small circle for each roi click
if len(roiPts)==4 and track_mode==False: #initialize the camshift
# convert the selected points to a box shape
roiBox = np.array(roiPts, dtype=np.int32)
s = roiBox.sum(axis=1)
tl = roiBox[np.argmin(s)]
br = roiBox[np.argmax(s)]
#extract the roi from the image and calculate the histograme
roi = frame[tl[1]:br[1], tl[0]:br[0]]
roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) #
roiHist = cv2.calcHist([roi], [0], None, [number_of_histogram_elements], [0, 180])
roiHist = cv2.normalize(roiHist, roiHist, 0, 255, cv2.NORM_MINMAX)
roiBox = (tl[0], tl[1], br[0], br[1])
track_mode = True #ready for camshift
if track_mode == True: #tracking mode
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
backProj = cv2.calcBackProject([hsv], [0], roiHist, [0, 180], 1)
#perfrom some noise reduction and smoothing
erosion = cv2.erode(backProj, kernel, iterations=2)
dilate = cv2.dilate(erosion, kernel, iterations=2)
(r, roiBox) = cv2.CamShift(dilate, roiBox, termination) #this takes prev roiBox and calculates the new roiBox
pts = np.int0(cv2.boxPoints(r))
cv2.polylines(frame, [pts], True, (0, 255, 0), 2) #tracking box
cv2.polylines(backProj, [pts], True, (0, 255, 0), 2) #tracking box
cv2.polylines(dilate, [pts], True, (0, 255, 0), 2) #tracking box
cv2.polylines(hsv, [pts], True, (0, 255, 0), 2) #tracking box
# plot histogram polyline across the windows
x = np.linspace(0,640,number_of_histogram_elements,dtype=np.int32)
y = roiHist.flatten().astype(np.int32, copy=False)-255 #note frame height needs to be greater than 255 which is the max histo value
y=np.absolute(y)
pts2 = np.stack((x, y), axis=1)
cv2.polylines(frame, [pts2], False, (0, 255, 0), 2)
cv2.polylines(hsv, [pts2], False, (0, 255, 0), 2)
cv2.imshow("backproject", backProj)
cv2.imshow("dilate", dilate)
cv2.imshow("hsv", hsv)
cv2.imshow("frame", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()

Categories