For getting the exact same results, we have to follow the tutorial stages precisely.
In Python the tutorial stages are relatively simple (if you know Python).
Translating from Python to C# with EmguCV, is a bit challenging, because we have to find EmguCV replacement for the NumPy operations.
I don't mind practicing some C# EmguCV, and followed the tutorial precisely:
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using System;
using System.Drawing;
namespace Testings
{
public class Program
{
static void Main(string[] args)
{
//# We start with finding an approximate estimate of the coins. For that, we can use the Otsu's binarization.
//img = cv2.imread('coins.png')
//assert img is not None, "file could not be read, check with os.path.exists()"
//gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
//ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
var img = new Image<Bgr, byte>(@"coins.png");
//var mask = img.Convert<Gray, byte>().ThresholdBinary(new Gray(100), new Gray(255));
//https://stackoverflow.com/a/25994631/4926757
Image<Gray, byte> gray = img.Convert<Gray, byte>();
Image<Gray, byte> thresh = gray.CopyBlank();
CvInvoke.Threshold(gray, thresh, 0, 255, Emgu.CV.CvEnum.ThresholdType.BinaryInv | Emgu.CV.CvEnum.ThresholdType.Otsu);
//# The remaining regions are those which we don't have any idea, whether it is coins or background. Watershed algorithm should find it.
//# These areas are normally around the boundaries of coins where foreground and background meet
//# noise removal
//kernel = np.ones((3,3),np.uint8)
//opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
//# sure background area
//sure_bg = cv2.dilate(opening,kernel,iterations=3)
//# Finding sure foreground area
//dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
//ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
//# Finding unknown region
//sure_fg = np.uint8(sure_fg)
//unknown = cv2.subtract(sure_bg,sure_fg)
Matrix<byte> kernel = new Matrix<byte>(new Byte[3, 3] { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } }); //https://stackoverflow.com/a/33646626/4926757
Image<Gray, Byte> opening = thresh.MorphologyEx(Emgu.CV.CvEnum.MorphOp.Open, kernel, new Point(-1, -1), 2, Emgu.CV.CvEnum.BorderType.Default, new MCvScalar());
Image<Gray, Byte> sureBg = opening.Dilate(3);
Mat distanceTransform = new Mat();
CvInvoke.DistanceTransform(opening, distanceTransform, null, Emgu.CV.CvEnum.DistType.L2, 5);
double minVal = 0, maxVal = 0;
Point minLoc = new Point(), maxLoc = new Point();
CvInvoke.MinMaxLoc(distanceTransform, ref minVal, ref maxVal, ref minLoc, ref maxLoc); //Find distanceTransform.max()
Mat sureFg0 = new Mat();
CvInvoke.Threshold(distanceTransform, sureFg0, 0.7*maxVal, 255, Emgu.CV.CvEnum.ThresholdType.Binary);
Mat sureFg = new Mat();
sureFg0.ConvertTo(sureFg, Emgu.CV.CvEnum.DepthType.Cv8U); //Convert from float to Byte
Mat unknown = new Mat();
CvInvoke.Subtract(sureBg, sureFg, unknown);
//# Watershed will consider it as unknown area. So we want to mark it with different integer. Instead, we will mark unknown region, defined by unknown, with 0
//# Marker labelling
//ret, markers = cv2.connectedComponents(sure_fg)
//# Add one to all labels so that sure background is not 0, but 1
//markers = markers+1
//# Now, mark the region of unknown with zero
//markers[unknown==255] = 0
Mat markers = new Mat();
CvInvoke.ConnectedComponents(sureFg, markers);
markers = markers + 1;
Mat zeros = markers - markers; //Create a matrix of zeros (with same type as markers).
zeros.CopyTo(markers, unknown); //markers[unknown==255] = 0 //Copy zeros to elements where unknown != 0.
//# Now our marker is ready. It is time for final step, apply watershed. Then marker image will be modified. The boundary region will be marked with -1.
//markers = cv2.watershed(img,markers)
//img[markers == -1] = [255,0,0]
Mat finalMarkers = new Mat();
CvInvoke.Watershed(img, markers);
Mat mask = new Mat();
zeros.SetTo(new MCvScalar(-1)); //Reuse zeros matrix - fill with (-1) values.
CvInvoke.Compare(markers, zeros, mask, CmpType.Equal);
mask.ConvertTo(mask, Emgu.CV.CvEnum.DepthType.Cv8U); //Convert from mask to Byte
Mat blue = new Mat(img.Rows, img.Cols, Emgu.CV.CvEnum.DepthType.Cv8U, 3);
blue.SetTo(new MCvScalar(255, 0, 0)); // Make blue image
blue.CopyTo(img, mask);
//Show images for testing
CvInvoke.Imshow("mask", mask);
CvInvoke.Imshow("img", img);
CvInvoke.WaitKey();
CvInvoke.Imwrite("mask.png", mask); //Save output for testing
CvInvoke.Imwrite("img.png", img); //Save output for testing
}
}
}
I hope you manage to follow the code sample - some parts are a bit challenging...
Output:

Note:
The colors in the tutorial are wrong (red and blue channels are swapped).
The above colors are correct.