Finding all polygons in an image OpenCV and Python - 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.

Related

Identify and count objects different from background

I try to use python, NumPy, and OpenCV to analyze the image below and just draw a circle on each object found. The idea here is not to identify the bug only identify any object that is different from the background.
Original Image:
Here is the code that I'm using.
import cv2
import numpy as np
img = cv2.imread('per.jpeg', cv2.IMREAD_GRAYSCALE)
if cv2.__version__.startswith('2.'):
detector = cv2.SimpleBlobDetector()
else:
detector = cv2.SimpleBlobDetector_create()
keypoints = detector.detect(img)
print(len(keypoints))
imgKeyPoints = cv2.drawKeypoints(img, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
status = cv2.imwrite('teste.jpeg',imgKeyPoints)
print("Image written to file-system : ",status)
But the problem is that I'm getting only a greyscale image as result without any counting or red circle, as shown below:
Since I'm new to OpenCV and object recognition world I'm not able to identify what is wrong, and any help will be very appreciated.
Here is one way in Python/OpenCV.
Threshold on the bugs color in HSV colorspace. Then use morphology to clean up the threshold. Then get contours. Then find the minimum enclosing circle around each contour. Then bias the radius to make a bit larger and draw the circle around each bug.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('bugs.jpg')
# convert image to hsv colorspace
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# threshold on bugs color
lower=(0,90,10)
upper=(100,250,170)
thresh = cv2.inRange(hsv, lower, upper)
# apply morphology to clean up
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (6,6))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# get external contours
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
result = img.copy()
bias = 10
for cntr in contours:
center, radius = cv2.minEnclosingCircle(cntr)
cx = int(round(center[0]))
cy = int(round(center[1]))
rr = int(round(radius)) + bias
cv2.circle(result, (cx,cy), rr, (0, 0, 255), 2)
# save results
cv2.imwrite('bugs_threshold.jpg', thresh)
cv2.imwrite('bugs_cleaned.jpg', morph)
cv2.imwrite('bugs_circled.jpg', result)
# display results
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image:
Morphology Cleaned Image:
Resulting Circles:

How calculate the area of irregular object in an image (opencv)?

So I have this image:
And I need calculate area of an especific part, so I made this code:
# packages
from imutils import perspective
from imutils import contours
import numpy as np
import imutils
import cv2
image = cv2.imread('image1.jpg')
# load the image, convert it to grayscale, and blur it slightly
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
edged = cv2.Canny(gray, 80, 150)
edged = cv2.dilate(edged, None, iterations=1)
edged = cv2.erode(edged, None, iterations=1)
# find contours
cnts = cv2.findContours(edged.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# sort the contours from left-to-right and initialize the
(cnts, _) = contours.sort_contours(cnts)
pixelsPerMetric = None
# draw the necessary contour
cv2.drawContours(image, max(cnts, key=cv2.contourArea), -1, (0, 0, 255), 5)
cv2.imshow("Image", image)
cv2.waitKey(0)
And I obtained this image:
And I would like to measure the area (mm²) with red contourn. As reference I have the coin (572.55 mm), and other question. Is it possible measure the red and black proportion inside the red contourn. Some suggestion.
I would go like this. Essentially:
in the first part you will work on the external contours: you will find the coin and the slice external borders
the coin area will help you measure the area in cm^2 of the entire slice, if you want it in mm^2 you can multiply that value by 100
at that point you just need to quantify the lean part of the slice, which was inspired by this great snippet and fmw42's great comment above
the fat part of the slice can be found by difference, i.e. with my values I get that 57.71% of the slice is lean, so the remaining 42.29% is fat
if you don't want the entire lean part as in this snippet just calculate the area of your red contour, you seem ready to do it: keep in mind that the slice would be fatter than what I calculated here
Without further ado, here is the code:
import cv2
import numpy as np
import math
# input image
path = "image1.jpg"
# 1 EUR coin diameter in cm
coinDiameter = 2.325
# real area for the coin in cm^2
coinArea = (coinDiameter/2)**2 * math.pi
# initializing the multiplying factor for real size
realAreaPerPixel = 1
# pixel to cm^2
def pixelToArea(objectSizeInPixel, coinSizeInPixel):
# how many cm^2 per pixel?
realAreaPerPixel = coinArea / coinSizeInPixel
print("realAreaPerPixel: ", realAreaPerPixel)
# object area in cm^2
objectArea = realAreaPerPixel * objectSizeInPixel
return objectArea
# finding coin and steak contours
def getContours(img, imgContour):
# find all the contours from the B&W image
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# needed to filter only our contours of interest
finalContours = []
# for each contour found
for cnt in contours:
# cv2.drawContours(imgContour, cnt, -1, (255, 0, 255), 2)
# find its area in pixel
area = cv2.contourArea(cnt)
print("Detected Contour with Area: ", area)
# minimum area value is to be fixed as the one that leaves the coin as the small object on the scene
if (area > 5000):
perimeter = cv2.arcLength(cnt, True)
# smaller epsilon -> more vertices detected [= more precision]
epsilon = 0.002*perimeter
# check how many vertices
approx = cv2.approxPolyDP(cnt, epsilon, True)
#print(len(approx))
finalContours.append([len(approx), area, approx, cnt])
# we want only two objects here: the coin and the meat slice
print("---\nFinal number of External Contours: ", len(finalContours))
# so at this point finalContours should have only two elements
# sorting in ascending order depending on the area
finalContours = sorted(finalContours, key = lambda x:x[1], reverse=False)
# drawing contours for the final objects
for con in finalContours:
cv2.drawContours(imgContour, con[3], -1, (0, 0, 255), 3)
return imgContour, finalContours
# sourcing the input image
img = cv2.imread(path)
cv2.imshow("Starting image", img)
cv2.waitKey()
# blurring
imgBlur = cv2.GaussianBlur(img, (7, 7), 1)
# graying
imgGray = cv2.cvtColor(imgBlur, cv2.COLOR_BGR2GRAY)
# canny
imgCanny = cv2.Canny(imgGray, 255, 195)
kernel = np.ones((2, 2))
imgDil = cv2.dilate(imgCanny, kernel, iterations = 3)
# cv2.imshow("Diluted", imgDil)
imgThre = cv2.erode(imgDil, kernel, iterations = 3)
imgFinalContours, finalContours = getContours(imgThre, img)
# first final contour has the area of the coin in pixel
coinPixelArea = finalContours[0][1]
print("Coin Area in pixel", coinPixelArea)
# second final contour has the area of the meat slice in pixel
slicePixelArea = finalContours[1][1]
print("Entire Slice Area in pixel", slicePixelArea)
# let's go cm^2
print("Coin Area in cm^2:", coinArea)
print("Entire Slice Area in cm^2:", pixelToArea(slicePixelArea, coinPixelArea))
# show the contours on the unfiltered starting image
cv2.imshow("Final External Contours", imgFinalContours)
cv2.waitKey()
# now let's detect and quantify the lean part
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# set lower and upper color limits
lowerVal = np.array([0, 159, 160])
upperVal = np.array([101, 255, 253])
# Threshold the HSV image to get only red colors
mask = cv2.inRange(hsv, lowerVal, upperVal)
# apply mask to original image - this shows the red with black blackground
final = cv2.bitwise_and(img, img, mask= mask)
# show selection
cv2.imshow("Lean Cut", final)
cv2.waitKey()
# convert it to grayscale because countNonZero() wants 1 channel images
gray = cv2.cvtColor(final, cv2.COLOR_BGR2GRAY)
# cv2.imshow("Gray", gray)
# cv2.waitKey()
meatyPixelArea = cv2.countNonZero(gray)
print("Red Area in pixel: ", meatyPixelArea)
print("Red Area in cm^2: ", pixelToArea(meatyPixelArea, coinPixelArea))
# finally the body-fat ratio calculation
print("Body-Fat Ratio: ", meatyPixelArea/slicePixelArea*100, "%")
cv2.destroyAllWindows()

Draw contours around images of the same color with openCV python

I have this image with 3 channels RGB (a result of a VARI Index computation) and I would like to draw bounding boxes (rectangles) around the plants, represented in green here. What is the best and easiest way to do it with OpenCV / python?
I guess it's an easy problem for OpenCV experts, but I could not find good tutorials online to do this for multiple objects.
The closest tutorial I found was: determining-object-color-with-opencv
The assumptions for the bounding boxes should/could be:
green is the dominant color.
it should be more than X pixels.
Thanks in advance!
Just answering my own question after stumbling upon this resource: https://docs.opencv.org/3.4/da/d0c/tutorial_bounding_rects_circles.html
May not be the best answer but it somehow solves my problem!
import cv2
import numpy as np
image = cv2.imread('vari3.png')
# https://www.pyimagesearch.com/2016/02/15/determining-object-color-with-opencv/
# https://docs.opencv.org/3.4/da/d0c/tutorial_bounding_rects_circles.html
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
# mask: green is dominant.
thresh = np.array((image.argmax(axis=-1) == 1) * 255, dtype=np.uint8)
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
contours_poly = [None] * len(cnts)
boundRect = [None] * len(cnts)
for i, c in enumerate(cnts):
contours_poly[i] = cv2.approxPolyDP(c, 3, True)
boundRect[i] = cv2.boundingRect(contours_poly[i])
for i in range(len(cnts)):
# cv2.drawContours(image, contours_poly, i, (0, 255, 0), thickness=2)
pt1 = (int(boundRect[i][0]), int(boundRect[i][1]))
pt2 = (int(boundRect[i][0] + boundRect[i][2]), int(boundRect[i][1] + boundRect[i][3]))
if np.sqrt((pt2[1] - pt1[1]) * (pt2[0] - pt1[0])) < 30:
continue
cv2.rectangle(image, pt1, pt2, (0, 0, 0), 2)
cv2.imwrite('result.png', image)
cv2.imshow("Image", image)
cv2.waitKey(0)
You need to do HSV filtering
Change image colors from BGR to HSV (Hue Saturation Value).
Filter a certain range of saturation and hue that matches green by
thresholding.
Refer to this page for code to do the first 2
https://pythonprogramming.net/color-filter-python-opencv-tutorial/
Do some morphological operations like Erosion, Dilation, Opening,
Closing to remove the small bits of green that don't represent trees
and to connect the trees that look broken together.
Refer to https://docs.opencv.org/master/d9/d61/tutorial_py_morphological_ops.html
Detect the contours then draw the rectangles
import cv2
import numpy as np
img = cv2.imread('8FGo1.jpg',1)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_red = np.array([45,100,50])
upper_red = np.array([75,255,255])
mask = cv2.inRange(hsv, lower_red, upper_red)
kernel = np.ones((5,5),np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
x,y,w,h = cv2.boundingRect(contour)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow('img',img)
#cv2.imshow('mask',mask)
Output

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

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:

How to detect and find checkboxes in a form using Python OpenCV?

I have several images for which I need to do OMR by detecting checkboxes using computer vision.
I'm using findContours to draw contours only on the checkboxes in scanned document. But the algorithm extracts each and every contours of the text.
from imutils.perspective import four_point_transform
from imutils import contours
import numpy as np
import argparse, imutils, cv2, matplotlib
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
image = cv2.imread("1.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 75, 200)
im_test = [blurred, cv2.GaussianBlur(gray, (7, 7), 0), cv2.GaussianBlur(gray, (5, 5), 5), cv2.GaussianBlur(gray, (11, 11), 0)]
im_thresh = [ cv2.threshold(i, 127, 255, 0) for i in im_test ]
im_thresh_0 = [i[1] for i in im_thresh ]
im_cnt = [cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0] for thresh in im_thresh_0]
im_drawn = [cv2.drawContours(image.copy(), contours, -1, (0,255,0), 1) for contours in im_cnt]
plt.imshow(im_drawn[0])
plt.show()
Input Image:
Obtain binary image. Load the image, grayscale, Gaussian blur, and Otsu's threshold to obtain a binary black/white image.
Remove small noise particles. Find contours and filter using contour area filtering to remove noise.
Repair checkbox horizontal and vertical walls. This step is optional but in the case where the checkboxes may be damaged, we repair the walls for easier detection. The idea is to create a rectangular kernel then perform morphological operations.
Detect checkboxes. From here we find contours, obtain the bounding rectangle coordinates, and filter using shape approximation + aspect ratio. The idea is that a checkbox is essentially a square so its contour dimensions should be within a range.
Input image -> Binary image
Detected checkboxes highlighted in green
Checkboxes: 52
Another input image -> Binary image
Detected checkboxes highlighted in green
Checkboxes: 2
Code
import cv2
# Load image, convert to grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread('1.jpg')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Find contours and filter using contour area filtering to remove noise
cnts, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[-2:]
AREA_THRESHOLD = 10
for c in cnts:
area = cv2.contourArea(c)
if area < AREA_THRESHOLD:
cv2.drawContours(thresh, [c], -1, 0, -1)
# Repair checkbox horizontal and vertical walls
repair_kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5,1))
repair = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, repair_kernel1, iterations=1)
repair_kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (1,5))
repair = cv2.morphologyEx(repair, cv2.MORPH_CLOSE, repair_kernel2, iterations=1)
# Detect checkboxes using shape approximation and aspect ratio filtering
checkbox_contours = []
cnts, _ = cv2.findContours(repair, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2:]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.035 * peri, True)
x,y,w,h = cv2.boundingRect(approx)
aspect_ratio = w / float(h)
if len(approx) == 4 and (aspect_ratio >= 0.8 and aspect_ratio <= 1.2):
cv2.rectangle(original, (x, y), (x + w, y + h), (36,255,12), 3)
checkbox_contours.append(c)
print('Checkboxes:', len(checkbox_contours))
cv2.imshow('thresh', thresh)
cv2.imshow('repair', repair)
cv2.imshow('original', original)
cv2.waitKey()
Well... Are the checkboxes always in that region of the image? The checkboxes Always maintain the same size area on the image?
If yes, you can run findContours only in that region of the image...
Or maybe the Template Matching with Multiple Objects, example from OpenCV docs: https://docs.opencv.org/3.4.3/d4/dc6/tutorial_py_template_matching.html

Categories

Resources