I want to change this background into the original black. This background is not pure black. Its values contain 1, 2 or 3. After using the following code I got the background value very near to black but not black. Although the background looks black
img = cv2.imread("images.bmp")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2. THRESH_BINARY)
img[thresh == 5] = 0
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
erosion = cv2.erode(img, kernel, iterations = 1)
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.imshow("image", erosion)
cv2.waitKey(0)
cv2.destroyAllWindows()
This should fix your problem, to the best of my understanding.
import cv2
gray = cv2.imread(r"brain.png", cv2.IMREAD_GRAYSCALE)
thresh_val = 5
gray[gray < thresh_val] = 0
Besides that, watch out that
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
is basically going to set the whole image to 255, since the second argument is the threshold and every pixel above threshold is set to the third value, which is 255.
Try replacing this:
ret, thresh = cv2.threshold(gray, 0, 255, cv2. THRESH_BINARY)
img[thresh == 5] = 0
with this:
# threshold to 10% of the maximum
threshold = 0.10 * np.max(img)
img[gray <= threshold] = 0
The issue is that cv2.threshold() does not compute a threshold for you, but applies one and, for example, thresh in your code is already the thresholded image.
(EDITED)
Related
#Segmenting the red pointer
img = cv2.imread('flatmap.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_red = np.array([140, 110, 0])
upper_red = np.array([255, 255 , 255])
# Threshold with inRange() get only specific colors
mask_red = cv2.inRange(hsv, lower_red, upper_red)
# Perform bitwise operation with the masks and original image
red_pointer = cv2.bitwise_and(img,img, mask= mask_red)
# Display results
cv2.imshow('Red pointer', red_pointer)
cv2.imwrite('redpointer.jpg', red_pointer)
cv2.waitKey(0)
cv2.destroyAllWindows()
I have a map and need to extract the red arrow. The code works but the arrow has black patches in it. How would I go about altering the code to improve the output of the arrow so it's a solid shape?
You could use:
dilate to fill up the internal noise in the shape
external contour finding to get the outline of the triangle
convex hull to further smooth it out
import cv2
import numpy as np
img = cv2.imread('dCkpC.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_red = np.array([140, 60, 0])
upper_red = np.array([255, 255, 255])
mask_red = cv2.inRange(hsv, lower_red, upper_red)
element = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
mask_red = cv2.dilate(mask_red, element)
contours, _ = cv2.findContours(mask_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
hull_list = [cv2.convexHull(contour) for contour in contours]
drawing = np.zeros_like(img)
for hull in hull_list:
cv2.fillConvexPoly(img, hull, (255, 0, 0))
cv2.imshow('Image', img)
cv2.imwrite('out.jpg', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
out.jpg ends up looking like
where the triangle has been filled in with blue.
I've looked at the channels in HSL/HSV space.
The arrows are the only stuff in the picture that has any saturation. That would be one required (but insufficient) aspect to get a lock on the desired arrow. I've picked those pixels and they appear to have a bit more than 50% saturation, so I'll use a lower bound of 25% (64).
That red arrow's hue dithers around 0 degrees (red)... that means some of its pixels are on the negative side of 0, i.e. something like 359 degrees.
You need to use two inRange calls to collect all hues from 0 up, and all hues from 359 down. Since OpenCV encodes hues in 2-degree steps, that'll be a value of 180 and down. I'll select 0 +- 20 degrees (0 .. 10 and 170 .. 180).
In summary:
hsv_im = cv.cvtColor(im, cv.COLOR_BGR2HSV)
mask1 = cv.inRange(hsv_im, np.array([0, 64, 0]), np.array([10, 255, 255]))
mask2 = cv.inRange(hsv_im, np.array([170, 64, 0]), np.array([180, 255, 255]))
mask = mask1 | mask2
cv.imshow("mask", mask)
cv.waitKey()
I am using opencv to take images using my webcam.
cam = cv2.VideoCapture(0)
cv2.namedWindow("Handwritten Number Recognition")
img_counter = 0
while True:
ret, frame = cam.read()
if not ret:
print("failed to grab frame")
break
cv2.imshow("Handwritten Number Recognition", frame)
k = cv2.waitKey(1)
if k%256 == 27:
# ESC pressed
print("Prediction is underway...")
break
elif k%256 == 32:
# SPACE pressed
img_name = "opencv_frame_{}.png".format(img_counter)
cv2.imwrite(img_name, frame)
print("Image taken!")
img_counter += 1
cam.release()
cv2.destroyAllWindows()
I then convert the image into grayscale and downsize it:
user_test = img_name
col = Image.open(user_test)
gray = col.convert('L')
bw = gray.point(lambda x: 0 if x<100 else 255, '1')
bw.save("bw_image.jpg")
bw
img_array = cv2.imread("bw_image.jpg", cv2.IMREAD_GRAYSCALE)
img_array = cv2.bitwise_not(img_array)
plt.imshow(img_array, cmap = plt.cm.binary)
plt.show()
img_size = 28
new_array = cv2.resize(img_array, (img_size,img_size))
final_array = new_array.reshape(1,-1)
plt.imshow(new_array, cmap = plt.cm.binary)
plt.show()
But the images have a very dark patch in the bottom which hampers the predictions I want to make with my data:
Original image:
What can I do to get past this problem? Interestingly this only happens if I click the image using opencv. If I use an image clicked through the same webcam but through the camera appliucation the error is not visible (Adding path of the image for preprocessing).
You have two options:
Choosing a better global threshold for the gray values. This is the easier less generic solution. Normally, people would choose the Otsu method to automatically select the optimal threshold. Have a look at: Opencv Thresholding Tutorial
threshold, dst_img = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)
Using an adaptive threshold. Adaptive simply means using a calculated threshold for each sliding window location based on some criteria. Have a look at: Niblack's Binarization methods
Using option one:
img = cv2.imread("thresh.jpg", cv2.IMREAD_GRAYSCALE)
threshold, img = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)
cv2.imwrite("thresh_bin.jpg", img)
Output:
this problem because of used thresholding method
bw = gray.point(lambda x: 0 if x<100 else 255, '1')
to solve this you can change the low limit value (100) to 75 or using opencv auto threshold
the, bw = cv2.threshold(gray_img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
Here is one way to do that in Python/OpenCV using division normalization, thresholding and some morphology.
Input:
import cv2
import numpy as np
# read the image
img = cv2.imread('five.jpg')
# convert to gray
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# blur
smooth = cv2.GaussianBlur(gray, (555,555), 0)
# divide smooth by gray image
division = cv2.divide(smooth, gray, scale=255)
# invert
division = 255 - division
# threshold
thresh = cv2.threshold(division, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# add white border to help morphology close
border = cv2.copyMakeBorder(thresh, 60,60,60,60, cv2.BORDER_CONSTANT, value=(255,255,255))
hh, ww = border.shape
# morphology close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (29,29))
result = cv2.morphologyEx(border, cv2.MORPH_CLOSE, kernel)
# remove border
result = result[60:hh-60, 60:ww-60]
# save results
cv2.imwrite('five_division_threshold.jpg',result)
# show results
cv2.imshow('smooth', smooth)
cv2.imshow('division', division)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
I've been trying to clean this image for OCR but getting mixed results:
Best I achieved:
def image_smoothening(img):
ret1, th1 = cv2.threshold(img, 180, 255, cv2.THRESH_BINARY)
ret2, th2 = cv2.threshold(th1, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
blur = cv2.GaussianBlur(th2, (1, 1), 0)
ret3, th3 = cv2.threshold(
blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
return th3
def remove_noise_and_smooth(img):
filtered = cv2.adaptiveThreshold(img.astype(
np.uint8), 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 45, 3)
kernel = np.ones((1, 1), np.uint8)
opening = cv2.morphologyEx(filtered, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel)
img = image_smoothening(img)
or_image = cv2.bitwise_or(img, closing)
return or_image
Any clue as to what I'm missing?
My MATLAB code to solve it. I know you are writing in Python so you'll have to translate.
%Read in
im = imread('DuQy7.png');
%Convert to grayscale
img = rgb2gray(im);
img = rescale(img);
%Binarize with threshold of 0.7/1.0
imbw = imbinarize(img,0.7/1);
%Flip blacks/whites
imbw = imcomplement(imbw);
%Label, L is labelled image, n is # of labels
[L,n] = bwlabeln(imbw);
count = zeros(n,1);
[y,x] = size(L);
%Get count for each label
L = uint8(L);
for j=1:y
for i=1:x
if L(j,i) ~= 0
count(L(j,i)) = count(L(j,i)) + 1;
end
end
end
%Find label with most values in image
max = 0;
maxi = 1;
for index=1:n
if max < count(index)
max = count(index);
maxi = index;
end
end
%Replace large region and color other labels to white
for j=1:y
for i=1:x
if L(j,i) == maxi
L(j,i) = 0;
elseif L(j,i) ~= 0
L(j,i) = 256;
end
end
end
%view and save
imshow(L)
imwrite(L,'outputTXT.bmp');
You could probably better adjust the threshold to better cut out background regions that got included. You could also look for labelled regions that are very small and remove them since they are probably erroneously included.
Some parts of the background are going to be impossible to get rid of since they are indistinguishable from the actual symbols. For example, between symbol x2,y1 and x2,y2 there is a black background region between the outlined white which is the same value as the symbols. Therefore it would be very difficult to parse out.
You can do "division normalization" in Python/OpenCV to remove the background. But that will not help with the outline font issue.
Input:
import cv2
import numpy as np
# read the image
img = cv2.imread('img.png')
# convert to gray
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# apply morphology
kernel = cv2.getStructuringElement(cv2.MORPH_RECT , (3,3))
smooth = cv2.morphologyEx(gray, cv2.MORPH_DILATE, kernel)
# alternate blur in place of morphology
#smooth = cv2.GaussianBlur(gray, (15,15), 0)
# divide gray by morphology image
division = cv2.divide(gray, smooth, scale=255)
# threshold
result = cv2.threshold(division, 0, 255, cv2.THRESH_OTSU )[1]
# save results
cv2.imwrite('img_thresh.png',result)
# show results
cv2.imshow('smooth', smooth)
cv2.imshow('division', division)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
I'm trying to detect colorful dots on a white/gray background. The dots are 3 different colors (yellow, purple, blue) of different sizes. Here is the original image:
I converted the image to HSV and found lower and upper bounds for each image then applied contour detection to find those dots. The following code detects most of the dots:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('image1_1.png')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_yellow = np.array([22,25,219])
upper_yellow = np.array([25,75,225])
lower_purple = np.array([141,31,223])
upper_purple = np.array([143,83,225])
lower_blue = np.array([92,32,202])
upper_blue = np.array([96,36,208])
mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)
mask_purple = cv2.inRange(hsv, lower_purple, upper_purple)
mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)
res_blue = cv2.bitwise_and(img,img, mask=mask_blue)
res_purple = cv2.bitwise_and(img,img, mask=mask_purple)
res_yellow = cv2.bitwise_and(img,img, mask=mask_yellow)
gray_blue = cv2.cvtColor(res_blue, cv2.COLOR_BGR2GRAY)
gray_purple = cv2.cvtColor(res_purple, cv2.COLOR_BGR2GRAY)
gray_yellow = cv2.cvtColor(res_yellow, cv2.COLOR_BGR2GRAY)
_,thresh_blue = cv2.threshold(gray_blue,10,255,cv2.THRESH_BINARY)
_,thresh_purple = cv2.threshold(gray_purple,10,255,cv2.THRESH_BINARY)
_,thresh_yellow = cv2.threshold(gray_yellow,10,255,cv2.THRESH_BINARY)
contours_blue, hierarhy1 = cv2.findContours(thresh_blue,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
contours_purple, hierarhy2 = cv2.findContours(thresh_purple,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
contours_yellow, hierarhy3 = cv2.findContours(thresh_yellow,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
result = img.copy()
cv2.drawContours(result, contours_blue, -1, (0, 0, 255), 2)
cv2.drawContours(result, contours_purple, -1, (0, 0, 255), 2)
cv2.drawContours(result, contours_yellow, -1, (0, 0, 255), 2)
cv2.imwrite("_allContours.jpg", result)
Here are the detected contours:
The problem is that some of the colored dots are not detected. I understand by fine-tuning the color ranges (lower and upper) it's possible to detect more dots. But that is very time consuming and not generalizable to similar images. For example the following image looks similar to the first image above and has the same colorful dots but the background is slightly different, once I ran it through above code it was not able to detect even one of the dots. Am I on the right track? Is there a more scalable and reliable solution with less need to tune color parameters in order to solve this problem? Here is the other image I tried:
I would suggest simply using adaptiveThreshold in Python/OpenCV
import cv2
import numpy as np
# read image
img = cv2.imread("dots.png")
# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# do adaptive threshold on gray image
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 25, 6)
# write results to disk
cv2.imwrite("dots_thresh.jpg", thresh)
# display it
cv2.imshow("thresh", thresh)
cv2.waitKey(0)
I need to remove the gray drawing from the image background and only need symbols drawn over it.
Here is my code to do that using morphologyEx but it did not remove the entire gray drawing that is in background.
img_path = "images/new_drawing.png"
img = cv2.imread(img_path)
kernel = np.ones((2,2), dtype=np.uint8)
result = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations=1)
cv2.imshow('Without background',result);
cv2.waitKey(0)
cv2.destroyAllWindows()
I tried this also and got expected results in grayscale but unable to convert it to BGR.
Here is my code
img = cv2.imread('images/new_drawing.png')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
med_blur = cv2.medianBlur(gray_img, ksize=3)
_, thresh = cv2.threshold(med_blur, 190, 255, cv2.THRESH_BINARY)
blending = cv2.addWeighted(gray_img, 0.5, thresh, 0.9, gamma=0)
cv2.imshow("blending", blending);
Also i used contours to identify symbols and draw them to white image but problem is that it also identify background drawing that i don't want.
Input image
Expected output image
Also the drawing will be always in gray color as in image.
Please help me out to get better result.
You are almost there...
Instead of using cv2.inRange to "catch" the non-gray pixel I suggest using cv2.inRange for catching all the pixels you want to change to white color:
mask = cv2.inRange(hsv, (0, 0, 100), (255, 5, 255))
The hue range is irrelevant.
The saturation is close to zero (shades of gray).
The brightness excludes the black pixels (you like to keep).
In order to get a nicer solution, I also used the following additional stages:
Build a mask of non-black pixels:
nzmask = cv2.inRange(hsv, (0, 0, 5), (255, 255, 255))
Erode the above mask:
nzmask = cv2.erode(nzmask, np.ones((3,3)))
Apply and operation between mask and nzmask:
mask = mask & nzmask
The above stages keeps the gray pixels around the black text.
Without the above stages, the black text gets thinner.
The last stage is replacing mask pixels with white:
new_img = img.copy()
new_img[np.where(mask)] = 255
Here is the code:
import numpy as np
import cv2
img_path = "new_drawing.png"
img = cv2.imread(img_path)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, (0, 0, 100), (255, 5, 255))
cv2.imshow('mask before and with nzmask', mask);
# Build mask of non black pixels.
nzmask = cv2.inRange(hsv, (0, 0, 5), (255, 255, 255))
# Erode the mask - all pixels around a black pixels should not be masked.
nzmask = cv2.erode(nzmask, np.ones((3,3)))
cv2.imshow('nzmask', nzmask);
mask = mask & nzmask
new_img = img.copy()
new_img[np.where(mask)] = 255
cv2.imshow('mask', mask);
cv2.imshow('new_img', new_img);
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
Here is one way to do that in Python/OpenCV.
Read the input
Convert to HSV and separate channels
Threshold the saturation channel
Threshold the value channel and invert
Combine the two threshold images as a mask
Apply the mask to the input to write white where the mask is black
Save the result
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('symbols.png')
# convert image to hsv colorspace
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
# threshold saturation image
thresh1 = cv2.threshold(s, 92, 255, cv2.THRESH_BINARY)[1]
# threshold value image and invert
thresh2 = cv2.threshold(v, 128, 255, cv2.THRESH_BINARY)[1]
thresh2 = 255 - thresh2
# combine the two threshold images as a mask
mask = cv2.add(thresh1,thresh2)
# use mask to remove lines in background of input
result = img.copy()
result[mask==0] = (255,255,255)
# display IN and OUT images
cv2.imshow('IMAGE', img)
cv2.imshow('SAT', s)
cv2.imshow('VAL', v)
cv2.imshow('THRESH1', thresh1)
cv2.imshow('THRESH2', thresh2)
cv2.imshow('MASK', mask)
cv2.imshow('RESULT', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
# save output image
cv2.imwrite('symbols_thresh1.png', thresh1)
cv2.imwrite('symbols_thresh2.png', thresh2)
cv2.imwrite('symbols_mask.png', mask)
cv2.imwrite('symbols_cleaned.png', result)
Saturation channel thresholded:
Value channel thresholded and inverted:
Mask:
Result: