Extract external contour or silhouette of image in Python - python

I want to extract the silhouette of an image, and I'm trying to do it using the contour function of MatplotLib. This is my code:
from PIL import Image
from pylab import *
# read image to array
im = array(Image.open('HOJA.jpg').convert('L'))
# create a new figure
figure()
# show contours with origin upper left corner
contour(im, origin='image')
axis('equal')
show()
This is my original image:
And this is my result:
But I just want to show the external contour, the silhouette. Just the read lines in this example.
How can I do it? I read the documentation of the contour function, but I can't get what I want.
If you know a better way to do this in Python, please tell me! (MatplotLib, OpenCV, etc.)

If you want to stick with your contour approach you can simply add a levels argument with a value 'thresholding' the image between the white background and the leaf.
You could use the histogram to find an appropriate value. But in this case any value slightly lower than 255 will do.
So:
contour(im, levels=[245], colors='black', origin='image')
Make sure you checkout Scikit-Image if you want to do some serious image processing. It contains several edge detection algoritms etc.
http://scikit-image.org/docs/dev/auto_examples/

For those who want the OpenCV solution, here it is:
ret,thresh = cv2.threshold(image,245,255,0)
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
tam = 0
for contorno in contours:
if len(contorno) > tam:
contornoGrande = contorno
tam = len(contorno)
cv2.drawContours(image,contornoGrande.astype('int'),-1,(0,255,0),2)
cv2.imshow('My image',image)
cv2.waitKey()
cv2.destroyAllWindows()
In this example, I only draw the biggest contour. Remember that 'image' must be a single-channel array.
You should change the parameters of the threshold function, the findContours function and the drawContours function to get what you want.
threshold Documentation
findContours Documentation
drawContours Documentation
I do the conversion to 'int' in the drawContours function because there is a bug in the Open CV 2.4.3 version, and if you don't do this conversion, the program breaks.
This is the bug.

I would recommand using OpenCV for performance.
It has a findContour functions accessible from python using the cv2 binding.
This function can be set to return only the external contour.
You will have to threshold your image as well.

Related

How do I crop multiple images from one picture based on the background color?

So I have an image and I want to cut it up into multiple images to feed into OCR to read.
image example
I only want the messages with the white bubbles and exclude anything with the grey bubbles. I can't figure out how to make a loop to separate each white bubble.
import numpy as np
from PIL import ImageGrab, Image, ImageFilter
img = Image.open('test1.png').convert('RGB')
na = np.array(img)
orig = na.copy()
img = img.filter(ImageFilter.MedianFilter(3))
whiteY, whiteX = np.where(np.all(na==[255,255,255],axis=2))
top, bottom = whiteY[1], whiteY[-1]
left, right = whiteX[1], whiteX[-1]
You could try using the opencv threshold function, followed by the findContours function. This will, if you threshold the image correctly, give you the 'borders' of the bubbles above. Using that, you could then crop out each text bubble.
Here's a simple example of contours being used:
https://www.geeksforgeeks.org/find-and-draw-contours-using-opencv-python/
Otherwise if you'd like to understand better how the opencv functions I mentioned or those that are used in the article above, have a look at the opencv documentation.

How can I calculate the perimeter of an object in an image?

I have an image. I want to get the perimeter of every object in my image. For example, in this image , the perimeter of an object is 33 (the number of pixels at its edges).
I have written the following algorithm, but it is very timely.
Does anyone have an idea to increase the speed of the algorithm?
What I have tried:
def cal_perimeter_object(object, image):
peri_ = 0
for pixel_ in image:
if pixel_is_in_neigbor_of_object() is True:
peri_ += 1
return peri_
As mentioned in the comment by #Piinthesky having a boolean (or labelled image) where you know the label for the object you want to find the contour for is the first step. There are a number of ways of doing this, the simplest of which is thresholding. Once you have your labelled image you can find the perimeter in a number of ways - e.g. the number of pixels along the border. To give you a head start here is a way to do it on the image you put in the link. I have used scikit-image but there are other python libraries you may use.
# If your python version is not 3.x uncomment line below
#from __future__ import print_function
from skimage.measure import label, regionprops
import skimage.io as io
# read in the image (enter the path where you downloaded it on your computer below
im = io.imread('/home/kola/Downloads/perimeter.png')
# To simplify things I am only using the first channel and thresholding
# to get a boolean image
bw = im[:,:,0] > 230
regions = regionprops(bw.astype(int))
print(regions[0].perimeter)

