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

Я пытаюсь вырезать несколько изображений с зеленым фоном. Центр изображений зеленый, и я хочу вырезать остальное из изображения. Проблема в том, что я получил изображения из видео, поэтому иногда зеленый центр больше, а иногда меньше. Моя настоящая задача - использовать К-средние для узлов, поэтому у меня, например, зеленый фон и две веревки, одна синяя и одна красная.

Я использую python с opencv, numpy и matplotlib.

Я уже обрезал центр, но иногда я режу слишком много, а иногда слишком мало. В этом примере размер моего изображения составляет 1920 x 1080.

Здесь узел остался, и осталось еще разрубить

Здесь узел находится в центре

Вот еще один пример

Вот мой желаемый результат с изображения 1

Пример 1, который работает не со всеми алгоритмами

Пример 2, который работает не со всеми алгоритмами

Пример 3, который работает не со всеми алгоритмами

Вот мой код на данный момент:

import numpy as np
import cv2
import matplotlib.pyplot as plt
from PIL import Image, ImageEnhance

img = cv2.imread('path')

print(img.shape)

imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

crop_img = imgRGB[500:500+700, 300:300+500]

plt.imshow(crop_img)
plt.show()

person Mob    schedule 16.01.2019    source источник
comment
Можете ли вы добавить примеры изображений: одно с изображением, с которым вы имеете дело, и одно с желаемым результатом?   -  person Daweo    schedule 16.01.2019
comment
добавьте минимальный код того, что вы сделали до сих пор.   -  person Ghilas BELHADJ    schedule 16.01.2019
comment
Да, я согласен. Я новичок, поэтому я не знал, как загружать фотографии, но теперь вы можете их увидеть   -  person Mob    schedule 16.01.2019


Ответы (4)


Код ниже делает то, что вы хотите. Сначала он преобразует изображение в цветовое пространство HSV, что упрощает выбор цветов. Затем создается маска, в которой выделяются только зеленые части. Убирается некоторый шум, и строки и столбцы суммируются. Наконец, новое изображение создается на основе первых / последних строк / столбцов, которые попадают в зеленую область выделения.

Поскольку во всех приведенных примерах нужно было обрезать немного лишнего верха, я добавил код для этого. Сначала я перевернул маску. Теперь вы можете использовать сумму строк / столбцов, чтобы найти строку / столбец, полностью находящуюся в зеленом выделении. Это делается для верха. На изображении ниже окно «Roi2» - это окончательное изображение.

Изменить: обновленный код после комментария ts.
Обновленный результат:

введите описание изображения здесь

Код:

import numpy as np 
import cv2

# load image
img = cv2.imread("gr.png")
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) 
# set lower and upper color limits
lower_val = (30, 0, 0)
upper_val = (65,255,255)
# Threshold the HSV image to get only green colors
# the mask has white where the original image has green
mask = cv2.inRange(hsv, lower_val, upper_val)
# remove noise
kernel =  np.ones((8,8),np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)

# sum each row and each volumn of the image
sumOfCols = np.sum(mask, axis=0)
sumOfRows = np.sum(mask, axis=1)

# Find the first and last row / column that has a sum value greater than zero, 
# which means its not all black. Store the found values in variables
for i in range(len(sumOfCols)):
    if sumOfCols[i] > 0:
        x1 = i
        print('First col: ' + str(i))
        break

for i in range(len(sumOfCols)-1,-1,-1):
    if sumOfCols[i] > 0:
        x2 = i
        print('Last col: ' + str(i))
        break

for i in range(len(sumOfRows)):
    if sumOfRows[i] > 0:
        y1 = i
        print('First row: ' + str(i))
        break

for i in range(len(sumOfRows)-1,-1,-1):
    if sumOfRows[i] > 0:
        y2 = i
        print('Last row: ' + str(i))
        break

# create a new image based on the found values
#roi = img[y1:y2,x1:x2]

#show images
#cv2.imshow("Roi", roi)



# optional: to cut off the extra part at the top:
#invert mask, all area's not green become white
mask_inv = cv2.bitwise_not(mask)
# search the first and last column top down for a green pixel and cut off at lowest common point
for i in range(mask_inv.shape[0]):
    if mask_inv[i,0] == 0 and mask_inv[i,x2] == 0:
        y1 = i
        print('First row: ' + str(i))
        break

# create a new image based on the found values
roi2 = img[y1:y2,x1:x2]

