Python - средний цвет части изображения

Мне нужно найти средний цвет указанной части изображения. В частности, мне нужно знать, красный ли это цвет, зеленый, коричневый, черный или синий.

Изображения имеют размер 640x640, и область, которую мне нужно проанализировать, представляет собой прямоугольник между пикселями x1=20, y1=600 и пикселями x2=620, y2=640. (где x0y0 — верхний левый угол)

Я нашел несколько примеров, например этот Как найти средний цвет изображения в Python с помощью OpenCV?, но все они относятся ко всему изображению.

Как я могу получить средний цвет только определенной области?

Обязательным является то, что это должно быть как можно быстрее, менее 5 мс.

мой подход состоял бы в том, чтобы пройтись по каждому пикселю в диапазоне и сделать математику, но у меня такое чувство, что открытые CV или подобные библиотеки уже могут это сделать.

Любая помощь приветствуется.

используйте np.mean() с изображением маски для области, которую вы хотите

fmw42 11.05.2022 17:28

Можете ли вы опубликовать пример изображения, для которого вы хотите найти среднее значение для некоторого региона?

fmw42 11.05.2022 18:54
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
2
67
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вот один из способов сделать это в Python/OpenCV/Numpy.

  • Прочитать ввод
  • Порог цвета области с использованием cv2.inRange()
  • При необходимости примените морфологию для очистки небольших областей.
  • При желании получите самый большой контур и нарисуйте его белой заливкой на черном фоне в качестве финальной маски.
  • Вычислите средние значения компонента BGR для региона, используя маску
  • Определите выбранные тестовые цвета с именами цветов в виде массивов Numpy.
  • Определить массив цветовых массивов
  • Перебрать каждый массив в массиве массивов и отделить цветовые компоненты BGR от цветов.
  • Вычислить RMSE между средним цветом и тестовым цветом
  • Найдите минимальное среднеквадратичное отклонение и соответствующее ему название цвета.
  • Распечатать результаты

Вход:

Тест для желтой области:

import cv2
import numpy as np
import math

# load image
img = cv2.imread("sailboat.png")
hh, ww = img.shape[:2]

# threshold
lower = (0,200,200)
upper = (50,255,255)
thresh = cv2.inRange(img, lower, upper)

# apply open morphology
#kernel = np.ones((5,5), np.uint8)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)

# get bounding box coordinates from largest external contour
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)

# draw white filled contour on black background
mask = np.zeros((hh,ww), dtype=np.uint8)
cv2.drawContours(mask, [big_contour], 0, 255, cv2.FILLED)

# compute mean color for region
mean = cv2.mean(img, mask=mask)[0:3]
blue_mean = mean[0]
green_mean = mean[1]
red_mean = mean[2]
print("region_mean_color:", mean)
print("")

# define colors
red = np.array(["red",(0,0,255)],dtype=object)
green = np.array(["green",(0,255,0)],dtype=object)
brown = np.array(["brown",(20,70,140)],dtype=object)
black = np.array(["black",(0,0,0)],dtype=object)
blue = np.array(["blue",(255,0,0)],dtype=object)
yellow = np.array(["yellow",(0,255,255)],dtype=object)

min_rmse = 1000000
colors = np.array([red, green, brown, black, blue, yellow])
print("colorname", "rmse")
for color in colors:
    bb = color[1][0]
    gg = color[1][1]
    rr = color[1][2]
    rmse = math.sqrt( ( (red_mean-rr)*(red_mean-rr) + (green_mean-gg)*(green_mean-gg) + (blue_mean-bb)*(blue_mean-bb) )/3 )
    colorname = color[0]
    print(colorname,rmse)
    if rmse < min_rmse:
        min_rmse = rmse
        match_color = color[0]
print("")
print("match_color:", match_color)
print("rmse:", min_rmse)

# write result to disk
cv2.imwrite("sailboat_thresh.jpg", thresh)
cv2.imwrite("sailboat_morph.jpg", morph)
cv2.imwrite("sailboat_mask.jpg", mask)

# display results
cv2.imshow("THRESH", thresh)
cv2.imshow("MORPH", morph)
cv2.imshow("MASK", mask)
cv2.waitKey(0)
cv2.destroyAllWindows()

Изображение маски:

Текстовая информация:

region_mean_color: (0.0, 254.65158457426497, 254.65158457426497)

colorname rmse
red 147.02329851590747
green 147.02329851590747
brown 126.01745055239607
black 207.92214813271502
blue 254.7677759924176
yellow 0.2844800038551275

match_color: yellow
rmse: 0.2844800038551275

Люблю образец изображения?

Mark Setchell 12.05.2022 18:21

@MarkSetchell Есть ли более эффективный способ сделать это?

fmw42 12.05.2022 18:39