Blob detection using OpenCV

I am trying to do some white blob detection using OpenCV. But my script failed to detect the big white block which is my goal while some small blobs are detected. I am new to OpenCV, and am i doing something wrong when using simpleblobdetection in OpenCV? [Solved partially, please read below]
And here is the script:
#!/usr/bin/python
# Standard imports
import cv2
import numpy as np;
from matplotlib import pyplot as plt
# Read image
im = cv2.imread('whiteborder.jpg', cv2.IMREAD_GRAYSCALE)
imfiltered = cv2.inRange(im,255,255)
#OPENING
kernel = np.ones((5,5))
opening = cv2.morphologyEx(imfiltered,cv2.MORPH_OPEN,kernel)
#write out the filtered image
cv2.imwrite('colorfiltered.jpg',opening)
# Setup SimpleBlobDetector parameters.
params = cv2.SimpleBlobDetector_Params()
params.blobColor= 255
params.filterByColor = True
# Create a detector with the parameters
ver = (cv2.__version__).split('.')
if int(ver[0]) < 3 :
detector = cv2.SimpleBlobDetector(params)
else :
detector = cv2.SimpleBlobDetector_create(params)
# Detect blobs.
keypoints = detector.detect(opening)
# Draw detected blobs as green circles.
# cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures
# the size of the circle corresponds to the size of blob
print str(keypoints)
im_with_keypoints = cv2.drawKeypoints(opening, keypoints, np.array([]), (0,255,0), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# Show blobs
##cv2.imshow("Keypoints", im_with_keypoints)
cv2.imwrite('Keypoints.jpg',im_with_keypoints)
cv2.waitKey(0)
EDIT:
By adding a bigger value of area maximum value, i am able to identify a big blob but my end goal is to identify the big white rectangle exist or not. And the white blob detection i did returns not only the rectangle but also the surrounding areas as well. [This part solved]
EDIT 2:
Based on the answer from #PSchn, i update my code to apply the logic, first set the color filter to only get the white pixels and then remove the noise point using opening. It works for the sample data and i can successfully get the keypoint after blob detection.
If you just want to detect the white rectangle you can try to set a higher threshold, e.g. 253, erase small object with an opening and take the biggest blob. I first smoothed your image, then thresholding it:
and the opening:
now you just have to use findContours and take the boundingRect. If your rectangle is always that white it should work. If you get lower then 251 with your threshold the other small blobs will appear and your region merges with them, like here:
Then you could still do an opening several times and you get this:
But i dont think that it is the fastest idea ;)
You could try setting params.maxArea to something obnoxiously large (somewhere in the tens of thousands): the default may be something lower than the area of the rectangle you're trying to detect. Also, I don't know how true this is or not, but I've heard that detection by color is bugged with a logic error, so it may be worth a try disabling it just in case that is causing problems (this has probably been fixed in later versions, but it could still be worth a try)

Deciding parameters in HoughCircles method of OpenCV

