opencv 无法在C#中通过EmguCV正确使用分水岭算法

smdncfj3  于 2023-02-23  发布在  C#
关注(0)|答案(1)|浏览(216)

基本上,我想重新创建分水岭算法,如opencv python教程所示:https://docs.opencv.org/3.4/d3/db4/tutorial_py_watershed.html
本教程使用分水岭算法查找此图像上的等值线:

,结果为:

我用C#和EmguCV编写的代码如下:

var img = new image<Bgr,byte>("coins.png");

    var mask = img.Convert<Gray, byte>().ThresholdBinary(new Gray(100), new Gray(255));
    
    Mat distanceTransform = new Mat();
    CvInvoke.DistanceTransform(mask, distanceTransform, null, Emgu.CV.CvEnum.DistType.L2, 3);
    CvInvoke.Normalize(distanceTransform, distanceTransform, 0, 255, Emgu.CV.CvEnum.NormType.MinMax);

    var threholded = distanceTransform.ToImage<Gray, byte>().ThresholdBinary(new Gray(200), new Gray(255));

    CvInvoke.Threshold(threholded, threholded, 0, 255, Emgu.CV.CvEnum.ThresholdType.Binary);

    CvInvoke.ConnectedComponents(threholded, threholded);
    
    var finalMarkers = threholded.Convert<Gray, Int32>();

    CvInvoke.Watershed(img, finalMarkers);

    Image<Gray, byte> boundaries = finalMarkers.Convert<byte>(delegate (Int32 x)
    {
        return (byte)(x == -1 ? 255 : 0);
    });

    CvInvoke.Imshow("ame", boundaries);

使用这段代码,我得到了以下结果:

在某种程度上它离预期的结果不远但显然不正确,它似乎自动填补了一些轮廓中硬币之间的空白区域。
我尝试了很多方法,但是我被卡住了。我试着添加"确定背景区域",但没有任何成功,无法改进这段代码,所以我真的不知道该怎么做。
谢谢你的帮忙!

pbgvytdp

pbgvytdp1#

为了获得完全相同的结果,我们必须严格按照教程的步骤进行操作。
在Python中,教程阶段相对简单(如果你了解Python)。
使用EmguCV从Python转换到C#有点挑战性,因为我们必须找到NumPy操作的EmguCV替换。
我不介意练习一些C# EmguCV,并且严格按照教程进行操作:

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            
        }
    }
}

我希望你能按照代码样本-有些部分是有点挑战性...
输出:

注:
教程中的颜色错误(红色和蓝色通道交换)。
以上颜色正确。

相关问题