1

I have a set of images ( examples ), where parts of it is covered in a red colour. I'd like to extract the percentage of the map that is red for each picture in a folder, and have done so using the code below. The problem is that the returned picture is way to low, even when I try to set the target_color variable to what paint gives as the colour in one of the maps, and a very high color_range. Any obvious error as to why this happens? Thanks in advance for any help!

import cv2
import os
import csv
import numpy as np

# Define the target color and color range (in RGB format)
target_color = (245,480,176)
color_range = 50

# Define the folder containing the images
folder = "Data/pictures/renamed/"

# Define the path to the output CSV file
output_file = "Data/pictures/area.csv"

# Open the CSV file for writing
with open(output_file, 'w', newline='') as csvfile:
    writer = csv.writer(csvfile)

    # Write the header row
    writer.writerow(["Filename", "Percentage"])

    # Loop over every file in the folder
    for filename in os.listdir(folder):

        # Make sure the file is an image
        if filename.endswith(".jpg") or filename.endswith(".jpeg") or filename.endswith(".png"):

            # Construct the full path to the image
            filepath = os.path.join(folder, filename)

            # Load the image
            img = cv2.imread(filepath)

            # Calculate the percentage of the image that is within the color range of the target color
            lower_bound = np.clip(np.array(target_color) - color_range, 0, 255)
            upper_bound = np.clip(np.array(target_color) + color_range, 0, 255)
            mask = cv2.inRange(img, lower_bound, upper_bound)
            num_pixels = img.shape[0] * img.shape[1]
            target_pixels = cv2.countNonZero(mask)
            percentage = (target_pixels / num_pixels) * 100

            # Write the filename and percentage to the CSV file
            writer.writerow([filename, percentage])
pippo1980
  • 2,181
  • 3
  • 14
  • 30
Eric Nilsen
  • 91
  • 1
  • 9
  • googling for examoples its like the error could be in lower_bound = np.clip(np.array(target_color) - color_range, 0, 255) upper_bound = np.clip(np.array(target_color) + color_range, 0, 255) – pippo1980 May 05 '23 at 14:31
  • 1
    maybe target vcolor is more in the range of (245,180,176) ??? – pippo1980 May 05 '23 at 14:44

1 Answers1

3

As hinted by @pippo1980, the 480 value in (245, 480, 176) is incorrect, because the color channels' range is [0, 255].

Other issue is that OpenCV color channels ordering is BGR (not RGB).

Replace target_color = (245,480,176) with:

target_color = (176, 180, 245)  # 176 applies the blue color channel

For testing we may save the mask to a PNG file.

Updated code sample:

import cv2
import os
import csv
import numpy as np

# Define the target color and color range (in RGB format)
#target_color = (245,480,176)
target_color = (176, 180, 245)  # OpenCV uses BGR channel ordring, and not RGB.
color_range = 20 #50  # range of 20 looks fine (at least for the first image).

# Define the folder containing the images
folder = "Data/pictures/renamed/"

# Define the path to the output CSV file
output_file = "Data/pictures/area.csv"

# Open the CSV file for writing
with open(output_file, 'w', newline='') as csvfile:
    writer = csv.writer(csvfile)

    # Write the header row
    writer.writerow(["Filename", "Percentage"])

    # Loop over every file in the folder
    for filename in os.listdir(folder):

        # Make sure the file is an image
        if filename.endswith(".jpg") or filename.endswith(".jpeg") or filename.endswith(".png"):

            # Construct the full path to the image
            filepath = os.path.join(folder, filename)

            # Load the image
            img = cv2.imread(filepath)

            # Calculate the percentage of the image that is within the color range of the target 
            lower_bound = np.clip(np.array(target_color) - color_range, 0, 255)
            upper_bound = np.clip(np.array(target_color) + color_range, 0, 255)
            mask = cv2.inRange(img, lower_bound, upper_bound)
            cv2.imwrite('mask.png', mask)  # Save mask for testing
            num_pixels = img.shape[0] * img.shape[1]
            target_pixels = cv2.countNonZero(mask)
            percentage = (target_pixels / num_pixels) * 100

            # Write the filename and percentage to the CSV file
            writer.writerow([filename, percentage])

mask.png (used for testing):
enter image description here

Walter Tross
  • 12,237
  • 2
  • 40
  • 64
Rotem
  • 30,366
  • 4
  • 32
  • 65
  • didn't know about BGR, why on Earth is that? – Walter Tross May 05 '23 at 23:11
  • oh, and wasn't a more joyful map available? – Walter Tross May 05 '23 at 23:14
  • noticed that when I was able to make it run with img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB), but they says inRange works better with hsv, my pc is slow I had to use a small piece of your image , how long does it take to proces the entire image ? any way to get a counter on it ? – pippo1980 May 05 '23 at 23:14
  • 1
    Why BGR? Check [this post](https://stackoverflow.com/questions/14556545/why-opencv-using-bgr-colour-space-instead-of-rgb). Try converting to HSV in case the desired color is slightly different between images (may adjust different range for H, S and V). The image is not joyful, but it's the first image in your link. There are multiple options of [measuring processing time](https://stackoverflow.com/questions/7370801/how-do-i-measure-elapsed-time-in-python). Simply use `t = time.process_time()` at the beginning and `elapsed_time = time.process_time() - t` at the end. – Rotem May 06 '23 at 08:33
  • thanks @Rotem! I'm not the OP and didn't notice the example images of the OP. In the end, following links, I think [this](https://retrocomputing.stackexchange.com/a/3025) is where BGR comes from. – Walter Tross May 06 '23 at 10:36
  • Quite a sad picture yes, but seems best to use the actual examples I'm working with. To answer the question, on my computer using the code in @Rotem's answer it took 2.3 seconds to get through 395 pictures – Eric Nilsen May 06 '23 at 10:42