I am curious about an image processing algorithm used in this code pulled from this github repo. I have posted an explanation of the code below.
import numpy as np import cv2 import sys def calc_sloop_change(histo, mode, tolerance): sloop = 0 for i in range(0, len(histo)): if histo[i] > max(1, tolerance): sloop = i return sloop else: sloop = i def process(inpath, outpath, tolerance): original_image = cv2.imread(inpath) tolerance = int(tolerance) * 0.01 #Get properties width, height, channels = original_image.shape color_image = original_image.copy() blue_hist = cv2.calcHist([color_image], , None, , [0, 256]) green_hist = cv2.calcHist([color_image], , None, , [0, 256]) red_hist = cv2.calcHist([color_image], , None, , [0, 256]) blue_mode = blue_hist.max() blue_tolerance = np.where(blue_hist == blue_mode) * tolerance green_mode = green_hist.max() green_tolerance = np.where(green_hist == green_mode) * tolerance red_mode = red_hist.max() red_tolerance = np.where(red_hist == red_mode) * tolerance sloop_blue = calc_sloop_change(blue_hist, blue_mode, blue_tolerance) sloop_green = calc_sloop_change(green_hist, green_mode, green_tolerance) sloop_red = calc_sloop_change(red_hist, red_mode, red_tolerance) gray_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY) gray_hist = cv2.calcHist([original_image], , None, , [0, 256]) largest_gray = gray_hist.max() threshold_gray = np.where(gray_hist == largest_gray) #Red cells gray_image = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 85, 4) _, contours, hierarchy = cv2.findContours(gray_image, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) c2 = [i for i in contours if cv2.boundingRect(i) > 15] cv2.drawContours(color_image, c2, -1, (0, 0, 255), 1) cp = [cv2.approxPolyDP(i, 0.015 * cv2.arcLength(i, True), True) for i in c2] countRedCells = len(c2) for c in cp: xc, yc, wc, hc = cv2.boundingRect(c) cv2.rectangle(color_image, (xc, yc), (xc + wc, yc + hc), (0, 255, 0), 1) #Malaria cells gray_image = cv2.inRange(original_image, np.array([sloop_blue, sloop_green, sloop_red]), np.array([255, 255, 255])) _, contours, hierarchy = cv2.findContours(gray_image, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) c2 = [i for i in contours if cv2.boundingRect(i) > 8] cv2.drawContours(color_image, c2, -1, (0, 0, 0), 1) cp = [cv2.approxPolyDP(i, 0.15 * cv2.arcLength(i, True), True) for i in c2] countMalaria = len(c2) for c in cp: xc, yc, wc, hc = cv2.boundingRect(c) cv2.rectangle(color_image, (xc, yc), (xc + wc, yc + hc), (0, 0, 0), 1) #Write image cv2.imwrite(outpath, color_image) #Write statistics with open(outpath + '.stats', mode='w') as f: f.write(str(countRedCells) + '\n') f.write(str(countMalaria) + '\n')
First, the code is generating 3 histograms, one for each color signal.
calcHist returns a histogram(array) of size
1x256. Each index represents pixel intensity levels of a 2D image and the value inside the index represents the number of pixels that have that intensity.
Next, in this array, the
where method calculates which index has the greatest value, or in other words, which pixel intensity is most predominant. This value is multiplied by a tolerance percentage, this new value is called
I'll explain what happens with calc_sloop_change with an example below.
Let me illustrate with an example:
Let's say my tolerance passed into
process() is 50. Let's say
blue_hist returns an array [1, 2, 3, 4, 100, 0, ..., 0] and the largest value in this array is 100 at index 4. This indicates that there are a 100 pixels with an intensity of 4 in the gray scale version of the color image when just the blue signal is extracted. In this situation, the function
where(blue_hist = blue_mode) will return
4. This value is multiplied by
0.01*tolerance giving us
So, if the value
4 is pixel intensity value then multiplying it by a scalar only gives another pixel intensity value (in our case,
(4 * (0.01*50)) = 2. This new pixel intensity is passed into
calc_sloop_change(). In this function, compares
histo[i] which returns the number of pixels at intensity
i with tolerance(which is the pixel value we calculated earlier). So in our case, the first value greater than 2 happens when
3 is returned.
This is where I'm confused. Why is this being done? It seems illogical to compare the number of pixels vs pixel intensity. They are not even the same entity. So, why are they using this algorithm? I must add that this code actually performs really well. So something must be right.
Lastly, the three values calculated by
calc_sloop_change(), one for each color signal, acts as a lower cutoff to produce a binary image. Anything less than those values(which are actually pixel intensity values) becomes black and everything above those values becomes white.