简体   繁体   中英

Most efficient way to read in a JPEG image and determine brightness

I'm using the method below to get perceived brightness. I'm curious if there are faster ways to do this?

def brightness( im_file ):
   im = Image.open(im_file)
   stat = ImageStat.Stat(im)
   r,g,b = stat.rms
   return math.sqrt(0.241*(r**2) + 0.691*(g**2) + 0.068*(b**2))

Requirements

  • Perfomance is the most important factor
  • Image files are under 30KB
  • Brightness will be similar across the image. There'll be small differences but looking at a fraction of the image should be sufficient.

PIL is terribly inefficient for this. PIL is implemented in pure Python, and so it's looping in Python through your image. Let's take a look at the implementation of the ImageStat.Stat.rms attribute from the source code :

def _getrms(self):
    "Get RMS for each layer"

    v = []
    for i in self.bands:
        v.append(math.sqrt(self.sum2[i] / self.count[i]))
    return v

which uses the helper functions

def _getsum2(self):
    "Get squared sum of all pixels in each layer"

    v = []
    for i in range(0, len(self.h), 256):
        sum2 = 0.0
        for j in range(256):
            sum2 += (j ** 2) * float(self.h[i + j])
        v.append(sum2)
    return v

def _getcount(self):
    "Get total number of pixels in each layer"

    v = []
    for i in range(0, len(self.h), 256):
        v.append(functools.reduce(operator.add, self.h[i:i+256]))
    return v

I'm confused on why there's a function for count ...but whatever. Anyways, as is evidenced here, this is highly inefficient. The above can be written much more efficiently directly with numpy functions.


Further, OpenCV is far faster at reading images than PIL. From one of my other answers , I timed the standard libraries for reading images and OpenCV was the fastest.


Lastly I don't think you should be summing the RMS with a Euclidean distance function for two reasons:

  1. The weights you used are of the type used to convert between grayscale images which is multiplied and added linearly.
  2. If the images are floats, then this scales the values down , whereas if the values are uint8 , then this scales values up . I'm not sure if you actually want that type of scale. I think a linear scale makes it easier.

I'm also unsure of the weights you've used here, 0.068 for blue is very small (only 6% of brightness?). The YUV standard uses the following weights:

Y = 0.299*R + 0.587*G + 0.114*B

With that said my answer doesn't provide the actual code to solve your problems so you should select the one that does, like user1767754 's answer. I just wanted to point out some of the reasons why PIL is slow here.

So here is one solution that is on aver almost twice faster:

Timing Stats: 10000 Runs Sum Methods: 9.4s ImageStat (Yours): 24.s

1) Calculate Sum of all Pixels

2) Calculate Sum of a perfectly white image

3) Now map the calculated sum of pixels between 0 (black) and 1 (white)

import numpy as np
import cv2
from scipy.interpolate import interp1d

#Load Image
img1 = cv2.imread('testing_1.png',3) #READ BGR

width, height, depth = img1.shape
maxValue = width * height * depth * 255
imageValue = np.sum(img1)

#Map Value between 0 and 1
m = interp1d([0,maxValue],[0,1])
print m(imageValue)

Test1: A very dark image: Value: 0.126771993983

在此处输入图片说明

Test1: A very bright image: Value: 0.65562396666

在此处输入图片说明

ImageStat.Stat can work on only a region of the image:

ImageStat.Stat(image, mask) ⇒ Stat instance

Calculates statistics for the give image. If a mask is included, only the regions covered by that mask are included in the statistics.

You say that brightness will be similar across the entire image, so you can choose a mask size that is as small as possible for your use case. The smallest possible mask size is of course a single pixel. But I guess that most of your images might not be completely gray, so you can increase the mask size until you find a value that is satisfactory for you.

There are methods you can use to try to find the optimal mask size. Have a bunch of test images. Calculate their brightness without a mask, as well as with different sizes of the mask. Then check what difference you get. Try to minimize the difference.

Also try to pick different regions and shapes: Center, left corner, square, round, every other pixel. As Alexander Reynolds points out in the comments, a mask with random pixels might be the best option since it represents the entire image without bias.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM