下面介绍了一种查找每个 bean 中心的方法。在不同但连续的时间分析帧中分割对象的中心位置,可以跟踪它们。保持视觉轮廓或分析其路径可以提高跟踪算法在一个对象与另一个对象交叉或有一些重叠的情况下的准确性。
我使用了Marvin Image Processing Framework 和 Java。
寻找中心方法
我使用了三种基本算法:阈值、形态侵蚀和填充分割。第一步是去除背景的阈值,如下图。
下一步是应用形态侵蚀来分离豆子。在小内核矩阵的情况下,我可以将小豆子分开,但将较大的豆子放在一起,如下所示。使用每个独立片段的质量(像素数)进行过滤,可以只选择较小的片段,如下所示。
使用大核矩阵,我可以将较大的核矩阵分开,小核矩阵消失,如下所示。
结合这两个结果 - 删除距离太近且可能来自同一个 bean 的中心点 - 我得到了下面的结果。
即使没有每个豆子的实际部分,使用中心位置也可以计算和跟踪它们。这些中心还可用于找出每个豆段。
源代码
源代码是Java,但解决方案中采用的图像处理算法由大多数框架提供。
编辑:我编辑了源代码以保存每个步骤的图像。可以优化源代码,删除这些调试步骤并创建重用代码的方法。创建一些对象和列表只是为了演示这些步骤,也可以删除。
import static marvin.MarvinPluginCollection.floodfillSegmentation;
import static marvin.MarvinPluginCollection.thresholding;
import marvin.image.MarvinColorModelConverter;
import marvin.image.MarvinImage;
import marvin.image.MarvinSegment;
import marvin.io.MarvinImageIO;
import marvin.math.MarvinMath;
import marvin.plugin.MarvinImagePlugin;
import marvin.util.MarvinPluginLoader;
public class CoffeeBeansSeparation {
private MarvinImagePlugin erosion = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.morphological.erosion.jar");
public CoffeeBeansSeparation(){
// 1. Load Image
MarvinImage image = MarvinImageIO.loadImage("./res/coffee.png");
MarvinImage result = image.clone();
// 2. Threshold
thresholding(image, 30);
MarvinImageIO.saveImage(image, "./res/coffee_threshold.png");
// 3. Segment using erosion and floodfill (kernel size == 8)
List<MarvinSegment> listSegments = new ArrayList<MarvinSegment>();
List<MarvinSegment> listSegmentsTmp = new ArrayList<MarvinSegment>();
MarvinImage binImage = MarvinColorModelConverter.rgbToBinary(image, 127);
erosion.setAttribute("matrix", MarvinMath.getTrueMatrix(8, 8));
erosion.process(binImage.clone(), binImage);
MarvinImageIO.saveImage(binImage, "./res/coffee_bin_8.png");
MarvinImage binImageRGB = MarvinColorModelConverter.binaryToRgb(binImage);
MarvinSegment[] segments = floodfillSegmentation(binImageRGB);
// 4. Just consider the smaller segments
for(MarvinSegment s:segments){
if(s.mass < 300){
listSegments.add(s);
}
}
showSegments(listSegments, binImageRGB);
MarvinImageIO.saveImage(binImageRGB, "./res/coffee_center_8.png");
// 5. Segment using erosion and floodfill (kernel size == 18)
listSegments = new ArrayList<MarvinSegment>();
binImage = MarvinColorModelConverter.rgbToBinary(image, 127);
erosion.setAttribute("matrix", MarvinMath.getTrueMatrix(18, 18));
erosion.process(binImage.clone(), binImage);
MarvinImageIO.saveImage(binImage, "./res/coffee_bin_8.png");
binImageRGB = MarvinColorModelConverter.binaryToRgb(binImage);
segments = floodfillSegmentation(binImageRGB);
for(MarvinSegment s:segments){
listSegments.add(s);
listSegmentsTmp.add(s);
}
showSegments(listSegmentsTmp, binImageRGB);
MarvinImageIO.saveImage(binImageRGB, "./res/coffee_center_18.png");
// 6. Remove segments that are too near.
MarvinSegment.segmentMinDistance(listSegments, 10);
// 7. Show Result
showSegments(listSegments, result);
MarvinImageIO.saveImage(result, "./res/coffee_result.png");
}
private void showSegments(List<MarvinSegment> segments, MarvinImage image){
for(MarvinSegment s:segments){
image.fillRect((s.x1+s.x2)/2, (s.y1+s.y2)/2, 5, 5, Color.red);
}
}
public static void main(String[] args) {
new CoffeeBeansSeparation();
}
}