Python:如何从图像中剪切具有特定颜色的区域(OpenCV,Numpy)

11
所以我一直在尝试编写一个Python脚本,它以图像作为输入,然后剪切具有特定背景颜色的矩形。然而,对我的编码技能造成问题的是,矩形在每个图像上不是固定位置(位置将是随机的)。
我真的不明白如何管理numpy函数。我也读到了一些关于OpenCV的东西,但对此完全陌生。到目前为止,我只是通过“.crop”函数裁剪图像,但然后我将不得不使用固定值。
这是输入图像可能看起来的样子,现在我想检测黄色矩形的位置,然后将图像裁剪到其大小。
感谢您的帮助,提前致谢。

An example of how the picture could look (initial example)

Updated image, how it really looks

编辑:@MarkSetchell的方法效果不错,但是对于另一张测试图片发现了一个问题。另一张图片的问题在于图片顶部和底部有两个相同颜色的小像素,导致裁剪时出现错误或者裁剪效果不佳。


1
我认为这个问题太过宽泛,不适合在Stack Overflow上讨论,很可能会被视为不相关的话题。请参考[提问指南]和[帮助/关于主题]。 - AMC
1
我已经更新了我的答案 - 请再看一遍。 - Mark Setchell
感谢您!非常感谢。 - Keanu
3个回答

9

更新的答案

我已经更新了我的答案,以应对与黄色框相同颜色的嘈杂离群像素点。这通过先在图像上运行3x3中值滤波器来消除斑点:

#!/usr/bin/env python3

import numpy as np
from PIL import Image, ImageFilter

# Open image and make into Numpy array
im = Image.open('image.png').convert('RGB')
na = np.array(im)
orig = na.copy()    # Save original

# Median filter to remove outliers
im = im.filter(ImageFilter.MedianFilter(3))

# Find X,Y coordinates of all yellow pixels
yellowY, yellowX = np.where(np.all(na==[247,213,83],axis=2))

top, bottom = yellowY[0], yellowY[-1]
left, right = yellowX[0], yellowX[-1]
print(top,bottom,left,right)

# Extract Region of Interest from unblurred original
ROI = orig[top:bottom, left:right]

Image.fromarray(ROI).save('result.png')

原始回答

好的,你的黄色颜色是rgb(247,213,83),所以我们想要找到所有黄色像素的X,Y坐标:

#!/usr/bin/env python3

from PIL import Image
import numpy as np

# Open image and make into Numpy array
im = Image.open('image.png').convert('RGB')
na = np.array(im)

# Find X,Y coordinates of all yellow pixels
yellowY, yellowX = np.where(np.all(na==[247,213,83],axis=2))

# Find first and last row containing yellow pixels
top, bottom = yellowY[0], yellowY[-1]
# Find first and last column containing yellow pixels
left, right = yellowX[0], yellowX[-1]

# Extract Region of Interest
ROI=na[top:bottom, left:right]

Image.fromarray(ROI).save('result.png')

enter image description here


你可以在终端中使用ImageMagick来完成完全相同的操作:
# Get trim box of yellow pixels
trim=$(magick image.png -fill black +opaque "rgb(247,213,83)" -format %@ info:)

# Check how it looks
echo $trim
251x109+101+220

# Crop image to trim box and save as "ROI.png"
magick image.png -crop "$trim" ROI.png

如果仍在使用 ImageMagick v6 而不是 v7,请将 magick 替换为 convert

感谢您的帮助!我已经尝试了一下,它非常好用——至少对于我上传的示例图片而言。但是我用其他图片进行测试时,出现了一个“ValueError: tile cannot extend outside image”的错误,该错误与“Image.fromarray(ROI).save('result.png')”有关。 - Keanu
2
@Karlo,你不应该期望在Stack Overflow上找到你没有发布的情况下的答案。问题应该是具体而不是泛泛而谈的。你可以尝试发布更多的图片,但一定要展示你解决问题的努力(发布你的代码)。 - Rotem
真话,应该想到了-抱歉。我目前正在尝试找出问题所在,因为@MarkSetchell的代码对于我的大多数测试图像确实有效,但有一些不起作用。我认为这是因为其他具有相同颜色的小像素。 - Keanu

5
我看到的是顶部和两侧有深灰色和浅灰色区域,一个白色区域以及一个带有灰色三角形的黄色矩形。
我建议的第一步是将图像从RGB颜色空间转换为HSV颜色空间。在HSV空间中,S颜色通道是“颜色饱和度通道”。所有无色(灰色/黑色/白色)都是零,黄色像素在S通道中高于零。
接下来的步骤:
  • 对S通道应用阈值(将其转换为二进制图像)。黄色像素变为255,其他像素变为零。
  • 在阈值图像中查找轮廓(仅查找外部轮廓-即矩形轮廓)。
  • 反转矩形内部像素的极性。灰色三角形变为255,其他像素变为零。
  • 在阈值图像中查找轮廓-查找灰色三角形。

这里是代码:
import numpy as np
import cv2

# Read input image
img = cv2.imread('img.png')

# Convert from BGR to HSV color space
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# Get the saturation plane - all black/white/gray pixels are zero, and colored pixels are above zero.
s = hsv[:, :, 1]

# Apply threshold on s - use automatic threshold algorithm (use THRESH_OTSU).
ret, thresh = cv2.threshold(s, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)

# Find contours in thresh (find only the outer contour - only the rectangle).
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2]  # [-2] indexing takes return value before last (due to OpenCV compatibility issues).

# Mark rectangle with green line
cv2.drawContours(img, contours, -1, (0, 255, 0), 2)

# Assume there is only one contour, get the bounding rectangle of the contour.
x, y, w, h = cv2.boundingRect(contours[0])

# Invert polarity of the pixels inside the rectangle (on thresh image).
thresh[y:y+h, x:x+w] = 255 - thresh[y:y+h, x:x+w]

# Find contours in thresh (find the triangles).
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2]  # [-2] indexing takes return value before last (due to OpenCV compatibility issues).

# Iterate triangle contours
for c in contours:
    if cv2.contourArea(c) > 4:  #  Ignore very small contours
        # Mark triangle with blue line
        cv2.drawContours(img, [c], -1, (255, 0, 0), 2)

# Show result (for testing).
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

在HSV颜色空间中的S通道:
enter image description here

thresh - 阈值后的S:
enter image description here

矩形取反极性后的thresh
enter image description here

结果(矩形和三角形标记出来了):
enter image description here


更新:

如果背景上有一些彩色的点,您可以裁剪最大的彩色轮廓:

import cv2
import imutils  # https://pypi.org/project/imutils/

# Read input image
img = cv2.imread('img2.png')

# Convert from BGR to HSV color space
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# Get the saturation plane - all black/white/gray pixels are zero, and colored pixels are above zero.
s = hsv[:, :, 1]

cv2.imwrite('s.png', s)

# Apply threshold on s - use automatic threshold algorithm (use THRESH_OTSU).
ret, thresh = cv2.threshold(s, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)

# Find contours
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnts = imutils.grab_contours(cnts) 

# Find the contour with the maximum area.
c = max(cnts, key=cv2.contourArea)

# Get bounding rectangle
x, y, w, h = cv2.boundingRect(c)

# Crop the bounding rectangle out of img
out = img[y:y+h, x:x+w, :].copy()

结果:

结果:
在此输入图片描述


1
感谢您的帮助!非常感激您的努力。 - Keanu
2
你可以裁剪最大的彩色轮廓。我更新了我的帖子。 - Rotem

2

谢谢。不幸的是,我没有成功。 - Keanu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接