将轮廓叠加到新图像上

Overlay contour onto new image

我正在从图像中提取 object 并将其叠加到背景上以生成计算机视觉任务的场景。每次生成场景时都会添加随机定位、缩放、旋转和过滤。

我面临的问题是,除非 object 是一个完美的垂直矩形,否则我在叠加图像时会出现黑色边框。

如何在叠加时使轮廓外的区域透明?

在下面找到一些示例代码和输出。

import cv2
from matplotlib import pyplot as plt
from skimage import io

#Import Card
card = io.imread('https://i.ebayimg.com/thumbs/images/g/BZoAAOSwj0RfkZuD/s-l225.jpg') 
plt.imshow(card)

#Import Background
background = io.imread('https://www.robots.ox.ac.uk/~vgg/data/dtd/images/cracked/cracked_0049.jpg') 
plt.imshow(background)

#Find contours and extract card
edged=cv2.Canny(card,30,200)
contours, hierarchy=cv2.findContours(edged,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
contour = [c for c in contours if c.size >=1300][0]

#Crop out background
x,y,w,h = cv2.boundingRect(contour)
card_cropped = card[y:y+h, x:x+w]

#Resize
card_cropped_resized = cv2.resize(card_cropped, (int(card_cropped.shape[1]/0.8), int(card_cropped.shape[0]/0.8)))

#Generate Scene
card_cropped_resized_grayscale = cv2.cvtColor(card_cropped_resized, cv2.COLOR_RGB2GRAY)
background_grayscale = cv2.cvtColor(background, cv2.COLOR_RGB2GRAY)

h, w = card_cropped_resized_grayscale.shape[:2]
hh, ww = background_grayscale.shape[:2]

yoff = round(hh/4)
xoff = round(ww/4)

xMin = xoff
yMin = yoff
xMax = xoff+w
yMax = yoff+h

scene = background_grayscale.copy()
scene[yMin:yMax, xMin:xMax] = card_cropped_resized_grayscale

plt.imshow(cv2.cvtColor(scene,cv2.COLOR_GRAY2RGB))

我在尝试你的代码时发现你的轮廓有误。所以我做了一些改变,最值得注意的是,使用阈值而不是 Canny 边缘作为轮廓的基础。为了去除黑色边框,我只是在获取轮廓之前腐蚀了阈值图像。您可以通过简单地编辑您的 x、y、w、h 来删除周围的几个像素来做同样的事情。你得到黑色边框的原因是你的 Canny 边缘图像有噪声,这使得轮廓更大。但主要是因为卡片的形状不是矩形,而是顶部比底部窄。所以矩形边界框将是顶部或底部中最大的尺寸,黑色是实际区域和边界框之间的部分。在 Canny 边缘操作之后添加中值滤波器会改善该部分,但不会改善形状问题。

输入:

import cv2
from matplotlib import pyplot as plt
from skimage import io

#Import Card
card = io.imread('https://i.ebayimg.com/thumbs/images/g/BZoAAOSwj0RfkZuD/s-l225.jpg') 
plt.imshow(card)
#plt.show()

#Import Background
background = io.imread('https://www.robots.ox.ac.uk/~vgg/data/dtd/images/cracked/cracked_0049.jpg') 
plt.imshow(background)
#plt.show()

#Find contours and extract card
#edged=cv2.Canny(card,30,200)
edged = cv2.cvtColor(card, cv2.COLOR_BGR2GRAY)
edged = cv2.threshold(edged, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT , (5,5))
edged = cv2.morphologyEx(edged, cv2.MORPH_ERODE, kernel)
plt.imshow(edged)
#plt.show()


#contours, hierarchy=cv2.findContours(edged,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
#contour = [c for c in contours if c.size >=1300][0]
contours = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
contour = max(contours, key=cv2.contourArea)


#Crop out background
x,y,w,h = cv2.boundingRect(contour)
card_cropped = card[y:y+h, x:x+w]

#Resize
card_cropped_resized = cv2.resize(card_cropped, (int(card_cropped.shape[1]/0.8), int(card_cropped.shape[0]/0.8)))

#Generate Scene
card_cropped_resized_grayscale = cv2.cvtColor(card_cropped_resized, cv2.COLOR_RGB2GRAY)
background_grayscale = cv2.cvtColor(background, cv2.COLOR_RGB2GRAY)

h, w = card_cropped_resized_grayscale.shape[:2]
hh, ww = background_grayscale.shape[:2]

yoff = round(hh/4)
xoff = round(ww/4)

xMin = xoff
yMin = yoff
xMax = xoff+w
yMax = yoff+h

scene = background_grayscale.copy()
scene[yMin:yMax, xMin:xMax] = card_cropped_resized_grayscale

plt.imshow(cv2.cvtColor(scene,cv2.COLOR_GRAY2RGB))
plt.show()

cv2.imwrite("scene_grayscale.jpg",scene)

结果:

考虑到您的担忧,这里有一个更强大的解决方案,在 Python/OpenCV 中使用蒙版合成。我们从卡片的轮廓创建一个填充蒙版,并将卡片和蒙版裁剪到轮廓的边界框。接下来我们将卡片插入背景中所需的位置。然后我们将蒙版插入到与背景大小相同的黑色背景图像中。然后我们将原来的背景和新的背景与插入的卡片进行合成,使用遮罩来控制使用哪个。

背景:

卡片:

import cv2
import numpy as np
from matplotlib import pyplot as plt
from skimage import io    

#Import Card
card = io.imread('https://i.ebayimg.com/thumbs/images/g/BZoAAOSwj0RfkZuD/s-l225.jpg') 

# Apply median filter to card
card_median = cv2.medianBlur(card, 3)

#Import Background
background = io.imread('https://www.robots.ox.ac.uk/~vgg/data/dtd/images/cracked/cracked_0049.jpg') 
hh, ww = background.shape[:2]

#Find edges of card
edged=cv2.Canny(card_median,30,200)
plt.imshow(edged)
plt.show()

# get largest contour
contours = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
cntr = max(contours, key=cv2.contourArea)

# draw white filled contour on black background the size of card 
card_mask = np.zeros_like(card)
cv2.drawContours(card_mask, [cntr], 0, (255,255,255), -1)

# erode contour just a little to ensure contour encloses no black from outside card
kernel = cv2.getStructuringElement(cv2.MORPH_RECT , (3,3))
card_mask = cv2.morphologyEx(card_mask, cv2.MORPH_ERODE, kernel)

#Get bounding box
x,y,w,h = cv2.boundingRect(cntr)

# crop card and card_mask
card_cropped = card[y:y+h, x:x+w]
card_mask_cropped = card_mask[y:y+h, x:x+w]

# define insert location and size
yoff = round(hh/4)
xoff = round(ww/4)

xMin = xoff
yMin = yoff
xMax = xoff+w
yMax = yoff+h

# insert cropped card into background
scene = background.copy()
scene[yMin:yMax, xMin:xMax] = card_cropped

# insert card_mask into black background the size of background image (and make single channel)
mask = np.zeros_like(background)
mask[yMin:yMax, xMin:xMax] = card_mask_cropped
mask = mask[:,:,0]

#composite scene with background using mask
scene_masked = cv2.bitwise_and(scene, scene, mask=mask)
background_masked = cv2.bitwise_and(background, background, mask=(255-mask))
result = cv2.add(scene_masked,background_masked)

# show results
plt.imshow(scene)
plt.show()
plt.imshow(mask, cmap='gray')
plt.show()
plt.imshow(result)
plt.show()

# save results
result = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
cv2.imwrite("card_composite.jpg",result)

结果:

这是使用这张卡片图片的结果:

卡片:

结果: