helpkate1991
helpkate1991

Reputation: 35

How to fill openCV contours with a color specified by its area in Python?

I have segmented and binary image of biological cells and using openCV I have extracted the areas and perimeters of the contours. I am trying to label and color with a colormap each cell according to a parameter q=perimeter/Sqrt(area) but have no idea where to even start. Essentially each cell will have a unique color according to this value.

Any help would be greatly appreciated! Here is what I have so far:

> #specify folders
filelocat = '/Users/kate/Desktop/SegmenterTest3/SegmentedCells/'

#process image
img = cv2.imread(str(filelocat) + 'Seg3.png')
image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(image, 60, 255, cv2.THRESH_BINARY)[1]
kernel = np.ones((20,20), np.uint8)
closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
#inverts image so that the objects are white (for analysis)
imagem = cv2.bitwise_not(closing)

#Find contours
cnts = cv2.findContours(imagem.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

#calculate moments and extract cell shape info
moment_dict = {}
for index, cnt in enumerate(cnts):
    moment_dict[index] = cv2.moments(cnt)
    
obj_properties = {}
for index, (key, obj_moments) in enumerate(moment_dict.items()):
    if obj_moments['m00'] > 1000 and obj_moments['m00'] < 20000:
        area = obj_moments['m00']
        cx = obj_moments['m10'] / obj_moments['m00']
        cy = obj_moments['m01'] / obj_moments['m00']
        peri = cv2.arcLength(cnts[index], True)
        q = (peri/(math.sqrt(area)))
        props = {}
        props['q']=q
        props['peri']=peri
        props['area']=area
        props['cx']=cx
        props['cy']=cy
        obj_properties[key] = props

Thank you for your help!!

Upvotes: 2

Views: 3204

Answers (1)

Basil
Basil

Reputation: 704

To solve this problem, you need to collect all the q's so that you can scale them according to the observed range of q's. You can do that with a list comprehension like so:

all_the_q = [v['q'] for k, v in obj_properties.items()]

You also need to pick some colormap. I leave that as an exercise for the reader based on suggestions in the previous comments. For a quick idea, you can see a preliminary result just by scaling your q's to 8 bits of RGB.

See the complete code below. Note that index in your moment_dict is the key in your obj_properties dictionary, so the whole enumerate construct is unnecessary. I took the liberty of dropping enumerate completely. Your filtering loop picks up the correct contour index anyway. After you select your contours based on your criteria, collect all the q's and calculate their min/max/range. Then use those to scale individual q's to whatever scale you need. In my example below, I scale it to 8-bit values of the green component. You can follow that pattern for the red and blue as you wish.

Note that in this image most of the q's are in the 4.0 - 4.25 range, with a few outliers at 5.50 (plot a histogram to see that distribution). That skews the color map, so most cells will be colored with a very similar-looking color. However, I hope this helps to get you started. I suggest applying a logarithmic function to the q's in order to "spread out" the lower end of their distribution visually.

import matplotlib.pyplot as plt
import math
import os
import cv2
import imutils
import numpy as np
# specify folders
filelocat = '/Users/kate/Desktop/SegmenterTest3/SegmentedCells/'

# process image
img = cv2.imread(os.path.join(filelocat, 'Seg3.png'))
image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(image, 60, 255, cv2.THRESH_BINARY)[1]
kernel = np.ones((20, 20), np.uint8)
closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# inverts image so that the objects are white (for analysis)
imagem = cv2.bitwise_not(closing)

# Find contours
cnts = cv2.findContours(imagem.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

# calculate moments and extract cell shape info
moment_dict = {}
for index, cnt in enumerate(cnts):
    moment_dict[index] = cv2.moments(cnt)

obj_properties = {}
for index, obj_moments in moment_dict.items():
    if obj_moments['m00'] > 1000 and obj_moments['m00'] < 20000:
        area = obj_moments['m00']
        cx = obj_moments['m10'] / obj_moments['m00']
        cy = obj_moments['m01'] / obj_moments['m00']
        peri = cv2.arcLength(cnts[index], True)
        q = (peri/(math.sqrt(area)))
        props = {}
        props['q'] = q
        props['peri'] = peri
        props['area'] = area
        props['cx'] = cx
        props['cy'] = cy
        obj_properties[index] = props

all_the_q = [v['q'] for k, v in obj_properties.items()]
min_q = min(all_the_q)
max_q = max(all_the_q)
range_q = max_q - min_q

# colormapping of q scalars to BGR values
cmap = plt.cm.get_cmap('terrain')
for index, prop in obj_properties.items():
    v = (prop['q'] - min_q) / range_q
    r, g, b, a = [int(x) for x in cmap(v, bytes=True)]
    cv2.drawContours(img, cnts, index, (b, g, r), -1)

cv2.imwrite('colored.png', img)
cv2.imshow('Biocells', img)
cv2.waitKey(10000)

Upvotes: 3

Related Questions