OpenCV, cv2.approxPolyDP() Draws double lines on closed contour - python

I want to create some polygons out of this mask:
image 1 - Mask
So i created these contours with openCV findcontours():
image 2 - Contours
When creating polygons I get these polygons:
image 3 - Polygons
As you can see some polygons are drawn using double lines. How do I prevent this?
See my code:
import glob
from PIL import Image
import cv2
import numpy as np
# Let's load
image = cv2.imread(path + "BigOneEnhanced.tif")
# Grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Find Canny edges
edged = cv2.Canny(gray, 30, 200)
# Finding Contours
contours, hierarchy = cv2.findContours(edged,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_L1)
canvas = np.zeros(image.shape, np.uint8)
# creating polygons from contours
polygonelist = []
for cnt in contours:
# define contour approx
perimeter = cv2.arcLength(cnt,True)
epsilon = 0.005*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
polygonelist.append(approx)
cv2.drawContours(canvas, polygonelist, -1, (255, 255, 255), 3)
imgB = Image.fromarray(canvas)
imgB.save(path + "TEST4.png")

The problem source is the Canny edges detection:
After applying edge detection you are getting two contours for every original contour - one outside the edge and one inside the edge (and other weird stuff).
You may solve it by applying findContours without using Canny.
Here is the code:
import glob
from PIL import Image
import cv2
import numpy as np
path = ''
# Let's load
#image = cv2.imread(path + "BigOneEnhanced.tif")
image = cv2.imread("BigOneEnhanced.png")
# Grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply threshold (just in case gray is not binary image).
ret, thresh_gray = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# Find Canny edges
#edged = cv2.Canny(gray, 30, 200)
# Finding Contours cv2.CHAIN_APPROX_TC89_L1
#contours, hierarchy = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contours, hierarchy = cv2.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
canvas = np.zeros(image.shape, np.uint8)
# creating polygons from contours
polygonelist = []
for cnt in contours:
# define contour approx
perimeter = cv2.arcLength(cnt, True)
epsilon = 0.005*perimeter #0.005*cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
polygonelist.append(approx)
cv2.drawContours(canvas, polygonelist, -1, (255, 255, 255), 3)
imgB = Image.fromarray(canvas)
imgB.save(path + "TEST4.png")
Result:

Related

opencv: how to merge near contours to get the one big outest contour?

I am trying to digitize the kid's drawing into SVG or transparent png file format so that they can be used in Scratch. The white paper should be replaced by transparent background and all the drawing part should be preserved.
My plan is to get the outest contour of the drawing and generate a mask, then use the mask to get the drawing part without paper background.
The problem is the drawing may not consecutive which means there may have some small holes leading to break the entire drawing contour to many many small contours.
Now I want to concatenate the near outest contours to form a big outest contour for masking.
The original drawing and the processed result is attached.
Code:
from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
import random as rng
rng.seed(12345)
def thresh_callback(val):
threshold = val
# Detect edges using Canny
canny_output = cv.Canny(src_gray, threshold, threshold * 2)
# Find contours
contours, hierarchy = cv.findContours(canny_output, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# Draw contours
drawing = np.zeros((canny_output.shape[0], canny_output.shape[1], 3), dtype=np.uint8)
for i in range(len(contours)):
color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256))
cv.drawContours(drawing, contours, i, color, 2, cv.LINE_8, hierarchy, 0)
# Show in a window
cv.imshow('Contours', drawing)
# Load source image
parser = argparse.ArgumentParser(description='Code for Finding contours in your image tutorial.')
parser.add_argument('--input', help='Path to input image.', default='IMG_4446.jpg')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
print('Could not open or find the image:', args.input)
exit(0)
# Convert image to gray and blur it
src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
src_gray = cv.blur(src_gray, (3,3))
# Create Window
source_window = 'Source'
cv.namedWindow(source_window)
cv.imshow(source_window, src)
max_thresh = 255
thresh = 100 # initial threshold
cv.createTrackbar('Canny Thresh:', source_window, thresh, max_thresh, thresh_callback)
thresh_callback(thresh)
cv.waitKey()
import cv2, numpy as np
# Read Image
img = cv2.imread('/home/stephen/Desktop/test_img.png')
img =cv2.resize(img, (750,1000))
# Find the gray image
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Gray
gray = cv2.blur(gray, (2,2))
cv2.imwrite('/home/stephen/Desktop/gray.png',gray)
# Find the canny image
canny = cv2.Canny(gray, 30, 150) # Canny
# Find contours
contours, _ = cv2.findContours(canny,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# Draw contours on canny (this connects the contours)
cv2.drawContours(canny, contours, -1, 255, 6)
cv2.imwrite('/home/stephen/Desktop/contours.png',canny)
# Get mask for floodfill
h, w = canny.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)
# Floodfill from point (0, 0)
cv2.floodFill(canny, mask, (0,0), 123)
cv2.imwrite('/home/stephen/Desktop/floodfill.png',canny)
# Exclude everying but the floodfill region
canny = cv2.inRange(canny, 122, 124)
cv2.imwrite('/home/stephen/Desktop/inrange.png',canny)

