0

I implemented an algorithm in Python, which is used to reduce the intensity variations (flickering) in an image. The algorithm first calculates the cumulative histogram for each row of the image. Then, it filters the accumulated row cumulative histograms in the vertical direction (across the columns) with a Gaussian kernel, so that the differences between the row cumulative histograms are reduced. In the final part, each row cumulative histogram of the original image should be matched to the corresponding obtained Gaussian-filtered row cumulative histogram. Hence, a histogram matching operation (per each row) is performed, and the desired rows are reconstructed. Naturally, the image is reconstructed in the end by simply stacking all the rows vertically on top each other.

In my code, the last part, where there are two for-loops in each other (iterating for each row and inside each row, for each intensity level [0,255]) takes a lot of time, such that the usage of the algorithm is no more feasible. For a single 4592x3448 (16MP) image, the execution time is over 10 minutes on my machine. Of course, iteration over 256 intensity values for each of the 3448 rows should be slowing down the algorithm quite a bit, but I can't see any other way to avoid these for-loops due to the nature of the algorithm.

I'm very new to Python, so I might be committing serious crimes in terms of programming here. I would appreciate any hints and code reviews. You can find an example image under this link: http://s3.postimg.org/a17b3otpf/00000_cam0.jpg

import time
import cv2
import numpy as np
import scipy.ndimage as ndi
from matplotlib import pyplot as plt

start_time = time.time()
### Algorithm: Filtering row cumulative histograms with different Gaussian variances
T = 200     # threshold
img = cv2.imread('flicker.jpg',0)
rows,cols = img.shape
cdf_hist = np.zeros((rows,256))
for i in range(0,rows):    
    # Read one row
    img_row = img[i,]
    # Calculate the row histogram
    hist_row = cv2.calcHist([img_row],[0],None,[256],[0,256])
    # Calculate the cumulative row histogram
    cdf_hist_row = hist_row.cumsum()
    # Accumulate the cumulative histogram of each row
    cdf_hist[i,:] = cdf_hist_row

# Apply Gaussian filtering on the row cumulative histograms along the columns (vertically)   
# For dark pixels, use higher sigma
Gauss1_cdf = ndi.gaussian_filter1d(cdf_hist, sigma=6, axis=0, output=np.float64, mode='nearest')
# For bright pixel, use lower sigma
Gauss2_cdf = ndi.gaussian_filter1d(cdf_hist, sigma=3, axis=0, output=np.float64, mode='nearest')

##
print("--- %s seconds ---" % (time.time() - start_time))
### UNTIL HERE: it takes in my computer approx. 0.25 sec with a 16MP image

### This part takes too much time ###### START ######################
# Perform histogram matching (for each row) to either 'Hz1' or 'Hz2'
img_match = np.copy(img)
for r in range(0,rows):
    row = img[r,:]
    Hy = cdf_hist[r,:]       # Original row histogram
    Hz1 = Gauss1_cdf[r,:]    # Histogram 1 to be matched
    Hz2 = Gauss2_cdf[r,:]    # Histogram 2 to be matched
    row_match = img_match[r,:]
    for i in range(0,255):      # for each intensity value
        # Find the indices of the pixels in the row whose intensity = i
        ind = [m for (m,val) in enumerate(row) if val==i]
        j = Hy[i]
        while True:
            # use the appropriate CDF (Hz1 or Hz2) according to the bin number
            if i<T:
                k = [m for (m,val) in enumerate(Hz1) if val>j-1 and val<j+1]
            else:
                k = [m for (m,val) in enumerate(Hz2) if val>j-1 and val<j+1]
            if len(k)>0:
                break
            else:
                j = j+1
        row_match[ind] = k[0]        
###################### END ####################################    

# Set upper bound to the change of intensity values to avoid brightness overflows etc.
alfa = 5
diff_img = cv2.absdiff(img,img_match)
img_match2 = np.copy(img_match)
img_match2[diff_img>alfa] = img[diff_img>alfa] + alfa

## Plots
plt.subplot(121), plt.imshow(img,'gray')
plt.subplot(122), plt.imshow(img_match2, 'gray')
plt.show()

