Remove background of scanned foot

Hello,

i’m new in this forum, and i hope someone can help me.
I have tried a lot of tutorials and also a lot of python code examples to get a good result.

I have a scanner, to scan feet. The result looks like that:
Sometimes the quality is better or worse. Depending on the light.

I need a withe background and i need to find the extreme points of each foot.

It would be so nice, if someone can help me.

best regards
Dominik

you should make the first… step. show your attempts. show what you’ve learned in your studies of image processing and computer vision. there are resources out there. do not expect people to recite books to you.

ok, no problem. I can do this…

import sys
import cv2
import numpy as np

n = len(sys.argv)

test = '...\Simulation\Scan.bmp' #sys.argv[1]
outputPath = '...\Simulation'
              #sys.argv[2]

# load image
img = cv2.imread(test)

# convert to graky
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# threshold input image as mask
mask = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY_INV)[1]

# negate mask
mask = 255 - mask

# apply morphology to remove isolated extraneous noise
# use borderconstant of black since foreground touches the edges
kernel = np.ones((3,3), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

# anti-alias the mask -- blur then stretch
# blur alpha channel
mask = cv2.GaussianBlur(mask, (0,0), sigmaX=2, sigmaY=2, borderType = cv2.BORDER_DEFAULT)

mask = (2*(mask.astype(np.float32))-255.0).clip(0,255).astype(np.uint8)

# put mask into alpha channel
result = img.copy()
result = cv2.cvtColor(result, cv2.COLOR_BGR2BGRA)
result[:, :, 3] = mask

#make mask of where the transparent bits are
trans_mask = result[:,:,3] == 0

#replace areas of transparency with white
result[trans_mask] = [255, 255, 255, 255]

#new image without alpha channel...
new_img = cv2.cvtColor(result, cv2.COLOR_BGRA2BGR)

# Find contours
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
c = max(cnts, key=cv2.contourArea)

cv2.drawContours(new_img, [c], -1, (255, 0, 0), 5)
cv2.circle(new_img, left, 8, (0, 50, 255), 15)
cv2.circle(new_img, right, 8, (0, 255, 255), 15)
cv2.circle(new_img, top, 8, (255, 50, 0), 15)
cv2.circle(new_img, bottom, 8, (255, 255, 0), 15)

# save resulting masked image
cv2.imwrite('test.jpg', new_img)

# display result, though it won't show transparency
##cv2.imshow("output", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

The result ist that:

Problems:

  1. The quality of background removal is not the best.
  2. The contour is only on the left side. Sure, i did the code accordingly, but i don’t know how to change, that it is on both feet.
  3. The contour quality is also not the best.

I hope someone can give me some ideas.

BR/ Dominik

you can use the boundingRect function on a contour:

https://docs.opencv.org/master/dd/d49/tutorial_py_contour_features.html

beyond that… you get possibly multiple contours. explore the list of contours you get. I’m sure the second foot is there.

Thank you for your answer. It is not clear for me why i should use the boundRec funktion. As is understand, it returs me a rectangle arround each foot. I need the exact contour.

Could be, that i have both contours in the list, but how can i access them to get the extreme points??

just saying, findContours expects white things on black bg,
seems you got it the opposite way

alright, those aren’t determined by the bounding box as such… but they are the minimal and maximal points with respect to x or y, of all the points of a contour.

so you take a contour and you use python’s min or max with an appropriate key= predicate

you want to isolate each foot’s contour and warp it straight upright, before taking any measurement

Now im a little bit confused.

I already use the max function. As you said, i have a lot of contours in my array. So i need to find also the second max contour.

Can someone give a hint how to to that?

The next problem is, that the background is not really perfect. Can someone help me wirh that too??

Thank you

you must sort the contours by area and take last two.

simple threshold does most of the job but I almost always use blur “before” threshold to get a smoother mask.

Edit: I also create a clean mask and fill the contours I picked on this clean mask, it’s a good way to clean noise.

import cv2
import numpy as np

def main():
	img_bgr = cv2.imread('feet.jpg')
	img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)

	img = cv2.blur(img, (7,7))
	_, img = cv2.threshold(img, 127, 255, cv2.THRESH_OTSU)

	mask = np.zeros_like(img)
	cont, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
	sort = np.argsort([cv2.contourArea(x) for x in cont])
	i, j = sort[-2:]

	cv2.fillPoly(mask, pts=[cont[i]], color=255)
	cv2.fillPoly(mask, pts=[cont[j]], color=255)

	bg = np.where(mask[...,None] == 0, 255, img_bgr)

	cv2.imwrite('mask.png', bg)

main()

Thanks a lot dodo. The result looks perfect.
Now i need the extreme points of each foot. Top/bottom/left/right.
My solutions for that is not working any longer.

you have the contours, the job is finding the edge with smallest x, largest x, smallest y and largest y. those 4 edges are the extreme points.

import cv2
import numpy as np

def get_extreme(cont):
	cont = cont[:,0,:]
	x, y = cont[...,0], cont[...,1]
	left, right = cont[x.argmin()], cont[x.argmax()]
	top, bottom = cont[y.argmin()], cont[y.argmax()]
	return top, bottom, left, right

def main():
	img_bgr = cv2.imread('feet.jpg')
	img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)

	img = cv2.blur(img, (7,7))
	_, img = cv2.threshold(img, 127, 255, cv2.THRESH_OTSU)

	cont, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
	sort = np.argsort([cv2.contourArea(x) for x in cont])
	i, j = sort[-2:]

	mask = np.zeros_like(img)
	cv2.fillPoly(mask, pts=[cont[i]], color=255)
	cv2.fillPoly(mask, pts=[cont[j]], color=255)

	bg = np.where(mask[...,None] == 0, 255, img_bgr)

	for cont in [cont[i], cont[j]]:
		for p in get_extreme(cont):
			cv2.circle(bg, p, 2, (255,0,0), 2)

	cv2.imwrite('mask.png', bg)

main()

1 Like

Perfect!!! thank you very much dodo!