How do I adjust brightness, contrast and vibrance with opencv python? - 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:

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, 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.

Unsupported depth of input image: 'VDepth::contains(depth)' where 'depth' is 4 (CV_32S)

I'm trying to process some images in OpenCV. Specifically, swapping color panes using the following functions.
def green_ble_swap(image)
im_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
im_copy = np.copy(im_rgb)
blue = im_copy[:,:,2].copy()
green = im_copy[:,:,1].copy()
im_copy[:,:,2] = green
im_copy[:,:,1] = blue
return im_copy
However I get the following error.
> Unsupported depth of input image:
> 'VDepth::contains(depth)'
> where
> 'depth' is 4 (CV_32S)
Not sure whats the error here.
You're encountering the error because you're trying to perform a 3-channel operation on a 4-channel image. Specifically, the error comes from trying to convert a BGR image to RGB when the input image has a transparent channel. The correct method would be to do cv2.COLOR_BGRA2RGB instead of cv2.COLOR_BGR2RGB. You can swap the blue and green channels in-place using cv2.split() to obtain the BGR channels (for 3-channel image) and BGRA channels for (4-channel image) then swap the channels using Numpy indexing. You also need to use the cv2.IMREAD_UNCHANGED flag when loading the image or the alpha channel will be dropped. Example:
Input -> Output
import cv2
import numpy as np
def green_blue_swap(image):
# 3-channel image (no transparency)
if image.shape[2] == 3:
b,g,r = cv2.split(image)
image[:,:,0] = g
image[:,:,1] = b
# 4-channel image (with transparency)
elif image.shape[2] == 4:
b,g,r,a = cv2.split(image)
image[:,:,0] = g
image[:,:,1] = b
return image
# Load image
image = cv2.imread('1.png', cv2.IMREAD_UNCHANGED)
cv2.imshow('image', image)
# Swap channels
swapped = green_blue_swap(image)
cv2.imshow('swapped', swapped)
cv2.waitKey()

Image de-blurring

This post is divided in two
Part One
I have a little issue converting an image from grayscale back to RGB.
Image in question:
I use this code to convert it:
equ = cv2.cvtColor(equ, cv2.COLOR_GRAY2RGB)
without any success though...
Part Two
Moreover I need to de-blur such image. Here I found some code that uses a wiener filter to do so, but when I implement it it doesn't seem to work effectively. Here is the code:
psf = np.ones((5, 5)) / 25
img = convolve2d(equ, psf, 'same')
img += 0.1 * img.std() * np.random.standard_normal(img.shape)
#deconvolved_img = restoration.wiener(img, psf, 1100)
deconvolved = restoration.wiener(img, psf, 1, clip=False)
plt.imshow(deconvolved, cmap='gray')
and this is the output:
Any help for the two problems is greatly appreciated!
To equalize a color image, it seems a common thing to do is
convert the image to HSV or YUV
split the image into separate components (e.g. H, S, V)
equalize on Value channel (or all three if you want)
merge the channels back together
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
split = cv2.split(hsv) # split is a 3D array containing H S V info
split[2] = cv2.equalizeHist(split[2])
hsv = cv2.merge(split)
img = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
For "deblurring", I sometimes use an unsharp mask. From the Wikipedia page on unsharp masking, the formula for this operation is
sharpened = original + (original − blurred) × amount
which can be rearranged to
sharpened = original×(1 + amount) + blurred×(-amount)
Wikipedia says a good starting point for amount is 0.5 to 1.5. In my app I have a spinbox that let's it vary between 0 and 10. For blurring I use a Gaussian blur with kernel size varying from 1 to 31 (must be odd and integer). To do the matrix math, I prefer to use OpenCV functions because they are often faster than NumPy and they will usually autoscale output to values between 0 and 255 (e.g. for 8 bit and 8 bit/3 channel images). Here we use addWeighted which does
dst = src1*alpha + src2*beta + gamma;
amount = 1.5
ksize = 3
blur = cv2.GaussianBlur(img, ksize, 0, 0)
unsharp = cv.addWeighted(img, 1 + amount, blur, -amount, 0)

How do I increase the contrast of an image in Python OpenCV

