Debbie
Debbie

Reputation: 969

How can I remove double lines detected along the edges?

I'm trying to take real time input for hand gestures with web cam, then processing the images to feed them to a neural network. I wrote this processing function to make the hand features look prominent:

    img = cv2.imread('hand.png')
   
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray,(5,5),2)

    th3 = cv2.adaptiveThreshold(blur,10,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY_INV,11,2)
    ret, res = cv2.threshold(th3, 225, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) 
    res = cv2.Canny(res,100,200) 
    cv2.imshow("Canny", res)

The input and the output images are as follows:

enter image description here

enter image description here

It's obvious that double lines, instead of one, are detected along the edges (allover the hand, not only contour). I want to make them single. If I apply just Canny edge detection algo, then the edges are not very prominent.

Upvotes: 0

Views: 1924

Answers (2)

Prefect
Prefect

Reputation: 1777

It looks like you are on the correct way, but as @CrisLuengo mentioned, Canny is applied on grayscale images rather than binary images. Here is an approach.

import numpy as np
import matplotlib.pyplot as plt
import cv2


img_gray = cv2.imread('hand.png',0)

sigma = 2
threshold1=30
threshold2=60

img_blur = cv2.GaussianBlur(img_gray,(5,5),sigmaX=sigma,sigmaY=sigma)
res = cv2.Canny(img_blur,threshold1=threshold1,threshold2=threshold2)
fig,ax = plt.subplots(1,2,sharex=True,sharey=True)
ax[0].imshow(img_gray,cmap='gray')
ax[1].imshow(res,cmap='gray')
plt.show()

After playing around with the parameters of the gaussian filter and the Canny threshold values, this is what I am getting:

fingers

As you can see most of the fingers are clearly detected except the thumb. The lighting conditions make it difficult for Canny to calculate a proper gradient there. You might either try to improve the contrast of your images through your setup (which is the easiest solution to me), or to apply some contrast enhancements methods like Contrast Limited Adaptive Histogram Equalization (CLAHE) before going for Canny. I did not get any better results than the one above after a few trials with CLAHE, though, but it might be worth to look at it. Good luck!

Upvotes: 0

stateMachine
stateMachine

Reputation: 5805

One straightforward solution would be flood-fill the background with white and then with black using cv2.floodFill, like this:

import cv2
import numpy as np

# image path
path = "D://opencvImages//"
fileName = "hand.png"

# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)

# Convert the image to Grayscale:
binaryImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)

# Flood fill bakcground (white + black):
cv2.floodFill(binaryImage, mask=None, seedPoint=(int(0), int(0)), newVal=(255))

cv2.floodFill(binaryImage, mask=None, seedPoint=(int(0), int(0)), newVal=(0))

cv2,imshow("floodFilled", binaryImage)
cv2.waitKey(0)

This is the result:

If you want to get a solid mask of the hand, you could try to fill the holes inside the hand's contour, also using flood-fill and some image arithmetic, like this:

# image path
path = "D://opencvImages//"
fileName = "hand.png"

# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)

# Convert the image to Grayscale:
binaryImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)

# Isolate holes on input image:
holes = binaryImage.copy()
# Get rows and cols from input:
(rows, cols) = holes.shape[:2]

# Remove background via flood-fill on 4 outermost corners
cv2.floodFill(holes, mask=None, seedPoint=(int(0), int(0)), newVal=(255))
cv2.floodFill(holes, mask=None, seedPoint=(int(10), int(rows-10)), newVal=(255))
cv2.floodFill(holes, mask=None, seedPoint=(int(cols-10), int(10)), newVal=(255))
cv2.floodFill(holes, mask=None, seedPoint=(int(cols-10), int(rows-10)), newVal=(255))

# Get holes:
holes = 255 - holes
# Final image is original imput + isolated holes:
mask = binaryImage + holes

# Deep copy for further results:
maskCopy = mask.copy()
maskCopy = cv2.cvtColor(maskCopy, cv2.COLOR_GRAY2BGR)

These are the isolated holes and hand mask:

You can then detect the bounding rectangle by processing contours, filtering small-area blobs and approximating to a rectangle, like this:

# Find the big contours/blobs on the processed image:
contours, hierarchy = cv2.findContours(mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

# Get bounding rectangles:
for c in contours:

    # Filter contour by area:
    blobArea = cv2.contourArea(c)
    maxArea = 100

    if blobArea > maxArea:

        # Approximate the contour to a polygon:
        contoursPoly = cv2.approxPolyDP(c, 3, True)
        # Get the polygon's bounding rectangle:
        boundRect = cv2.boundingRect(contoursPoly)

        # Get the dimensions of the bounding rect:
        rectX = boundRect[0]
        rectY = boundRect[1]
        rectWidth = boundRect[2]
        rectHeight = boundRect[3]

        # Draw rectangle:
        color = (0, 255, 0)
        cv2.rectangle(maskCopy, (int(rectX), int(rectY)), (int(rectX + rectWidth), int(rectY + rectHeight)), color, 3)

        cv2.imshow("Bounding Rectangle", maskCopy)
        cv2.waitKey(0)

This is the result:

Upvotes: 1

Related Questions