使用OpenCV和Python从图像中删除背景色

4

我有许多标本的图像,其中有些图像具有无法控制的背景颜色。一些图像有黑色背景、一些有白色背景、一些有绿色背景等。

我想去除给定图像中仅有一个标本物体的背景颜色。我尝试了这段代码,但它没有按照我的期望工作。

def get_holes(image, thresh):
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

        im_bw = cv2.threshold(gray, thresh, 255, cv2.THRESH_BINARY)[1]
        im_bw_inv = cv2.bitwise_not(im_bw)

        _, contour, _ = cv2.findContours(im_bw_inv, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
        for cnt in contour:
            cv2.drawContours(im_bw_inv, [cnt], 0, 255, -1)

        nt = cv2.bitwise_not(im_bw)
        im_bw_inv = cv2.bitwise_or(im_bw_inv, nt)
        return im_bw_inv


    def remove_background(image, thresh, scale_factor=.25, kernel_range=range(1, 15), border=None):
        border = border or kernel_range[-1]

        holes = get_holes(image, thresh)
        small = cv2.resize(holes, None, fx=scale_factor, fy=scale_factor)
        bordered = cv2.copyMakeBorder(small, border, border, border, border, cv2.BORDER_CONSTANT)

        for i in kernel_range:
            kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2*i+1, 2*i+1))
            bordered = cv2.morphologyEx(bordered, cv2.MORPH_CLOSE, kernel)

        unbordered = bordered[border: -border, border: -border]
        mask = cv2.resize(unbordered, (image.shape[1], image.shape[0]))
        fg = cv2.bitwise_and(image, image, mask=mask)
        return fg

file = your_file_location
img = cv2.imread(file)
nb_img = dm.remove_background(img, 255)

以下是一些示例图片:

示例1

示例2

请您给出建议。

1个回答

5
这里提供一种简单的方法,假设每张图片只有一个样本。
  1. Kmeans颜色量化。 我们加载图像,然后执行Kmeans颜色量化以将图像分割成指定颜色簇。例如,对于clusters=4,图像将被标记为四种颜色。

  2. 获取二进制图像。 转换为灰度图像、高斯模糊、自适应阈值。

  3. 在掩模上绘制最大外接圆。 查找轮廓,使用轮廓面积过滤排序以获得最大轮廓,然后使用cv2.minEnclosingCircle在掩模上绘制最大外接圆。

  4. 按位与。 由于我们已经隔离了要提取的所需部分,因此我们只需对掩模和输入图像进行按位与操作即可。


输入图像 -> Kmeans -> 二进制图像

检测到的最大外接圆 -> 掩模 -> 结果

以下是第二张图像的输出

输入图像 -> Kmeans -> 二进制图像

检测到的最大外接圆 -> 掩模 -> 结果

代码

import cv2
import numpy as np

# Kmeans color segmentation
def kmeans_color_quantization(image, clusters=8, rounds=1):
    h, w = image.shape[:2]
    samples = np.zeros([h*w,3], dtype=np.float32)
    count = 0

    for x in range(h):
        for y in range(w):
            samples[count] = image[x][y]
            count += 1

    compactness, labels, centers = cv2.kmeans(samples,
            clusters, 
            None,
            (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10000, 0.0001), 
            rounds, 
            cv2.KMEANS_RANDOM_CENTERS)

    centers = np.uint8(centers)
    res = centers[labels.flatten()]
    return res.reshape((image.shape))

# Load image and perform kmeans
image = cv2.imread('2.jpg')
original = image.copy()
kmeans = kmeans_color_quantization(image, clusters=4)

# Convert to grayscale, Gaussian blur, adaptive threshold
gray = cv2.cvtColor(kmeans, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,21,2)

# Draw largest enclosing circle onto a mask
mask = np.zeros(original.shape[:2], dtype=np.uint8)
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
    ((x, y), r) = cv2.minEnclosingCircle(c)
    cv2.circle(image, (int(x), int(y)), int(r), (36, 255, 12), 2)
    cv2.circle(mask, (int(x), int(y)), int(r), 255, -1)
    break

# Bitwise-and for result
result = cv2.bitwise_and(original, original, mask=mask)
result[mask==0] = (255,255,255)

cv2.imshow('thresh', thresh)
cv2.imshow('result', result)
cv2.imshow('mask', mask)
cv2.imshow('kmeans', kmeans)
cv2.imshow('image', image)
cv2.waitKey()

如果我的样本不是圆形而是椭圆形,我该怎么办?我认为我应该修改cv2.minEncloseingCircle(c)这一行代码。请问您有什么建议吗? - Ratchainant Thammasudjarit
你可以尝试使用 cv2.fitEllipse - nathancy
如果不是一个完美的圆形而是椭圆形,你可以尝试另一种方法。不要使用某些圆/椭圆拟合函数来获取掩码,而是可以使用cv2.findContours查找轮廓,然后使用cv2.drawContours将最大的轮廓绘制到掩码上。这应该适用于任何方向。 - nathancy

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