I want to detect how many number of cards are present in this image using python.I was trying with white pixel but not getting the correct result.
My code is given below:
import cv2
import numpy as np
img = cv2.imread('imaagi.jpg', cv2.IMREAD_GRAYSCALE)
n_white_pix = np.sum(img == 255)
print('Number of white pixels:', n_white_pix)
I am a beginner. So unable to find out the way.
This solution is with respect to the image you have provided and the implementation is in OpenCV.
Code:
im = cv2.imread('C:/Users/Jackson/Desktop/cards.jpg', 1)
#--- convert the image to HSV color space ---
hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
cv2.imshow('H', hsv[:,:,0])
cv2.imshow('S', hsv[:,:,1])
#--- find Otsu threshold on hue and saturation channel ---
ret, thresh_H = cv2.threshold(hsv[:,:,0], 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
ret, thresh_S = cv2.threshold(hsv[:,:,1], 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
#--- add the result of the above two ---
cv2.imshow('thresh', thresh_H + thresh_S)
#--- some morphology operation to clear unwanted spots ---
kernel = np.ones((5, 5), np.uint8)
dilation = cv2.dilate(thresh_H + thresh_S, kernel, iterations = 1)
cv2.imshow('dilation', dilation)
#--- find contours on the result above ---
(_, contours, hierarchy) = cv2.findContours(dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
#--- since there were few small contours found, retain those above a certain area ---
im2 = im.copy()
count = 0
for c in contours:
if cv2.contourArea(c) > 500:
count+=1
cv2.drawContours(im2, [c], -1, (0, 255, 0), 2)
cv2.imshow('cards_output', im2)
print('There are {} cards'.format(count))
Result:
On the terminal I got: There are 6 cards
Depending on how exactly your "white pixel approach" was working (please share more details on that if possible), you could try a simple image binarization, which is a well-established way of separating different objects/entities in your image. Granted, it will work only on grayscale images, but that is something you can also easily fix with sklearn.
It might provide optimal results right away, especially if the lighting conditions vary across images, or you have (as seen above) cards that contain a wide variety of colors.
To circumvent this, you could also try to look into different color spaces, e..g HSV.
If that still does not work, I would recommend using image segmentation libraries from OpenCV or similra libraries. The problem is that they usually also bring some unwanted complexity to your project, which might not be necessary if it works with a simple approach such as the binarization.
Related
Testing image of voltmeter
So I'm fairly new to the area and have been dabbling with images. I've been having some unstable results for lack of better words. I'm essentially trying to detect the voltmeter in variations of the above image and extracting the white region or as close to the white region as possible.
The problem is, it works for some variations of images but not for others. So, my questions are:
Is there a somewhat quick and efficient method of detecting and extracting the required region? (Quick being somewhat secondary)
What is the thought process behind a task like this for some of you with actual experience and expertise in the field?
I've tried:
Checking various color spaces and specific planes in those color spaces for ease of information extraction
Histogram Equalization, threshold, blurring, edge detection
Contouring and a couple of odd things in between.
I'm a bit stuck on achieving a "robust" or somewhat "generalized" way of detecting the said area in variations of the above image.
Thanks for any feedback.
Here's the code:
import cv2
from matplotlib import pyplot as plt
im = cv2.imread("volt.jpg")
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 150, 200, 0)
#imedge = cv2.Canny(imgray, 30, 200)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
plt.imshow(thresh)
voltcnt = 0
x = 0
for i in range(0, len(contours)):
if cv2.contourArea(contours[i]) > 50000 and cv2.contourArea(contours[i]) < 100000:
voltcnt = cv2.contourArea(contours[i])
x = i
cv2.drawContours(im, contours[x], -1, (255, 0, 0), 3)
plt.imshow(im)
Here's the output:
You can also try with "cv2.SimpleBlobDetector()"
I have images of Math question papers which have multiple questions per page. Example:
Math questions image
I want to use Python to extract the contents of each question separately and store them in a database table. From my research, I have a rough idea for my workflow: Pre-process image --> Find contours of each question --> Snip and send those individual images to pyTesseract --> Store the transcribed text.
I was very happy to find a great thread about a similar problem, but when I tried that approach on my image, the ROI that was identified covered the whole page. In other words, it identified all the questions as one block of text.
How do I make OpenCV recognize multiple ROIs within a page and draw bounding boxes? Is there something different to be done during the pre-processing?
Please suggest an approach - thanks so much!
First you need to convert the image into grayscale
Perform otsu'threshold which does better binarization in removing the background.
Specify structure shape and kernel size. Kernel size increases or decreases the area of the rectangle to be detected.
Applying dilation on the threshold image with the kernel when you dilated it gets thicker.
Finding contours
Looping through the identified contours Then the rectangular part is can be drawn using cv2.rectangle method
import cv2
img = cv2.imread("text.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),0)
ret, thresh1 = cv2.threshold(blur, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (18, 18))
dilation = cv2.dilate(thresh1, rect_kernel, iterations = 1)
contours, hierarchy = cv2.findContours(dilation, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_NONE)
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
# Drawing a rectangle on copied image
rect = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imwrite('drawed.png', img)
Sample output iamge
I'm having problems with contouring a live video capture. I'm using OpenCV library on Python
My objective is to measure tilapia fishes that go through a tube one by one and get their approx sizes by getting the rectangle dimensions. Here is a sample of 1 tilapia passing.
Here is one more with backlighting
The problem is that the water is also detected as a contour and I can't seem to isolate the fish. I have tried using a mix of blurring techniques, dilation, erosion but it can't seem to solve my problem.
Are there any other algorithms I can use? Or should I switch to Object Detection?
Here's my code:
#!/usr/bin/env python3
import numpy as np
import cv2
import time
kernel = np.ones((5,5),np.uint8)
cap = cv2.VideoCapture('white.mp4')
foreground_background = cv2.createBackgroundSubtractorMOG2()
while True:
ret, frame = cap.read()
if(type(frame) == type(None)):
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#gray = cv2.GaussianBlur(gray, (21, 21), 0)
#gray = cv2.medianBlur(gray,11)
gray = cv2.bilateralFilter(gray,9,75,75)
ret,thresh = cv2.threshold(gray, 127, 255 , cv2.THRESH_BINARY_INV)
thresh = cv2.erode(thresh, kernel, iterations=3)
thresh = cv2.dilate(thresh, kernel, iterations=3)
im2, cnts, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
c=None
for c in cnts:
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 255, 0), 2)
cv2.imshow('Output', thresh)
cv2.waitKey(2)
cap.release()
cv2.destroyAllWindows()
Following added by Mark Setchell... if anyone wants to see the individual frames, I extracted at 10 fps and montaged the frames as follows:
Commands used:
ffmpeg -i a.mov -r 10 frame_%05d.png
montage -tile 6x frame_*png -geometry +10+10 result.png
I would try several things including:
Various ways to remove the background -- I see you used cv2.createBackgroundSubtractorMOG2() but simpler methods might yield better results. Try taking the average of several dozen frames when there are no fish in the frame and then subtract that average background from all frames. Something like this: background = (A+B+C)/3 where A, B, and C are background frames/matrices without fish. Now you can simply remove the background from all frames with a subtraction. Additionally, you could try subtracting the previous 1 (or several) frames from the current frame to highlight the changes between frames (effectively a time derivative). This could be done in a loop.
Sum along rows and columns -- column and row projections are a very easy way to highlight high contrast regions within an image. Presumably (if dark values > light values) column summing an image where the fish was in the middle of the image would yield a Gaussian looking vector with a peak index corresponding to the middle column of the image (see figure below). This would also give you a means to estimate the size of the fish by noting the width of the Gaussian looking vector.
Note that the initial high values on the column sum vector are a result of the lighting conditions and would be remedied via background removal (see step 1).
From the numpy package in python you can perform column sums with something like:
import numpy as np
np.sum(M,axis=1)
Where M is the current image/matrix/frame of interest.
I am trying to get the external contour of an image using opencv and python.
I found a solution to this problem here (Process image to find external contour) but the solution does not work for me - instead of the contour image it opens two new images (one which is all black and the other one black and white).
This is the code I am using:
import cv2 # Import OpenCV
import numpy as np # Import NumPy
# Read in the image as grayscale - Note the 0 flag
im = cv2.imread("img.jpg", 0)
# Run findContours - Note the RETR_EXTERNAL flag
# Also, we want to find the best contour possible with CHAIN_APPROX_NONE
_ ,contours, hierarchy = cv2.findContours(im.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Create an output of all zeroes that has the same shape as the input
# image
out = np.zeros_like(im)
# On this output, draw all of the contours that we have detected
# in white, and set the thickness to be 3 pixels
cv2.drawContours(out, contours, -1, 255, 3)
# Spawn new windows that shows us the donut
# (in grayscale) and the detected contour
cv2.imshow('Donut', im)
cv2.imshow('Output Contour', out)
# Wait indefinitely until you push a key. Once you do, close the windows
cv2.waitKey(0)
cv2.destroyAllWindows()
The illustration shows the two windows I get instead of the contour.
You are doing some mistakes that compromise your result. Reading from the documentation it says that:
For better accuracy, use binary images (see step 3).
finding contours is like finding white object from black background (see step 2).
You don't stick with these rules so you don't get good results. Also you are plotting your results to a black image and they are not visible.
Below is the full solution for your case.
I am also using an adaptive threshold for better results.
# Step 1: Read in the image as grayscale - Note the 0 flag
im = cv2.imread("/home/jorge/Downloads/input.jpg", 0)
cv2.imshow('Original', im)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Step 2: Inverse the image to get black background
im2 = im.copy()
im2 = 255 - im2
cv2.imshow('Inverse', im2)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Step 3: Get an adaptive binary image
im3 = cv2.adaptiveThreshold(im2, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 2)
cv2.imshow('Inverse_binary', im3)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Step 4: find contours
_, contours, hierarchy = cv2.findContours(im3.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Step 5: This creates a white image instead of a black one to plot contours being black
out = 255*np.ones_like(im)
cv2.drawContours(out, contours, -1, (0, 255, 0), 3)
cv2.drawContours(im, contours, -1, (0, 255, 0))
I am trying to do characters detection, have to draw a box around them, then crop and then feed to a neural network for recognition. Everything is working but before I was using sets of characters on a single color background image and segmentation was easily done.
However with real photos I have different lighting conditions and really struggle to find the contours.
After applying some adaptive thresholding I managed to get the folowing results, but starting from that I really can't figure how to properly proceed and detect each character. I can detect half of the characters easily, but not all of them. Probably because they are surrounded by lots of small irrelevant contours.
I have a feeling there is one step left but I can't figure which one.
Find Countours is capable of finding only about half of the characters.
For now, in short, im doing:
im_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
im_gray = cv2.GaussianBlur(im_gray, (5, 5), 0)
_, th1 = cv2.threshold(im_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
cim, ctrs, hier = cv2.findContours(th1.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
and
th2 = cv2.adaptiveThreshold(im_gray,255,cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY,11,2)
Images below - original image and some variations of intermediate results.
Original picture:
After some thresholding:
After some thresholding:
Inverse thresholding:
So the question is - what is the step/steps after to segment the characters?
You can perform difference of gaussians. The idea is to blur the image with two different kernels and subtract their respective results:
Code:
im = cv2.imread(img, 0)
#--- it is better to take bigger kernel sizes to remove smaller edges ---
kernel1 = 15
kernel2 = 31
blur1 = cv2.GaussianBlur(im,(kernel1, kernel1), 0)
blur2 = cv2.GaussianBlur(im,(kernel2, kernel2), 0)
cv2.imshow('Difference of Gaussians',blur2 - blur1)
Result: