Calculate the area of the masks (in pixels) in grey scale images with python

The following coins figure is a greyscale image with different masks (shown in different colors). Is there a way to calculate the area of these masks (in pixels) for each coin in greyscale images with python.

enter image description here

Labels for the coin masks

{"classes": [{"title": "coin1", "shape": "polygon", "color": "#BE5C3C", "geometry_config": {}}, {"title": "coin2", "shape": "polygon", "color": "#961D82", "geometry_config": {}}, {"title": "coin3", "shape": "polygon", "color": "#C1BB5C", "geometry_config": {}}, {"title": "coin4", "shape": "polygon", "color": "#D0021B", "geometry_config": {}}, {"title": "coin5", "shape": "polygon", "color": "#417505", "geometry_config": {}}], "tags": []}

Annotation for the coin masks

{"tags": [], "description": "", "objects": [{"description": "", "bitmap": null, "tags": [], "classTitle": "coin1", "points": {"exterior": [[59.0, 85.0], [65.0, 70.0], [76.0, 63.0], [89.0, 61.0], [105.0, 63.0], [116.0, 78.0], [118.0, 98.0], [103.0, 117.0], [80.0, 118.0], [61.0, 103.0]], "interior": []}}, {"description": "", "bitmap": null, "tags": [], "classTitle": "coin2", "points": {"exterior": [[103.0, 43.0], [104.0, 28.0], [118.0, 17.0], [136.0, 16.0], [151.0, 22.0], [161.0, 34.0], [159.0, 53.0], [150.0, 68.0], [127.0, 73.0], [109.0, 62.0], [105.0, 54.0]], "interior": []}}, {"description": "", "bitmap": null, "tags": [], "classTitle": "coin3", "points": {"exterior": [[112.0, 143.0], [121.0, 129.0], [148.0, 124.0], [165.0, 141.0], [166.0, 160.0], [159.0, 175.0], [138.0, 184.0], [119.0, 174.0], [112.0, 161.0]], "interior": []}}, {"description": "", "bitmap": null, "tags": [], "classTitle": "coin4", "points": {"exterior": [[44.0, 137.0], [69.0, 134.0], [81.0, 152.0], [80.0, 171.0], [64.0, 181.0], [46.0, 178.0], [37.0, 168.0], [33.0, 151.0]], "interior": []}}, {"description": "", "bitmap": null, "tags": [], "classTitle": "coin5", "points": {"exterior": [[183.0, 117.0], [189.0, 100.0], [201.0, 93.0], [220.0, 98.0], [226.0, 111.0], [223.0, 126.0], [211.0, 136.0], [194.0, 135.0]], "interior": []}}], "size": {"height": 206, "width": 244}}

Here's a method using OpenCV. We Otsu's threshold to obtain a binary image which leaves us with the desired foreground objects in white and the background is black. From here we use cv2.countNonZero() which returns the number of white pixels on the mask


To find the number of white pixels

pixels = cv2.countNonZero(thresh) # OR
# pixels = len(np.column_stack(np.where(thresh > 0)))

pixels 198580

We could also calculate the pixel to total image area percent ratio

image_area = image.shape[0] * image.shape[1]
area_ratio = (pixels / image_area) * 100

area ratio 24.43351838727459

import cv2
import numpy as np

image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray,0,255,cv2.THRESH_OTSU + cv2.THRESH_BINARY)[1]
pixels = cv2.countNonZero(thresh)
# pixels = len(np.column_stack(np.where(thresh > 0)))

image_area = image.shape[0] * image.shape[1]
area_ratio = (pixels / image_area) * 100

print('pixels', pixels)
print('area ratio', area_ratio)
cv2.imshow('thresh', thresh)
cv2.waitKey(0)

If you wanted to get individual coin pixel areas then you can iterate through each contour. The total area should be the same

import cv2
import numpy as np

image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray,0,255,cv2.THRESH_OTSU + cv2.THRESH_BINARY)[1]
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
total = 0

for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    mask = np.zeros(image.shape, dtype=np.uint8)
    cv2.fillPoly(mask, [c], [255,255,255])
    mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
    pixels = cv2.countNonZero(mask)
    total += pixels
    cv2.putText(image, '{}'.format(pixels), (x,y - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255,255,255), 2)

print(total)
cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.waitKey(0)