How Can I Detect If There are Secondary Objects in an Image - python

I am looking for a way to detect if there are secondary objects in an image or if the image just has the one main object. I've done a bit of research, but I haven't been able to find anything quite like what I am looking for.
An example image would be:
The main object being the two detergent bottles since they overlap and the secondary object would be the "2 pack" pop up bubble in the top right. I would expect this image to return something like: "This image has secondary objects" or a count of the objects.

Here is one way to do that in Python/OpenCV
Read the input
Convert to gray and invert
OTSU threshold
Morphology close
Get external contours
Draw contours on image
Count contours
Print messages
Save results
Input:
import cv2
import numpy as np
# read image
img = cv2.imread("tide.jpg")
# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# invert gray image
gray = 255 - gray
# threshold gray image
#thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)[1]
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# apply morphology close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# Get contours
cntrs = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cntrs = cntrs[0] if len(cntrs) == 2 else cntrs[1]
result = img.copy()
for c in cntrs:
cv2.drawContours(result, [c], -1, (0,0,255), 1)
count = len(cntrs)
print("")
print("count =",count)
print("")
if count > 1:
print("This image has secondary objects")
else:
print("This image has primary object only")
# write results to disk
cv2.imwrite("tide_thresh.png", thresh)
cv2.imwrite("tide_morph.png", morph)
cv2.imwrite("tide_object_contours.png", result)
# display it
cv2.imshow("thresh", thresh)
cv2.imshow("morph", morph)
cv2.imshow("result", result)
cv2.waitKey(0)
Thresholded image:
Morphology close image:
Contours on image:
Count of contours and messages:
count = 2
This image has secondary objects

