使用Sobel边缘检测去除图像背景

0 投票
1 回答
84 浏览
提问于 2025-04-13 21:24

我有一堆代表硬币的图片,其中一些背景很杂乱(比如有字母或者背景颜色不同)。我想把每个硬币的背景去掉,只留下硬币本身,但我用OpenCV的cv2.findContours函数时,无法只检测到硬币的主要轮廓,反而会把其他部分也删掉,或者留下背景的一些杂音。

以下是我使用的代码,整个过程是这样的:

  1. 从字节对象读取图片,转成numpy数组。
  2. 把它解码成彩色图片。
  3. 转换成灰度图像。
  4. 加上高斯模糊来去除噪声。
  5. 通过应用sobel滤波器来检测图像中的边缘,使用edgedetect()函数。这里计算了X和Y方向的sobel,并通过Otsu阈值处理转换成二值图像。
  6. 计算图像的平均值,把低于这个值的部分设为零,以去除噪声。
  7. 找到显著的轮廓(findSignificantContours())。
  8. 根据轮廓创建一个掩码,反转并去掉它以获取背景。
  9. 把掩码设为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)

作为示例,这里是原始图像,可以看到右侧有一些字母,这是我想去掉的。其他的图像也类似,虽然有些背景颜色不只是白色。

enter image description here

接下来是经过edgedetect()函数处理后得到的边缘图像。

enter image description here

最后一张是背景“去掉”后的最终图像,但遗憾的是,它仍然包含一些字母,我不知道自己哪里做错了,或者如何改进我的代码以达到想要的效果。有人能帮我吗?

enter image description here

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

contour1 contour2

撰写回答