Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove hair from skin images using opencv?

I am working with recognition of skin spots. For this, I work with a number of images with different noises. One of these noises are the hairs, because I have images with hairs over the area of ​​the stain (ROI). How to decrease or remove these types of image noise?

The code below decreases the area where hairs are, but does not remove hairs that are above the area of ​​interest (ROI).

import numpy as np
import cv2

IMD = 'IMD436'
# Read the image and perfrom an OTSU threshold
img = cv2.imread(IMD+'.bmp')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh =     cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

# Remove hair with opening
kernel = np.ones((2,2),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)

# Combine surrounding noise with ROI
kernel = np.ones((6,6),np.uint8)
dilate = cv2.dilate(opening,kernel,iterations=3)

# Blur the image for smoother ROI
blur = cv2.blur(dilate,(15,15))

# Perform another OTSU threshold and search for biggest contour
ret, thresh =     cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
contours, hierarchy =     cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)

# Create a new mask for the result image
h, w = img.shape[:2]
mask = np.zeros((h, w), np.uint8)

# Draw the contour on the new mask and perform the bitwise operation
cv2.drawContours(mask, [cnt],-1, 255, -1)
res = cv2.bitwise_and(img, img, mask=mask)

# Display the result
cv2.imwrite(IMD+'.png', res)
cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()

enter image description here

Exit: enter image description here

How can I remove hair from the top of my region of interest?

Images used: enter image description here

enter image description here

enter image description here

like image 297
Carlos Diego Avatar asked Oct 17 '18 15:10

Carlos Diego


2 Answers

I am responding to your tag on a related post. As I understand you and another colege are working together on a project to locate the moles on the skin? Because I think I have already gave help to one or maybe both of you on similar questions and already mentioned that the removal of the hair is very tricky and difficult task. If you remove the hair on the image you lose information and you can't replace that part of the image (no program or alghorithm can guess what is under the hair - but it can make an estimation). What you could do as I mentioned in other posts and I think that it would be the best approach is to learn about deep neural networks and make your own for the hair removal. You can google "watermark removal deep neural network" and see what I mean. That being said, your code does not seem to extract all ROIs (the moles) you have given in the example image. I have made another example on how you can better extract the moles. Basically you should perform closing before transforming to binary and you will get better results.

For the second part - hair removal, if you do not wish to make a neural network, I think that alternative solution could be, that you calculate the mean pixel intesity of the region that contains the mole. Then iterate throug every pixel and make some sort of criteria on how much can the pixel differ from the mean. Hair seem to be presented with pixels that are darker than the mole area. So when you find the pixel, replace it with the neigbour pixel that does not fall in this criteria. In the example I have made a simple logic which will not work with every image but it can serve as an example. To make a fully operational solution you should make a better, more complex alghorithm which I guess will take quite some time. Hope it helps a bit! Cheers!

import numpy as np
import cv2
from PIL import Image

# Read the image and perfrom an OTSU threshold
img = cv2.imread('skin2.png')
kernel = np.ones((15,15),np.uint8)

# Perform closing to remove hair and blur the image
closing = cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel, iterations = 2)
blur = cv2.blur(closing,(15,15))

# Binarize the image
gray = cv2.cvtColor(blur,cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)


# Search for contours and select the biggest one
_, contours, hierarchy =     cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)

# Create a new mask for the result image
h, w = img.shape[:2]
mask = np.zeros((h, w), np.uint8)

# Draw the contour on the new mask and perform the bitwise operation
cv2.drawContours(mask, [cnt],-1, 255, -1)
res = cv2.bitwise_and(img, img, mask=mask)

# Calculate the mean color of the contour
mean = cv2.mean(res, mask = mask)
print(mean)

# Make some sort of criterion as the ratio hair vs. skin color varies
# thus makes it hard to unify the threshold.
# NOTE that this is only for example and it will not work with all images!!!

if mean[2] >182:
    bp = mean[0]/100*35
    gp = mean[1]/100*35
    rp = mean[2]/100*35   

elif 182 > mean[2] >160:
    bp = mean[0]/100*30
    gp = mean[1]/100*30
    rp = mean[2]/100*30

elif 160>mean[2]>150:
    bp = mean[0]/100*50
    gp = mean[1]/100*50
    rp = mean[2]/100*50

elif 150>mean[2]>120:
    bp = mean[0]/100*60
    gp = mean[1]/100*60
    rp = mean[2]/100*60

else:
    bp = mean[0]/100*53
    gp = mean[1]/100*53
    rp = mean[2]/100*53

# Write temporary image
cv2.imwrite('temp.png', res)

# Open the image with PIL and load it to RGB pixelpoints
mask2 = Image.open('temp.png')
pix = mask2.load()
x,y = mask2.size

# Itearate through the image and make some sort of logic to replace the pixels that
# differs from the mean of the image
# NOTE that this alghorithm is for example and it will not work with other images

for i in range(0,x):
    for j in range(0,y):
        if -1<pix[i,j][0]<bp or -1<pix[i,j][1]<gp or -1<pix[i,j][2]<rp:
            try:
                pix[i,j] = b,g,r
            except:
                pix[i,j] = (int(mean[0]),int(mean[1]),int(mean[2]))
        else:
            b,g,r = pix[i,j]

# Transform the image back to cv2 format and mask the result         
res = np.array(mask2)
res = res[:,:,::-1].copy()
final = cv2.bitwise_and(res, res, mask=mask)

# Display the result
cv2.imshow('img', final)
cv2.waitKey(0)
cv2.destroyAllWindows()

enter image description here

enter image description here

enter image description here

enter image description here

like image 64
kavko Avatar answered Sep 27 '22 16:09

kavko


You can try the following steps, at least to get a road map to the proper solution implementation:

  1. Find the hair region using adaptive local thresholding - Otsu's method or any other method. I think "local thresholding" or even "local histogram equalization and then global thresholding" will find the hair regions.
  2. To fill the hair regions, use "texture synthesis" to synthesize skin like texture for the hair region.

One good and easy method for texture synthesis is described in "A.A. Efros and T.K. Leung, Texture synthesis by non-parametric sampling', In Proceedings of the International Conference on Computer Vision (ICCV), Kerkyra, Greece, 1999". Texture synthesis will give a better result than averaging or median filtering to estimate the pixels in the hair region.

Also, take a look at this paper, it should help you a lot:

http://link.springer.com/article/10.1007%2Fs00521-012-1149-1?LI=true

like image 35
Abdul Rehman Avatar answered Sep 27 '22 16:09

Abdul Rehman