I am attempting to threshold a wave so that the white background appears black and the wave itself which was originally black is white, however it only seems to return an entirely black image. What am I doing wrong?
import cv2
src = cv2.imread("C:\\Users\\ksatt\\Desktop\\SoundByte\\blackwaveblackaxis (1).PNG",0)
maxValue = 255
thresh= 53
if not src is None:
th, dst = cv2.threshold(src, thresh, maxValue, cv2.THRESH_BINARY_INV)
cv2.imshow("blackwave.PNG", dst)
cv2.imwrite("blackwave.PNG", dst)
cv2.waitKey(0)
else:
print 'Image could not be read'
Your threshold is too low, and the dark paper is going to pick up values that you don't want anyways. Basically, the contrast of the image is too low.
One easy solution is to subtract out the background. The simple way to do this is to dilate() your grayscale image, which will expand the white area and overtake the black lines. Then you can apply a small GaussianBlur() to that dilated image, and this will give you a "background" image that you can subtract from your original image to get a clear view of the lines. From there you'll have a much better image to threshold(), and you can even use OTSU thresholding to automatically set the threshold level for you.
import cv2
import numpy as np
# read image
src = cv2.imread('wave.png',0)
# create background image
bg = cv2.dilate(src, np.ones((5,5), dtype=np.uint8))
bg = cv2.GaussianBlur(bg, (5,5), 1)
# subtract out background from source
src_no_bg = 255 - cv2.absdiff(src, bg)
# threshold
maxValue = 255
thresh = 240
retval, dst = cv2.threshold(src_no_bg, thresh, maxValue, cv2.THRESH_BINARY_INV)
# automatic / OTSU threshold
retval, dst = cv2.threshold(src_no_bg, 0, maxValue, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
You can see that manual thresholding gives the same results as OTSU, but you don't have to play around with the values for OTSU, it'll find them for you. This isn't always the best way to go but it can be quick sometimes. Check out this tutorial for more on different thresholding operations.
if you take a look at http://docs.opencv.org/2.4/modules/imgproc/doc/miscellaneous_transformations.html#threshold it will tell you what each parameter does of the function.
Also here is a good tutorial:
http://docs.opencv.org/trunk/d7/d4d/tutorial_py_thresholding.html
Python: cv.Threshold(src, dst, threshold, maxValue, thresholdType) →
None
Is the prototype which gets further explenation in the mentioned API.
So simply change your code to:
cv2.threshold(src,RESULT, thresh, maxValue, cv2.THRESH_BINARY_INV)
cv2.imshow("blackwave.PNG", RESULT)
Could you post a picture of the wave? Have you tried using standard python? Something like this should work:
import numpy as np
import matplotlib.pyplot as plt
maxValue = 255
thresh= 53
A = np.load('file.png')
# For each pixel, see if it's above/below the threshold
for i in range(A.shape[0]): # Loop along the X direction
for j in range(A.shape[1]): # Loop along the Y direction
# Set to black the background
if A[i,j] > thresh:
A[i,j] = 0
if A[i,j] == 0:
A[i,j] = 255
Or something similar.
Related
How to detect filled rectangles in image?
I need to get the bounding box for the 4 white (filled with white) rectangles in the right side of the image, but not the big rectangle in the middle with a white outline
You can isolate each contour by drawing the contour on a mask. Then you can use that mask on the image to calculate the average color. A high average indicates that the contour contains mostly white, so it is likely a contour you want.
Result:
Code:
import numpy as np
import cv2
#load the image
img = cv2.imread("form.png")
# create grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#Find contours (external only):
im, contours, hierarchy = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#draw contours on original image
for cnt in contours:
# disregard small contours cause by logo and noise
if cv2.contourArea(cnt) > 10000:
#isolate contour and calculate average pixel value
mask = np.zeros(gray.shape[:2],np.uint8)
cv2.drawContours(mask,[cnt],0,255,-1)
mean_val = cv2.mean(gray,mask = mask)
# a high value indicates the contour contains mostly white, so draw the contour (I used the boundingRect)
if mean_val[0] > 200:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img, (x,y),(x+w,y+h), (0,0,255), thickness=4)
# show/save image
cv2.imshow("Image", mask)
cv2.imwrite("result.jpg", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Note: you can also load the image as grayscale and skip creating one, but I used it here so I could draw more obvious red boxes.
Also be aware the code given might not generalize well, but it shows the concept.
The following python script should split overlapping cells apart which does work quite good. The problem is now that it also splits some of the cells apart which don't overlap with other cells. To make things clear to you i'll add my input image and the output image.
The input:input image
The output:
output image
Output image where I marked two "bad" segmented cells:Output image with marked errors
Thresholded image: Thresholded image
Does someone have an idea how to avoid this problem or is the whole approach not good enough to process these kind of images?
I am using the following piece of code to segment the cells:
from skimage.feature import peak_local_max
from skimage.morphology import watershed
from scipy import ndimage
import numpy as np
import cv2
# load the image and perform pyramid mean shift filtering
# to aid the thresholding step
image = cv2.imread('C:/Users/Root/Desktop/image13.jpg')
shifted = cv2.pyrMeanShiftFiltering(image, 41, 51)
# convert the mean shift image to grayscale, then apply
# Otsu's thresholding
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
im = gray.copy()
D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, indices=False, min_distance=3,
labels=thresh)
# perform a connected component analysis on the local peaks,
# using 8-connectivity, then apply the Watershed algorithm
markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]
labels = watershed(-D, markers, mask=thresh)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1))
conts=[]
for label in np.unique(labels):
# if the label is zero, we are examining the 'background'
# so simply ignore it
if label == 0:
continue
# otherwise, allocate memory for the label region and draw
# it on the mask
mask = np.zeros(gray.shape, dtype="uint8")
mask[labels == label] = 255
# detect contours in the mask and grab the largest one
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)[-2]
c = max(cnts, key=cv2.contourArea)
rect = cv2.minAreaRect(c)
box = cv2.boxPoints(rect)
box = np.int0(box)
if cv2.contourArea(c) > 150:
#cv2.drawContours(image,c,-1,(0,255,0))
cv2.drawContours(image,[box],-1,(0,255,0))
cv2.imshow("output", image)
cv2.waitKey()
i have a picture from a laser line and i would like to extract that line out of the image.
As the laser line is red, i take the red channel of the image and then searching for the highest intensity in every row:
The problem now is, that there are also some points which doesnt belong to the laser line (if you zoom into the second picture, you can see these points).
Does anyone have an idea for the next steps (to remove the single points and also to extract the lines)?
That was another approach to detect the line:
First i blurred out that "black-white" line with a kernel, then i thinned(skeleton) that blurred line to a thin line, then i applied an OpenCV function to detect the line.. the result is in the below image:
NEW:
Now i have another harder situation.
I have to extract a green laser light.
The problem here is that the colour range of the laser line is wider and changing.
On some parts of the laser line the pixel just have high green component, while on other parts the pixel have high blue component as well.
Getting the highest value in every row will always output a value, instead of ignoring when the value isn't high enough. Consider using a threshold too, so that you can discard ones that aren't high enough.
However, that's not a very efficient way to do this at all. A much better and easier solution would be to use the OpenCV function inRange(); define a lower and upper bound for the red color in all three channels, and this will return a binary image with white pixels where the image intensity is within that BGR range.
This is in python but it does the job, should be easy to see how to use the function:
import cv2
import numpy as np
img = cv2.imread('image.png')
lowerb = np.array([0, 0, 120])
upperb = np.array([100, 100, 255])
red_line = cv2.inRange(img, lowerb, upperb)
cv2.imshow('red', red_line)
cv2.waitKey(0)
This produces the output:
This could be further processed by finding contours or other methods to turn the points into a nice curve.
I'm really sorry for the short answer without any code, but I suggest you take contours and process them.
I dont know exact what you need, so here are two approaches for you:
just collect as much as possible contours on single line (use centers and try find straight line with smallest mean)
as first way, but trying heuristically combine separated lines.... it's much harder, but this may give you almost full laser line from image.
--
Some example for yours picture:
import cv2
import numpy as np
import math
img = cv2.imread('image.png')
hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
# filtering red area of hue
redHueArea = 15
redRange = ((hsv[:, :, 0] + 360 + redHueArea) % 360)
hsv[np.where((2 * redHueArea) > redRange)] = [0, 0, 0]
# filtering by saturation
hsv[np.where(hsv[:, :, 1] < 95)] = [0, 0, 0]
# convert to rgb
rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
# select only red grayscaled channel with low threshold
gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)
gray = cv2.threshold(gray, 15, 255, cv2.THRESH_BINARY)[1]
# contours processing
(_, contours, _) = cv2.findContours(gray.copy(), cv2.RETR_LIST, 1)
for c in contours:
area = cv2.contourArea(c)
if area < 8: continue
epsilon = 0.1 * cv2.arcLength(c, True) # tricky smoothing to a single line
approx = cv2.approxPolyDP(c, epsilon, True)
cv2.drawContours(img, [approx], -1, [255, 255, 255], -1)
cv2.imshow('result', img)
cv2.waitKey(0)
In your case it's work perfectly, but, as i already said, you will need to do much more work with contours.
Can you anyone tell me how to get threshold image using frame differencing
I have an idea about various background subtraction method but i donot want to use them.
cv2.substract(img1,img2) gives this image
substract:
but i want this result threshold
Can anyone tell me how to do that
i donot want to use cv2.createBackgroundSubtractorMOG2() or such kind function
here is my
import cv2
import numpy as np
t=0.01
background=cv2.imread('background2.png')
cap = cv2.VideoCapture('car.mp4')
ret,img=cap.read()
avg_img = np.float32(img)
while 1:
ret,img=cap.read()
cv2.accumulateWeighted(img,avg_img,0.1)
res1 = cv2.convertScaleAbs(avg_img)
sub_img=cv2.subtract(res1,img)
cv2.imshow('sub_img',sub_img)
cv2.imshow('sub_img',sub_img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
My opencv version 3.0.0 python 2.7
Since you are OK with this being done outside of cv2:
# Subtraction, assuming img1 and img2 are numpy arrays with same dimension; optionally, use np.abs(img1-img2) if you don't care about the sign of the difference
sub = img1 - img2
# Thresholding
threshold = 128 # Set your threshold value
sub[sub >= threshold] = 255 # Above or equal threshold go to max value
sub[sub < threshold] = 0 # Below threshold go to min value
I want to be able to count the number of pixels in a detected object. I'm using the cv2.threshold function. Here is some sudo code.
import cv2
import numpy as np
import time
while True:
cam= cv2.VideoCapture(0)
while(cam.isOpened())
ret, image = cam.read()
image = cv2.GaussianBlur(image, (5,5), 0)
Image1 = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower= np.array([30,40,40], dtype='uint8')
upper= np.array([95,240,240], dtype='uint8')
Thresh= cv2.inRange(Image1, lower, upper)
From here on out, I have no idea how to count the pixels of my objects. How do you find the contours of a binary image? I suppose it could be possible to cv2.bitwise_and a full black image over the Thresh/ mask, but that seems like it could be slow and also I don't know how to create a fully black and white image like that.
So TD:LR, how do you count the number of pixels in an object from a binary image?
Note: I'm actually just after the largest object and only need the number of pixels, not the image.
Edit: not trying to count the total number of pixels detected, I've already done that. Want the number of pixels detected from the object with the largest number.
This is how I did it
import cv2
import numpy as np
import time
from scipy.ndimage import (labeled_comprehension, label, measurements, generate_binary_structure) # new import
while True:
cam= cv2.VideoCapture(0)
while(cam.isOpened())
ret, image = cam.read() # record image
image = cv2.GaussianBlur(image, (5,5), 0) # blur to remove noise
Image1 = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # convert to better color scheme
lower= np.array([30,40,40], dtype='uint8') # low green
upper= np.array([95,240,240], dtype='uint8') # high green
Thresh= cv2.inRange(Image1, lower, upper) # returns array with 255 as pixel if in threshold
struct = generate_binary_structure(2,2) # seems necessary for some reason
Label, features = label(Thresh, struct) # label is object, features is number of objects
Arange = np.arange(1, features+1) # seems necessary for some reason
Biggest = sorted(labeled_comprehension(Thresh, Label, Arange, np.sum, float, -1))[features-1]//255 # counts and organises the objects based on size. [features-1] means last object, ie: biggest. //255 because that's each pixel work (from thresh)