I am new to Python OpenCV. I have read some documents and answers here but I am unable to figure out what the following code means:
if (self.array_alpha is None):
self.array_alpha = np.array([1.25])
self.array_beta = np.array([-100.0])
# add a beta value to every pixel
cv2.add(new_img, self.array_beta, new_img)
# multiply every pixel value by alpha
cv2.multiply(new_img, self.array_alpha, new_img)
I have come to know that Basically, every pixel can be transformed as X = aY + b where a and b are scalars.. Basically, I have understood this. However, I did not understand the code and how to increase contrast with this.
Till now, I have managed to simply read the image using img = cv2.imread('image.jpg',0)
Thanks for your help
I would like to suggest a method using the LAB color space.
LAB color space expresses color variations across three channels. One channel for brightness and two channels for color:
L-channel: representing lightness in the image
a-channel: representing change in color between red and green
b-channel: representing change in color between yellow and blue
In the following I perform adaptive histogram equalization on the L-channel and convert the resulting image back to BGR color space. This enhances the brightness while also limiting contrast sensitivity. I have done the following using OpenCV 3.0.0 and python:
Code:
import cv2
import numpy as np
img = cv2.imread('flower.jpg', 1)
# converting to LAB color space
lab= cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l_channel, a, b = cv2.split(lab)
# Applying CLAHE to L-channel
# feel free to try different values for the limit and grid size:
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl = clahe.apply(l_channel)
# merge the CLAHE enhanced L-channel with the a and b channel
limg = cv2.merge((cl,a,b))
# Converting image from LAB Color model to BGR color spcae
enhanced_img = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)
# Stacking the original image with the enhanced image
result = np.hstack((img, enhanced_img))
cv2.imshow('Result', result)
Result:
The enhanced image is on the right
You can run the code as it is.
To know what CLAHE (Contrast Limited Adaptive Histogram Equalization) is about, refer this Wikipedia page
For Python, I haven't found an OpenCV function that provides contrast. As others have suggested, there are some techniques to automatically increase contrast using a very simple formula.
In the official OpenCV docs, it is suggested that this equation can be used to apply both contrast and brightness at the same time:
new_img = alpha*old_img + beta
where alpha corresponds to a contrast and beta is brightness. Different cases
alpha 1 beta 0 --> no change
0 < alpha < 1 --> lower contrast
alpha > 1 --> higher contrast
-127 < beta < +127 --> good range for brightness values
In C/C++, you can implement this equation using cv::Mat::convertTo, but we don't have access to that part of the library from Python. To do it in Python, I would recommend using the cv::addWeighted function, because it is quick and it automatically forces the output to be in the range 0 to 255 (e.g. for a 24 bit color image, 8 bits per channel). You could also use convertScaleAbs as suggested by #nathancy.
import cv2
img = cv2.imread('input.png')
# call addWeighted function. use beta = 0 to effectively only operate one one image
out = cv2.addWeighted( img, contrast, img, 0, brightness)
output = cv2.addWeighted
The above formula and code is quick to write and will make changes to brightness and contrast. But they yield results that are significantly different than photo editing programs. The rest of this answer will yield a result that will reproduce the behavior in the GIMP and also LibreOffice brightness and contrast. It's more lines of code, but it gives a nice result.
Contrast
In the GIMP, contrast levels go from -127 to +127. I adapted the formulas from here to fit in that range.
f = 131*(contrast + 127)/(127*(131-contrast))
new_image = f*(old_image - 127) + 127 = f*(old_image) + 127*(1-f)
To figure out brightness, I figured out the relationship between brightness and levels and used information in this levels post to arrive at a solution.
#pseudo code
if brightness > 0
shadow = brightness
highlight = 255
else:
shadow = 0
highlight = 255 + brightness
new_img = ((highlight - shadow)/255)*old_img + shadow
brightness and contrast in Python and OpenCV
Putting it all together and adding using the reference "mandrill" image from USC SIPI:
import cv2
import numpy as np
# Open a typical 24 bit color image. For this kind of image there are
# 8 bits (0 to 255) per color channel
img = cv2.imread('mandrill.png') # mandrill reference image from USC SIPI
s = 128
img = cv2.resize(img, (s,s), 0, 0, cv2.INTER_AREA)
def apply_brightness_contrast(input_img, brightness = 0, contrast = 0):
if brightness != 0:
if brightness > 0:
shadow = brightness
highlight = 255
else:
shadow = 0
highlight = 255 + brightness
alpha_b = (highlight - shadow)/255
gamma_b = shadow
buf = cv2.addWeighted(input_img, alpha_b, input_img, 0, gamma_b)
else:
buf = input_img.copy()
if contrast != 0:
f = 131*(contrast + 127)/(127*(131-contrast))
alpha_c = f
gamma_c = 127*(1-f)
buf = cv2.addWeighted(buf, alpha_c, buf, 0, gamma_c)
return buf
font = cv2.FONT_HERSHEY_SIMPLEX
fcolor = (0,0,0)
blist = [0, -127, 127, 0, 0, 64] # list of brightness values
clist = [0, 0, 0, -64, 64, 64] # list of contrast values
out = np.zeros((s*2, s*3, 3), dtype = np.uint8)
for i, b in enumerate(blist):
c = clist[i]
print('b, c: ', b,', ',c)
row = s*int(i/3)
col = s*(i%3)
print('row, col: ', row, ', ', col)
out[row:row+s, col:col+s] = apply_brightness_contrast(img, b, c)
msg = 'b %d' % b
cv2.putText(out,msg,(col,row+s-22), font, .7, fcolor,1,cv2.LINE_AA)
msg = 'c %d' % c
cv2.putText(out,msg,(col,row+s-4), font, .7, fcolor,1,cv2.LINE_AA)
cv2.putText(out, 'OpenCV',(260,30), font, 1.0, fcolor,2,cv2.LINE_AA)
cv2.imwrite('out.png', out)
I manually processed the images in the GIMP and added text tags in Python/OpenCV:
Note: #UtkarshBhardwaj has suggested that Python 2.x users must cast the contrast correction calculation code into float for getting floating result, like so:
...
if contrast != 0:
f = float(131*(contrast + 127))/(127*(131-contrast))
...
Contrast and brightness can be adjusted using alpha (α) and beta (β), respectively. These variables are often called the gain and bias parameters. The expression can be written as
OpenCV already implements this as cv2.convertScaleAbs(), just provide user defined alpha and beta values
import cv2
image = cv2.imread('1.jpg')
alpha = 1.5 # Contrast control (1.0-3.0)
beta = 0 # Brightness control (0-100)
adjusted = cv2.convertScaleAbs(image, alpha=alpha, beta=beta)
cv2.imshow('original', image)
cv2.imshow('adjusted', adjusted)
cv2.waitKey()
Before -> After
Note: For automatic brightness/contrast adjustment take a look at automatic contrast and brightness adjustment of a color photo
There are quite a few answers here ranging from simple to complex. I want to add another on the simpler side that seems a little more practical for actual contrast and brightness adjustments.
def adjust_contrast_brightness(img, contrast:float=1.0, brightness:int=0):
"""
Adjusts contrast and brightness of an uint8 image.
contrast: (0.0, inf) with 1.0 leaving the contrast as is
brightness: [-255, 255] with 0 leaving the brightness as is
"""
brightness += int(round(255*(1-contrast)/2))
return cv2.addWeighted(img, contrast, img, 0, brightness)
We do the a*x+b adjustment through the addWeighted() function. However, to change the contrast without also modifying the brightness, the data needs to be zero centered. That's not the case with OpenCVs default uint8 datatype. So we also need to adjust the brightness according to how the distribution is shifted.
Best explanation for X = aY + b (in fact it f(x) = ax + b)) is provided at https://math.stackexchange.com/a/906280/357701
A Simpler one by just adjusting lightness/luma/brightness for contrast as is below:
import cv2
img = cv2.imread('test.jpg')
cv2.imshow('test', img)
cv2.waitKey(1000)
imghsv = cv2.cvtColor(img, 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.waitKey(1000)
raw_input()
img = cv2.imread("/x2.jpeg")
image = cv2.resize(img, (1800, 1800))
alpha=1.5
beta=20
new_image=cv2.addWeighted(image,alpha,np.zeros(image.shape, image.dtype),0,beta)
cv2.imshow("new",new_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Categories