I am trying to detect circles in an image, and am using OpenCV Python for the same. I am facing problems when I use the HoughCircles method. I am using the following custom image , but my code is unable to detect both circles.
I tried the following implementation
circles = cv2.HoughCircles(thresh1,cv2.cv.CV_HOUGH_GRADIENT,2,1,param1=100,param2=100,minRadius=0,maxRadius=1000)
and this is only properly detecting the bigger circle in the image. I'm pretty sure if I tinker around with the parameters , I might hit upon a combination that works, but is there any way I can calculate, or figure out the parameters, given an image?
EDIT
Here is the entire code that I have written:
import cv2
import numpy as np
def show(s , i):
cv2.imshow(s , i)
cv2.waitKey(0)
cv2.destroyAllWindows()
img = cv2.imread('ball2.jpg')
show("img" , img)
img = cv2.medianBlur(img,5)
cimg = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh1 = cv2.threshold(cimg,10,255,cv2.THRESH_BINARY)
show('thresh' , thresh1)
circles = cv2.HoughCircles(thresh1,cv2.cv.CV_HOUGH_GRADIENT,2,1,param1=100,param2=100,minRadius=0,maxRadius=1000)
print circles
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(thresh1,(i[0],i[1]),i[2],(100,150,120),2)
# draw the center of the circle
cv2.circle(thresh1,(i[0],i[1]),2,(0,0,0),3)
cv2.imshow('detected circles',thresh1)
cv2.waitKey(0)
cv2.destroyAllWindows()
Have you seen http://www.pyimagesearch.com/2014/07/21/detecting-circles-images-using-opencv-hough-circles/?
Author there suggests to tinker with minDist, as the most important parameter, but you have that set to 1, so rather we should expect false positive than not found circles.
I suggest also to increase param1 to 200 to set upper threshold for the internal Canny edge detector for increased detection.
Also I found some people reported weird anomaly, where increasing maxradius resulted in getting fewer circles. Sometimes it's good idea to leave optional parameters as default (value 0).
From my experience with openCV it often ends up with tinkering parameters to get best results.

Using PIL to fill empty image space with nearby colors (aka inpainting)

I create an image with PIL:
I need to fill in the empty space (depicted as black). I could easily fill it with a static color, but what I'd like to do is fill the pixels in with nearby colors. For example, the first pixel after the border might be a Gaussian blur of the filled-in pixels. Or perhaps a push-pull type algorithm described in The Lumigraph, Gortler, et al..
I need something that is not too slow because I have to run this on many images. I have access to other libraries, like numpy, and you can assume that I know the borders or a mask of the outside region or inside region. Any suggestions on how to approach this?
UPDATE:
As suggested by belisarius, opencv's inpaint method is perfect for this. Here's some python code that uses opencv to achieve what I wanted:
import Image, ImageDraw, cv
im = Image.open("u7XVL.png")
pix = im.load()
#create a mask of the background colors
# this is slow, but easy for example purposes
mask = Image.new('L', im.size)
maskdraw = ImageDraw.Draw(mask)
for x in range(im.size[0]):
for y in range(im.size[1]):
if pix[(x,y)] == (0,0,0):
maskdraw.point((x,y), 255)
#convert image and mask to opencv format
cv_im = cv.CreateImageHeader(im.size, cv.IPL_DEPTH_8U, 3)
cv.SetData(cv_im, im.tostring())
cv_mask = cv.CreateImageHeader(mask.size, cv.IPL_DEPTH_8U, 1)
cv.SetData(cv_mask, mask.tostring())
#do the inpainting
cv_painted_im = cv.CloneImage(cv_im)
cv.Inpaint(cv_im, cv_mask, cv_painted_im, 3, cv.CV_INPAINT_NS)
#convert back to PIL
painted_im = Image.fromstring("RGB", cv.GetSize(cv_painted_im), cv_painted_im.tostring())
painted_im.show()
And the resulting image:
A method with nice results is the Navier-Stokes Image Restoration. I know OpenCV has it, don't know about PIL.
Your example:
I did it with Mathematica.
Edit
As per your reuquest, the code is:
i = Import["http://i.stack.imgur.com/uEPqc.png"];
Inpaint[i, ColorNegate#Binarize#i, Method -> "NavierStokes"]
The ColorNegate# ... part creates the replacement mask.
The filling is done with just the Inpaint[] command.
Depending on how you're deploying this application, another option might be to use the Gimp's python interface to do the image manipulation.
The doc page I linked to is oriented more towards writing GIMP plugins in python, rather than interacting with a background gimp instance from a python app, but I'm pretty sure that's also possible (it's been a while since I played with the gimp/python interface, I'm a little hazy).
You can also create the mask with the function CreateImage(), for instance:
inpaint_mask = cv.CreateImage(cv.GetSize(im), 8, 1)

Categories