Find If Image Is Bright Or Dark - python

I would like to know how to write a function in Python 3 using OpenCV which takes in an image and a threshold and returns either 'dark' or 'light' after heavily blurring it and reducing quality (faster the better). This might sound vague , but anything that just works will do.

You could try this :
import imageio
import numpy as np
f = imageio.imread(filename, as_gray=True)
def img_estim(img, thrshld):
is_light = np.mean(img) > thrshld
return 'light' if is_light else 'dark'
print(img_estim(f, 127))

Personally, I would not bother writing any Python, or loading up OpenCV for such a simple operation. If you absolutely have to use Python, please just disregard this answer and select a different one.
You can just use ImageMagick at the command-line in your Terminal to get the mean brightness of an image as a percentage, where 100 means "fully white" and 0 means "fully black", like this:
convert someImage.jpg -format "%[fx:int(mean*100)]" info:
Alternatively, you can use libvips which is less common, but very fast and very lightweight:
vips avg someImage.png
The vips answer is on a scale of 0..255 for 8-bit images.
Note that both these methods will work for many image types, from PNG, through GIF, JPEG and TIFF.
However, if you really want Python/OpenCV code, I note none of the existing answers do that - some use a different library, some are incomplete, some do superfluous blurring and some read video cameras for some unknown reason, and none handle more than one image or errors. So, here's what you actually asked for:
#!/usr/bin/env python3
import cv2
import sys
import numpy as np
# Iterate over all arguments supplied
for filename in sys.argv[1:]:
# Load image as greyscale
im = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
if im is None:
print(f'ERROR: Unable to load {filename}')
continue
# Calculate mean brightness as percentage
meanpercent = np.mean(im) * 100 / 255
classification = "dark" if meanpercent < 50 else "light"
print(f'{filename}: {classification} ({meanpercent:.1f}%)')
Sample Output
OpenCVBrightOrDark.py g*png nonexistant
g30.png: dark (30.2%)
g80.png: light (80.0%)
ERROR: Unable to load nonexistant

You could try this, considering image is a grayscale image -
blur = cv2.blur(image, (5, 5)) # With kernel size depending upon image size
if cv2.mean(blur) > 127: # The range for a pixel's value in grayscale is (0-255), 127 lies midway
return 'light' # (127 - 255) denotes light image
else:
return 'dark' # (0 - 127) denotes dark image
Refer to these -
Smoothing, Mean, Thresholding

import numpy as np
import cv2
def algo_findDark(image):
blur = cv2.blur(image, (5, 5))
mean = np.mean(blur)
if mean > 85:
return 'light'
else:
return 'dark'
cam = cv2.VideoCapture(0)
while True:
check, frame = cam.read()
frame_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
ans = algo_findDark(frame_gray)
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(frame, ans, (10, 450), font, 3, (0, 0, 255), 2, cv2.LINE_AA)
cv2.imshow('video', frame)
key = cv2.waitKey(1)
if key == 27:
break
cam.release()
cv2.destroyAllWindows()

Related

I can't read long distance text with pytesseract

