Reputation: 3016
I want to calculate the Perceived brightness ,based on the formula from this link, of this image:
The idea is to loop over each pixel and calculate its Perceived brightness according to this formula:
Pb = sqrt(0.241 R² + 0.691 G² + 0.068 B²)
then sum all the values and calculate the mean.
Here is the code I wrote:
import cv2
from math import sqrt
img = cv2.imread('e.png')
H, W = img.shape[:2]
pr = 0.241
pg = 0.691
pb = 0.068
p = []
for h in range(0, H):
for w in range(0, W):
p.append(sqrt(pr * pow(img[h][w][2], 2) + pg * pow(img[h][w][1], 2) + pb * pow(img[h][w][0], 2)))
arr = np.reshape(p, (H, W))
cv2.imwrite('loop_img.jpg', arr)
print(np.mean(arr))
The image I got at the end is this:
And the mean is 82.04557421656007
However when I repeated the same process using numpy (to avoid looping over each pixel), I got different values!
Here is the code I used:
import cv2
import numpy as np
img = cv2.imread('e.png')
b, g, r = cv2.split(img)
pr = 0.241
pg = 0.691
pb = 0.068
P = np.sqrt(pr * pow(r, 2) + pg * pow(g, 2) + pb * pow(b, 2))
cv2.imwrite('np_img.jpg', P)
print(np.mean(P))
The image I got is this:
And the mean is 1.6438602314083277
The most weird is that when I applied the same methods on a random numpy array, I got similar results!
import numpy as np
import cv2
from math import sqrt
pr = 0.241
pg = 0.691
pb = 0.068
arr = np.array([[[255, 127, 0],
[255, 127, 0]],
[[255, 133, 0],
[255, 133, 0]],
[[255, 138, 0],
[255, 138, 0]]])
b, g, r = cv2.split(arr)
p = []
for h in range(0, 3):
for w in range(0, 2):
print(arr[h][w])
p.append(sqrt(pr * pow(arr[h][w][2], 2) + pg * pow(arr[h][w][1], 2) + pb * pow(arr[h][w][0], 2)))
arr_p = np.reshape(p, (3, 2))
print('arr_p:', arr_p)
np_p = np.sqrt(pr * pow(r, 2) + pg * pow(g, 2) + pb * pow(b, 2))
print('np_ap:', np_p)
print('loop_mean:', np.mean(arr_p))
print('numpy_mean:', np.mean(np_p))
The results I got:
arr_p: [[124.7671391 124.7671391 ]
[129.01472397 129.01472397]
[132.59375551 132.59375551]]
np_ap: [[124.7671391 124.7671391 ]
[129.01472397 129.01472397]
[132.59375551 132.59375551]]
loop_mean: 128.79187285939827
numpy_mean: 128.79187285939827
Is there any explanation why I got different results with the image and similar results with the second array? (could it be related to the array elements type?)
N.B: I use
python==3.6
numpy==1.16.1
opencv-contrib-python==4.0.0.21
opencv-python==4.0.0.21
Upvotes: 3
Views: 252
Reputation: 16796
The problem is due to the difference of data type conversion rules between numpy array
and raw data types.
In the case of numpy array, the calculation is being done as follows:
P = np.sqrt(pr * pow(r, 2) + pg * pow(g, 2) + pb * pow(b, 2))
The culprit operation here is pow
. Since the default data type of image read using cv2.imread
is np.uint8
so consequently, r
, g
and b
also have the same type. Now, when pow
function is applied on the numpy array, the resultant array tends to have the same integer type. The values in the result are truncated to the range of uint8
type thus causing invalid results. Since the results are truncated, the mean value becomes very small as being observed.
1. Convert input image to floating point type:
img = cv2.imread('e.png')
img = img.astype(np.float)
2. Use floating point operands in pow
:
P = np.sqrt(pr * pow(r, 2.0) + pg * pow(g, 2.0) + pb * pow(b, 2.0))
p.append(sqrt(pr * pow(img[h][w][2], 2) + pg * pow(img[h][w][1], 2) + pb * pow(img[h][w][0], 2)))
Apparently, applying pow
on a single integer instead of numpy array
results in a value of larger integer type (int64
) thus avoiding the issue of truncation.
Upvotes: 4
Reputation: 11420
The problem is the pow
function to a np.uint8
array. First, lets have a simple example:
>> a = np.arange(20, dtype=np.uint8).reshape(4,5)
which gives:
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]], dtype=uint8)
It is important to test with np.uint8 which is the type of a loaded image. Then we do pow or np.power (they behave exactly the same) and the result is the following:
>> np.power(a,2)
array([[ 0, 1, 4, 9, 16],
[ 25, 36, 49, 64, 81],
[100, 121, 144, 169, 196],
[225, 0, 33, 68, 105]], dtype=uint8)
>> pow(a,2)
array([[ 0, 1, 4, 9, 16],
[ 25, 36, 49, 64, 81],
[100, 121, 144, 169, 196],
[225, 0, 33, 68, 105]], dtype=uint8)
As you can see, the power function did not change the type... and this leads to overflow...
You have 2 options to solve it:
Cast the type and then cast it back like
b = np.float32(b) #same for g and r or to the whole image
# or this
b, g, r = cv2.split(np.float32(img))
and then before saving use np.uint8()
, opencv saving functions usually work only with uint8... maybe the newer versions doesn't.
The other thing is to use np.float_power
which will return float32 type and the correct numbers.
Upvotes: 3