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

Posted

技术标签:

【中文标题】如何在 Python 中从图片的其余部分剪切绿色背景和前景?【英文标题】:How can I cut a green background with the foreground from the rest of the picture in Python? 【发布时间】:2019-01-16 12:06:17 【问题描述】:

我正在尝试用绿色背景剪切多张图像。图片的中心是绿色的,我想从图片中剪掉其余部分。问题是,我从视频中得到了图片,所以有时绿色中心更大,有时更小。我真正的任务是在结上使用 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()

【问题讨论】:

您能否添加示例图像:一张是您正在处理的图像,另一张是所需的输出? 添加您目前所做的最少代码。 是的,我会的。我是新人,所以我不知道如何上传图片,但现在你可以看到它们了 【参考方案1】:

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

【讨论】:

非常感谢!你能告诉我你是如何使用 findContours 函数的吗?我在文档中尝试过,但是 im2 图片不是我想要的...docs.opencv.org/3.3.1/d4/d73/tutorial_py_contours_begin.html 您知道在 python 中获得完整 HSV 色相值范围的简单方法吗?在 Matlab 中,范围是从 0 到 1 的两倍。如果我在opencv中转换它,范围是0-179,或者如果我使用HSV_FULL,那么范围是0-255。但我想要完整的范围而不丢失数据。【参考方案2】:

下面的代码可以满足您的需求。首先,它将图像转换为 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()

【讨论】:

谢谢!我工作得很好。但问题是图片,我不需要剪任何东西。该算法仍然切割图片,只显示没有绳索的绿色部分。有时第一个“唯一的绿色行”是底部没有任何绳索的行...... 我明白了。我更新代码以仅查看第一列和最后一列。这应该可行,因为绿色区域似乎有直边。 谢谢!它工作得很好。仍然有一些例子,它不能完美地工作。我认为是底部。例如我上传的最后一张图片。顶部切割完美,底部仍有棕色部分。右边也是。 既然您似乎了解 opencv 和 HSV:您知道获得完整 HSV 色相值范围的简单方法吗?在 Matlab 中,范围是从 0 到 1 的两倍。如果我在opencv中转换它,范围是0-179,或者如果我使用HSV_FULL,那么范围是0-255。但我想要完整的范围而不丢失数据。 我不太明白这个问题,但你在opencv的范围内是对的。我个人使用的 get full range 从 (0,0,0) 到 (179,255,255)。【参考方案3】:

第一步是从您的图像中提取绿色通道,这对于 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 数组,仅由0s 和255s 组成。现在你必须决定如何寻找切割的地方,我建议如下:

包含至少 50% 的 255 秒的最顶行像素 包含至少 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 数组,每列/行包含 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)

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

编辑:cv2.threshold 的固定行

【讨论】:

非常感谢!在带有两个参数 0 和 imgt 的 np.count_nonzeros 行中,我在那里得到一个错误:numpy.core._internal.AxisError: axis 0 is out of bounds for array of dimension 0 现在它可以工作了,但不幸的是并非一直如此。我上传了新图片,但它并不完美。但也许它没有得到一个完美的配额...... 我的方法旨在去除绿色画布上方、下方、左侧或右侧的过多非绿色区域。因此,这张图片不需要剪裁:i.stack.imgur.com/HPyYt.jpg 和这张i.stack.imgur.com/JVgUn.png 可能会被剪裁,但需要远远超过 50% 的值 我明白了。谢谢【参考方案4】:

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

假设您想要一个只有绳索的裁剪图像,该解决方案与之前的答案非常相似。但是,这次使用 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()

【讨论】:

以上是关于如何在 Python 中从图片的其余部分剪切绿色背景和前景?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 UIView 中剪切路径?

如何在 c# 中从 3x3 单应矩阵获取旋转、平移、剪切

我如何画一条线,然后在 three.js 中从它创建一个剪切平面?里面的工作示例

Camtasia如何剪辑视频,剪切视频的片段

background

如何剪切部分文本并用Python和RegEx替换每一行