Search code examples
pythonopencvimage-processingscikit-image

How can I find the center of the pattern and the distribution of a color around it on python with opencv/skimage?


I have an image, that I want to process. I'm using Opencv and skimage. My goal is to find the distribution of the red dots around the barycenter of all the dots. I proceed as follows : first I select the color, and then I binarize the image that I obtain. Eventually, I would just count the red pixel that are on the rings with a certain width around that barycenter, in order to have an average distribution with regards to the radius supposing a cylindrical symmetry.

My issue is that I have no idea how to find the position of the barycenter.

I would also like to know if there is an short way to count the red pixels in the rings.

Here is my code :

import cv2
import matplotlib.pyplot as plt
from skimage import io, filters, measure, color, external

I'm uploading the image :

sph = cv2.imread('image_sper.jpg')
sph = cv2.cvtColor(sph, cv2.COLOR_BGR2RGB)
plt.imshow(sph)
plt.show()

enter image description here

I want to select the red color. Following https://realpython.com/python-opencv-color-spaces/, I'm converting it in HSV, and I'm using a mask.

hsv_sph = cv2.cvtColor(sph, cv2.COLOR_RGB2HSV)

light_red = (1, 100, 100)
dark_red = (18, 255, 255)

mask = cv2.inRange(hsv_sph, light_red, dark_red)

result = cv2.bitwise_and(sph, sph, mask=mask)

And here is the result :

plt.imshow(result)
plt.show()

enter image description here

Now I'm binarizing the image, since it'll be easier to process it afterwards.

red_image = result[:,:,1]
red_th = filters.threshold_otsu(red_image)

red_mask = red_image > red_th;
red_mask.dtype ;
io.imshow(red_mask);

And here we are :

enter image description here

What I would like some help now to find the barycenter of the white pixels.

Thx

Edit : The binarization gives the image boolean values False/True for the pixels. I don't know how to transform them to 0/1 pixels. If False was 0 and True 1, a code to find the barycenter would be :

np.shape(red_mask)
(* (321L, 316L) *) 
bari=0
barj=0
N=0
for i in range(321):
    for j in range(316):
        bari=bari+red_mask[i,j]*i
        barj=barj+red_mask[i,j]*j
        N=N+red_mask[i,j]

bari=bari/N
barj=barj/N

Solution

  • Another question that should have been asked here: http://answers.opencv.org/questions/

    But, let's go!

    The process that I have implemented uses mostly structural analysis (https://docs.opencv.org/3.3.1/d3/dc0/group__imgproc__shape.html#ga17ed9f5d79ae97bd4c7cf18403e1689a)

    First I got your image:

    import cv2
    import matplotlib.pyplot as plt
    import numpy as np
    from skimage import io, filters, measure, color, external
    
    sph = cv2.imread('points.png')
    ret,thresh = cv2.threshold(sph,200,255,cv2.THRESH_BINARY)
    

    enter image description here

    Then eroded and converted it for noise reduction

    kernel = np.ones((2,2),np.uint8)
    opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
    opening = cv2.cvtColor(opening, cv2.COLOR_BGR2GRAY);
    opening = cv2.convertScaleAbs(opening)
    

    enter image description here

    Then used "cv::findContours (InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())" to find all blobs.

    After that, just calculate the center of each region and do a weighted average based on the contour area. This way, I got the points centroid (X:143.4202820443726 , Y:154.56471750651224).

    enter image description here

    im2, contours, hierarchy = cv2.findContours(opening, cv2.RETR_TREE,   cv2.CHAIN_APPROX_SIMPLE)
    
    areas = []
    
    centersX = []
    centersY = []
    
    for cnt in contours:
    
        areas.append(cv2.contourArea(cnt))
    
        M = cv2.moments(cnt)
        centersX.append(int(M["m10"] / M["m00"]))
        centersY.append(int(M["m01"] / M["m00"]))
    
    
    full_areas = np.sum(areas)
    
    acc_X = 0
    acc_Y = 0
    
    for i in range(len(areas)):
    
        acc_X += centersX[i] * (areas[i]/full_areas) 
        acc_Y += centersY[i] * (areas[i]/full_areas)
    
    print (acc_X, acc_Y) 
    cv2.circle(sph, (int(acc_X), int(acc_Y)), 5, (255, 0, 0), -1)
    
    plt.imshow(sph)
    plt.show()