Skin detection from hue-saturation histogram - OpenCV Python - python

I'm working on a little program in python to estimate the direction of pointing gestures with 2D picture from a monocular camera and I'm using OpenCV 2.3.
I know it's a bit tricky but I'm motivated! :)
My approach is fisrt to use the face detection to detect an area into which I'm sure there is a lot of skin:
img = cv2.imread("/home/max/recordings/cameras/imageTEST.jpg",1)
img_hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
hc1 = cv2.CascadeClassifier("/home/max/haarcascade_frontalface_alt.xml")
faces1 = hc1.detectMultiScale(img)
for (x,y,w,h) in faces1:
cv2.rectangle(img, (x,y), (x+w,y+h), 255)
crop_img = img[y+2:y+w, x+2:x+h]
I really want to use this method because I want my detection to be robust to light variation. Then I compute the hue-saturation histogram of the picture of the detected face to make a back projection:
roihist = cv2.calcHist([crop_img],[0,1], None, [180, 256], [0, 180, 0, 256] )
dst = cv2.calcBackProject([img],[0,1],roihist,[0,180,0,256],1)
And finally I would be able to binarize the picture with a threshold and track the head and hands blobs to estimate the direction of pointing.
I've no problem with my code but the skin is not detected...
What am I doing wrong?
Thx for your help!
Max

Have you tried using the Cr channel from the YCbCr format? I had some luck with Cr when I had previously worked on hand detection using skin colour. Also, there is this paper, which uses a nice method for detecting hands. But keep in mind that as long as you use skin colour, the detection will not work for all hands, but can be tuned for a given user or a bunch of users.

I've been working through the available opencv examples on the web lately (just the basic stuff for fun). I've moved of from the face recognition (interesting, but too black box for my liking) to manually selecting the roi in the HSV space, then using 'camshift' to track. I was still getting variable results I didn't understand so I also plot all the intermediate processing windows such as the hsv image and the backproject image, also graph the histogram across the windows. Suddenly all is clear - you can see exactly what the computer is trying to work with.
Here is my working code for python3.4, opencv3. You can manually select the skin. Credit mostly to other examples I've found on the web.
The 'cv2.calcBAckProject' function thresholds out the skin features nicely.
import numpy as np
import cv2
roiPts = []
track_mode = False
termination = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
roiBox = None
kernel = np.ones((5, 5), np.uint8)
frame_width_in_px = 640
number_of_histogram_elements=16
def selectROI(event, x,y,flags,param):
global track_mode, roiPts
if (event == cv2.EVENT_LBUTTONDOWN) and (len(roiPts)==4): #reselecting ROI points so take out of tracking mode and empty current roipoints
roiPts=[]
track_mode = False
if (event==cv2.EVENT_LBUTTONDOWN) and (len(roiPts) < 4): #ROI point selection
roiPts.append([x, y])
cap = cv2.VideoCapture(0)
cv2.namedWindow("frame")
cv2.setMouseCallback("frame", selectROI)
while True:
ret, frame = cap.read()
if len(roiPts)<=4 and len(roiPts)>0:
for x,y in roiPts:
cv2.circle(frame, (x,y), 4, (0, 255, 0), 1) # draw small circle for each roi click
if len(roiPts)==4 and track_mode==False: #initialize the camshift
# convert the selected points to a box shape
roiBox = np.array(roiPts, dtype=np.int32)
s = roiBox.sum(axis=1)
tl = roiBox[np.argmin(s)]
br = roiBox[np.argmax(s)]
#extract the roi from the image and calculate the histograme
roi = frame[tl[1]:br[1], tl[0]:br[0]]
roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) #
roiHist = cv2.calcHist([roi], [0], None, [number_of_histogram_elements], [0, 180])
roiHist = cv2.normalize(roiHist, roiHist, 0, 255, cv2.NORM_MINMAX)
roiBox = (tl[0], tl[1], br[0], br[1])
track_mode = True #ready for camshift
if track_mode == True: #tracking mode
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
backProj = cv2.calcBackProject([hsv], [0], roiHist, [0, 180], 1)
#perfrom some noise reduction and smoothing
erosion = cv2.erode(backProj, kernel, iterations=2)
dilate = cv2.dilate(erosion, kernel, iterations=2)
(r, roiBox) = cv2.CamShift(dilate, roiBox, termination) #this takes prev roiBox and calculates the new roiBox
pts = np.int0(cv2.boxPoints(r))
cv2.polylines(frame, [pts], True, (0, 255, 0), 2) #tracking box
cv2.polylines(backProj, [pts], True, (0, 255, 0), 2) #tracking box
cv2.polylines(dilate, [pts], True, (0, 255, 0), 2) #tracking box
cv2.polylines(hsv, [pts], True, (0, 255, 0), 2) #tracking box
# plot histogram polyline across the windows
x = np.linspace(0,640,number_of_histogram_elements,dtype=np.int32)
y = roiHist.flatten().astype(np.int32, copy=False)-255 #note frame height needs to be greater than 255 which is the max histo value
y=np.absolute(y)
pts2 = np.stack((x, y), axis=1)
cv2.polylines(frame, [pts2], False, (0, 255, 0), 2)
cv2.polylines(hsv, [pts2], False, (0, 255, 0), 2)
cv2.imshow("backproject", backProj)
cv2.imshow("dilate", dilate)
cv2.imshow("hsv", hsv)
cv2.imshow("frame", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()

Related

How to get only water level contour with OpenCV python on raspberry pi

I am using raspberry pi4 (8GB) with pi camera to detect water level . I have defined a line from 0,375 to 800,375 . If top most point of water level contour goes above this line then I want to call a function. Here is my code and attached image of setup. How do I get water level contour only. Does it require canny edge detection over contours to get clear water level ? first I am getting largest contour and then defining its top most point.
import numpy as np
import cv2
import time
from datetime import datetime
#color=(255,0,0)
color=(0,255,0)
thickness=2
kernel = np.ones((2,2),np.uint8) # added 01/07/2021
picflag = 0 # set value to 1 once picture is taken
# function to take still picture when water level goes beyond threshold
def takepicture(frame):
currentTime = datetime.now()
picTime = currentTime.strftime("%d.%m.%Y-%H%M%S") # Create file name for our picture
text = currentTime.strftime("%d.%m.%Y-%H:%M:%S")
font = cv2.FONT_HERSHEY_SIMPLEX # font
org = (05, 20) # org
fontScale = 0.5 # fontScale
color = (0, 0, 255) # Red color in BGR
thickness = 1 # Line thickness of 2 px
picName = picTime + '.png'
image = cv2.putText(frame, text, org, font, fontScale, color, thickness, cv2.LINE_AA, False)
cv2.imwrite(picName , image)
picflag = 1
return
cap = cv2.VideoCapture(0)
while(True):
# Capture frame-by-frame
ret, frame = cap.read() # ret = 1 if the video is captured; frame is the image
# Our operations on the frame come here
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
#blur = cv2.GaussianBlur(gray,(21,21),0)
gray= cv2.medianBlur(gray, 3) #to remove salt and paper noise
#ret,thresh = cv2.threshold(gray,10,20,cv2.THRESH_BINARY_INV)
ret,thresh = cv2.threshold(gray,127,127,cv2.THRESH_BINARY_INV)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_GRADIENT, kernel) # get outer boundaries only added 01/07/2021
thresh = cv2.dilate(thresh,kernel,iterations = 5) # strengthen weak pixels added 01/07/2021
img1, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
#img1,contours,hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) #added 01/07/2021
cv2.line(frame, pt1=(0,375), pt2=(800,375), color=(0,0,255), thickness=2) # added 01/07/2021
if len(contours) != 0:
c = max(contours, key = cv2.contourArea) # find the largest contour
#x,y,w,h = cv2.boundingRect(c) # get bounding box of largest contour
img2=cv2.drawContours(frame, c, -1, color, thickness) # draw largest contour
#img2=cv2.drawContours(frame, contours, -1, color, thickness) # draw all contours
#img3 = cv2.rectangle(img2,(x,y),(x+w,y+h),(0,0,255),2) # draw red bounding box in img
#center = (x, y)
#print(center)
left = tuple(c[c[:, :, 0].argmin()][0])
right = tuple(c[c[:, :, 0].argmax()][0])
top = tuple(c[c[:, :, 1].argmin()][0])
bottom = tuple(c[c[:, :, 1].argmax()][0])
# Draw dots onto frame
cv2.drawContours(frame, [c], -1, (36, 255, 12), 2)
cv2.circle(frame, left, 8, (0, 50, 255), -1)
cv2.circle(frame, right, 8, (0, 255, 255), -1)
cv2.circle(frame, top, 8, (255, 50, 0), -1)
cv2.circle(frame, bottom, 8, (255, 255, 0), -1)
#print('left: {}'.format(left))
#print('right: {}'.format(right))
#print(format(top))
top_countour_point = top[1]
print(top_countour_point)
#print('bottom: {}'.format(bottom))
#if ((top_countour_point <= 375) and (picflag == 0)): #checking if contour top point is above line
#takepicture(frame)
#continue
#if ((top_countour_point > 375) and (picflag == 0)) :
#picflag = 0
#continue
# Display the resulting image
# cv2.line(frame, pt1=(0,375), pt2=(800,375), color=(0,0,255), thickness=2) # added 01/07/2021
#cv2.imshow('Contour',img3)
#cv2.imshow('thresh' ,thresh)
cv2.imshow('Contour',frame)
if cv2.waitKey(1) & 0xFF == ord('q'): # press q to quit
break
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
Caveats
As it was pointed out in the comments, there is very little to work with based on your post. In general, I agree with s0mbre that you'd be better of with a water level sensor, and with kavko, that if you do want to use a camera, you need to better control your environment, with lighting, camera angles, background, etc.
However, that is not to say that it is not possible with your current setup, assuming, that it is a static setup except for the water level. As such, there are some assumptions that we can make.
Here are the steps that I took to get an approximate approach:
Gather image data
You have only provided one image with lines already on it, so that's not a lot to go on. What I did is that I removed the line that you added:
Fortunately there wasn't too much of a line left afterwards.
Image processing:
I have loaded the image (this would come from the code you have posted).
Using the assumptions above, I have decided to focus on a narrow slice of the image. I selected only the middle 60 pixels (1)
slc = frame[:, 300:360]
Next, I have converted it to greyscale (2)
gray_slc = cv2.cvtColor(slc, cv2.COLOR_BGR2GRAY)
I have used Canny edge detection (see docs here) to find the edges in the image (3)
edges = cv2.Canny(gray_slc, 50, 90)
After that, I have applied a Hough Transform, to find all the edges (related Stack Overflow answer) (4)
rho = 1
theta = np.pi / 180
threshold = 15
min_line_length = 50
max_line_gap = 20
lines = cv2.HoughLinesP(edges, rho, theta, threshold, np.array([]), min_line_length, max_line_gap)
Given that I can assume that all of the top lines are the edge of the container, I averaged the y coordinates of the lines, and picked the lower most one as the water level:
y_avgs = [(line[0, 1] + line[0, 3]) / 2 for line in lines]
water_level = max(y_avgs)
Having this, I just checked, if it is over the threshold you have selected:
trigger_threshold = 375
if water_level > trigger_threshold:
print("Water level is under the selected line")
Now, keep in mind, I only had the one image to go on. Considering lighting conditions, yours results may vary.

