Search code examples
pythonopencvscipyscikit-image

How to find regional maxima/minima in images?


I am trying to find the regional maximum in this image:

enter image description here

to make a cut in its position like this:

enter image description here

I found a method how to filter regional maxima here but I can't make it work for my case.

My code so far:

import numpy as np
import cv2
import skimage as sm
from skimage.morphology import reconstruction
import scipy as sp

img = cv2.imread('img.png', 0)

img = sm.img_as_float(img)
img = sp.ndimage.gaussian_filter(img, 1)

seed = np.copy(img)
seed[1:-1,1:-1] = img.min()
mask = img
dilated = reconstruction(seed, mask, method = 'dilation')
img = img - dilated

cv2.imshow('img', img)
cv2.waitKey()

My Solution:

import numpy as np
import cv2

img = cv2.imread('img.png', 0)

_, thresh = cv2.threshold(img, 250, 255, cv2.THRESH_BINARY)

rows = np.sum(thresh/255, axis = 1)
ol = len(np.nonzero(rows)[0])
L = []
z = 0
for idx, row in enumerate(rows):
    if row > 0:
        if z > 5 and z < ol - 5:
            L.append(idx)
        z += 1
split = np.min(rows[L])
thresh[np.where(rows == split)[0][0]] = 0

cv2.imshow('img', thresh)
cv2.waitKey()

HansHirse wrote a more professional approach:

import numpy as np
import cv2

img = cv2.imread('img.png', 0)

_, thresh = cv2.threshold(img, 250, 255, cv2.THRESH_BINARY)

rows = np.sum(thresh/255, axis = 1)
exclude = 5
idx = np.where(rows > 0)[0]
idx = idx[exclude : len(idx) - exclude]
cut = idx[np.argmin(rows[idx])]
thresh[cut] = 0

cv2.imshow('img', thresh)
cv2.waitKey()

Both result in:

enter image description here

It would be interesting to see an approach, that is not limited to horizontal pixels.


Solution

  • If your "chromatids" (I will refer to the shown structure in that way, because it kind of looks like one) are all aligned in that way, you can simply count the white pixels per row, and search for the minimum.

    Please have a look at the following code, which is hopefully self-explanatory:

    import cv2
    import numpy as np
    
    # Load input image
    input = cv2.imread('images/Q6YM9.png', cv2.IMREAD_GRAYSCALE)
    
    # Extract "chromatid" (the structure looks like one...)
    _, chromatid = cv2.threshold(input, 250, 255, cv2.THRESH_BINARY)
    
    # Sum row-wise pixel values
    rowPixelSum = np.sum(chromatid / 255, axis=1)
    
    # Detect all rows with non-zero elements
    ind = np.where(rowPixelSum > 0)[0]
    
    # Exclude n rows at the top and bottom of the "chromatid"
    # Caveat: Check for plausibility (index out of bounds, etc.)
    nEx = 15
    ind = ind[15:len(ind)-nEx]
    
    # Detect index of row with minimum pixel count
    cutRow = ind[np.argmin(rowPixelSum[ind])]
    
    # Detect start and end of "chromatid" on row with minimum pixel count
    row = np.where(chromatid[cutRow, :] > 0)[0]
    xStart = row[0]
    xEnd = row[-1]
    
    # For visualization: Draw black line through row with minimum pixel count
    cv2.line(input, (xStart, cutRow), (xEnd, cutRow), 0, 3)
    cv2.line(chromatid, (xStart, cutRow), (xEnd, cutRow), 0, 3)
    
    # Write output image
    cv2.imwrite('images\input.png', input)
    cv2.imwrite('images\chromatid.png', chromatid)
    

    The outputs look like this:

    Chromatid

    Input

    If your "chromatids" have varying orientations, one could utilize some rotation prior to the above-mentioned code, based on the "principal component" of the chromatid.

    Hope that helps!