cv2.imshow("Roi2", roi2)
cv2.imwrite("img_cropped.jpg", roi2)
cv2.waitKey(0)
cv2.destroyAllWindows()
person J.D.    schedule 16.01.2019
comment
Спасибо! Я хорошо работаю. Но проблема в картинках, где не нужно ничего резать. Алгоритм по-прежнему обрезает картинку и показывает только зеленую часть без веревок. Иногда первый зеленый ряд - это нижний ряд без веревки ... - person Mob; 16.01.2019
comment
Я понимаю. Я обновляю код, чтобы смотреть только на первый и последний столбцы. Это должно сработать, потому что кажется, что у зеленой области прямые края. - person J.D.; 16.01.2019
comment
Спасибо! Работает очень хорошо. Тем не менее есть примеры, когда это не работает идеально. Думаю, это дно. Например, последнее загруженное мной изображение. Верх идеально скроен, у низа еще есть коричневая часть. И правая сторона тоже. - person Mob; 17.01.2019
comment
Поскольку вы, кажется, знакомы с opencv и HSV: знаете ли вы простой способ получить полный диапазон значений оттенка HSV? В Matlab диапазон от 0 до 1 в двойном размере. Если я конвертирую его в opencv, диапазон будет от 0 до 179, или если я использую HSV_FULL, то диапазон будет от 0 до 255. Но мне нужен полный спектр без потери данных. - person Mob; 17.01.2019
comment
Я не совсем понимаю вопрос, но вы правы в диапазонах opencv. Я бы лично использовал полный диапазон от (0,0,0) до (179,255,255). - person J.D.; 17.01.2019
comment
Обычно значение оттенка находится в диапазоне от 0 до 359, потому что это значения в градусах. Но поскольку 8Byte может отображать только числа от 0 до 255, значение оттенка, которое я получаю от функции cv2.COLOR_BGR2HSV, составляет только от 0 до 179. Я решил проблему с преобразованием изображения в float32, а затем использовал функцию cv2.COLOR_BGR2HSV_FULL - person Mob; 18.01.2019

Вы можете изменить цвет на hsv.

src = cv2.imread('path')
imgRGB = cv2.cvtColor(src, cv2.COLOR_BGR2RGB)
imgHSV = cv2.cvtColor(imgRGB, cv2.COLOR_BGR2HSV)

Затем используйте inRange, чтобы найти только зеленые значения.

lower = np.array([20, 0, 0])    #Lower values of HSV range; Green have Hue value equal 120, but in opencv Hue range is smaler [0-180]
upper = np.array([100, 255, 255])  #Uppervalues of HSV range
imgRange = cv2.inRange(imgHSV, lower, upper)

Затем используйте операции морфологии, чтобы заполнить дыры после не зеленых линий.

#kernels for morphology operations
kernel_noise = np.ones((3,3),np.uint8) #to delete small noises
kernel_dilate = np.ones((30,30),np.uint8)  #bigger kernel to fill holes after ropes
kernel_erode = np.ones((38,38),np.uint8)  #bigger kernel to delete pixels on edge that was add after dilate function

imgErode = cv2.erode(imgRange, kernel_noise, 1)
imgDilate = cv2.dilate(imgErode , kernel_dilate, 1)
imgErode = cv2.erode(imgDilate, kernel_erode, 1)

Нанесите маску на полученное изображение. Теперь вы можете легко находить углы зеленого экрана (функция findContours) или использовать в следующих шагах изображение результата

res = cv2.bitwise_and(imgRGB, imgRGB, mask = imgErode)  #put mask with green screen on src image
person Piotr Kurowski    schedule 16.01.2019
comment
Большое тебе спасибо! Вы можете показать мне, как вы используете функцию findContours? Я пробовал, как в документации, но картинка im2 не такая, как я хочу ... docs.opencv.org/3.3.1/d4/d73/tutorial_py_contours_begin.html - person Mob; 16.01.2019
comment
А знаете ли вы, как легко получить полный диапазон значений оттенка HSV в Python? В Matlab диапазон от 0 до 1 в двойном размере. Если я конвертирую его в opencv, диапазон будет от 0 до 179, или если я использую HSV_FULL, то диапазон будет от 0 до 255. Но мне нужен полный спектр без потери данных. - person Mob; 17.01.2019

Первый шаг - извлечь зеленый канал из вашего изображения, это легко с OpenCV numpy и будет создавать изображение в оттенках серого (2D-массив numpy)

import numpy as np
import cv2
img = cv2.imread('knots.png')
imgg = img[:,:,1] #extracting green channel 

Второй шаг - использование порога, что означает превращение изображения в градациях серого в двоичное (ТОЛЬКО черно-белое) изображение, для которого OpenCV имеет готовую функцию: https://docs.opencv.org/3.4.0/d7/d4d/tutorial_py_thresholding.html

imgt = cv2.threshold(imgg,127,255,cv2.THRESH_BINARY)[1]

Теперь imgt - это 2D numpy массив, состоящий только из 0s и 255s. Теперь вам нужно определиться, как вы будете искать места порезов, предлагаю следующее:

  • самая верхняя строка пикселя, содержащая не менее 50% от 255
  • самая нижняя строка пикселя, содержащая не менее 50% от 255
  • крайний левый столбец пикселя, содержащий не менее 50% от 255
  • крайний правый столбец пикселя, содержащий не менее 50% от 255

Теперь нам нужно подсчитать количество появлений в каждой строке и каждом столбце.

height = img.shape[0]
width = img.shape[1]
columns = np.apply_along_axis(np.count_nonzero,0,imgt)
rows = np.apply_along_axis(np.count_nonzero,1,imgt)

Теперь столбцы и строки представляют собой одномерные массивы numpy, содержащие количество 255s для каждого столбца / строки, зная высоту и ширину, мы могли бы получить одномерные массивы numpy значений bool следующим образом:

columns = columns>=(height*0.5)
rows = rows>=(width*0.5)

Здесь 0.5 означает 50%, упомянутые ранее, не стесняйтесь изменять это значение в соответствии с вашими потребностями. Теперь пора найти индекс первого True и последнего True в столбцах и строках.

icolumns = np.argwhere(columns)
irows = np.argwhere(rows)
leftcut = int(min(icolumns))
rightcut = int(max(icolumns))
topcut = int(min(irows))
bottomcut = int(max(irows))

Используя argwhere, я получил множество одномерных массивов индексов Trues, а затем нашел самый низкий и самый большой. Наконец, вы можете вырезать изображение и сохранить его.

imgout = img[topcut:bottomcut,leftcut:rightcut]
cv2.imwrite('out.png',imgout)

Есть два места, которые могут потребовать настройки:% от 255s (в моем примере 50%) и пороговое значение (127 в cv2.threshold).

РЕДАКТИРОВАТЬ: фиксированная линия с cv2.threshold

person Daweo    schedule 16.01.2019
comment
Большое тебе спасибо! В строке с np.count_nonzeros с двумя аргументами 0 и imgt я получаю сообщение об ошибке: numpy.core._internal.AxisError: ось 0 выходит за границы для массива размерности 0 - person Mob; 16.01.2019
comment
Сейчас это работает, но, к сожалению, не всегда. Я загружаю новые картинки, где они работают не идеально. Но, возможно, у него нет идеальной квоты ... - person Mob; 16.01.2019
comment
Мой метод направлен на удаление лишних НЕ-зеленых областей, которые находятся выше, ниже, слева или справа от зеленого холста. Таким образом, это изображение не нуждалось в вырезании: i.stack.imgur.com/HPyYt.jpg и этот i.stack.imgur.com/JVgUn.png, возможно, может быть обрезан , но потребуется гораздо больше, чем 50% стоимости - person Daweo; 16.01.2019
comment
Я понимаю. Спасибо - person Mob; 17.01.2019

Основываясь на новых изображениях, которые вы добавили, я предполагаю, что вы не только хотите вырезать незеленые части, как вы просили, но и что вам нужна рамка меньшего размера вокруг веревок / узла. Это правильно? Если нет, вам следует загрузить видео и немного подробнее описать цель / цель кадрирования, чтобы мы могли лучше вам помочь.

Предполагая, что вам нужно обрезанное изображение только с веревками, решение очень похоже на предыдущий ответ. Однако на этот раз красный и синий цвета веревок выбраны с помощью HSV. Изображение обрезается на основе полученной маски. Если вы хотите, чтобы изображение было несколько больше, чем просто веревки, вы можете добавить дополнительные поля, но обязательно учтите / проверьте край изображения.

Примечание: приведенный ниже код работает для изображений с полностью зеленым фоном, поэтому я предлагаю вам объединить его с одним из решений, которое выделяет только зеленую область. Я проверил это для всех ваших изображений следующим образом: я взял код из своего другого ответа, поместил его в функцию и добавил return roi2 в конце. Этот вывод передается во вторую функцию, которая содержит приведенный ниже код. Все изображения обработаны успешно.

Результат:

введите описание изображения здесь

Код:

import numpy as np 
import cv2

# load image
img = cv2.imread("image.JPG")
# blue
lower_val_blue = (110, 0, 0)
upper_val_blue = (179,255,155)
# red
lower_val_red = (0, 0, 150)
upper_val_red = (10,255,255)
# Threshold the HSV image
mask_blue = cv2.inRange(img, lower_val_blue, upper_val_blue)
mask_red = cv2.inRange(img, lower_val_red, upper_val_red)
# combine masks
mask_total = cv2.bitwise_or(mask_blue,mask_red)

# remove noise
kernel =  np.ones((8,8),np.uint8)
mask_total = cv2.morphologyEx(mask_total, cv2.MORPH_CLOSE, kernel)

# sum each row and each volumn of the mask
sumOfCols = np.sum(mask_total, axis=0)
sumOfRows = np.sum(mask_total, axis=1)

# Find the first and last row / column that has a sum value greater than zero, 
# which means its not all black. Store the found values in variables
for i in range(len(sumOfCols)):
    if sumOfCols[i] > 0:
        x1 = i
        print('First col: ' + str(i))
        break

for i in range(len(sumOfCols)-1,-1,-1):
    if sumOfCols[i] > 0:
        x2 = i
        print('Last col: ' + str(i))
        break

for i in range(len(sumOfRows)):
    if sumOfRows[i] > 0:
        y1 = i
        print('First row: ' + str(i))
        break

for i in range(len(sumOfRows)-1,-1,-1):
    if sumOfRows[i] > 0:
        y2 = i
        print('Last row: ' + str(i))
        break

# create a new image based on the found values
roi = img[y1:y2,x1:x2]

#show image
cv2.imshow("Result", roi)
cv2.imshow("Image", img)

cv2.waitKey(0)
cv2.destroyAllWindows()
person J.D.    schedule 19.01.2019