Мне нужно найти средний цвет указанной части изображения. В частности, мне нужно знать, красный ли это цвет, зеленый, коричневый, черный или синий.
Изображения имеют размер 640x640, и область, которую мне нужно проанализировать, представляет собой прямоугольник между пикселями x1=20, y1=600 и пикселями x2=620, y2=640. (где x0y0 — верхний левый угол)
Я нашел несколько примеров, например этот Как найти средний цвет изображения в Python с помощью OpenCV?, но все они относятся ко всему изображению.
Как я могу получить средний цвет только определенной области?
Обязательным является то, что это должно быть как можно быстрее, менее 5 мс.
мой подход состоял бы в том, чтобы пройтись по каждому пикселю в диапазоне и сделать математику, но у меня такое чувство, что открытые CV или подобные библиотеки уже могут это сделать.
Любая помощь приветствуется.
Можете ли вы опубликовать пример изображения, для которого вы хотите найти среднее значение для некоторого региона?






Вот один из способов сделать это в Python/OpenCV/Numpy.
Вход:
Тест для желтой области:
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
Люблю образец изображения?
@MarkSetchell Есть ли более эффективный способ сделать это?
Я так не думаю, вы, как обычно, в деньгах, AFAIK. Моя интерпретация вопроса отличается, хотя ... Я понял, что OP просто хочет получить среднее значение прямоугольной области интереса. Я предполагаю, что вы поняли, что ему нужно среднее значение области определенного цвета, и ваш ответ делает это идеально. Итак, я думаю, нам придется подождать и посмотреть, чего на самом деле хотел ОП. У тебя уже есть мой голос?
@MarkSetchell Вы правы. Я проглядел его спецификацию для региона. Теперь я вижу, что он хочет прямоугольник. Не стесняйтесь опубликовать решение для этого.
спасибо за ваш ответ, извините за поздний ответ, я получил covid. да, как указал @MarkSetchell, мне действительно нужно среднее значение для указанного региона, тем не менее ваш ответ чрезвычайно интересен и является хорошим примером для новичка, такого как я, поэтому, пожалуйста, оставьте его здесь. +1 от меня тоже..
Поскольку ваша область интереса (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()?
Можно уменьшить размер изображения с помощью INTER_AREA до одного пикселя, а затем просто прочитать значение пикселя. Возможно, OpenCV CUDA может сделать это очень быстро?
@fmw42 Да, на мили быстрее с cv2.mean()
@ fmw42 Я оставлю вас попробовать ваше предложение CUDA на вашем новом Mac ?
Вы написали: «Итак, 11,4 микросекунды с cv2.mean() против 214 микросекунд с cv2.mean():». Вы имели в виду np.mean() для 214, а не cv2.mean()?
Как насчет изменения размера без CUDA? Будет ли это быстрее, чем cv2.mean()?
@ fmw42 Нет, если я это сделаю, это будет медленнее на 25 микросекунд cv2.resize(im[600:640, 20:620], (1,1), interpolation=cv2.INTER_AREA)
Спасибо. Очень интересно. cv2.resize быстрее, чем Numpy. Но я подумал, что может быть так. Но медленнее, чем cv2.mean. Хорошее исследование эффективности!
@MarkSetchell извините за поздний ответ, заболел covid. Это выглядит действительно хорошо. Теперь я мог бы просто преобразовать значения RGB в HSL, чтобы получить оттенок и определить диапазон для каждого цвета...
используйте np.mean() с изображением маски для области, которую вы хотите