OpenCV - Draw contours on fingers using convex-hulls & adaptive thresholding

I am pretty new to OpenCV and am trying to achieve drawing simple contours along the outline of my hand using a webcam. I decided on using cv2.adaptiveThreshold() to deal with the different light intensities when the camera adjusts to the hand moving. Everything seems to work fine except that it is struggling with finding the fingers and then also drawing closed contours.
See here:
I thought about trying to detect a convex hull and detect anything deviating from it somehow.
How do I go about this best? Firstly I need to manage to maybe not find weird closed contours and then go from there?
Here's the code, I fixed the trackbar values for you :)
import cv2
import numpy as np
#####################################
winWidth = 640
winHeight = 840
brightness = 100
cap = cv2.VideoCapture(0)
cap.set(3, winWidth)
cap.set(4, winHeight)
cap.set(10, brightness)
kernel = (7, 7)
#######################################################################
def empty(a):
pass
cv2.namedWindow("TrackBars")
cv2.resizeWindow("TrackBars", 640, 240)
cv2.createTrackbar("cVal", "TrackBars", 10, 40, empty)
cv2.createTrackbar("bSize", "TrackBars", 77, 154, empty)
def preprocessing(frame, value_BSize, cVal):
imgGray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# mask = cv2.inRange(imgHsv, lower, upper)
imgBlurred = cv2.GaussianBlur(imgGray, kernel, 4)
gaussC = cv2.adaptiveThreshold(imgBlurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, value_BSize,
cVal)
imgDial = cv2.dilate(gaussC, kernel, iterations=3)
imgErode = cv2.erode(imgDial, kernel, iterations=1)
return imgDial
def getContours(imPrePro):
contours, hierarchy = cv2.findContours(imPrePro, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
for cnt in contours:
area = cv2.contourArea(cnt)
if area > 60:
cv2.drawContours(imgCon, cnt, -1, (0, 255, 0), 2, cv2.FONT_HERSHEY_SIMPLEX)
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
#######################################################################################################
while cap.isOpened():
success, frame = cap.read()
cVal = cv2.getTrackbarPos("cVal", "TrackBars")
bVal = cv2.getTrackbarPos("bVal", "TrackBars")
value_BSize = cv2.getTrackbarPos("bSize", "TrackBars")
value_BSize = max(3, value_BSize)
if (value_BSize % 2 == 0):
value_BSize += 1
if success == True:
frame = cv2.flip(frame, 1)
imgCon = frame.copy()
imPrePro = preprocessing(frame, value_BSize, cVal)
getContours(imPrePro)
cv2.imshow("Preprocessed", imPrePro)
cv2.imshow("Original", imgCon)
if cv2.waitKey(1) & 0xFF == ord("q"):
cv2.destroyAllWindows()
break
L*a*b color space can help find objects brighter than the background. One advantage is that color space is hardware independent, so it should yield relatively similar results from any camera. Using the OTSU option to threshold the image can help it work in different lightning conditions, as it calculates the optimal threshold intensity to separate bright and dark areas in the image. Obviously it is not a silver bullet and will NOT work perfectly in every situation, especially in extreme cases, but as long your hand's brightness is relatively different from the background, it should work.
lab = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)
tv, thresh = cv2.threshold(lab[:,:,0], 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
plt.imshow(thresh)
Once the hand is properly thresholded, you can proceed to find the contours and do your analysis as needed.
Note: the artifacts in the threholded image are caused by removing the green contour lines from the original posted image.
I'm using a light threshold so this might work differently depending on the image, but here's what works for this one.
import cv2
import numpy as np
# load image
img = cv2.imread("hand.jpg");
# lab
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB);
l,a,b = cv2.split(lab);
# threshold
thresh = cv2.inRange(l, 90, 255);
# contour
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
# filter contours by size
marked = img.copy();
cv2.drawContours(marked, contours, -1, (0, 255, 0), 3);
# show
cv2.imshow("marked", marked);
cv2.imshow("Thresh", thresh);
cv2.waitKey(0);
# save
cv2.imwrite("marked_hand.png", marked);
Have you looked into google's mediapipe, as an alternate to opencv?
Also, I wonder if adding a thin bottom black border to the frame would help the contour to know to wrap around the wrist.

