I am new to Histogram comparisons.
This code uses these images to make a histogram comparison. The result was impressive with a 0.99 %, however I think that the result resulted in 99% because of the background color. Can someone tell me how can I ignore the white color and compare the actual fruit.
The following code was found here.
# Load the images
img1 = cv2.imread('D:/downloads/app1.jpg')
img2 = cv2.imread('D:/downloads/app2.jpg')
# Convert it to HSV
img1_hsv = cv2.cvtColor(img1, cv2.COLOR_BGR2HSV)
img2_hsv = cv2.cvtColor(img2, cv2.COLOR_BGR2HSV)
# Calculate the histogram and normalize it
hist_img1 = cv2.calcHist([img1_hsv], [0,1], None, [180,256], [0,180,0,256])
cv2.normalize(hist_img1, hist_img1, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX);
hist_img2 = cv2.calcHist([img2_hsv], [0,1], None, [180,256], [0,180,0,256])
cv2.normalize(hist_img2, hist_img2, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX);
# find the metric value
metric_val = cv2.compareHist(hist_img1, hist_img2, cv2.HISTCMP_BHATTACHARYYA)
Using some mask as Fred suggested seems to be the cleanest solution, but Fred's comment regarding the HSV color space is even more important here! But, first of all, the reported metric value of 0.99... (also in the linked article) was obtained using cv2.HISTCMP_CORREL, not using cv2.HISTCMP_BHATTACHARYYA!
Now, let's stick to OpenCV's common BGR color space, and adapt the code:
import cv2
# Load the images
img1 = cv2.imread('app1.png')
img2 = cv2.imread('app2.png')
# Calculate the histograms, and normalize them
hist_img1 = cv2.calcHist([img1], [0, 1, 2], None, [256, 256, 256], [0, 256, 0, 256, 0, 256])
cv2.normalize(hist_img1, hist_img1, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX)
hist_img2 = cv2.calcHist([img2], [0, 1, 2], None, [256, 256, 256], [0, 256, 0, 256, 0, 256])
cv2.normalize(hist_img2, hist_img2, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX)
# Find the metric value
metric_val = cv2.compareHist(hist_img1, hist_img2, cv2.HISTCMP_CORREL)
print(metric_val)
# 0.9995753648895891
The metric value is still something at 99.9 %.
So, now, let's ignore all white pixels by manually setting hist_imgx[255, 255, 255] = 0:
import cv2
# Load the images
img1 = cv2.imread('app1.png')
img2 = cv2.imread('app2.png')
# Calculate the histograms, set bin for (255, 255, 255) to 0, and normalize them
hist_img1 = cv2.calcHist([img1], [0, 1, 2], None, [256, 256, 256], [0, 256, 0, 256, 0, 256])
hist_img1[255, 255, 255] = 0
cv2.normalize(hist_img1, hist_img1, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX)
hist_img2 = cv2.calcHist([img2], [0, 1, 2], None, [256, 256, 256], [0, 256, 0, 256, 0, 256])
hist_img2[255, 255, 255] = 0
cv2.normalize(hist_img2, hist_img2, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX)
# Find the metric value
metric_val = cv2.compareHist(hist_img1, hist_img2, cv2.HISTCMP_CORREL)
print(metric_val)
# 0.6199666001215806
And, the metric value drops to 62 %!
So, your assumption seems to be correct, the white background distorts the whole histogram comparison.
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.9.1
PyCharm: 2021.1.1
OpenCV: 4.5.1
----------------------------------------
You'll only need to raise the minimum saturation value for an HSV mask to effectively mask away all the white background:
import cv2
import numpy as np
def get_masked(img):
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower = np.array([0, 50, 0])
upper = np.array([179, 255, 255])
mask = cv2.inRange(img_hsv, lower, upper)
return cv2.bitwise_and(img, img, mask=mask)
img1 = cv2.imread("apple1.png")
img2 = cv2.imread("apple2.png")
cv2.imshow("Apple 1", get_masked(img1))
cv2.imshow("Apple 2", get_masked(img2))
cv2.waitKey(0)
Related
I am writing a program that observes an area on the screen that displays a number between 0 and 100. When the number is 100, it has green text. When the number is 0, it has red text. As the number decreases from 100, it slowly shifts in a gradient from solid green to solid red.
Some examples:
100 is rgb(0, 255, 0)
84 is rgb(73, 255, 0),
65 is rgb(172, 255, 0),
41 is rgb(255, 213, 0),
17 is rgb(255, 88, 0)
0 is rgb(255, 0, 0)
Question
Is it possible to isolate any/all colors in this range using cv2.inRange(img, lower, upper) where lower and upper are numpy arrays in the form [B, G, R]? If there is a suitable range, can you explain why it works?
I have this code:
import cv2
import numpy as np
image = cv2.imread("image_of_text.png")
# Generate mask for color range
lower = [0, 0, 0]
upper = [0, 0, 0]
mask = cv2.inRange(image, np.array(lower), np.array(upper))
# Create black mask
h, w = image.shape[:2]
mask_b = np.zeros([h, w, 1], dtype=np.uint8)
# Combine masks
mask = cv2.bitwise_or(mask_b, mask)
cv2.imshow('result', mask)
cv2.waitKey(0)
I would prefer to avoid HSV ranges as the rest of my program is not well suited for switching color spaces on the fly.
Well, I solved this as I was writing up the question.
From what I can tell, we need to isolate two separate ranges:
GREEN --> lower = rgb(0, 255, 0), upper = rgb(255, 255, 0)
RED --> lower = rgb(255, 0, 0), upper = rgb(255, 255, 0)
Code:
import cv2
import numpy as np
image = cv2.imread("image_of_text.png")
upper = [0, 255, 255] # uppper is same for both
# Green range
lower = [0, 255, 0]
mask_g = cv2.inRange(image, np.array(lower), np.array(upper))
# Red range
lower = [0, 0, 255]
mask_r = cv2.inRange(image, np.array(lower), np.array(upper))
# Create black mask
h, w = image.shape[:2]
mask_b = np.zeros([h, w, 1], dtype=np.uint8)
# Combine masks
mask = cv2.bitwise_or(mask_b, mask_g)
mask = cv2.bitwise_or(mask, mask_r)
cv2.imshow('result', mask)
cv2.waitKey(0)
Just from looking at the text colors I listed in the question, I noticed how both middle numbers were close to rgb(255, 255, 0), but since there were two distinct ends to this gradient, I needed to specify two separate lower bounds while they both have the same upper bound.
i am new in python , my probleme it's about edit some changes in an image grayscale , i wanna make a binarization for this image , the values of pixels bigger then 100 take the value 1 (white), and the values low than 100 takes the value 0 (black)
so any suggestion plz (sorry for my bad english)
my code :
`import numpy as np
import cv2
image = cv2.imread('Image3.png', 0)
dimension = image.shape
height = dimension[0]
width = dimension[1]
#finalimage = np.zeros((height, width))
for i in range(height) :
for j in range(width):
if (image[i, j] > 100):
image[i][j] = [1]
else:
image[i][j] = [0]
cv2.imshow('binarizedImage',image)
cv2.waitKey(0)
cv2.destroyAllWindows()
You can try use OpenCV function cv2.threshold for binarize.
import cv2
img = cv2.imread('Image3.png', cv2.IMREAD_GRAYSCALE)
thresh = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY)[1]
cv2.imshow('binarizedImage',thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()
I think you just want to use np.where():
import numpy as np
image = np.array([[200, 50, 200],[50, 50 ,50],[10, 255, 10]]) #this will be your image instead
In [11]: image
Out[11]:
array([[200, 50, 200],
[ 50, 50, 50],
[ 10, 255, 10]])
In [12]: np.where(image > 100, 1, 0)
Out[12]:
array([[1, 0, 1],
[0, 0, 0],
[0, 1, 0]])
Super simple question, I have a single channel image consisting of 0's and 255's and I would like to convert it into a 4 channel image of [0,0,0,0] and [255,255,255,255] in the most efficient manner possible. cv2.COLOR_GRAY2RGBA causes the alpha to all be at 255 and np.where() feels like black magic to me so any help would be appreciated.
One-liner solution using cv2.merge function.
Let your input image be "img" which is of 1 channel.
To convert it to 4 channel, do this:
img = cv2.merge((img.copy(), img.copy(), img.copy(), img.copy()))
Now the "img" will contain 4 channels and all the channels will be the same wrt values.
Just create a numpy array with more layers and then change the values of this array to the values of your image.
img = cv2.imread('img.jpg')
h, w, d = img.shape #(217, 232, 3)
nr_of_new_layers = 1
alpha_img = np.zeros((h, w, d+nr_of_new_layers))
alpha_img[:,:,:3] = img #alpha_img shape = (217, 232, 4)
Or if there isn't any 3rd dimension then you can do
d = 0
img = cv2.imread('img.jpg', d)
h, w = img.shape #(217, 232)
nr_of_new_layers = 4
alpha_img = np.zeros((h, w, d+nr_of_new_layers))
alpha_img[:,:,d] = img #alpha_img shape = (217, 232, 4)
Just stack 4 copies of your single channel image with np.dstack():
RGBA = np.dstack((im,im,im,im)) # or more tersely RGBA = np.dstack(([im]*4))
Example:
import numpy as np
# Make image
im = np.array([0,255,255,0,0], dtype=np.uint8)
Look at it:
array([ 0, 255, 255, 0, 0], dtype=uint8)
Stack it depth-wise with np.dstack():
RGBA = np.dstack((im,im,im,im))
Look at it:
array([[[ 0, 0, 0, 0],
[255, 255, 255, 255],
[255, 255, 255, 255],
[ 0, 0, 0, 0],
[ 0, 0, 0, 0]]], dtype=uint8)
I have been trying to reduce the noise in the attached image. Basically, I want to remove the background dust from the image. Currently, I have tried looking for small points throughout the image(anything that fits within a 10 by 10 grid with low Green pixel intensity) and then blacked out the 10 by 10 region. However, I was hoping to remove more noise from the image. Is there a possibly way to run some filters in OpenCv to do so.
A simple approach can be: Convert the image to grayscale, threshold it, and the apply morphological opening in it to get estimate results.
img = cv2.imread("commitdust.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, th = cv2.threshold(gray, 80, 255, cv2.THRESH_BINARY)
k = np.array([[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[1, 1, 1, 1, 1],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0]], dtype=np.uint8)
th = cv2.morphologyEx(th, cv2.MORPH_OPEN, k)
cv2.imshow("th", th)
cv2.waitKey(0)
cv2.destroyAllWindows()
After remove captcha's background.
The image remain digits and noise.
Noise line is all in one color : RGB(127,127,127)
And then using morphology method.
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
self.im = cv2.morphologyEx(self.im, cv2.MORPH_CLOSE, kernel)
Some part of digit will be remove.
How to use morphologyEx() remove only color in RGB(127,127,127) ?
In order to eliminate color within a particular range you have to use cv2.inRange() function.
Here is the code:
lower = np.array([126,126,126]) #-- Lower range --
upper = np.array([127,127,127]) #-- Upper range --
mask = cv2.inRange(img, lower, upper)
res = cv2.bitwise_and(img, img, mask= mask) #-- Contains pixels having the gray color--
cv2.imshow('Result',res)
This is what I got for the two images you have:
Image 1:
Image 2:
You carry on from here.
COLOR RANGE
color_dict_HSV = {'black': [[180, 255, 30], [0, 0, 0]],
'white': [[180, 18, 255], [0, 0, 231]],
'red1': [[180, 255, 255], [159, 50, 70]],
'red2': [[9, 255, 255], [0, 50, 70]],
'green': [[89, 255, 255], [36, 50, 70]],
'blue': [[128, 255, 255], [90, 50, 70]],
'yellow': [[35, 255, 255], [25, 50, 70]],
'purple': [[158, 255, 255], [129, 50, 70]],
'orange': [[24, 255, 255], [10, 50, 70]],
'gray': [[180, 18, 230], [0, 0, 40]]}
CREDITS:
Ali Hashemian
HOW TO REMOVE A COLOR FROM YOUR IMAGE USING OPENCV
Since most of you would like to do that, i.e. in my case the task was to remove blue color from the image, I used the following code, to remove blue ink stamps and, blue tick marks from my image in order for proper OCR using Tesseract.
[COLOR REMOVAL] CODE
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# image path:
#path = "D://opencvImages//"
#fileName = "out.jpg"
# Reading an image in default mode:
inputImage = cv2.imread('0.jpg')
# Convert RGB to grayscale:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Convert the BGR image to HSV:
hsvImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2HSV)
# Create the HSV range for the blue ink:
# [128, 255, 255], [90, 50, 70]
lowerValues = np.array([90, 50, 70])
upperValues = np.array([128, 255, 255])
# Get binary mask of the blue ink:
bluepenMask = cv2.inRange(hsvImage, lowerValues, upperValues)
# Use a little bit of morphology to clean the mask:
# Set kernel (structuring element) size:
kernelSize = 3
# Set morph operation iterations:
opIterations = 1
# Get the structuring element:
morphKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
# Perform closing:
bluepenMask = cv2.morphologyEx(bluepenMask, cv2.MORPH_CLOSE, morphKernel, None, None, opIterations, cv2.BORDER_REFLECT101)
# Add the white mask to the grayscale image:
colorMask = cv2.add(grayscaleImage, bluepenMask)
_, binaryImage = cv2.threshold(colorMask, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
cv2.imwrite('bwimage.jpg',binaryImage)
thresh, im_bw = cv2.threshold(binaryImage, 210, 230, cv2.THRESH_BINARY)
kernel = np.ones((1, 1), np.uint8)
imgfinal = cv2.dilate(im_bw, kernel=kernel, iterations=1)
cv2.imshow(imgfinal)
BEFORE [Original Image]
Blue Mark Extraction
Final Image
Here you can see that all of the tick marks are almost, removed the reason is that because there is always room for improvement, but this, as it seems, is the best we can get because even removing these little marks is not going to have a profound effect on the OCR using Tesseract.
HOPE THAT HELPS!
Here is my solution.
Your answer is obvious better than my.
def mop_close(self):
def morphological(operator=min):
height, width, _ = self.im.shape
# create empty image
out_im = np.zeros((height,width,3), np.uint8)
out_im.fill(255) # fill with white
for y in range(height):
for x in range(width):
try:
if self.im[y,x][0] ==127 and self.im[y,x][1] ==127 and self.im[y,x][2] ==127:
nlst = neighbours(self.im, y, x)
out_im[y, x] = operator(nlst,key = lambda x:np.mean(x))
else:
out_im[y,x] = self.im[y,x]
except Exception as e:
print(e)
return out_im
def neighbours(pix,y, x):
nlst = []
# search pixels around im[y,x] add them to nlst
for yy in range(y-1,y+1):
for xx in range(x-1,x+1):
try:
nlst.append(pix[yy, xx])
except:
pass
return np.array(nlst)
def erosion(im):
return morphological(min)
def dilation(im):
return morphological(max)
self.im = dilation(self.im)
self.im = erosion(self.im)
final result: