我有一张图像,其中物体的颜色与背景非常相似。我想从图像中检测到这个对象,并将该对象设置为白色,背景应该为黑色。
我尝试过思考解决方案,但还没有找到。我尝试转换为灰度,然后用
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()
但话又说回来,我不确定如何从这里继续图像处理,以便我能够检测到该对象。我怎样才能检测到它?
该图像让我想起了一些粒子扫描,我曾经写过一些东西来计算主要和次要轮廓。在这里可以使用中值滤波器来消除噪声,这是我导入的:
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
在下面的代码中,我这样做:
代码位于 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
结果:
注意盒子比应有的尺寸要大一些,这里你应该稍微调整一下上限,或者使用变形操作来清理遮罩。
如果您告诉我您到底想要什么(也许您自己绘制所需的结果作为边界框并将其附加到帖子中),我也许可以为您提供进一步的帮助。
我的做法:
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)