Following #fmw42's advice, I did a bit of research and found a script that worked well after a little bit of tinkering:
import cv2
import numpy as np
import sys
img = cv2.imread(sys.argv[1], cv2.IMREAD_UNCHANGED)
#convert img to grey
img_grey = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#set a thresh
thresh = 230
#get threshold image
ret,thresh_img = cv2.threshold(img_grey, thresh, 255, cv2.THRESH_BINARY_INV)
#find contours
contours, hierarchy = cv2.findContours(thresh_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#create an empty image for contours
# img_contours = np.zeros(img.shape)
img_contours = np.zeros_like(img)
# draw the contours on the empty image
cv2.drawContours(img_contours, contours, -1, 255, 3)
#save image
cv2.imshow('contours',img_contours)
# Wait indefinitely until you push a key. Once you do, close the windows
print len(contours)
cv2.waitKey(0)
cv2.destroyAllWindows()
My main issue was the threshold setting and I found that 230 worked best with my sample images, although it still is not perfect. I'm hoping there is a better way or something I can add to this.
this image returned 1 as expected, but my initial test image returns 3 at this threshold setting when I would expect 2. At 200 thresh it returns 2, but I was willing to compromise because the main thing I need to know is if it is more than 1.

Related

Image cropping on white boundaries from a Complex Image using python

Problem Summary: I have got many complex histopathology images with different dimensions. Complex means a single image having multiple images in it as shown in below input image examples. I need to separate out or crop each single image only and not the text/label/caption of it from that input complex image and further save each of them individually. For the bounding boxes I have gone through the white boundaries (separation) along the single images.
Complex Input Image Example 1:
Complex Input Image Example 2:
Complex Input Image Example 3:
Code I have tried:
import cv2
import numpy as np
# reading the input image
img = cv2.imread('cmp.jpg')
cv2.imshow("histology image", img)
# defining border color
lower = (0, 80, 110)
upper = (0, 120, 150)
# applying thresholding on border color
mask = cv2.inRange(img, lower, upper)
cv2.imshow("masked", mask)
# Using dilate threshold
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15))
mask = cv2.morphologyEx(mask, cv2.MORPH_DILATE, kernel)
# coloring border to white for other images
img[mask==255] = (255,255,255)
cv2.imshow("white_border", img)
# converting image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# applying otsu threshold
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU )[1]
cv2.imshow("thresholded", thresh)
# applying 'Open' morphological operation
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17,17))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
morph = 255 - morph
cv2.imshow("morphed", morph)
# finding contours and bounding boxes
bboxes = []
bboxes_img = img.copy()
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
for cntr in contours:
x,y,w,h = cv2.boundingRect(cntr)
cv2.rectangle(bboxes_img, (x, y), (x+w, y+h), (0, 0, 255), 1)
bboxes.append((x,y,w,h))
cv2.imshow("boundingboxes", bboxes_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
I am not getting exact bounding boxes for each of single images present in the input complex image and further I need to save each cropped image individually. Any kind of help will be much appreciated.

How to select the bubbles inside the droplet correctly

This is Wendy.Image processing related question.
I have to select the bubbles properly to analyze its size and calculate the velocity among image sequences.
original image
enter image description here
image sequences
enter image description here//enter image description here//enter image description here
In the picture,the largest shape is a droplet,and inside the droplet contains lots of tiny bubbles.
So far, the questions are:
1.Because the shape of bubbles are broken, few contours are detected. At the same time, lots of edges detected. How can I fill out the edges to be contours ( like a coin, I just want the outline of each bubble and don't want to get the lines inside bubbles)
current progress
enter image description here
enter image description here
code(detect contours)
gray=cv2.imread("1000.tif")
blurred = cv2.GaussianBlur(gray, (3,3), 0)
edged = cv2.Canny(blurred, 5, 60)
cv2.imshow('edged ', edged )
cv2.waitKey(0)
contours, hierarchy = cv2.findContours(edged,cv2.RETR_EXTERNAL,cv2.RETR_LIST)
img = gray.copy()
cv2.drawContours(img,contours,-1,(0,255),1)
cv2.imwrite("contours.tif", img)
cv2.imshow("contours", img)
cv2.waitKey(0)
code(fill holes)
im_floodfill = edged.copy()
h, w = edged.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)
cv2.floodFill(im_floodfill, mask, (0,0), 255)
im_floodfill_inv = cv2.bitwise_not(im_floodfill)
im_out = edged | im_floodfill_inv
cv2.imshow("Thresholded Image", edged)
cv2.imshow("Floodfilled Image", im_floodfill)
cv2.imshow("Inverted Floodfilled Image", im_floodfill_inv)
cv2.imshow("Foreground", im_out)
cv2.waitKey(0)
cv2.imwrite("fill.jpg", im_out)
the result will be
enter image description here
The velocity of the bubbles is my objective. I've tried to put the binary image sequences into Iamgej and used the "trackmate" (a tool for automated, and semi-automated particle tracking). It has a fixed bubble size setting to track, however, the size of the bubbles is quite different in my case, so is there any method to track bubbles moving?
If this isn't clear enough please let me know and I can try and be more specific. Thank you for your time.
Here is one way in Python/OpenCV.
Read the input
Convert to gray
Threshold
Fill the holes with morphology close
Get the contours and the largest contour (though presumably just one)
Draw the contour on a copy of the input
Save the results
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('vapor.jpg')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# threshold
#thresh = cv2.threshold(gray,5,255,cv2.THRESH_BINARY)[1]
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# apply close morphology to fill white circles
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (45,45))
thresh_cleaned = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# get largest contour
contours = cv2.findContours(thresh_cleaned, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# draw contour
result = img.copy()
cv2.drawContours(result,[big_contour],0,(0,0,255),1)
# save image with points drawn
cv2.imwrite('vapor_thresh.jpg',thresh)
cv2.imwrite('vapor_cleaned_thresh.jpg',thresh_cleaned)
cv2.imwrite('vapor_contour.jpg',result)
cv2.imshow("thresh", thresh)
cv2.imshow("thresh_cleaned", thresh_cleaned)
cv2.imshow("contour", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold image:
Morphology filled image:
Contour image:

Counting special elements on image with OpenCV and Python

I want to count number of trees on this picture from above.
I know how to count elements, but till now I used images with white background, so counting is much easier. But on image like this i do not know what to do:
I converted the image to gray, and then done the threshold *(threshold value is done by hand, is there a way to find it automaticly?), my next idea is to find the 'centers' of black dots, or to 'group' them.
I also tried to change brightness and contrast but it didnt work.
What should I do?
This is the code that I wrote:
import cv2
import numpy as np
# Read image
img = cv2.imread('slika.jpg')
# Convert image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Show grayscale image
cv2.imshow('gray image', gray)
cv2.waitKey(0)
#BIG PROBLEM: IM FINDING VALUE OF `40` IN THE LINE BELOW MANUALLY
# Inverse binary threshold image with threshold at 40,
_, threshold_one = cv2.threshold(gray, 40 , 255, cv2.THRESH_BINARY_INV)
# Show thresholded image
cv2.imshow('threshold image', threshold_one)
cv2.waitKey(0)
# Find contours
contours, h = cv2.findContours(threshold_one, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print('Number of trees found:', len(contours)) #GIVES WRONG RESULT
# Iterate all found contours
for cnt in contours:
# Draw contour in original/final image
cv2.drawContours(img, [cnt], 0, (0, 0, 255), 1)
# Show final image
cv2.imshow('result image', img)
cv2.waitKey(0)
This is the image with treshold, I have tried to blur it (in order to connect black dots), but the final output is the same:
This is the result image:
Here's a rough method to estimate the number of trees. The idea to to model each tree as a blob then use contour filtering with a minimum threshold area to ignore the noise. To determine automatic threshold levels, you can use Otsu's threshold by appending cv2.THRESH_OTSU or Adaptive threshold with cv2.adaptiveThreshold(). This approach has problems when the trees are very close together since they form a single blob. Possible improvements could be to find the average area of each tree then find the floor with a large blob. You would probably need to train a classifier and use deep/machine learning for better accuracy
Trees: 102
import cv2
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
opening = cv2.morphologyEx(close, cv2.MORPH_OPEN, kernel, iterations=2)
cnts = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
trees = 0
for c in cnts:
area = cv2.contourArea(c)
if area > 50:
x,y,w,h = cv2.boundingRect(c)
cv2.drawContours(image, [c], -1, (36,255,12), 2)
trees += 1
print('Trees:', trees)
cv2.imshow('image', image)
cv2.waitKey()

How improve image quality to extract text from image using Tesseract

I'm trying to use Tessract in the code below to extract the two lines of the image. I tryied to improve the image quality but even though it didn't work.
Can anyone help me?
from PIL import Image, ImageEnhance, ImageFilter
import pytesseract
img = Image.open(r'C:\ocr\test00.jpg')
new_size = tuple(4*x for x in img.size)
img = img.resize(new_size, Image.ANTIALIAS)
img.save(r'C:\\test02.jpg', 'JPEG')
print( pytesseract.image_to_string( img ) )
Given the comment by #barny I don't know if this will work, but you can try the code below. I created a script that selects the display area and warps this into a straight image. Next a threshold to a black and white mask of the characters and the result is cleaned up a bit.
Try if it improves recognition. If it does, also look at the intermediate stages so you'll understand all that happens.
Update: It seems Tesseract prefers black text on white background, inverted and dilated the result.
Result:
Updated result:
Code:
import numpy as np
import cv2
# load image
image = cv2.imread('disp.jpg')
# create grayscale
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# perform threshold
retr, mask = cv2.threshold(gray_image, 190, 255, cv2.THRESH_BINARY)
# findcontours
ret, contours, hier = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# select the largest contour
largest_area = 0
for cnt in contours:
if cv2.contourArea(cnt) > largest_area:
cont = cnt
largest_area = cv2.contourArea(cnt)
# find the rectangle (and the cornerpoints of that rectangle) that surrounds the contours / photo
rect = cv2.minAreaRect(cont)
box = cv2.boxPoints(rect)
box = np.int0(box)
#### Warp image to square
# assign cornerpoints of the region of interest
pts1 = np.float32([box[2],box[3],box[1],box[0]])
# provide new coordinates of cornerpoints
pts2 = np.float32([[0,0],[500,0],[0,110],[500,110]])
# determine and apply transformationmatrix
M = cv2.getPerspectiveTransform(pts1,pts2)
tmp = cv2.warpPerspective(image,M,(500,110))
# create grayscale
gray_image2 = cv2.cvtColor(tmp, cv2.COLOR_BGR2GRAY)
# perform threshold
retr, mask2 = cv2.threshold(gray_image2, 160, 255, cv2.THRESH_BINARY_INV)
# remove noise / close gaps
kernel = np.ones((5,5),np.uint8)
result = cv2.morphologyEx(mask2, cv2.MORPH_CLOSE, kernel)
#draw rectangle on original image
cv2.drawContours(image, [box], 0, (255,0,0), 2)
# dilate result to make characters more solid
kernel2 = np.ones((3,3),np.uint8)
result = cv2.dilate(result,kernel2,iterations = 1)
#invert to get black text on white background
result = cv2.bitwise_not(result)
#show image
cv2.imshow("Result", result)
cv2.imshow("Image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

How to improve edge detection and remove background from an image?

I am using the code below to remove the backroung of the images and highlight only my region of interest (ROI), however, the algorithm behaves in a wrong way in some images, discarding the stain (ROI) and deleting along with the background.
import numpy as np
import cv2
#Read the image and perform threshold
img = cv2.imread('photo.bmp')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray,5)
_,thresh = cv2.threshold(blur,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
#Search for contours and select the biggest one
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
#Create a new mask for the result image
h, w = img.shape[:2]
mask = np.zeros((h, w), np.uint8)
#Draw the contour on the new mask and perform the bitwise operation
cv2.drawContours(mask, [cnt],-1, 255, -1)
res = cv2.bitwise_and(img, img, mask=mask)
#Display the result
cv2.imwrite('photo.png', res)
#cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
I don't know if I understand correctly because when I run your code I do not get the output you posted (exit). If you would like to obtain only the mole it can't be done by simply thresholding because the mole is too near the border plus if you look at your image closley you will see that it has some sort of frame. However there is a simple way to do this for this image but it may not work in other cases. You can draw a fake border over your image and seperate the ROI from other noise area. Then make a threshold for which contour you wish to display. Cheers!
Example:
#Import all necessery libraries
import numpy as np
import cv2
#Read the image and perform threshold and get its height and weight
img = cv2.imread('moles.png')
h, w = img.shape[:2]
# Transform to gray colorspace and blur the image.
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),0)
# Make a fake rectangle arround the image that will seperate the main contour.
cv2.rectangle(blur, (0,0), (w,h), (255,255,255), 10)
# Perform Otsu threshold.
_,thresh = cv2.threshold(blur,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# Create a mask for bitwise operation
mask = np.zeros((h, w), np.uint8)
# Search for contours and iterate over contours. Make threshold for size to
# eliminate others.
_, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
for i in contours:
cnt = cv2.contourArea(i)
if 1000000 >cnt > 100000:
cv2.drawContours(mask, [i],-1, 255, -1)
# Perform the bitwise operation.
res = cv2.bitwise_and(img, img, mask=mask)
# Display the result.
cv2.imwrite('mole_res.jpg', res)
cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:

Categories