如何从Python中的图片的其余部分剪切出绿色背景和前景?

How can I cut a green background with the foreground from the rest of the picture in Python?

我正在尝试用绿色背景剪切多张图片。图片的中心是绿色的,我想把图片的其余部分剪掉。问题是,我从视频中获取图片,所以有时绿色中心较大,有时较小。我真正的任务是在结上使用 K-Means,因此我有例如绿色背景和两根绳子,一根蓝色和一根红色。

我将 python 与 opencv、numpy 和 matplotlib 一起使用。

我已经切了中心,但有时我切得太多,有时我切得太少。在这个例子中我的图像大小是 1920 x 1080。

Here the knot is left and there is more to cut

Here the knot is in the center

Here is another example

Here is my desired output from picture 1

Example 1 which doesn't work with all algorithm

Example 2 which doesn't work with all algorithm

Example 3 which doesn't work with all algorithm

到目前为止,这是我的代码:

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()

第一步是从图像中提取绿色通道,使用 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 是二维 numpy 数组,仅由 0255 组成。现在你必须决定如何寻找切割的地方,我建议如下:

  • 最上面一行像素至少包含 50% 的 255s
  • 包含至少 50% 的 255 像素的最底行像素
  • 最左边的像素列至少包含 50% 的 255s
  • 最右边的像素列至少包含 50% 的 255s

现在我们必须计算每一行和每一列中出现的次数

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 数组,每个 column/row 包含 255s 个数,知道高度和宽度我们可以通过以下方式获得 bool 值的一维 numpy 数组:

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 的 numpy 一维索引数组,然后找到了最低和最高的。最后你可以剪辑你的图像并保存它

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

有两个地方可能需要调整:255 的百分比(在我的示例中为 50%)和阈值(cv2.threshold 中的127)。

编辑:固定行 cv2.threshold

您可以将颜色更改为 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

下面的代码可以满足您的需求。首先它将图像转换为 HSV 色彩空间,这使得选择颜色更加容易。接下来制作一个蒙版,其中仅选择绿色部分。去除了一些噪声,并对行和列求和。最后根据落在绿色选择中的 first/last rows/cols 创建一个新图像。

由于在所有提供的示例中需要裁掉顶部的一些额外部分,所以我添加了代码来执行此操作。首先我把面具倒过来了。现在您可以使用 rows/cols 的总和来找到完全在绿色选择范围内的 row/col。它是为顶部完成的。在下面的图像中,window '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()

根据您添加的新图像,我假设您不仅要按照您的要求剪掉非绿色部分,而且还希望 ropes/knot 周围的边框更小。那是对的吗?如果没有,您应该上传视频并描述一下裁剪的purpose/goal,以便我们更好地帮助您。

假设您想要一个只有绳索的裁剪图像,解决方案与之前的答案非常相似。但是,这次使用 HSV 选择绳索的红色和蓝色。根据生成的蒙版裁剪图像。如果您希望图片比绳子大一些,您可以添加额外的边距 - 但请确保图片的边缘 account/check。

注意:下面的代码适用于具有全绿色背景的图像,因此我建议您将其与仅选择绿色区域的解决方案之一结合使用。我对您的所有图像进行了如下测试:我从其他答案中获取代码,将其放入函数中并在末尾添加 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()