I have this image and I want to read the text on it but pytesseract returns blank
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
import cv2
import numpy as np
import math
from scipy import ndimage
import easyocr
import pytesseract
img = cv2.imread('cikti.jpg')
scale_percent = 220 # percent of original size
width = int(img.shape[1] * scale_percent / 100)
height = int(img.shape[0] * scale_percent / 100)
dim = (width, height)
# resize image
img = cv2.resize(img, dim, interpolation = cv2.INTER_AREA)
cv2.imshow('img', img)
cv2.waitKey(0)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
cv2.imshow('edges', edges)
cv2.waitKey(0)
angles = []
lines = cv2.HoughLinesP(edges, 1, math.pi / 180.0, 90)
for [[x1, y1, x2, y2]] in lines:
#cv2.line(img, (x1, y1), (x2, y2), (255, 0, 0), 3)
angle = math.degrees(math.atan2(y2 - y1, x2 - x1))
if(angle != 0):
angles.append(angle)
print(angles)
median_angle = np.median(angles)
img = ndimage.rotate(img, median_angle)
print(median_angle)
filiter = np.array([[-1,-1,-1],
[-1,9,-1],
[-1,-1,-1]])
cv2.imshow('filitird', img)
cv2.waitKey(0)
reader = easyocr.Reader(['tr'])
ocr_result = reader.readtext(img,)
print(ocr_result)
cv2.imshow('result', img)
k = cv2.waitKey(0)
cv2.destroyAllWindows()
here is the code i wrote
It may be because of the long distance, but enlarging the picture did not solve my problem.
what should I do
I was able successfully to read this image with tesseract by doing the following:
cropping out the pink border
reducing to grayscale (binarising)
running tesseract with --psm 8 (see this question )
I don't know if the cropping is necessary, but I couldn't get any output at all with any page segregation mode before binarising.
I did the processing manually here, but you will likely want to automate it. A good trick for setting thresholds is to look at the standard deviation of the image in question and use that to scale your thresholds, rather than picking some absolute value and having it fail on you.
Here's the image I got working:
And the run:
$ tesseract img3.png img3 --psm 8 txt
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
$ cat img3.txt
47 F02 43
I've not tried with pytesseract, but you should be able to set the same thing.
Easy ocr was able to read the image immediately, albeit inaccurately, when I tried with the web service
Update: grayscaling
This is a whole subject in itself. You might want to start with this tutorial from the opencv docs. There are basically two approaches--trying properly to binarise the image (convert it to two colour pixels, on or off) and just grayscaling it. Somewhere inbetween is 'posterising', where you reduce the number of tones (binarising is a special case of posterising, where the number of tones is 2). I normally handle grayscaling with the inbuilt function in PIL (pillow); I've had good result with a quick-and-dirty sort-of binarisation algorithm where I first normalise the brightness and contrast of an image and then apply a skewing function like
def filter_point(point: int) -> int:
if point < THRESH:
return round(point/1.2)
else:
return round(point *2)
This drives most pixels to fully white/black but leaves some intermediate values in place. It's a poor solution in that it depends on three magic numbers, but in my application (preparing scanned pdfs for human reading) I got better results than with automated thresholding or posterisation.
Thus sadly the answer is going to be 'play with it'. I'd suggest you start out with an image editor and see what the bare minimum you can do to the image to get tesseract to work is---perhaps just grayscaling (which you do earlier in the code anyway) will be enough; do you need to crop it, etc. Not drawing that pink box is going to help. I provided a very crude example filter to demonstrate that pixels are just numbers and you can do your image processing that way, but you are much better off using built in methods if you possibly can.
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
import cv2 as cv
import numpy as np
import easyocr
img = cv.imread('result.png',0)
th2 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 29, 10);
cv.imshow("ADAPTIVE_THRESH_MEAN_C", th2)
cv.waitKey(0)
cv.destroyAllWindows()
reader = easyocr.Reader(['tr'])
ocr_result = reader.readtext(th2,)
print(ocr_result)
it worked like this
Image befor ocr :
Result :

How to visualize a 16-bit grayscale image with cv2.imshow()?

I have a set of grayscale drone images in tiff format having 16-bit resolution where a person can be seen moving. How can I visualize these images in OpenCV as a normal image, so that I can see the information within the image in OpenCV? Currently, when I try to read and show the image, I see a black image.
import argparse
import cv2
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required = True, help = "Path to the image")
args = vars(ap.parse_args())
image = cv2.imread(args["image"],IMREAD_ANYCOLOR | IMREAD_ANYDEPTH)
cv2.imshow("image", image)
cv2.waitKey(0)
I have tried the above code, but it still displays a complete black image. However, when I convert the image to a png and then use the above code, then it works fine which I do not want to do due to loss of information.
Here is the link to sample image. All images contain different information.
https://filebin.net/n3oidsqn70eq8a9x/gelmer_gas_0_Raw_316_4245_2942_1455208775.tif?t=c2m8vnsn
The image should be like the below. This was opened with another software just for visual purpose
As you stated before, loading is easy:
img = cv2.imread("a.tif", cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH)
Then, one has different options to visualize a thermal image. The simple naive approach is to normalize from the min to the max value:
normed = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
After, you can color it with a colormap:
color = cv2.applyColorMap(normed, cv2.COLORMAP_JET)
My suggestion would be to fix the temperature range and clip the rest of the values to get an image with colors that can be compare between several images or video. To do that, you can take the idea from this answer assuming your new min and max are 0 and 255 and your old min and max are the range you need.
To be more specific, in your case you need something like:
def normalizeImg(low, high, img):
imgClip = np.clip(img, low, high)
maxVal = np.max(imgClip)
minVal = np.min(imgClip)
return np.uint8((255.)/(maxVal-minVal)*(imgClip-maxVal)+255.)
Where low and high are the raw values you want to normalize to. And then you use it like:
def celsiusToPixel(val):
return (val + 273.15) / 0.04
rangeToUse = [celsiusToPixel(20), celsiusToPixel(30)] # from 20-30° celsius
normed_range = normalizeImg(rangeToUse[0], rangeToUse[1], img)
I hope a didn't miss anything, but if you have questions, just ask :)

How to get low and high values of hsv color in python opencv

