我无法使用 Otsu 方法和 opencv 上的绿色通道获取柑橘类水果的修补区域

I can't get the patched regions of a citrus fruit using Otsu method with the Green channel on opencv

我正在尝试使用 Otsu 方法和 opencv 获取柑橘类水果的修补区域。根据这篇论文:https://www.intechopen.com/books/agricultural-robots-fundamentals-and-applications/multimodal-classification-of-mangoes/ 这些作者使用绿色通道 (G) 来获取芒果的斑块区域:

我也在做同样的事情,但使用了柠檬,但我无法获得柠檬的那些区域。

这是我的输入图像:


首先,我读取了图片,然后调用了一个函数来显示图片:

def show(img, titulo):
    plt.figure(figsize=(7,7))
    plt.title(titulo)
    plt.imshow(img)
    plt.show()

#read img
file = "lemons/bad/bad_5.jpg"
image = cv2.imread(file)
#convert from BGR to RGB
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
original = image
show(image, "original img "+str(image.shape))

然后我添加了一个模糊滤镜:

#(blur) filter
image = cv2.blur(image,(31,31),0)
show(image, "img with BLUR")

转换为 HSV:

hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
#get hsv channels
h, s, v = cv2.split(hsv)
show(s, "channel S of HSV")

然后我添加了大津的方法:

#OTSU
_, thr = cv2.threshold(s, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
show(thr, "Binarized image with the OTSU method")

最后我把这个 Otsu 面具放到我的原始图像中:

result = cv2.bitwise_and(original, original, mask=thr)
show(result, "Lemon Segmented")

从这里开始,只有这个孔没有问题,因为我得到了整个图像。

根据下面的研究URL https://www.intechopen.com/books/agricultural-robots-fundamentals-and-applications/multimodal-classification-of-mangoes/说要隔离补丁区域,我们应该得到绿色通道:

image = gray
B,green_ch,R = cv2.split(result)
show(green_ch, "Green channel 'G'")

这是输出:

这里有两个补丁区域的公证可视化,但是当我使用这个频道应用 Otsu 方法时,没有结果而不是我得到黑洞:


_, thr = cv2.threshold(green_ch, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
show(thr, "OTSU with Green channel")


结果:

result = cv2.bitwise_and(original, original, mask=thr)
show(result, "Lemon Segmented")


正如我们所见,两个修补区域未使用绿色通道进行分割。我尝试使用 HSL 颜色 space 但效果不佳。我的想法是检测这个补丁并获取颜色直方图,然后使用这些特征训练分类器。

我在测试代码时留下了第二张图片:


好吧,伙计们,我想看看你们的建议,以尝试在上面的论文中获得相同的结果,如果我有任何想法,我将不胜感激。
非常感谢。

编辑:我倒转了第二个蒙版以得到缺陷区域。

一旦你第一次使用 otsu's,它会给你一个分离前景(水果)和背景的遮罩。您可以在蒙版区域再次使用 otsu's 以获得另一个蒙版,将水果上的黑点分开。

不幸的是,OpenCV 没有一种简单的方法可以 运行仅在蒙版区域上设置 otsu。然而,otsu 只是在像素强度直方图上寻找产生最大方差的阈值。由于此直方图完全成比例,我们可以通过使所有未屏蔽像素与直方图比例匹配来强制 otsu 仅在屏蔽区域上 运行。

我转换成HSV并使用饱和度通道将水果与背景分开。

然后我使用直方图复制了色调通道未屏蔽像素的像素比例。

之前的色调

色调之后

然后我运行 otsu 第二次上hue频道了。

现在要得到最终的蒙版,我们只需 bitwise_and 将第一个和第二个蒙版放在一起(并进行打开和关闭操作以清理小孔)

import cv2
import numpy as np
import random

# apply histogram
def applyHist(gray, mask, hist):
    # get cumulative distribution
    cumulative = [];
    total = 0;
    for val in hist:
        total += val;
        cumulative.append(total);

    # apply to each pixel not in max
    positions = np.where(mask != 255);
    for a in range(len(positions[0])):
        # choose value
        rand = random.randint(0, cumulative[-1]);
        index = 0;
        while rand > cumulative[index]:
            index += 1;

        # apply
        y = positions[0][a];
        x = positions[1][a];
        gray[y,x] = index;

# load image
img = cv2.imread("lemon.png");

# hsv
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV);
h,s,v = cv2.split(hsv);

# use the saturation channel for the first mask
s = cv2.GaussianBlur(s, (5,5), 0);
_, mask = cv2.threshold(s, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU);

# grab positions from mask and make histogram
positions = np.where(mask == 255);
hist = [0 for a in range(256)];
for a in range(len(positions[0])):
    y = positions[0][a];
    x = positions[1][a];
    pix = h[y,x];
    hist[pix] += 1;

# opencv doesn't have a way to just let you otsu on a mask...
# eheheheheheh, AHAHAHAHAHA
# LET'S JUST MAKE THE REST OF THE IMAGE MATCH THE HISTOGRAM
applyHist(h, mask, hist);

# otsu the image
h = cv2.GaussianBlur(h, (5,5), 0);
_, second_mask = cv2.threshold(h, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU);
second_mask = cv2.bitwise_not(second_mask); # just get the defects

# combine with first mask
final_mask = cv2.bitwise_and(mask, second_mask);

# opening and closing to get rid of small holes
kernel = np.ones((3,3), np.uint8);

# closing
final_mask = cv2.dilate(final_mask, kernel, iterations = 2);
final_mask = cv2.erode(final_mask, kernel, iterations = 2);

# opening
final_mask = cv2.erode(final_mask, kernel, iterations = 2);
final_mask = cv2.dilate(final_mask, kernel, iterations = 2);

# mask the image
cropped = np.zeros_like(img);
cropped[final_mask == 255] = img[final_mask == 255];

# show image
cv2.imshow("image", img);
cv2.imshow("cropped", cropped);
cv2.imshow("final", final_mask);
cv2.waitKey(0);