print("--- %s seconds ---" % (time.time() - start_time))
chronosynclastic
  • 1,585
  • 3
  • 19
  • 40
  • This would be a better place to bring your question: http://codereview.stackexchange.com/ A problem with posing the question here is that there's a general desire that there be a single answer to the question. Your question isn't clearly enough posed that there's just one answer. I might see one error. Someone else might see another... – Joel Jan 19 '15 at 14:42
  • I assume time.time() is being used to measure how long the calculations are taking? A better approach is to use timeit. – Joel Jan 19 '15 at 14:46
  • rather than `if len(k)>0:`, you can do `if k:` but you shouldn't be defining `k` anyways. See next comment. – Joel Jan 19 '15 at 14:47
  • It looks like here: `k = [m for (m,val) in enumerate(Hz2) if val>j-1 and val0:`. Then do `row_match[ind] = Hz2.index(j)` – Joel Jan 19 '15 at 14:52
  • I"m going to give a solution - but it would be good if I understood what `ind` is. It looks like it's a list, but it's used as an index as well? – Joel Jan 19 '15 at 14:56
  • `ind` contains the indices of the pixels in `row` which have a value equal to `i`. For example, if `i=5`, it returns the position of all the pixels in `row` which are equal to `5`. – chronosynclastic Jan 19 '15 at 15:07
  • Ah, didn't notice that these were np arrays. Thought that there would be a problem with `row_match[ind] = k[0]` since `ind` was a list. – Joel Jan 19 '15 at 15:09

1 Answers1

0

I'm not going to get into using timeit, but it's worth doing. I'm going to leave the first part alone (last part as well).

### This part takes too much time ###### START ######################
# Perform histogram matching (for each row) to either 'Hz1' or 'Hz2'
img_match = np.copy(img)
for r in xrange(rows):  #no need to tell range to start from 0. and you should use xrange
    row = img[r,:]
    rowlength = len(row) #so you don't have to keep recalculating later.
    Hy = cdf_hist[r,:]       # Original row histogram
    Hz1 = Gauss1_cdf[r,:]    # Histogram 1 to be matched
    Hz2 = Gauss2_cdf[r,:]    # Histogram 2 to be matched
    Hz1Dict = {}
    Hz2dict = {}
    for index, item in enumerate(reversed(Hz1)):
        Hz1Dict[math.ceil(item)] = index
        Hz1Dict[math.floor(item)] = index
    for index, item in enumerate(reversed(Hz2)):
        Hz2Dict[math.ceil(item)] = index
        Hz2Dict[math.floor(item)] = index
    row_match = img_match[r,:]
    for i in xrange(255):      # for each intensity value
        # Find the indices of the pixels in the row whose intensity = i
        ind = [m for m in xrange(rowlength) if row[m]==i]
        j = Hy[i]
        while True:
            # use the appropriate CDF (Hz1 or Hz2) according to the bin number
            if i<T:
                if j in Hz1dict:
                    row_match[ind] = Hz1dict[j]
                    break
            else:
                if j in Hz2dict:
                    row_match[ind] = Hz2dict[j]
                    break
            else:
                j = j+1
###################### END ####################################    

Obviously you should check closely that I haven't changed the logic at all.

edit what I've now done is stored a dict for each Hz recording for each j the index of the first number between j-1 and j+1 (note the reverse in the enumeration). So now I just check whether the dict has the corresponding j as a key. If so, I set k row_match[ind] to be that value.

Joel
  • 22,598
  • 6
  • 69
  • 93
  • What I did for defining `ind` may not be optimal. See http://stackoverflow.com/questions/6294179/how-to-find-all-occurrences-of-an-element-in-a-list where the prevailing view seems to be what you had tried. – Joel Jan 19 '15 at 15:12
  • Thanks for the quick answer. I think you forgot to remove the last line, no? `row_match[ind] = k[0]`. One more question: Does `index()` work for np arrays? – chronosynclastic Jan 19 '15 at 15:24
  • The problem with this code is I'm actually searching for a range in which `j` can be, because both histogram `Hy` and `Hz1` (or `Hz2`) do not exactly contain matching values, so with your code `j` keeps increasing forever. To be more precise `Hy` e.g. contains values like `[2.0, 6.0, 8.0, 10.0 ...] ` whereas `Hz1` (or `Hz2`) contains values like `[7.1, 9.4, 11.7, 13.8 ...]` . So, `if j in Hz1` or `elif j in Hz2` never becomes true. – chronosynclastic Jan 19 '15 at 15:39
  • So I'm working on how to modify this part. Do you expect the loop in your code to exit after the first `j` typically, or do you expect it to loop through many times? I think the latter, in which case I think I know what to do, but won't have a chance till later to make the modification. – Joel Jan 20 '15 at 02:12
  • I think the modified code will work. This will still loop over the rows and `i`, but the inner loop should now be O(1). – Joel Jan 20 '15 at 09:05