Finding all polygons in an image OpenCV and Python

I want to calculate the area of cultivated fields of agricultural land. For that, I want to find all polygons in the image. The process I have followed is as follow
Convert image to gray.
Apply bilateralFilter filter for smoothing
Apply dilate function to connect lines
Apply erode function to fine the lines and edges
Apply canny for edge detection
Find contours
Find those contours which have area greater than some threshold
Draw contour.
The problem is that I am not able to find all the polygons. I am missing some polygons and the test image is simplest one. The complex test image can have more missing polygons. Can anyone help me in this regard.
Code is here
import cv2
import numpy as np
from scipy import misc
from scipy.ndimage import gaussian_filter
from scipy.signal import medfilt2d
import random
image = cv2.imread('img3.jpeg')
image = cv2.bilateralFilter(image, 15, 80, 80,None)
cv2.imshow('smoth', image)
cv2.waitKey(0)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow('gray', gray)
cv2.waitKey(0)
# Find Canny edges
# edged = cv2.Canny(gray, 30, 200)
thr1=50
thr2=200
kernel = np.ones((5,5 ),np.float32)/49
# gray = cv2.filter2D(gray,-1,kernel)
# kernel = np.ones((3,3), np.uint8)
gray = cv2.dilate(gray, kernel, iterations=3)
cv2.imshow('Edged dilate', gray)
cv2.waitKey(0)
gray = cv2.erode(gray, kernel, iterations=1)
cv2.imshow('Edged erode', gray)
cv2.waitKey(0)
edged = cv2.Canny(gray, thr1, thr2)
cv2.imshow("CannyImg_"+str(thr1) + "_" + str(thr2), edged)
cv2.waitKey(0)
kernel = np.ones((3,3), np.uint8)
contours, hierarchy = cv2.findContours(edged,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print("Number of Contours found = " + str(len(contours)))
cntFound = 0
finalCnt = []
for cnt in contours :
area = cv2.contourArea(cnt)
print("area",area)
# Shortlisting the regions based on there area.
if area > 100:
# approx = cv2.approxPolyDP(cnt,
# 0.009 * cv2.arcLength(cnt, True), True)
approx = cv2.approxPolyDP(cnt,0.001 * cv2.arcLength(cnt, True), True)
M = cv2.moments(cnt)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
# Checking if the no. of sides of the selected region is 7.
# if(len(approx) == 7):
r=random.randint(0,255)
g=random.randint(0,255)
b=random.randint(0,255)
cv2.drawContours(image, [approx], -1, (r, g, b), 3)
cv2.putText(image, str(cntFound), (cX - 20, cY - 20),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (r, g, b), 2)
cntFound = 1 + cntFound
finalCnt.append(cnt)
print("Total found after area threshold = ", cntFound)
cv2.imshow('Contours', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
The test image is
and the result is shown in the below picture
Edit 1:
After adding Otsu's thresholding the results are a little better on the test image. The results are as follow
But with a different image, the results become so bad. The results are shown below. The left side is the original image and the right side is the result
Any suggestion or opinion is welcome.

What kind of parameters should I use to find and crop objects in an image?

I am new to deep learning and try to implement a ML algorithm for image clustering. The problem is that I can't crop the objects in an image in Python using OpenCV.
Here is the code I have implemented and it works for some objects if the color of the object is very different(in RGB values) from the background but it doesn't work for the image I need for ML algorithm. What kind of parameters should I have/change? Any suggestions?
import cv2
import numpy as np
from PIL import Image
import tkinter as tk
from tkinter import filedialog as fd
from tkinter import*
import random
#!/usr/bin/python
from PIL import Image
import sys
myFile = 'Path' + '/crop.png'
nr_of_im = 1
q = 0
r = 0
x_list = []
y_list = []
img = cv2.imread(myFile, cv2.IMREAD_UNCHANGED)
ret, thresh = cv2.threshold(cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY) , 30, 255, cv2.THRESH_BINARY)
contours, hier = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
print("len",len(contours))
if cv2.contourArea(contour) > 80:
x, y, w, h = cv2.boundingRect(contour)
q = w
r = h
x_list.append(x)
y_list.append(y)
font = cv2.FONT_HERSHEY_SIMPLEX
ROI = img[y-10:y+10+h, x-10:x+10+w]
ROI = cv2.resize(ROI,(300,300))
file_all = "/images/%d.jpg"%nr_of_im
nr_of_im += 1
cv2.imwrite(file_all,ROI)
There are 21 objects in the image but the length of contours returns 1. The image looks like so
crop.png:
Your threshold is too low and produces a totally white image for me. You need to increase your threshold. Always view your thresholding to be sure it is working the way you expect. You can always remove the viewing later.
The following works for me using Otsu thresholding with a threshold value of 97. I get 21 contours.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('blocks.jpg')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# threshold
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
print(ret)
# apply morphology fill and separate large regions and remove small ones
kernel = cv2.getStructuringElement(cv2.MORPH_RECT , (9,9))
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT , (15,15))
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)
# get contours
result = img.copy()
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
# get count of contours
print(len(contours))
# draw bounding boxes on contours
for cntr in contours:
x,y,w,h = cv2.boundingRect(cntr)
cv2.rectangle(result, (x, y), (x+w, y+h), (0, 0, 255), 2)
#print("x,y,w,h:",x,y,w,h)
# save results
cv2.imwrite("blocks_thresh.jpg", thresh)
cv2.imwrite("blocks_morphology.jpg", morph)
cv2.imwrite("blocks_bboxes.jpg", result)
# show thresh and result
cv2.imshow("thresh", thresh)
cv2.imshow("morph", morph)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold image:
Morphology cleaned image:
Resulting bounding boxes from contours:

CV2 Find Contours detecting the floor and not my target

I am trying to detect the black tape on the floor, but whenever I try to increase the threshold to stop it from detecting the ground all it does is stop detecting the tape.
from cv2 import cv2
import numpy as np
img = cv2.imread('tape4.jpeg', cv2.IMREAD_UNCHANGED)
#convert img to grey
img_grey = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#set a thresh
thresh = 100
#get threshold image
ret,thresh_img = cv2.threshold(img_grey, thresh, 255, cv2.THRESH_BINARY)
#find contours
contours, hierarchy = cv2.findContours(thresh_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#create an empty image for contours
img_contours = np.zeros(img.shape)
# draw the contours on the empty image
cv2.drawContours(img_contours, contours, -1, (0,255,0), 3)
#save image
cv2.imwrite('contours.png',img_contours)
You may use Use cv2.THRESH_BINARY_INV instead of cv2.THRESH_BINARY for finding pixels below thresh instead of pixels above thresh.
The reason you are detecting the floor, is that cv2.threshold marks (with 255 value), the pixels above thresh.
You want to mark the dark pixels below thresh.
You can compute: thresh_img = 255 - thresh_img, or use cv2.THRESH_BINARY_INV (result is the same).
I also recommend using closing morphological operation, for removing clutter.
Code for finding the tape:
import cv2
import numpy as np
img = cv2.imread('tape4.jpeg', cv2.IMREAD_UNCHANGED)
#convert img to grey
img_grey = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#set a thresh
thresh = 20 # Set thresh to very low value (the tape is almost black).
#get threshold image
# Use cv2.THRESH_BINARY_INV instead of cv2.THRESH_BINARY for finding pixels below thresh instead of pixels above thresh
ret,thresh_img = cv2.threshold(img_grey, thresh, 255, cv2.THRESH_BINARY_INV)
# Apply closing morphological operation
thresh_img = cv2.morphologyEx(thresh_img, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (21,21)));
#find contours
contours, hierarchy = cv2.findContours(thresh_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#create an empty image for contours
img_contours = np.zeros(img.shape)
# draw the contours on the empty image
cv2.drawContours(img_contours, contours, -1, (0,255,0), 3)
#save image
cv2.imwrite('contours.png',img_contours)
Result:
For finding the tape, you can look for the largest contour within contours:
# Get contour with maximum area
c = max(contours, key=cv2.contourArea)
img_contours = np.zeros(img.shape)
cv2.drawContours(img_contours, [c], -1, (0,255,0), 3)
Result:

Detecting vertical lines using Hough transforms in opencv

I'm trying to remove the square boxes(vertical and horizontal lines) using Hough transform in opencv (Python). The problem is none of the vertical lines are being detected. I've tried looking through contours and hierarchy but there are too many contours in this image and I'm confused how to use them.
After looking through related posts, I've played with the threshold and rho parameters but that didn't help.
I've attached the code for more details. Why does Hough transform not find the vertical lines in the image?. Any suggestions in solving this task are welcome. Thanks.
Input Image :
Hough transformed Image:
Drawing contours:
import cv2
import numpy as np
import pdb
img = cv2.imread('/home/user/Downloads/cropped/robust_blaze_cpp-300-0000046A-02-HW.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 140, 255, 0)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, contours, -1, (0,0,255), 2)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
minLineLength = 5
maxLineGap = 100
lines = cv2.HoughLinesP(edges,rho=1,theta=np.pi/180,threshold=100,minLineLength=minLineLength,maxLineGap=maxLineGap)
for x1,y1,x2,y2 in lines[0]:
cv2.line(img,(x1,y1),(x2,y2),(0,255,0),2)
cv2.imwrite('probHough.jpg',img)
To be honest, rather than looking for the lines, I'd instead look for the white boxes.
Preparation
import cv2
import numpy as np
Load the image
img = cv2.imread("digitbox.jpg", 0)
Binarize it, so that both the boxes and the digits are black, rest is white
_, thresh = cv2.threshold(img, 200, 255, cv2.THRESH_BINARY)
cv2.imwrite('digitbox_step1.png', thresh)
Find contours. In this example image, it's fine to just look for external contours.
_, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
Process the contours, filtering out any with too small an area. Find convex hull of each contour, create a mask of all areas outside the contour. Store the bounding boxes of each found contour, sorted by x coordinate.
mask = np.ones_like(img) * 255
boxes = []
for contour in contours:
if cv2.contourArea(contour) > 100:
hull = cv2.convexHull(contour)
cv2.drawContours(mask, [hull], -1, 0, -1)
x,y,w,h = cv2.boundingRect(contour)
boxes.append((x,y,w,h))
boxes = sorted(boxes, key=lambda box: box[0])
cv2.imwrite('digitbox_step2.png', mask)
Dilate the mask (to shrink the black parts), to clip off any remains the the gray frames.
mask = cv2.dilate(mask, np.ones((5,5),np.uint8))
cv2.imwrite('digitbox_step3.png', mask)
Fill all the masked pixels with white, to erase the frames.
img[mask != 0] = 255
cv2.imwrite('digitbox_step4.png', img)
Process the digits as you desire -- i'll just draw the bounding boxes.
result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
for n,box in enumerate(boxes):
x,y,w,h = box
cv2.rectangle(result,(x,y),(x+w,y+h),(255,0,0),2)
cv2.putText(result, str(n),(x+5,y+17), cv2.FONT_HERSHEY_SIMPLEX, 0.6,(255,0,0),2,cv2.LINE_AA)
cv2.imwrite('digitbox_step5.png', result)
The whole script in one piece:
import cv2
import numpy as np
img = cv2.imread("digitbox.jpg", 0)
_, thresh = cv2.threshold(img, 200, 255, cv2.THRESH_BINARY)
_, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
mask = np.ones_like(img) * 255
boxes = []
for contour in contours:
if cv2.contourArea(contour) > 100:
hull = cv2.convexHull(contour)
cv2.drawContours(mask, [hull], -1, 0, -1)
x,y,w,h = cv2.boundingRect(contour)
boxes.append((x,y,w,h))
boxes = sorted(boxes, key=lambda box: box[0])
mask = cv2.dilate(mask, np.ones((5,5),np.uint8))
img[mask != 0] = 255
result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
for n,box in enumerate(boxes):
x,y,w,h = box
cv2.rectangle(result,(x,y),(x+w,y+h),(255,0,0),2)
cv2.putText(result, str(n),(x+5,y+17), cv2.FONT_HERSHEY_SIMPLEX, 0.6,(255,0,0),2,cv2.LINE_AA)
cv2.imwrite('digitbox_result.png', result)

Categories

Resources