Convert Gray background to white background without disturbing the original image - python

I have cropped few images which have gray back ground and need to convert them to white back ground to compare with Reference images.
The following code I implemented to convert:
import cv2
im_gray = cv2.imread('gray_bg.png', cv2.IMREAD_GRAYSCALE)
(thresh, im_bw) = cv2.threshold(im_gray, 255, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
cv2.imwrite('white_bg.png', im_bw)
input:
output:
expected output:
If you observe, my output image has some noise in edges of the original image (I hope I am not wrong in saying). Because of this, while comparing my output with Reference images, I am not getting the desired output. Can someone suggest me how to do it?
Here is the program We wrote to compare two images:
SourceImagePath = r'white_bg.png'
TemplateImagePath = r'ex_white_bg.png'
#def IconValidation(self,SourceImagePath,TemplateImagePath):
sourceImg=cv.imread(SourceImagePath)
templateImg=cv.imread(TemplateImagePath)
_,tempwidth,tempheight=templateImg.shape[::-1]
srcheight = np.size(sourceImg, 0)
srcwidth = np.size(sourceImg, 1)
if(srcwidth < tempwidth) and (srcheight < tempheight):
print("comparison")
resultImg = cv.matchTemplate(sourceImg,templateImg,cv.TM_CCOEFF_NORMED)
matchVal = resultImg[0][0]
threshold=0.95
if(matchVal>threshold):
print("passed")
else:
print("failed")

What you see is aliasing, not noise. It appears because of hard thresholding.
You input image does have a little noise, which you see by enlarging (possibly due to lossy compression at some stage), but it is not aliased.
You can turn the gray background to white while keeping the black by applying a gain of 1.4 (the gray level is around 180). This will avoid the introduction of aliasing.

Avoiding the change of color encoding achive the best results. Here is the code:
im_gray = cv2.imread('gray_bg.png', cv2.IMREAD_UNCHANGED)
b, g, r = cv2.split(im_gray)
t = [None] * 3
u = [None] * 3
for i, im in enumerate([b, g, r]):
t[i], u[i] = cv2.threshold(im, 255, 255, cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
dst = cv2.merge((*u,))
cv2.imwrite('white_bg.png', dst)
By comparing it with the original it gives 99.99% equality.
Than if you really need it you can convert the image to grayscale encoding with cv2.cvtColor(src, cv2.COLOR_BGR2GRAY).
Result vs Wanted:

Related

Python - replicating the GIMP's "Erase color" blend mode

I'm looking for a way to recreate the GIMP's Erase color blending mode in Python 3 & OpenCV2.
I know it's possible to erase color using the that library, but the code I run works on exactly one of them. Furthermore, I don't believe such small amount of code could do that advanced thing.
Looking for a solution, I found the blend-modes by flrs, but it also doesn't include the option I want.
Sadly, I have no experience in OpenCV2 at the moment, but I think developing such thing could be very helpful.
Can someone guide me how to make this more reliable, or is it even possible to do with things that I've got already?
OpenCV2 color removal
Code
import cv2
from PIL import Image
#-=-=-=-#
File_Name = r"Spectrogram.png"
SRC = cv2.imread(File_Name, 1)
TMP = cv2.cvtColor(SRC, cv2.COLOR_BGR2GRAY)
_, A = cv2.threshold(TMP, 0, 255, cv2.THRESH_BINARY)
B, G, R = cv2.split(SRC)
Colors = [B, G, R, A]
Picture = cv2.merge(Colors, 4)
#-=-=-=-#
# My CV2 image display doesn't include transparency
im = cv2.cvtColor(Picture, cv2.COLOR_BGR2RGB)
im = Image.fromarray(im)
im.show()
Result
Original
Result
GIMP Erase color blending-mode
Type
Background
Foreground
Result
Image
Blending
Normal
Erase color
Normal
Here is one simple way in Python/OpenCV.
Read the input
Choose a color range
Apply range to threshold the image
Invert the range as a mask to be used later for the alpha channel
Convert the image from BGR to BGRA
Put mask into the alpha channel of the BGRA image
Save the result
Input:
import cv2
import numpy as np
# load image and set the bounds
img = cv2.imread("red_black.png")
# choose color range
lower =(0,0,0) # lower bound for each BGR channel
upper = (140,0,190) # upper bound for each BRG channel
# create the mask
mask = cv2.inRange(img, lower, upper)
# invert mask
mask = 255 - mask
# convert image to BGRA
result = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
# put mask into alpha channel
result[:,:,3] = mask
# write result to disk
cv2.imwrite("red_black_color_removed.png", result)
# display it (though does not display transparency properly)
cv2.imshow("mask", mask)
cv2.imshow("results", result)
cv2.waitKey(0)
Result:

opencv threshold problem for finding diff images

I have 2 images like below. I want to have differences two of them.
I tried some codes with threshold. But no threshold different. two images threshold image is all black. How can I make differences are with color white on thresh?
(the difference is just on top-left like a small dark)
before = cv2.imread('1.png')
after = cv2.imread('2.png')
threshb = cv2.threshold(before, 0, 255, cv2.THRESH_BINARY_INV)[1]
thresha = cv2.threshold(after, 0, 255, cv2.THRESH_BINARY_INV)[1]
cv.imshow("before",threshb)
cv.imshow("after",thresha)
Not: I used "structural_similarity" link here for finding differences but it founds a lot of differences :(
I don't need small pixel differences. I need differences like seen with human eyes.
The way to handle that is to do absdiff followed by some gain in Python/OpenCV. When you do the absdiff with these images, the difference is small so will not be much above black. So you have to increase the gain to see the difference. (Or you could set an appropriate threshold)
Input 1:
Input 2:
import cv2
import numpy as np
from skimage import exposure as exposure
# read image 1
img1 = cv2.imread('gray1.png', cv2.IMREAD_GRAYSCALE)
# read image 2
img2 = cv2.imread('gray2.png', cv2.IMREAD_GRAYSCALE)
# do absdiff
diff = cv2.absdiff(img1,img2)
# apply gain
result1 = cv2.multiply(diff, 5)
# or do threshold
result2 = cv2.threshold(diff, 10, 255, cv2.THRESH_BINARY)[1]
# save result
cv2.imwrite('gray1_gray2_diff1.png', result1)
cv2.imwrite('gray1_gray2_diff2.png', result2)
# display result
cv2.imshow('diff', diff)
cv2.imshow('result1', result1)
cv2.imshow('result2', result2)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result 1 (gain):
Result 2 (threshold):

Opencv, Python - How to remove the gray pixels around the date text

I am trying to remove the grayish “noise” surrounding the dates using Python/OpenCV to help the OCR (Optical Character Recognition) to recognize the dates.
The original image looks like this: https://static.mothership.sg/1/2017/03/10-Feb-MC-1.jpg
The python script I tried looked as below. However, I have other similar images in which the contrast or lighting coditions varies.
import cv2
import numpy as np
img = cv2.imread("mc.jpeg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
alpha = 3.5
beta = -2
new = alpha * img + beta
new = np.clip(new, 0, 255).astype(np.uint8)
cv2.imwrite("cleaned.png", new)
I also tried Thresholding and/or adaptiveThresholding and some time, I was able to separate the dates from the grayish background. Sometimes it was very challenging. I wonder is there an automatic way to determine the threshold value ?
Below are example of what I hope to achieve.
Blurry Image:
Otsu's Binarization automatically calculates a threshold value from an image histogram.
# Otsu's thresholding after Gaussian filtering
blur = cv2.GaussianBlur(img,(5,5),0)
ret,Otsu = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imwrite("Otsu's_thresholding", Otsu)
see this link
You can try to build a model of the background and then weight each input pixel by that model. The output gain should be relatively constant during most of the image. These are the steps for this method:
Apply a soft median blur filter to get rid of small noise
Get the model of the background via local maximum. Apply a very strong close operation, with a big structuring element (I’m using a rectangular kernel of size 15)
Perform gain adjustment by dividing 255 between each local maximum pixel. Weight this value with each input image pixel.
You should get a nice image where the background illumination is pretty much normalized, threshold this image to get a binary mask of the text
This is the code:
import numpy as np
import cv2
# image path
path = "C:/opencvImages/sheet01.jpg"
# Read an image in default mode:
inputImage = cv2.imread(path)
# Remove small noise via median:
filterSize = 5
imageMedian = cv2.medianBlur(inputImage, filterSize)
# Get local maximum:
kernelSize = 15
maxKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
localMax = cv2.morphologyEx(imageMedian, cv2.MORPH_CLOSE, maxKernel, None, None, 1, cv2.BORDER_REFLECT101)
# Adjust image gain:
height, width, depth = localMax.shape
# Create output Mat:
outputImage = np.zeros(shape=[height, width, depth], dtype=np.uint8)
for i in range(0, height):
for j in range(0, width):
# Get current BGR pixels:
v1 = inputImage[i, j]
v2 = localMax[i, j]
# Gain adjust:
tempArray = []
for c in range(0, 3):
currentPixel = v2[c]
if currentPixel != 0:
gain = 255 / v2[c]
gain = v1[c] * gain
else:
gain = 0
# Gain set and clamp:
tempArray.append(np.clip(gain, 0, 255))
# Set pixel vec to out image:
outputImage[i, j] = tempArray
# Convert RGB to grayscale:
grayscaleImage = cv2.cvtColor(outputImage, cv2.COLOR_BGR2GRAY)
# Threshold:
threshValue = 110
_, binaryImage = cv2.threshold(grayscaleImage, threshValue, 255, cv2.THRESH_BINARY)
# Write image:
imageFilename = "C:/opencvImages/binaryMask2.png"
cv2.imwrite(imageFilename, binaryImage)
I get the following results testing the complete image:
And the cropped text:
Please note that the gain adjustment operations are not vectorized. The script is slow, mainly because I'm starting with Python and don’t know the proper Numpy syntax to speed-up this operation. I've been using C++ for a long time, so feel free to further improve the code.
Edit:
Please, be aware that your result can only be as good as the quality of your input. See your input and ask yourself "Is this a good input for an automated process?" (Automated processes are usually not very smart). The second picture you posted is very low quality. Not only is blurry but also is low res and has compression artifacts. All these factors will hinder automated processing.
With that said, here's an improvement you can include in the original:
Try to normalize brightness-contrast on the grayscale output:
grayscaleImage = np.uint8(cv2.normalize(grayscaleImage, grayscaleImage, 0, 255, cv2.NORM_MINMAX))
Your grayscale image goes from this:
to this:
A little bit darker and improved on contrast. Let's try to compute the optimal threshold value automatically via Otsu thresholding:
threshValue, binaryImage = cv2.threshold(grayscaleImage, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
It gets you this:
However, we can adjust the result if we add bias to Otsu's threshold, like this:
threshValue, binaryImage = cv2.threshold(grayscaleImage, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
bias = 0.9
threshValue = bias * threshValue
_, binaryImage = cv2.threshold(grayscaleImage, threshValue, 255, cv2.THRESH_BINARY)
That's the best quality you can get with these images using this method.
If you find these suggestions and tips useful, please, at least up-vote my answer.

I want to increase brightness and contrast of images in dynamic way so that the program is applicable for any new images

I have few images where I need to increase or decrease the contrast and brightness of the image in a dynamic way so that it is visible clearly. And the program needs to be dynamic so that it even works for new images also. I also want character should be dark.
I was able to increase brightness and contrast but it is not working properly for each image.
import cv2
import numpy as np
img = cv2.imread('D:\Bright.png')
image = cv2.GaussianBlur(img, (5, 5), 0)
#image = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY)[1]
#kernel = np.ones((2,1),np.uint8)
#dilation = cv2.dilate(img,kernel)
cv2.imshow('test', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
imghsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
imghsv[:,:,2] = [[max(pixel - 25, 0) if pixel < 190 else min(pixel + 25, 255) for pixel in row] for row in imghsv[:,:,2]]
cv2.imshow('contrast', cv2.cvtColor(imghsv, cv2.COLOR_HSV2BGR))
#cv2.imwrite('D:\\112.png',cv2.cvtColor(imghsv, cv2.COLOR_HSV2BGR))
cv2.waitKey(0)
cv2.destroyAllWindows()
#raw_input()
I want a program which works fine for every image and words are a little darker so that they are easily visible.
As Tilarion suggested, you could try "Auto Brightness And Contrast" to see if it works well. The theory behind this is explained well here in the solution section. The solution is in C++. I've written a version of it in python which you can directly use, works only on 1 channel at a time for colour images:
def auto_brightandcontrast(input_img, channel, clip_percent=1):
histSize=180
alpha=0
beta=0
minGray=0
maxGray=0
accumulator=[]
if(clip_percent==0):
#min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(hist)
return input_img
else:
hist = cv2.calcHist([input_img],[channel],None,[256],[0, 256])
accumulator.insert(0,hist[0])
for i in range(1,histSize):
accumulator.insert(i,accumulator[i-1]+hist[i])
maxx=accumulator[histSize-1]
minGray=0
clip_percent=clip_percent*(maxx/100.0)
clip_percent=clip_percent/2.0
while(accumulator[minGray]<clip_percent[0]):
minGray=minGray+1
maxGray=histSize-1
while(accumulator[maxGray]>=(maxx-clip_percent[0])):
maxGray=maxGray-1
inputRange=maxGray-minGray
alpha=(histSize-1)/inputRange
beta=-minGray*alpha
out_img=input_img.copy()
cv2.convertScaleAbs(input_img,out_img,alpha,beta)
return out_img
It is a very few lines of code to do it in Python Wand (which is based upon ImageMagick). Here is a script.
#!/bin/python3.7
from wand.image import Image
with Image(filename='task4.jpg') as img:
img.contrast_stretch(black_point=0.02, white_point=0.99)
img.save(filename='task4_stretch2_99.jpg')
Input:
Result:
Increase the black point value to make the text darker and/or decrease the white point value to make the lighter parts brighter.
Thanks to Eric McConville (the Wand developer) for correcting my arguments to make the code work.

How do I adjust brightness, contrast and vibrance with opencv python?

I am new to image processing. I program in Python3 and uses the OpenCV image processing library.I want to adjust the following attributes.
Brightness
Contrast
Vibrance
Hue
Saturation
Lightness
For 4, 5, 6. I am using the following code to convert to HSV space.
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
h += value # 4
s += value # 5
v += value # 6
final_hsv = cv2.merge((h, s, v))
img = cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
The only tutorial I found for 1 and 2 is here. The tutorial uses C++, but I program in Python. Also, I do not know how to adjust 3. vibrance. I would very much appreciate the help, thanks!.
Thanks to #MarkSetchell for providing the link.
In short, the answers uses numpy only and the formula can be presented as in below.
new_image = (old_image) × (contrast/127 + 1) - contrast + brightness
Here contrast and brightness are integers in the range [-127,127]. The scalar 127 is used for this range.
Also, below is the code I used.
brightness = 50
contrast = 30
img = np.int16(img)
img = img * (contrast/127+1) - contrast + brightness
img = np.clip(img, 0, 255)
img = np.uint8(img)
a simple way for brightness adjustment, proper for both color and monochrome images is
img = cv2.imread('your path',0)
brt = 40
img[img < 255-brt] += brt
cv2.imshow('img'+ img)
where brt could be a positive number for increase brightness or a negative for darkness.
The following links for a before and after of an image processed in this code, when the brt = 40 :
input image
output image
I am not sure if this would help, but for changing Brightness, Contrast I personally switch the image to PIL.Image and use PIL.ImageEnhance which comes in handy when using the ratios or percentages.
image = PIL.Image.open("path_to_image")
#increasing the brightness 20%
new_image = PIL.ImageEnhance.Brightness(image).enhance(1.2)
#increasing the contrast 20%
new_image = PIL.ImageEnhance.Contrast(image).enhance(1.2)
I still have not found a clean way for Vibrance. For more on ImageEnahance, I'd suggest to read the official doc - https://pillow.readthedocs.io/en/stable/reference/ImageEnhance.html
For Conversion, I use this ..
NOTE - OpenCV uses BGR and PIL uses RGB channels. So, can get messy if not converted properly.
#convert pil.image to opencv (numpy.ndarray)
#need numpy library for this
cv_image = numpy.array(pil_image)
#convert opencv to pil.image
image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
pil_image = Image.fromarray(image)
Here is one way to do the vibrance in Python/OpenCV.
Convert to HSV. Then create a sigmoid function LUT.
(The sigmoid function increases linearly from the origin, but then tapers off to flat.)
See https://en.wikipedia.org/wiki/Sigmoid_function
Apply the LUT to S channel.
Convert back to BGR.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('yellow_building.jpg')
# convert image to hsv colorspace as floats
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
print(np.amax(s), np.amin(s), s.dtype)
# set vibrance
vibrance=1.4
# create 256 element non-linear LUT for sigmoidal function
# see https://en.wikipedia.org/wiki/Sigmoid_function
xval = np.arange(0, 256)
lut = (255*np.tanh(vibrance*xval/255)/np.tanh(1)+0.5).astype(np.uint8)
# apply lut to saturation channel
new_s = cv2.LUT(s,lut)
# combine new_s with original h and v channels
new_hsv = cv2.merge([h,new_s,v])
# convert back to BGR
result = cv2.cvtColor(new_hsv, cv2.COLOR_HSV2BGR)
# save output image
cv2.imwrite('yellow_building_vibrance.jpg', result)
# display images
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:

Categories