Below is presented an approach to find the center of each bean. Analyzing the central position of segmented objects in frames in different, but sequential, time it is possible to track them. Keeping visual profiles or analyzing its path can increase the accuracy of the tracking algorithm in situations that an object cross the other or there are some overlap.
I used Marvin Image Processing Framework and Java.
Finding the center approach
I used three basic algorithms: threshold, morphological erosion and floodfill segmentation. The first step is the threshold for removing the background, as shown below.

The next step is the application of morphological erosion in order to separate the beans. In the case of a small kernel matrix I can separate the small beans but keep the bigger ones together, as shown below. Filtering using the mass (number of pixels) of each independent segment it is possible to select just the smaller ones, as shown below.

Using a big kernel matrix I can separate the bigger ones and the small ones disappear, as shown below.

Combining the two results - removing center points that are too near and probably from the same bean - I got the result presented below.

Even not having the real segment of each bean, using the center positions it is possible to count and track them. The centers can also be used to find out each bean segment.
Source code
The source code is in Java, but the image processing algorithms employed in the solution are provided by the most frameworks.
EDIT: I edited the source code in order to save the images of each step. The source code can be optimized removing these debug steps and creating methods to reuse code. Some objets and lists were created just to demonstrate theses steps and can be removed too.
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();
}
}