How to use Python and OpenCV to achieve a well edge detection to calibrate some very small things

I want to use Python and OpenCV to achieve a non-Neural Network edge detection to calibrate some very small things, such as sperms under the microscope. Unfortunately, I found that the sperms' tails are very difficult to calibrate and they're really similar with the background.
I used cv2.pyrMeanShiftFiltering() to achieve noise reduction and used cv2.findContours() to find contours. The result is like that:
result:
This is the original picture:
Here is my code:
import cv2 as cv
import numpy as np
import os
path = "/home/rafael/Desktop/2.jpg"
def detection(img):
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
#ret, dst = cv.threshold(gray, 200, 255, cv.THRESH_OTSU)
ret, dst = cv.threshold(gray, 188, 255, cv.THRESH_BINARY_INV)
return dst
image = cv.imread(path)
img = cv.pyrMeanShiftFiltering(src = image, sp = 5, sr = 40)
dst = detection(img)
src, contours, hierarchy = cv.findContours(dst, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
cv.drawContours(image, contours, -1, (0, 0, 255), 2)
cv.namedWindow('img', cv.WINDOW_NORMAL)
cv.imshow('img', image)
cv.waitKey(0)
I tried Luke's method, and the code is here:
import cv2 as cv
import numpy as np
import os
path = "/home/rafael/Desktop/2.jpg"
def enhance(img):
img = cv.resize(img, (0, 0), fx = 0.3, fy = 0.3)
blur = cv.GaussianBlur(img, (23, 23), 0)
img = cv.add(img[:, :, 1], (img[:, :, 1] - blur[:, :, 1]))
return img
def detection(img):
ret, dst = cv.threshold(img, 190, 255, cv.THRESH_BINARY_INV)
return dst
image = cv.imread(path)
img = enhance(image)
dst = detection(img)
src, contours, hierarchy = cv.findContours(dst, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
cv.drawContours(img, contours, -1, (0, 0, 255), 2)
cv.namedWindow('img', cv.WINDOW_NORMAL)
cv.imshow('img', img)
cv.waitKey(0)
This is the result:
The latest picture
Although I used a very big threshold(190), even appeared plenty of noises,the code still couldn't find the tails. How can I solve the problem?
So thanks a lot if anyone could teach me how to improve this simple edge detection program.
Are the sperm tails always green-blue on a gray background? In that case, you can use simple segmentation.
First convert the image to HSV, if the H value is in a range for blue/green, mark it as foreground.
import cv2
import numpy as np
img = cv2.imread('img.jpg')
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
lower = np.array([50, 10, 10])
upper = np.array([120, 255, 255])
mask = cv2.inRange(hsv, lower, upper)
res = cv2.bitwise_and(img,img, mask= mask)
cv2.imwrite('test.jpg', res)
kernel = np.ones((5,5), np.uint8) # note this is a horizontal kernel
d_im = cv2.dilate(mask, kernel, iterations=1)
e_im = cv2.erode(d_im, kernel, iterations=1)
cv2.imwrite('d.jpg', d_im)
cv2.imwrite('e.jpg', e_im)
Images in order are: image with mask applied, image mask with dilation, and image mask with erosion.
You might want enhance your image before performing any operation. Going through this Wikipedia page, I came across the following formula for unsharp-masking:
sharpened = original + (original − blurred) × amount
Assuming amount = 1, I wrote the following snippet:
#--- resized the image (too big !!!) ---
im = cv2.resize(im, (0, 0), fx = 0.3, fy = 0.3)
#--- smoothen the image ---
blur = cv2.GaussianBlur(im, (23, 23), 0)
#--- applied the formula assuming amount = 1---
cv2.imshow('sharpened', cv2.add(im[:,:,1], (im[:,:,1] - blur[:,:,1])))
This is what I got:
The tails are a lot more visible now. Use this to further enhance your detection mechanism.
There are a lot of creative ways to go about edge detection for high frequency edges (like sperm tails for example)
I recommend cv2.Canny() for general edge detection - you will have to play around with the inputs for your specific application.
Alternatively, you can do a difference of gaussian, where you take cv2.GaussianBlur() with two different sigmas and take the difference (https://en.wikipedia.org/wiki/Difference_of_Gaussians)
i.e,
blur1 = cv2.GaussianBlur(im, (5,5), sigmaX_1, sigmaY_1)
blur2 = cv2.GaussianBlur(im, (5,5), sigmaX_2, sigmaY_2)
DoG_edge = blur1 - blur2
One final possibility is that you could also try some histogram manipulation to enhance the tails of the sperm.

How to detect a barcode's 4 corners in python? [duplicate]

I'm trying to detect this Code128 barcode with Python + zbar module:
(Image download link here).
This works:
import cv2, numpy
import zbar
from PIL import Image
import matplotlib.pyplot as plt
scanner = zbar.ImageScanner()
pil = Image.open("000.jpg").convert('L')
width, height = pil.size
plt.imshow(pil); plt.show()
image = zbar.Image(width, height, 'Y800', pil.tobytes())
result = scanner.scan(image)
for symbol in image:
print symbol.data, symbol.type, symbol.quality, symbol.location, symbol.count, symbol.orientation
but only one point is detected: (596, 210).
If I apply a black and white thresholding:
pil = Image.open("000.jpg").convert('L')
pil = pil .point(lambda x: 0 if x<100 else 255, '1').convert('L')
it's better, and we have 3 points: (596, 210), (482, 211), (596, 212). But it adds one more difficulty (finding the optimal threshold - here 100 - automatically for every new image).
Still, we don't have the 4 corners of the barcode.
Question: how to reliably find the 4 corners of a barcode on an image, with Python? (and maybe OpenCV, or another library?)
Notes:
It is possible, this is a great example (but sadly not open-source as mentioned in the comments):
Object detection, very fast and robust blurry 1D barcode detection for real-time applications
The corners detection seems to be excellent and very fast, even if the barcode is only a small part of the whole image (this is important for me).
Interesting solution: Real-time barcode detection in video with Python and OpenCV but there are limitations of the method (see in the article: the barcode should be close up, etc.) that limit the potential use. Also I'm more looking for a ready-to-use library for this.
Interesting solution 2: Detecting Barcodes in Images with Python and OpenCV but again, it does not seem like a production-ready solution, but more a research in progress. Indeed, I tried their code on this image but the detection does not yield successful result. It has to be noted that it doesn't take any spec of the barcode in consideration for the detection (the fact there's a start/stop symbol, etc.)
import numpy as np
import cv2
image = cv2.imread("000.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gradX = cv2.Sobel(gray, ddepth = cv2.CV_32F, dx = 1, dy = 0, ksize = -1)
gradY = cv2.Sobel(gray, ddepth = cv2.CV_32F, dx = 0, dy = 1, ksize = -1)
gradient = cv2.subtract(gradX, gradY)
gradient = cv2.convertScaleAbs(gradient)
blurred = cv2.blur(gradient, (9, 9))
(_, thresh) = cv2.threshold(blurred, 225, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (21, 7))
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
closed = cv2.erode(closed, None, iterations = 4)
closed = cv2.dilate(closed, None, iterations = 4)
(_, cnts, _) = cv2.findContours(closed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
c = sorted(cnts, key = cv2.contourArea, reverse = True)[0]
rect = cv2.minAreaRect(c)
box = np.int0(cv2.boxPoints(rect))
cv2.drawContours(image, [box], -1, (0, 255, 0), 3)
cv2.imshow("Image", image)
cv2.waitKey(0)
Solution 2 is pretty good. The critical factor that made it fail on your image was the thresholding. If you drop the parameter 225 way down to 55, you'll get much better results.
I've reworked the code, making some tweaks here and there. The original code is fine if you prefer. The documentation for OpenCV is quite good, and there are very good Python tutorials.
import numpy as np
import cv2
image = cv2.imread("barcode.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# equalize lighting
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
gray = clahe.apply(gray)
# edge enhancement
edge_enh = cv2.Laplacian(gray, ddepth = cv2.CV_8U,
ksize = 3, scale = 1, delta = 0)
cv2.imshow("Edges", edge_enh)
cv2.waitKey(0)
retval = cv2.imwrite("edge_enh.jpg", edge_enh)
# bilateral blur, which keeps edges
blurred = cv2.bilateralFilter(edge_enh, 13, 50, 50)
# use simple thresholding. adaptive thresholding might be more robust
(_, thresh) = cv2.threshold(blurred, 55, 255, cv2.THRESH_BINARY)
cv2.imshow("Thresholded", thresh)
cv2.waitKey(0)
retval = cv2.imwrite("thresh.jpg", thresh)
# do some morphology to isolate just the barcode blob
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9))
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
closed = cv2.erode(closed, None, iterations = 4)
closed = cv2.dilate(closed, None, iterations = 4)
cv2.imshow("After morphology", closed)
cv2.waitKey(0)
retval = cv2.imwrite("closed.jpg", closed)
# find contours left in the image
(_, cnts, _) = cv2.findContours(closed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
c = sorted(cnts, key = cv2.contourArea, reverse = True)[0]
rect = cv2.minAreaRect(c)
box = np.int0(cv2.boxPoints(rect))
cv2.drawContours(image, [box], -1, (0, 255, 0), 3)
print(box)
cv2.imshow("found barcode", image)
cv2.waitKey(0)
retval = cv2.imwrite("found.jpg", image)
edge.jpg
thresh.jpg
closed.jpg
found.jpg
output from console:
[[596 249]
[470 213]
[482 172]
[608 209]]
For the following to work, you need to have contrib package installed using pip install opencv-contrib-python
Your OpenCV version would now have a separate class for detecting barcodes.
cv2.barcode_BarcodeDetector() comes equipped with 3 in-built functions:
decode(): returns decoded information and type
detect(): returns the 4 corner points enclosing each detected barcode
detectAndDecode(): returns all the above
Sample Image used is from pyimagesearch blog:
The 4 corners are captured in points.
Code:
img = cv2.imread('barcode.jpg')
barcode_detector = cv2.barcode_BarcodeDetector()
# 'retval' is boolean mentioning whether barcode has been detected or not
retval, decoded_info, decoded_type, points = barcode_detector.detectAndDecode(img)
# copy of original image
img2 = img.copy()
# proceed further only if at least one barcode is detected:
if retval:
points = points.astype(np.int)
for i, point in enumerate(points):
img2 = cv2.drawContours(img2,[point],0,(0, 255, 0),2)
# uncomment the following to print decoded information
#x1, y1 = point[1]
#y1 = y1 - 10
#cv2.putText(img2, decoded_info[i], (x1, y1), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 3, 2)
Result:
Detected barcode:
Detected barcode and information:
One solution not discussed here is PyZbar.
It is helpful to know there are a number of different types of barcode so reading this can be helpful. Now each solution for decoding might have limitations for the types it can decode. #Jeru Luke's solution seems to be only support EAN-13 barcodes currently see docs here.
Now using PyZbar a simple solution for getting the rect object (4 corners) with the decoding and the bonus of finding out which type the barcode it is can be done with this script.
Using this barcode
import cv2
from pyzbar.pyzbar import decode
file_path = r'c:\my_file'
img = cv2.imread(file_path)
detectedBarcodes = decode(img)
for barcode in detectedBarcodes:
(x, y, w, h) = barcode.rect
cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 5)
print(barcode.rect)
print(barcode.data)
print(barcode.type)
output
Rect(left=77, top=1, width=665, height=516)
b'9771234567003'
EAN13
Using #Jeru Luke's code you can drawContours and putText.
ZBar supports
EAN-13/UPC-A,
UPC-E, EAN-8,
Code 128,
Code 93,
Code 39,
Codabar,
Interleaved 2 of 5,
QR Code
SQ Code.
So I think PyZbar will also support these types.

Python OpenCV - Extrapolating the largest rectangle off of a set of contour points

I'm trying to make an OpenCV detect a bed in the image. I am running the usual Grayscale, Blur, Canny, and I've tried Convex Hull. However, since there's quite a number of "noise" which gives extra contours and messes up the object detection. Because of this, I am unable to detect the bed properly.
Here is the input image as well as the Canny Edge Detection result:
As you can see, it's almost there. I have the outline of the bed already, albeit, that the upper right corner has a gap - which is preventing me from detecting a closed rectangle.
Here's the code I'm running:
import cv2
import numpy as np
def contoursConvexHull(contours):
print("contours length = ", len(contours))
print("contours length of first item = ", len(contours[1]))
pts = []
for i in range(0, len(contours)):
for j in range(0, len(contours[i])):
pts.append(contours[i][j])
pts = np.array(pts)
result = cv2.convexHull(pts)
print(len(result))
return result
def auto_canny(image, sigma = 0.35):
# compute the mediam of the single channel pixel intensities
v = np.median(image)
# apply automatic Canny edge detection using the computed median
lower = int(max(0, (1.0 - sigma) * v))
upper = int(min(255, (1.0 + sigma) *v))
edged = cv2.Canny(image, lower, upper)
# return edged image
return edged
# Get our image in color mode (1)
src = cv2.imread("bed_cv.jpg", 1)
# Convert the color from BGR to Gray
srcGray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
# Use Gaussian Blur
srcBlur = cv2.GaussianBlur(srcGray, (3, 3), 0)
# ret is the returned value, otsu is an image
##ret, otsu = cv2.threshold(srcBlur, 0, 255,
## cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# Use canny
##srcCanny = cv2.Canny(srcBlur, ret, ret*2, 3)
srcCanny1 = auto_canny(srcBlur, 0.70)
# im is the output image
# contours is the contour list
# I forgot what hierarchy was
im, contours, hierarchy = cv2.findContours(srcCanny1,
cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
##cv2.drawContours(src, contours, -1, (0, 255, 0), 3)
ConvexHullPoints = contoursConvexHull(contours)
##cv2.polylines(src, [ConvexHullPoints], True, (0, 0, 255), 3)
cv2.imshow("Source", src)
cv2.imshow("Canny1", srcCanny1)
cv2.waitKey(0)
Since the contour of the bed isn't closed, I can't fit a rectangle nor detect the contour with the largest area.
The solution I can think of is to extrapolate the largest possible rectangle using the contour points in the hopes of bridging that small gap, but I'm not too sure how to proceed since the rectangle is incomplete.
Since you haven't provided any other examples, I provide an algorithm working with this case. But bare in mind that you will have to find ways of adapting it to however the light and background changes on other samples.
Since there is a lot of noise and a relatively high dynamic range, I suggest not to use Canny and instead use Adaptive Thresholding and Find Contours on that (it doesn't need edges as an input), that helps with choosing different threshold values for different parts of the image.
My result:
Code:
import cv2
import numpy as np
def clahe(img, clip_limit=2.0, grid_size=(8,8)):
clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=grid_size)
return clahe.apply(img)
src = cv2.imread("bed.png")
# HSV thresholding to get rid of as much background as possible
hsv = cv2.cvtColor(src.copy(), cv2.COLOR_BGR2HSV)
lower_blue = np.array([0, 0, 120])
upper_blue = np.array([180, 38, 255])
mask = cv2.inRange(hsv, lower_blue, upper_blue)
result = cv2.bitwise_and(src, src, mask=mask)
b, g, r = cv2.split(result)
g = clahe(g, 5, (3, 3))
# Adaptive Thresholding to isolate the bed
img_blur = cv2.blur(g, (9, 9))
img_th = cv2.adaptiveThreshold(img_blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 51, 2)
im, contours, hierarchy = cv2.findContours(img_th,
cv2.RETR_CCOMP,
cv2.CHAIN_APPROX_SIMPLE)
# Filter the rectangle by choosing only the big ones
# and choose the brightest rectangle as the bed
max_brightness = 0
canvas = src.copy()
for cnt in contours:
rect = cv2.boundingRect(cnt)
x, y, w, h = rect
if w*h > 40000:
mask = np.zeros(src.shape, np.uint8)
mask[y:y+h, x:x+w] = src[y:y+h, x:x+w]
brightness = np.sum(mask)
if brightness > max_brightness:
brightest_rectangle = rect
max_brightness = brightness
cv2.imshow("mask", mask)
cv2.waitKey(0)
x, y, w, h = brightest_rectangle
cv2.rectangle(canvas, (x, y), (x+w, y+h), (0, 255, 0), 1)
cv2.imshow("canvas", canvas)
cv2.imwrite("result.jpg", canvas)
cv2.waitKey(0)

Categories