Reputation: 87
I am trying to deal with a simple application which counts bacterium colonies at photos of Petri dishes. I am using python and cv2 library mainly.
I'm using the code above:
#reading image (reading is fixed for tests) and putting Opening morphological transformation to improve edge visibility
img = cv2.imread("image1.jpg",1)
img = cv2.resize(img,(500,500))
kernel = py.ones((7,7),py.uint8)
open = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
#converting colors to GRAY scale, setting threshold and contours. Setting a copy for result comprasion
img_gray = cv2.cvtColor(open, cv2.COLOR_BGR2GRAY)
copy = img.copy()
ret,thresh = cv2.threshold(img_gray,190,255,cv2.THRESH_BINARY)
im2,contours2,hierarchies = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
#drawing and counting countours (colonies)
visible_colonies = 0
for contour in (contours2):
(x,y),radius = cv2.minEnclosingCircle(contour)
center = (int(x),int(y))
radius = int(radius)
if(radius>2 and radius<25):
cv2.circle(img,center,radius,(255,0,255),2)
visible_colonies += 1
#showing result
plt.imshow(img)
plt.show()
print(visible_colonies)
plt.imshow(copy)
plt.show()
Nontheless, results are not enough accurate. To provide a sample:
I can clearly see that there are many contours(colonies) which are not circled. At the same time, there are contours which point just nothing.
What I've tried to do:
What I'm suspecting:
And here goes my question finally - how to improve finding contours accuracy as much as possible? I don't want to use any simplifications there, I want this to be as accurate as it's possible.
Upvotes: 3
Views: 3342
Reputation: 46600
A good approach here might be color thresholding with cv2.inRange()
. The idea is to convert the image to HSV format and use a lower/upper color threshold to segment the colonies. We draw the detected colonies onto a mask then find contours on the mask.
Color segmented colonies drawn onto mask
Results
We can also keep track of the number of colonies
244
Potential optimizations would be to provide a higher resolution image for more accurate results. Other filters would be to use cv2.contourArea()
and a minimum threshold area size if you wanted to only detect medium/large colonies.
import numpy as np
import cv2
image = cv2.imread('1.png')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([0, 71, 0], dtype="uint8")
upper = np.array([179, 255, 255], dtype="uint8")
mask = cv2.inRange(hsv, lower, upper)
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
colonies = []
for c in cnts:
cv2.drawContours(image, [c], -1, (36, 255, 12), 2)
colonies.append(c)
print(len(colonies))
cv2.imshow('mask', mask)
cv2.imshow('image', image)
cv2.imwrite('mask.png', mask)
cv2.imwrite('image.png', image)
cv2.waitKey()
The lower and upper bounds can be found using this script
import cv2
import sys
import numpy as np
def nothing(x):
pass
useCamera=False
# Check if filename is passed
if (len(sys.argv) <= 1) :
print("'Usage: python hsvThresholder.py <ImageFilePath>' to ignore camera and use a local image.")
useCamera = True
# Create a window
cv2.namedWindow('image')
# create trackbars for color change
cv2.createTrackbar('HMin','image',0,179,nothing) # Hue is from 0-179 for Opencv
cv2.createTrackbar('SMin','image',0,255,nothing)
cv2.createTrackbar('VMin','image',0,255,nothing)
cv2.createTrackbar('HMax','image',0,179,nothing)
cv2.createTrackbar('SMax','image',0,255,nothing)
cv2.createTrackbar('VMax','image',0,255,nothing)
# Set default value for MAX HSV trackbars.
cv2.setTrackbarPos('HMax', 'image', 179)
cv2.setTrackbarPos('SMax', 'image', 255)
cv2.setTrackbarPos('VMax', 'image', 255)
# Initialize to check if HSV min/max value changes
hMin = sMin = vMin = hMax = sMax = vMax = 0
phMin = psMin = pvMin = phMax = psMax = pvMax = 0
# Output Image to display
if useCamera:
cap = cv2.VideoCapture(0)
# Wait longer to prevent freeze for videos.
waitTime = 330
else:
img = cv2.imread(sys.argv[1])
output = img
waitTime = 33
while(1):
if useCamera:
# Capture frame-by-frame
ret, img = cap.read()
output = img
# get current positions of all trackbars
hMin = cv2.getTrackbarPos('HMin','image')
sMin = cv2.getTrackbarPos('SMin','image')
vMin = cv2.getTrackbarPos('VMin','image')
hMax = cv2.getTrackbarPos('HMax','image')
sMax = cv2.getTrackbarPos('SMax','image')
vMax = cv2.getTrackbarPos('VMax','image')
# Set minimum and max HSV values to display
lower = np.array([hMin, sMin, vMin])
upper = np.array([hMax, sMax, vMax])
# Create HSV Image and threshold into a range.
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower, upper)
output = cv2.bitwise_and(img,img, mask= mask)
# Print if there is a change in HSV value
if( (phMin != hMin) | (psMin != sMin) | (pvMin != vMin) | (phMax != hMax) | (psMax != sMax) | (pvMax != vMax) ):
print("(hMin = %d , sMin = %d, vMin = %d), (hMax = %d , sMax = %d, vMax = %d)" % (hMin , sMin , vMin, hMax, sMax , vMax))
phMin = hMin
psMin = sMin
pvMin = vMin
phMax = hMax
psMax = sMax
pvMax = vMax
# Display output image
cv2.imshow('image',output)
# Wait longer to prevent freeze for videos.
if cv2.waitKey(waitTime) & 0xFF == ord('q'):
break
# Release resources
if useCamera:
cap.release()
cv2.destroyAllWindows()
Upvotes: 7