如何从图像中检测物体和背景颜色非常相似的物体?

问题描述 投票:0回答:2

我有一张图像,其中物体的颜色与背景非常相似。我想从图像中检测到这个对象,并将该对象设置为白色,背景应该为黑色。

我尝试过思考解决方案,但还没有找到。我尝试转换为灰度,然后用

cv2.equalizeHist
均衡直方图,我尝试用
cv2.Canny
检测边缘,我也尝试更改 HSV 通道,但我对此没有经验,所以我不确定它是否有帮助或如何我应该这么做。 CLAHE 是让物体更加明显的一件事:

import cv2
import tkinter.filedialog as fd

def main():
    filepath = fd.askopenfilename()
    image = cv2.imread(filepath)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    clahe = cv2.createCLAHE(clipLimit=5)
    cla = clahe.apply(gray)
    cv2.imshow("Image", cla)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

main()

但话又说回来,我不确定如何从这里继续图像处理,以便我能够检测到该对象。我怎样才能检测到它?

python-3.x opencv object-detection
2个回答
1
投票

该图像让我想起了一些粒子扫描,我曾经写过一些东西来计算主要和次要轮廓。在这里可以使用中值滤波器来消除噪声,这是我导入的:

import cv2
import numpy as np
%matplotlib notebook
import matplotlib.pyplot as plt
from scipy.ndimage import median_filter
def RunningMedian(x, N): # define the runningMedian filter
    '''
    Taken from here: https://stackoverflow.com/a/71764962/16815358
    '''
    return median_filter(x[x != 0], N)
im = cv2.cvtColor(cv2.imread("blue.jpg"), cv2.COLOR_BGR2RGB) # read the image
r, g, b = cv2.split(im) # split to get the blue channel

在下面的代码中,我这样做:

  1. 遍历蓝色通道中的每一行
  2. 拟合抛物线来消除数据趋势,这就是您尝试使用 CLAHE 做的事情
  3. 对 10 个点的数据应用运行中位数,这是为了稍微平滑数据
  4. 将公差上限定义为平均值 + 3 * std
  5. 获取线高于该限制的索引
  6. 为该行中的像素分配 1

代码位于 for 循环中,如果您在 jupyter 上运行,它应该向您显示动画:

fig, axs = plt.subplots(nrows = 3, ncols = 1, gridspec_kw={'height_ratios': [1, 1, 1]}, figsize = (3,6))
mask = np.zeros_like(b) # get a blank image for filling
for i in range(0,b.shape[0]-1):
    # calculations
    y = b[i,:] # get the row
    x = np.arange(len(y)) # defin the range
    coefficients = np.polyfit(x, y, 2) # find the coefficients
    polyFit = np.poly1d(coefficients) # define the polynomical function
    detrended = y - polyFit(x) # remove the trend
    detrendedMedian = np.abs(RunningMedian(detrended,10)) # get the median
    upperTolerance = np.mean(detrendedMedian)+3*np.std(detrendedMedian) # define upper tolerance as mu+3sigma
    idxTrue = np.where(detrendedMedian>upperTolerance) # get the pixels that exceed that limit
    mask[i,idxTrue] = 1 # assign true to those pixels
    # animation
    axs[0].cla() # clear plot for animation
    axs[1].cla() # clear plot for animation
    axs[2].cla() # clear plot for animation
    # plot the image with the current line
    axs[0].imshow(b) # show image
    axs[0].axhline(i, linewidth=5, color ="k") # line profiler
    # plot the profile
    axs[1].plot(detrendedMedian) # for the sake of this question, abs would be a better representation
    axs[1].axhline(upperTolerance, color = "r") # get the mean + 3*std
    axs[1].set_ylim((0,10)) # set the ylimit
    # plot the generated mask
    axs[2].imshow(mask)
    fig.canvas.draw() # update plot
    # plt.savefig("imagesForStack/"+str(i).zfill(5)+".png", dpi = 330) # save for gif

动画本身看起来像这样:

最后,您会得到一个足够好的蒙版,可以在扩张甚至打开后找到轮廓:

maskDilated = cv2.dilate(mask, np.ones((5,5), dtype = np.uint8), iterations = 1) # dilate
contours, _ = cv2.findContours(maskDilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # find the contour
largestContour = max(contours, key=cv2.contourArea) # get largest contour by area
x, y, w, h = cv2.boundingRect(largestContour) # get bounding box properties
imContoured = cv2.rectangle(im.copy(), (x, y), (x + w, y + h), (255, 0, 0), 1) # draw rectangle
plt.figure() # create figure
plt.imshow(imContoured) # imshow the contoured image

结果:

注意盒子比应有的尺寸要大一些,这里你应该稍微调整一下上限,或者使用变形操作来清理遮罩。

如果您告诉我您到底想要什么(也许您自己绘制所需的结果作为边界框并将其附加到帖子中),我也许可以为您提供进一步的帮助。


0
投票

我的做法:

  • 一些模糊以消除图像中的莫尔条纹
  • 强模糊来估计基线
  • 减去
  • 一些阈值
  • 一点形态学
  • 边界框
im_u8 = cv.imread("I0klu.jpg")
im = im_u8 * np.float32(1/255)

background = cv.medianBlur(im_u8, 21) * np.float32(1/255)

blurred = im
blurred = cv.medianBlur(blurred, 5)
blurred = cv.stackBlur(blurred, ksize=(5,5))
show((blurred - background) * 10 + 0.5)

diff = blurred - background
diff /= diff.std()
mask = np.abs(diff) > 5
mask = mask.any(axis=-1)

bbox = cv.boundingRect(mask.astype(np.uint8))
(x, y, w, h) = bbox
pad = 10
bbox = (x-pad, y-pad, w+2*pad, h+2*pad)
canvas = im_u8.copy()
cv.rectangle(img=canvas, rec=bbox, color=(0, 255, 0), thickness=1)

© www.soinside.com 2019 - 2024. All rights reserved.