I am trying detect few colors in python opencv. For this I need to define the low and high hsv values so that the code can read it and detect colors. Now the issue I am facing is how do I get the high and low hsv colors. I am referring to below image
I need to detect this jacket and thus need to input its high and low hsv. For this I got a reference to this code which allows to select any part of image and will output the high and low hsv values for it. But as far as I know, hsv value cannot be larger than 100 but this code and most of the other codes online gives hsv values which are greater than 100, and this is very I am getting confused as to how these values can be greater than 100.
Can anyone please explain how can we get the values of low and high hsv values
Try below code:
import cv2
import numpy as np
img = cv2.imread("jacket.jpg")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# mask of green (36,25,25) ~ (86, 255,255)
mask = cv2.inRange(hsv, (36, 25, 25), (70, 255,255))
green = cv2.bitwise_and(img,img, mask= mask)
cv2.imshow('Image', green)
cv2.waitKey(0)
cv2.destroyAllWindowss()
output:
Check this stackoverflow discussion on how to correctly select the upper and lower hsv values for color detection.
Couldnt find the resource but found something like this and made it
useful, thanks to the author
import cv2
import imutils
import numpy as np
image_hsv = None # global
pixel = (20,60,80) # some stupid default
# mouse callback function
def pick_color(event,x,y,flags,param):
if event == cv2.EVENT_LBUTTONDOWN:
pixel = image_hsv[y,x]
#you might want to adjust the ranges(+-10, etc):
upper = np.array([pixel[0] + 10, pixel[1] + 10, pixel[2] + 40])
lower = np.array([pixel[0] - 10, pixel[1] - 10, pixel[2] - 40])
print(pixel, lower, upper)
image_mask = cv2.inRange(image_hsv,lower,upper)
cv2.imshow("mask",image_mask)
def main():
import sys
global image_hsv, pixel # so we can use it in mouse callback
image_src = cv2.imread("myimage.jpeg") # pick.py my.png
image_src = imutils.resize(image_src, height=800)
if image_src is None:
print ("the image read is None............")
return
cv2.imshow("bgr",image_src)
## NEW ##
cv2.namedWindow('hsv')
cv2.setMouseCallback('hsv', pick_color)
# now click into the hsv img , and look at values:
image_hsv = cv2.cvtColor(image_src,cv2.COLOR_BGR2HSV)
cv2.imshow("hsv",image_hsv)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__=='__main__':
main()
Image loaded will look like this:
After clicking on the ball you will get an image like,
And finally: true BGR value, lower and upper HSV boundaries will be printed in the terminal as follows,

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.

Compare images Python PIL

How can I compare two images? I found Python's PIL library, but I do not really understand how it works.
To check does jpg files are exactly the same use pillow library:
from PIL import Image
from PIL import ImageChops
image_one = Image.open(path_one)
image_two = Image.open(path_two)
diff = ImageChops.difference(image_one, image_two)
if diff.getbbox():
print("images are different")
else:
print("images are the same")
based on Victor Ilyenko's answer, I needed to convert the images to RGB as PIL fails silently when the .png you're trying to compare contains a alpha channel.
image_one = Image.open(path_one).convert('RGB')
image_two = Image.open(path_two).convert('RGB')
It appears that Viktor's implementation can fail when the images are different sizes.
This version also compares alpha values. Visually identical pixels are (I think) treated as identical, such as (0, 0, 0, 0) and (0, 255, 0, 0).
from PIL import ImageChops
def are_images_equal(img1, img2):
equal_size = img1.height == img2.height and img1.width == img2.width
if img1.mode == img2.mode == "RGBA":
img1_alphas = [pixel[3] for pixel in img1.getdata()]
img2_alphas = [pixel[3] for pixel in img2.getdata()]
equal_alphas = img1_alphas == img2_alphas
else:
equal_alphas = True
equal_content = not ImageChops.difference(
img1.convert("RGB"), img2.convert("RGB")
).getbbox()
return equal_size and equal_alphas and equal_content
Another implementation of Viktor's answer using Pillow:
from PIL import Image
im1 = Image.open('image1.jpg')
im2 = Image.open('image2.jpg')
if list(im1.getdata()) == list(im2.getdata()):
print("Identical")
else:
print ("Different")
Note:
This might not work correctly if the images have an alpha channel, or are using different modes.
That did work for me, you just have to set the quantity of different pixels you accept, in my case 100, as the image diference was a complete black image but still had 25 diferent pixels. I tested other completelly diffferent images and they had diferrent pixels by thousands:
from PIL import ImageChops
if len(set(ImageChops.difference(img1, img2).getdata())) > 100:
print("Both images are diffent!")
You can use ImageChops.difference in combination with getbbox:
from PIL import Image
from PIL import ImageChops
def are_equal(image_a: Image, image_b: Image) -> bool:
diff = ImageChops.difference(image_a, image_b)
channels = diff.split()
for channel in channels:
if channel.getbbox() is not None:
return False
return True
The reason why we need to split the channels is because of the (possible presence of an) alpha channel. If you have two images that have exactly the same alpha channel (e.g. by both being fully opaque), then ImageChops.difference will produce an image whose alpha channel is zero all over. This means that for the pixel values of the image, we ignore the other channels because we know they final pixel is just black. Thus, the bbox of the difference of two fully opaque images is None, which is not what we want.

Categories