beam42
beam42

Reputation: 13

OpenCV: per color pixel count in irregularly shaped area?

Say I have a multicolored map of the United States, and I want to know how many pixels in a particular state (Nevada, for example) are purple, how many are green, and how many are white. Can I do this with OpenCV?

I've tried to tackle this by turning each state on an uncolored "basemap" into its own contour using cv2.drawContours, then overlaying the two images (This is where things start to feel wrong).

I know that I can then use the following:

Nevada = contours[21]
area = cv2.contourArea(Nevada)
print(area) 

to print the total number of pixels in a given state/contour, but I have no idea whether a similar function exists that will show me the number of pixels of a certain color within that state/contour. Is there a way to do this? Any guidance would be much appreciated.

Upvotes: 1

Views: 831

Answers (1)

fmw42
fmw42

Reputation: 53089

Here is one way to do that in Python/OpenCV.

  • Read the basemap image as grayscale and threshold
  • Read the map image
  • Get all the contours from the thresholded baseman image
  • Define the colors
  • Loop over the contours and select contours in a specified range of areas (Adjust the lower bound to get more state contours)
  • For each acceptable contour, draw it filled in white on a black image
  • Mask the map image to show only the given contour
  • Use numpy to sum all the colored pixels in the masked map image
  • Print the index and color counts
  • Optionally view each masked map region
  • Get the centroid of the contour
  • Draw the index number at the centroid on the map image
  • At the end, after the loop, save the labeled map image


Base Map: enter image description here

Map: enter image description here

import cv2
import numpy as np

# read basemap image as grayscale
basemap = cv2.imread('basemap.png', cv2.COLOR_BGR2GRAY)

# threshold basemap and make single channel
thresh = cv2.threshold(basemap, 200, 255, cv2.THRESH_BINARY)[1]
thresh = thresh[:,:,0]

# read map
map = cv2.imread('map.png')

# define colors
red = (255,0,255)
green = (125,196,147)
blue = (232,197,159)
orange = (102,102,224)


# get contours
contours = cv2.findContours(thresh, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]

# print table header
print('{:^15}{:^15}{:^15}{:^15}{:^15}'.format("index", "red_count", "green_count", "blue_count", "orange_count"))

# initialize labeled map
map_labeled = map.copy()

# loop over index and corresponding contour (cntr)
for index, cntr in enumerate(contours):
    # filter on area
    area = cv2.contourArea(cntr)
    if area > 1000 and area < 20000 :           
        # draw contours on black image
        mask = np.zeros_like(basemap)
        cv2.drawContours(mask, contours, index, (255,255,255), cv2.FILLED)

        # copy map
        map_masked = map.copy()

        # do bitwise_and between copied map and mask for a given contour
        map_masked = cv2.bitwise_and(map_masked, mask)

        # get counts for given contour
        red_count = np.sum(np.where((map_masked == red).all(axis=2)))
        green_count = np.sum(np.where((map_masked == green).all(axis=2)))
        blue_count = np.sum(np.where((map_masked == blue).all(axis=2)))
        orange_count = np.sum(np.where((map_masked == orange).all(axis=2)))
        # print index and counts
        print('{:^15}{:^15}{:^15}{:^15}{:^15}'.format(index, red_count, green_count, blue_count, orange_count))

        # get centroid of contour for label placement
        M = cv2.moments(cntr)
        cx = int(M["m10"] / M["m00"])
        cy = int(M["m01"] / M["m00"])       

        # label map with index
        map_labeled = cv2.putText(map_labeled, str(index), (cx,cy), cv2.FONT_HERSHEY_PLAIN, 0.75, (0,0,0))

        # view each state region from map isolated by mask from contour
        # remove the following 3 lines if you do not want to hit the space key for each contour
        cv2.imshow("index", map_masked)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

# save labeled map
cv2.imwrite('map_labeled.png', map_labeled)


Labeled Map: enter image description here

Terminal Listing Output:

enter image description here

Upvotes: 1

Related Questions