Я так не думаю, вы, как обычно, в деньгах, AFAIK. Моя интерпретация вопроса отличается, хотя ... Я понял, что OP просто хочет получить среднее значение прямоугольной области интереса. Я предполагаю, что вы поняли, что ему нужно среднее значение области определенного цвета, и ваш ответ делает это идеально. Итак, я думаю, нам придется подождать и посмотреть, чего на самом деле хотел ОП. У тебя уже есть мой голос?

Mark Setchell 12.05.2022 18:54

@MarkSetchell Вы правы. Я проглядел его спецификацию для региона. Теперь я вижу, что он хочет прямоугольник. Не стесняйтесь опубликовать решение для этого.

fmw42 12.05.2022 19:00

спасибо за ваш ответ, извините за поздний ответ, я получил covid. да, как указал @MarkSetchell, мне действительно нужно среднее значение для указанного региона, тем не менее ваш ответ чрезвычайно интересен и является хорошим примером для новичка, такого как я, поэтому, пожалуйста, оставьте его здесь. +1 от меня тоже..

sharkyenergy 16.05.2022 06:55
Ответ принят как подходящий

Поскольку ваша область интереса (ROI) представляет собой всего лишь простой прямоугольник, я думаю, вы просто хотите использовать нарезку Numpy для ее идентификации.

Итак, я сделал тестовое изображение зеленого цвета там, где вы хотите его измерить:

Тогда код будет выглядеть так:

import cv2
import numpy as np

# Load the image
im = cv2.imread('start.png')

# Calculate mean of green area
A = np.mean(im[600:640, 20:620], axis=(0,1))

Неудивительно, что это получает зеленый:

array([  0., 255.,   0.])

Теперь добавьте часть черной области над зеленой, чтобы уменьшить среднее значение "зелень".

B = np.mean(im[500:640, 20:620], axis=(0,1))

Это дает... "чуть меньше зелени":

aarray([ 0.        , 72.85714286,  0.        ])

Полная выборка каждого пикселя в зеленой области занимает на моем Mac 214 микросекунд, а именно:

IIn [5]: %timeit A = np.mean(im[600:640, 20:620], axis=(0,1))
214 µs ± 150 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Обратите внимание, что вы почти наверняка можете сэмплировать каждый 4-й пиксель вниз и каждый 4-й пиксель поперек, как показано ниже, за 50,6 микросекунды и все равно получить очень показательный результат:

In [11]: %timeit A = np.mean(im[500:640:4, 20:620:4], axis=(0,1))
50.6 µs ± 29.3 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Вы можете сделать каждый пиксель, который вы сэмплируете, красной точкой вот так — смотрите внимательно:

im[600:640:4, 20:620:4] = [255,0,0]


Как предложил Фред (@fmw42), будет еще быстрее, если вы замените np.mean() на cv2.mean():

Итак, 11,4 микросекунды с cv2.mean() против 214 микросекунд с np.mean():

In [22]: %timeit cv2.mean(im[600:640, 20:620])
11.4 µs ± 11.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

И 7,85 микросекунд с cv2.mean() против 50,6 микросекунд с np.mean() при выборке каждого 4-го пикселя:

In [23]: %timeit cv2.mean(im[600:640:4, 20:620:4])
7.85 µs ± 6.42 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Какова разница в скорости при использовании cv2.mean() вместо np.mean()?

fmw42 12.05.2022 19:04

Можно уменьшить размер изображения с помощью INTER_AREA до одного пикселя, а затем просто прочитать значение пикселя. Возможно, OpenCV CUDA может сделать это очень быстро?

fmw42 12.05.2022 19:24

@fmw42 Да, на мили быстрее с cv2.mean()

Mark Setchell 12.05.2022 19:33

@ fmw42 Я оставлю вас попробовать ваше предложение CUDA на вашем новом Mac ?

Mark Setchell 12.05.2022 19:34

Вы написали: «Итак, 11,4 микросекунды с cv2.mean() против 214 микросекунд с cv2.mean():». Вы имели в виду np.mean() для 214, а не cv2.mean()?

fmw42 12.05.2022 19:38

Как насчет изменения размера без CUDA? Будет ли это быстрее, чем cv2.mean()?

fmw42 12.05.2022 19:38

@ fmw42 Нет, если я это сделаю, это будет медленнее на 25 микросекунд cv2.resize(im[600:640, 20:620], (1,1), interpolation=cv2.INTER_AREA)

Mark Setchell 12.05.2022 19:45

Спасибо. Очень интересно. cv2.resize быстрее, чем Numpy. Но я подумал, что может быть так. Но медленнее, чем cv2.mean. Хорошее исследование эффективности!

fmw42 12.05.2022 19:47

@MarkSetchell извините за поздний ответ, заболел covid. Это выглядит действительно хорошо. Теперь я мог бы просто преобразовать значения RGB в HSL, чтобы получить оттенок и определить диапазон для каждого цвета...

sharkyenergy 16.05.2022 06:51

Другие вопросы по теме