简体   繁体   中英

Color detection using opencv-python

图片

How to detect color of balls in the given image using python-opencv?

Introduction

I will dismantle the question in the following three sections

  • Obtain the English name of a color from a RGB or Hex value
  • Locate the circles on the image
  • Obtain the English name on per circle

Obtain color name from RGB or Hex

Using the following answer:

We are almost done, except for the small change that cv2 uses BGR instead of RGB, therefore we take RGB[2] (the blue channel) to match the red channel of the webcolors.

def color_rgb_to_name(rgb: tuple[int, int, int]) -> str:
    """
    Translates an rgb value to the closest English color name known

    Args:
        rgb: The rgb value that has to be translated to the color name.

    Returns:
        The name of the colors that most closely defines the rgb value in CSS3.
    """
    min_colours = {}
    for key, name in webcolors.CSS3_HEX_TO_NAMES.items():
        r_c, g_c, b_c = webcolors.hex_to_rgb(key)
        rd = (r_c - rgb[2]) ** 2
        gd = (g_c - rgb[1]) ** 2
        bd = (b_c - rgb[0]) ** 2
        min_colours[(rd + gd + bd)] = name
    return min_colours[min(min_colours.keys())]

Which is already enough to solve the question if you only care about the colors that are used in the image.

image = cv2.imread('image.jpg')
colors = set([color_rgb_to_name(val) for val in np.unique(image.reshape(-1, 3), axis=0)])

Colors:

{'firebrick', 'cadetblue', 'peru', 'indianred', 'darkturquoise', 'cyan', 'darkviolet', 'darkorange', 'midnightblue', 'indigo', 'lightseagreen', 'mediumturquoise', 'blue', 'brown', 'chocolate', 'saddlebrown', 'mediumblue', 'darkslateblue', 'turquoise', 'blueviolet', 'sienna', 'black', 'orangered', 'slateblue'}

Notes:

  • This uses the webcolors package, but you can create your own dictionary. This gives you a higher control on the colors that you allow / disallow.

Locate the Circles

The colors that we found above are all the unique colors that are contained in the image. This is often not really what we want. Instead we want to find the color that is most commonly used inside the circle.

In order to define the color in a circle there are several sources that we can use:

Which combines to the following code:

def locate_circles(img: np.ndarray, vmin=10, vmax=30) -> np.ndarray:
    """
    Locates circles on a gray image.

    Args:
        img: a gray image with black background.
        vmin: The minimum radius value of the circles.
        vmax: The maximum radius value of the circles.

    Returns:
        A numpy array containing the center location of the circles and the radius.
    """
    img = cv2.medianBlur(img, 5)
    circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=20, minRadius=vmin, maxRadius=vmax)
    circles = np.round(circles[0, :]).astype("int")
    return circles

I added the medianBlur to increase the consistency in locating the circles, alternatively you could play a bit more with the param values or radius sizes.

Test code:

image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

for (x, y, r) in locate_circles(gray, vmin=10, vmax=30):
    print(x, y, r)

Answers:

262 66 12
186 74 12
136 60 12

Obtain the English name per circle

Now that we know where the circle is located, we can get the average color per circle and combine this with the above code obtain the final result.

The following code locates all x and y values that are inside the circle.

def coordinates(x: int, y: int, r: int, width: int, height: int) -> np.ndarray:
    """
    Locates all valid x and y coordinates inside a circle.

    Args:
        x: Center column position.
        y: Center row position.
        r: Radius of the circle.
        width: the maximum width value that is still valid (in bounds)
        height: the maximum height values that is still valid (in bounds)

    Returns:
        A numpy array with all valid x and y coordinates that fall within the circle.
    """
    indices_x = [[x + dx for dx in range(-r, r) if 0 <= x + dx < width]]
    indices_y = [[y + dy for dy in range(-r, r) if 0 <= y + dy < height]]
    return np.array(np.meshgrid(indices_x, indices_y)).reshape(2, -1)

Which can then be used to obtain the average color value per circle.

image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

for (x, y, r) in locate_circles(gray, vmin=10, vmax=30):
    columns, rows = coordinates(x, y, r, *gray.shape[:2])
    color = np.average(image[rows, columns], axis=0).astype(np.uint8)
    name = color_rgb_to_name(color)

    # Draw the information on the screen
    cv2.putText(image, name, (x - 20, y - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 0, 0), 1)

Answer:

indigo
firebrick
darkturquoise

在此处输入图片说明

TL;DR

import cv2
import numpy as np
import webcolors


def imshow(img, delay=0):
    cv2.imshow('Test', img)
    cv2.waitKey(delay)


def locate_circles(img: np.ndarray, vmin=10, vmax=30) -> np.ndarray:
    """
    https://www.tutorialspoint.com/find-circles-in-an-image-using-opencv-in-python
    https://www.pyimagesearch.com/2014/07/21/detecting-circles-images-using-opencv-hough-circles/
    https://stackoverflow.com/questions/67764821/how-to-find-the-circle-in-the-given-images-using-opencv-python-hough-circles


    Locates circles on a gray image.

    Args:
        img: a gray image with black background.
        vmin: The minimum radius value of the circles.
        vmax: The maximum radius value of the circles.

    Returns:
        A numpy array containing the center location of the circles and the radius.
    """
    img = cv2.medianBlur(img, 5)
    circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=20, minRadius=vmin, maxRadius=vmax)
    circles = np.round(circles[0, :]).astype("int")
    return circles


def coordinates(x: int, y: int, r: int, width: int, height: int) -> np.ndarray:
    """
    Locates all valid x and y coordinates inside a circle.

    Args:
        x: Center column position.
        y: Center row position.
        r: Radius of the circle.
        width: the maximum width value that is still valid (in bounds)
        height: the maximum height values that is still valid (in bounds)

    Returns:
        A numpy array with all valid x and y coordinates that fall within the circle.
    """
    indices_x = [[x + dx for dx in range(-r, r) if 0 <= x + dx < width]]
    indices_y = [[y + dy for dy in range(-r, r) if 0 <= y + dy < height]]
    return np.array(np.meshgrid(indices_x, indices_y)).reshape(2, -1)


def draw_circles(img: np.ndarray, x: int, y: int, r: int):
    """
    draw the circle in the output image, then draw a rectangle corresponding to the center of the circle

    Args:
        img: Image on which to draw the circle location and center.
        x: Center column position.
        y: Center row position.
        r: Radius of the circle.

    Modifies:
        The input image by drawing a circle on it and a rectangle on the image.
    """
    cv2.circle(img, (x, y), r, (0, 255, 0), 4)
    cv2.rectangle(img, (x - 2, y - 2), (x + 2, y + 2), (0, 128, 255), -1)


def color_rgb_to_name(rgb: tuple[int, int, int]) -> str:
    """
    https://stackoverflow.com/questions/9694165/convert-rgb-color-to-english-color-name-like-green-with-python

    Translates an rgb value to the closest English color name known

    Args:
        rgb: The rgb value that has to be translated to the color name.

    Returns:
        The name of the colors that most closely defines the rgb value in CSS3.
    """
    min_colours = {}
    for key, name in webcolors.CSS3_HEX_TO_NAMES.items():
        r_c, g_c, b_c = webcolors.hex_to_rgb(key)
        rd = (r_c - rgb[2]) ** 2
        gd = (g_c - rgb[1]) ** 2
        bd = (b_c - rgb[0]) ** 2
        min_colours[(rd + gd + bd)] = name
    return min_colours[min(min_colours.keys())]


if __name__ == '__main__':
    image = cv2.imread('image.jpg')
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    for (x, y, r) in locate_circles(gray, vmin=10, vmax=30):
        columns, rows = coordinates(x, y, r, *gray.shape[:2])
        color = np.average(image[rows, columns], axis=0).astype(np.uint8)
        name = color_rgb_to_name(color)
        print(name)

        # Draw extra information on the screen
        # draw_circles(image, x, y, r)
        cv2.putText(image, name, (x - 20, y - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 0, 0), 1)

    # show the output image
    imshow(image)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM