'Shadow Removal in Python OpenCV
I am trying to implement shadow removal in python OpenCV using the method of entropy minimization by Finlayson, et. al.:
"Intrinsic Images by Entropy Minimization", Finlayson, et. al.
I can't seem to match the results from the paper. My entropy plot does not match up with those from the paper and I am getting the wrong minimum entropy.
Any thoughts? (I have much more source code and papers upon request)
#############
# LIBRARIES
#############
import numpy as np
import cv2
import os
import sys
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
from PIL import Image
import scipy
from scipy.optimize import leastsq
from scipy.stats.mstats import gmean
from scipy.signal import argrelextrema
from scipy.stats import entropy
from scipy.signal import savgol_filter
root = r'\path\to\my_folder'
fl = r'my_file.jpg'
#############
# PROGRAM
#############
if __name__ == '__main__':
#-----------------------------------
## 1. Create Chromaticity Vectors ##
#-----------------------------------
# Get Image
img = cv2.imread(os.path.join(root, fl))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
h, w = img.shape[:2]
plt.imshow(img)
plt.title('Original')
plt.show()
img = cv2.GaussianBlur(img, (5,5), 0)
# Separate Channels
r, g, b = cv2.split(img)
im_sum = np.sum(img, axis=2)
im_mean = gmean(img, axis=2)
# Create "normalized", mean, and rg chromaticity vectors
# We use mean (works better than norm). rg Chromaticity is
# for visualization
n_r = np.ma.divide( 1.*r, g )
n_b = np.ma.divide( 1.*b, g )
mean_r = np.ma.divide(1.*r, im_mean)
mean_g = np.ma.divide(1.*g, im_mean)
mean_b = np.ma.divide(1.*b, im_mean)
rg_chrom_r = np.ma.divide(1.*r, im_sum)
rg_chrom_g = np.ma.divide(1.*g, im_sum)
rg_chrom_b = np.ma.divide(1.*b, im_sum)
# Visualize rg Chromaticity --> DEBUGGING
rg_chrom = np.zeros_like(img)
rg_chrom[:,:,0] = np.clip(np.uint8(rg_chrom_r*255), 0, 255)
rg_chrom[:,:,1] = np.clip(np.uint8(rg_chrom_g*255), 0, 255)
rg_chrom[:,:,2] = np.clip(np.uint8(rg_chrom_b*255), 0, 255)
plt.imshow(rg_chrom)
plt.title('rg Chromaticity')
plt.show()
#-----------------------
## 2. Take Logarithms ##
#-----------------------
l_rg = np.ma.log(n_r)
l_bg = np.ma.log(n_b)
log_r = np.ma.log(mean_r)
log_g = np.ma.log(mean_g)
log_b = np.ma.log(mean_b)
## rho = np.zeros_like(img, dtype=np.float64)
##
## rho[:,:,0] = log_r
## rho[:,:,1] = log_g
## rho[:,:,2] = log_b
rho = cv2.merge((log_r, log_g, log_b))
# Visualize Logarithms --> DEBUGGING
plt.scatter(l_rg, l_bg, s = 2)
plt.xlabel('Log(R/G)')
plt.ylabel('Log(B/G)')
plt.title('Log Chromaticities')
plt.show()
plt.scatter(log_r, log_b, s = 2)
plt.xlabel('Log( R / 3root(R*G*B) )')
plt.ylabel('Log( B / 3root(R*G*B) )')
plt.title('Geometric Mean Log Chromaticities')
plt.show()
#----------------------------
## 3. Rotate through Theta ##
#----------------------------
u = 1./np.sqrt(3)*np.array([[1,1,1]]).T
I = np.eye(3)
tol = 1e-15
P_u_norm = I - u.dot(u.T)
U_, s, V_ = np.linalg.svd(P_u_norm, full_matrices = False)
s[ np.where( s <= tol ) ] = 0.
U = np.dot(np.eye(3)*np.sqrt(s), V_)
U = U[ ~np.all( U == 0, axis = 1) ].T
# Columns are upside down and column 2 is negated...?
U = U[::-1,:]
U[:,1] *= -1.
## TRUE ARRAY:
##
## U = np.array([[ 0.70710678, 0.40824829],
## [-0.70710678, 0.40824829],
## [ 0. , -0.81649658]])
chi = rho.dot(U)
# Visualize chi --> DEBUGGING
plt.scatter(chi[:,:,0], chi[:,:,1], s = 2)
plt.xlabel('chi1')
plt.ylabel('chi2')
plt.title('2D Log Chromaticities')
plt.show()
e = np.array([[np.cos(np.radians(np.linspace(1, 180, 180))), \
np.sin(np.radians(np.linspace(1, 180, 180)))]])
gs = chi.dot(e)
prob = np.array([np.histogram(gs[...,i], bins='scott', density=True)[0]
for i in range(np.size(gs, axis=3))])
eta = np.array([entropy(p, base=2) for p in prob])
plt.plot(eta)
plt.xlabel('Angle (deg)')
plt.ylabel('Entropy, eta')
plt.title('Entropy Minimization')
plt.show()
theta_min = np.radians(np.argmin(eta))
print('Min Angle: ', np.degrees(theta_min))
e = np.array([[-1.*np.sin(theta_min)],
[np.cos(theta_min)]])
gs_approx = chi.dot(e)
# Visualize Grayscale Approximation --> DEBUGGING
plt.imshow(gs_approx.squeeze(), cmap='gray')
plt.title('Grayscale Approximation')
plt.show()
P_theta = np.ma.divide( np.dot(e, e.T), np.linalg.norm(e) )
chi_theta = chi.dot(P_theta)
rho_estim = chi_theta.dot(U.T)
mean_estim = np.ma.exp(rho_estim)
estim = np.zeros_like(mean_estim, dtype=np.float64)
estim[:,:,0] = np.divide(mean_estim[:,:,0], np.sum(mean_estim, axis=2))
estim[:,:,1] = np.divide(mean_estim[:,:,1], np.sum(mean_estim, axis=2))
estim[:,:,2] = np.divide(mean_estim[:,:,2], np.sum(mean_estim, axis=2))
plt.imshow(estim)
plt.title('Invariant rg Chromaticity')
plt.show()
Output:
Solution 1:[1]
Shadow Removal Using Illumination Invariant Image Formation (Ranaweera, Drew) notes under Results and Discussion that the results from JPEG images and PNG images differ due to the JPEG compression. So expecting results exactly like what "Intrinsic Images by Entropy Minimization" (Finlayson, et. al.) shows may not be reasonable.
I also notice that you are not adding back the 'extra light' that the author recommends in other papers.
Also, while defining rg_chrom
, the order of the channels needs to be BGR instead of RGB like you have used.
I'm working on implementing the paper, so your code was extremely useful to me. Thanks for that
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Shawn Mathew |