使用Sobel边缘检测去除图像背景
我有一堆代表硬币的图片,其中一些背景很杂乱(比如有字母或者背景颜色不同)。我想把每个硬币的背景去掉,只留下硬币本身,但我用OpenCV的cv2.findContours
函数时,无法只检测到硬币的主要轮廓,反而会把其他部分也删掉,或者留下背景的一些杂音。
以下是我使用的代码,整个过程是这样的:
- 从字节对象读取图片,转成numpy数组。
- 把它解码成彩色图片。
- 转换成灰度图像。
- 加上高斯模糊来去除噪声。
- 通过应用sobel滤波器来检测图像中的边缘,使用
edgedetect()
函数。这里计算了X和Y方向的sobel,并通过Otsu阈值处理转换成二值图像。 - 计算图像的平均值,把低于这个值的部分设为零,以去除噪声。
- 找到显著的轮廓(
findSignificantContours()
)。 - 根据轮廓创建一个掩码,反转并去掉它以获取背景。
- 把掩码设为255,以在原始图像中去掉背景。
import cv2
import numpy as np
from google.colab.patches import cv2_imshow
def edgedetect(channel):
sobelX = cv2.Sobel(channel, cv2.CV_64F, 1, 0, ksize = 3, scale = 1)
sobelY = cv2.Sobel(channel, cv2.CV_64F, 0, 1, ksize = 3, scale = 1)
sobel = np.hypot(sobelX, sobelY)
sobel = cv2.convertScaleAbs(sobel)
sobel[sobel > 255] = 255 # Some values seem to go above 255. However RGB channels has to be within 0-255
_, sobel_binary = cv2.threshold(sobel, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
return cv2.bitwise_not(sobel_binary)
def findSignificantContours (img, edgeImg):
print(f'edgeimg:')
cv2_imshow(edgeImg)
contours, hierarchy = cv2.findContours(edgeImg, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Find level 1 contours
level1 = []
for i, tupl in enumerate(hierarchy[0]):
# Each array is in format (Next, Prev, First child, Parent)
# Filter the ones without parent
if tupl[3] == -1:
tupl = np.insert(tupl, 0, [i])
level1.append(tupl)
# From among them, find the contours with large surface area.
significant = []
tooSmall = edgeImg.size * 5 / 100 # If contour isn't covering 5% of total area of image then it probably is too small
for tupl in level1:
contour = contours[tupl[0]]
area = cv2.contourArea(contour)
if area > tooSmall:
significant.append([contour, area])
# Draw the contour on the original image
cv2.drawContours(img, [contour], 0, (0, 255, 0), 2, cv2.LINE_8)
significant.sort(key = lambda x: x[1])
return [x[0] for x in significant]
def remove_background(bytes_data):
# Read image.
image = np.asarray(bytearray(bytes_data.read()), dtype = "uint8")
img = cv2.imdecode(image, cv2.IMREAD_COLOR)
print(f'Original:')
cv2_imshow(img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(f'Gray:')
cv2_imshow(gray)
blurred_gray = cv2.GaussianBlur(gray, (3, 3), 0) # Remove noise.
print(f'Blurred Gray:')
cv2_imshow(blurred_gray)
edgeImg = np.max( np.array([edgedetect(blurred_gray[:, :])]), axis = 0)
mean = np.mean(edgeImg)
# Zero any value that is less than mean. This reduces a lot of noise.
edgeImg[edgeImg <= mean] = 0
edgeImg_8u = np.asarray(edgeImg, np.uint8)
# Find contours.
significant = findSignificantContours(img, edgeImg_8u)
# Mask.
mask = edgeImg.copy()
mask[mask > 0] = 0
cv2.fillPoly(mask, significant, 255)
mask = np.logical_not(mask) # Invert mask to get the background.
# Remove the background.
img[mask] = 255;
print(f'FINAL:')
cv2_imshow(img)
return img
if __name__ == '__main__':
imgUrl = 'http://images.numismatics.org/archivesimages%2Farchive%2Fschaefer_clippings_output_383_06_od.jpg/2648,1051,473,453/full/0/default.jpg'
obvPage = requests.get(imgUrl, stream = True, verify = False, headers = header)
img_final = remove_background(obvPage.raw)
作为示例,这里是原始图像,可以看到右侧有一些字母,这是我想去掉的。其他的图像也类似,虽然有些背景颜色不只是白色。
接下来是经过edgedetect()
函数处理后得到的边缘图像。
最后一张是背景“去掉”后的最终图像,但遗憾的是,它仍然包含一些字母,我不知道自己哪里做错了,或者如何改进我的代码以达到想要的效果。有人能帮我吗?
1 个回答
1
这里有一个处理流程的例子。最后会根据轮廓的圆度进行过滤。虽然这并不是完美的,但或许能提供一些帮助。
import numpy as np
import cv2
# Load an color image in grayscale
image = cv2.imread('archivesimages_archive_schaefer_clippings_output_383_06_od.jpg',0)
# blur the image
bkz = 10
blurred = cv2.blur(image, (bkz, bkz), 0)
# thresholding
(T, thresh) = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# Morphological filters
kernel = np.ones((5, 5), np.uint8)
thresh = cv2.erode(thresh, kernel, iterations=1)
#thresh = cv2.dilate(thresh, kernel, iterations=1)
# find contours
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# filter contours
contour_list = []
for contour in contours:
approx = cv2.approxPolyDP(contour,0.01*cv2.arcLength(contour,True),True)
area = cv2.contourArea(contour)
if ((len(approx) > 8) & (area > 50000)):
contour_list.append(contour)
print(len(contours))
print(len(contour_list))
# draw contours
thresh = cv2.cvtColor(thresh,cv2.COLOR_GRAY2RGB)
cv2.drawContours(thresh, contour_list, -1, (255,0,0), 3)
image = cv2.cvtColor(image,cv2.COLOR_GRAY2RGB)
cv2.drawContours(image, contour_list, -1, (255,0,0), 3)
# show the image
cv2.imshow('image1',thresh)
cv2.imshow('image2',image)
cv2.waitKey(0)
cv2.destroyAllWindows()