Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / artificial-intelligence / machine-learning

Getting Started With OpenCvSharp 3

3.34/5 (13 votes)
8 Apr 2016CPOL3 min read 118.6K   2.8K  
Sample code for OpenCvSharp 3 quick start

Introduction

I'm a newbie to image processing but not to programming. I uploaded this project as a demo code resource for other newbies who wish to get a quick start and feel on OpenCvSharp3 coding API. This is an article to show how C++ code is converted to OpenSharp3 code. You can experiment with the code parameters and look-up the C++ document for API's actions.

I considered the C# wrapper class emgu and OpenCV.NET... But I felt that OpenCvSharp3 was the simplest and better supported platform.

The demo code is sectioned off (as independent regions) to make grasping the code intent very simple. I had to compile the https://github.com/shimat/opencvsharp/releases sample code at least a couple of times to get a clean build.

Background

OpenVC is a popular C++ library for computer vision. It processes image pixels to find features of interest. However, C++ is an unmanaged code platform and a little awkward compared to C#. Because of this, I gave up C++ programming for C#.

Using the Code

To run the demo, create a new console app and copy the image and program files to it. Include one of the program files (program, program2 or program3) and exclude the other program files from the project build. Then uncomment (if applicable) one of the code regions and compile and execute that command selection. Change API parameters as desired and recompile and run. Note that you need to use the Visual Studio nuget manager plug-in tool to install the OpenCvSharp3 library. The basic package will include the OpenCv C++ DLLs.

C#
/*
 * More Samples are available at: https://github.com/shimat/opencvsharp/releases
 * Class NameSpace listing is at: http://shimat.github.io/opencvsharp/
*/

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.ML;

class Program
{
    static void Main()
    {
#region Canny Edge Detection

        //Mat source = new Mat("../../Images/lena.jpg", ImreadModes.Color);
        //Mat grayFiltered = new Mat();
        //Mat ClearEdge = new Mat();
        //Mat filtered = new Mat();
        //Mat d = createADiamond();
        //Mat x = createAXShape();
        //Mat Canny = new Mat();

        //Cv2.Canny(source, Canny, 32, 192);
        //binImage(source, out filtered);

        //// A discussion on erode and dilate is at:
        //// <a href="http://docs.opencv.org/2.4/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.html">http://docs.opencv.org/2.4/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.html</a>

        //Cv2.Erode(filtered, ClearEdge, x);
        //Cv2.Dilate(ClearEdge, ClearEdge, x);
        //Cv2.Erode(ClearEdge, ClearEdge, d);
        //Cv2.Dilate(ClearEdge, ClearEdge, d);

        //grayFiltered = Mat.FromImageData(filtered.ToBytes(), ImreadModes.GrayScale);

        //new Window("source image", source);
        //new Window("CannyEdge image", Canny);
        //new Window("filtered image", filtered);
        //new Window("SharpEdge image", ClearEdge);
        //new Window("grayFiltered image", grayFiltered);
        //Cv2.WaitKey();

#endregion


#region Sobel Edge Detection

        //Mat dst = new Mat();
        //Mat grad_x = new Mat(), grad_y = new Mat();
        //Mat abs_grad_x = new Mat(), abs_grad_y = new Mat();
        //Mat src_gray = new Mat("../../Images/lena.jpg", ImreadModes.GrayScale);

        //// try to reduce image noises.
        //Cv2.GaussianBlur(src_gray, src_gray, new Size(3, 3), 1.5);

        //// Gradient X, ddepth is set it to CV_16S to avoid overflow
        //Cv2.Sobel(src_gray, grad_x, MatType.CV_16U, 1, 0, 3);
        //Cv2.ConvertScaleAbs(grad_x, abs_grad_x);

        //// Gradient Y
        //Cv2.Sobel(src_gray, grad_y, MatType.CV_16U, 0, 1, 3);
        //Cv2.ConvertScaleAbs(grad_y, abs_grad_y);

        //// Total Gradient (approximate)
        //Cv2.AddWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);

        //Cv2.ImShow("Sobel Edge", dst);

        //Cv2.WaitKey(0);

#endregion


#region Camcorder Capture

        //VideoCapture capture;
        //Mat frame;

        //capture = new VideoCapture();
        //frame = new Mat();
        //capture.Open(0);

        //// Read the video stream
        //{
        //    Cv2.NamedWindow("Video", WindowMode.AutoSize);
        //    while (true)
        //    {
        //        if (capture.Read(frame))
        //        {
        //            Cv2.ImShow("Video", frame);

        //            // press a key to end execution
        //            int c = Cv2.WaitKey(10);
        //            if (c != -1) { break; } // Assuming image has focus
        //        }
        //    }
        //}

#endregion


#region Assigning pixel values

        //Mat src = new Mat("../../Images/lena.jpg", ImreadModes.Color);

        //// place a green box at the upper left hand corner
        //for (int i = 10; i < 99; i++)
        //    for (int j = 10; j < 99; j++)
        //        src.Set<Vec3b>(i, j, new Vec3b(0, 255, 0));

        //using (new Window("dot image", src2))
        //{
        //    MessageBox.Show("Depth: " + src.Depth());
        //    MessageBox.Show("Channels: " + src.Channels());
        //    Cv2.WaitKey();
        //}

#endregion


#region Erode/Dilate Morphing

        //Mat src, dst;
        //src = Cv2.ImRead("../../Images/lena.jpg", ImreadModes.Color);
        //dst = new Mat();

        // Create a structuring element
        //int erosion_size = 6;
        //Mat element = Cv2.GetStructuringElement(MorphShapes.Cross,
        //      new Size(2 * erosion_size + 1, 2 * erosion_size + 1),
        //      new Point(erosion_size, erosion_size));

        // Apply erosion "OR" dilation on the image
        //Cv2.Erode(src, dst, element);
        //Cv2.Dilate(src, dst, element);

        //using (new Window("Display window", src))
        //using (new Window("Result window", dst))
        //    Cv2.WaitKey(0);

#endregion


#region draw a line

        //// Create black empty images
        //Mat image = Mat.Zeros(400, 400, MatType.CV_8UC3);

        //// Draw a line
        //Cv2.Line(image, new Point(15, 20), new Point(375, 375),
        //    new Scalar(255, 128, 0), 2);
        //using (new Window("Image", image))
        //    Cv2.WaitKey(0);

#endregion


#region draw a circle

        //// Create black empty images
        //Mat image = Mat.Zeros(400, 400, MatType.CV_8UC3);

        //// Draw a line
        //Cv2.Circle(image, new Point(200, 200), 100, new Scalar(255, 128, 0), 2);
        //using (new Window("Image", image))
        //    Cv2.WaitKey(0);

#endregion


#region draw a Polygon

        //Mat src = new Mat(new Size(400, 400), MatType.CV_8U, new Scalar(1));
        //src.SetTo(Scalar.Black);

        //List<List<Point>> ListOfListOfPoint = new List<List<Point>>();
        //List<Point> points = new List<Point>();

        //points.Add(new Point(100, 100));
        //points.Add(new Point(100, 300));
        //points.Add(new Point(300, 300));
        //points.Add(new Point(300, 100));
        //ListOfListOfPoint.Add(points);

        //src.FillPoly(ListOfListOfPoint, Scalar.White);
        //Cv2.ImShow("Square", src);
        //Cv2.WaitKey();;

#endregion


#region draw a text string

        //// Create black empty images
        //Mat src = Mat.Zeros( 400, 400, MatType.CV_8UC3 );

        //Cv2.PutText(src, "Hi all...", new Point(50,100),
        //    HersheyFonts.HersheySimplex, 1, new Scalar(0,200,200), 4);
        //using (new Window("Image", src))
        //    Cv2.WaitKey(0);

#endregion


#region weighed filter

        //Mat src = new Mat("../../Images/lena.jpg", ImreadModes.GrayScale);
        //Mat kernel = new Mat(3, 3, MatType.CV_32F, new Scalar(0));
        //Mat dst = new Mat();

        //kernel.Set<float>(1, 1,  5.0f);
        //kernel.Set<float>(0, 1, -1.0f);
        //kernel.Set<float>(2, 1, -1.0f);
        //kernel.Set<float>(1, 0, -1.0f);
        //kernel.Set<float>(1, 2, -1.0f);

        //Cv2.Filter2D(src, dst, MatType.CV_32F, kernel);
        //using (new Window("src image", src))
        //using (new Window("dst image", dst))
        //{
        //    Cv2.WaitKey();
        //}

#endregion


#region find circles in image

        //Mat train = new Mat("../../Images/cartoon-train.png", ImreadModes.GrayScale);
        //CircleSegment[] circles;
        //Mat dst = new Mat();

        //Cv2.GaussianBlur(train, dst, new Size(5, 5), 1.5, 1.5);

        //// Note, the minimum distance between concentric circles is 25. Otherwise
        //// false circles are detected as a result of the circle's thickness.
        //circles = Cv2.HoughCircles(dst, HoughMethods.Gradient, 1, 25, 75, 60, 5, 200);

        //for (int i = 0; i < circles.Length; i++)
        //{
        //    Cv2.Circle(dst, circles[i].Center, (int)circles[i].Radius, new Scalar(0), 2);
        //}

        //using (new Window("Circles", dst))
        //{
        //    Cv2.WaitKey();
        //}

#endregion


#region Get corners on image

        //Mat src = new Mat("../../Images/building.jpg", ImreadModes.GrayScale);

        //// Show Edges
        //Mat edges = getEdges(src, 50);
        //new Window("Edges", edges);

        //// Corner detection
        //// Get All Processing Images
        //Mat cross = createACross();
        //Mat diamond = createADiamond();
        //Mat square = createASquare();
        //Mat x = createAXShape();
        //Mat dst = new Mat();

        //// Dilate with a cross
        //Cv2.Dilate(src, dst, cross);

        //// Erode with a diamond
        //Cv2.Erode(dst, dst, diamond);

        //Mat dst2 = new Mat();

        //// Dilate with a X
        //Cv2.Dilate(src, dst2, x);

        //// Erode with a square
        //Cv2.Erode(dst2, dst2, square);

        //// Corners are obtain by differencing the two closed images
        //Cv2.Absdiff(dst, dst2, dst);
        //applyThreshold(dst, 45);

        //// The following code Identifies the founded corners by
        //// drawing circle on the src image.
        //IDTheCorners(dst, src);
        //new Window("Corner on Image", src);
        //Cv2.WaitKey();

#endregion


#region Machine Learning

        // Translated from C++ article:
        // http://docs.opencv.org/3.1.0/d1/d73/tutorial_introduction_to_svm.html

        // Data for visual representation
        int width = 512, height = 512;
        Mat image = Mat.Zeros(height, width, MatType.CV_8UC3);

        // Set up training data
        int[] labels = new int[] { 1, -1, -1, -1 };
        float[,] trainingData = new float[,] { { 501, 10 }, { 255, 10 }, { 501, 255 }, { 10, 501 } };
        Mat trainingDataMat = new Mat(4, 2, MatType.CV_32FC1, trainingData);
        Mat labelsMat = new Mat(4, 1, MatType.CV_32SC1, labels);

        // Train the SVM
        SVM svm = SVM.Create();
        svm.Type = SVM.Types.CSvc;
        svm.KernelType = SVM.KernelTypes.Linear;
        svm.TermCriteria = new TermCriteria(CriteriaType.MaxIter, 100, 1e-6);
        svm.Train(trainingDataMat, SampleTypes.RowSample, labelsMat);

        // Show the decision regions given by the SVM
        Vec3b green = new Vec3b(0, 255, 0), blue = new Vec3b(255, 0, 0);
        for (int i = 0; i < image.Rows; ++i)
            for (int j = 0; j < image.Cols; ++j)
            {
                Mat sampleMat = new Mat(1, 2, MatType.CV_32F, new float[] { j, i });
                float response = svm.Predict(sampleMat);
                if (response == 1)
                    image.Set<Vec3b>(i, j, green);
                else if (response == -1)
                    image.Set<Vec3b>(i, j, blue);
            }

        // Show the training data
        int thickness = -1;
        Cv2.Circle(image, new Point(501, 10), 5, Scalar.Black, thickness);
        Cv2.Circle(image, new Point(255, 10), 5, Scalar.White, thickness);
        Cv2.Circle(image, new Point(501, 255), 5, Scalar.White, thickness);
        Cv2.Circle(image, new Point(10, 501), 5, Scalar.White, thickness);

        // Show support vectors
        thickness = 2;
        Mat sv = svm.GetSupportVectors();
        for (int i = 0; i < sv.Rows; ++i)
        {
            unsafe
            {
                float* v = (float*)sv.Ptr(i);
                Cv2.Circle(image, new Point((int)v[0], (int)v[1]), 6, Scalar.Gray, thickness);
                Console.WriteLine("{0:d}, {1:d}", (int)v[0], (int)v[1]);
            }
        }
        Cv2.ImWrite("result.png", image);

        // save the image
        Cv2.ImShow("SVM Simple Example", image); // show it to the user
        Cv2.WaitKey(0);

#endregion


#region Feature SURF flann

        //// Token from C++ example:
        //// http://docs.opencv.org/3.1.0/d5/d6f/tutorial_feature_flann_matcher.html
        //// As of the writing of this routine,
        //// SIFT and SURF is a non-free code and is moved to the
        //// contrib repository and then link to the xfeatures2d library.

        //// So, you will get runtime error:
        //// Unable to find an entry point named 'xfeatures2d_SURF_create'
        //// in DLL 'OpenCvSharpExtern'.

        //// See these 2 links for an method on how to install the contrib library,
        //// it's a bit trick but do-able:
        //// https://github.com/shimat/opencvsharp/issues/146
        //// https://github.com/shimat/opencvsharp/issues/180


        //Mat img_1 = Cv2.ImRead("../../Images/icons.png", ImreadModes.GrayScale);
        //Mat img_2 = Cv2.ImRead("../../Images/subIcons.png", ImreadModes.GrayScale);

        ////-- Step 1: Detect the keypoints using SURF Detector, compute the descriptors
        //int minHessian = 400;
        //SURF detector = SURF.Create(minHessian);

        //KeyPoint[] keypoints_1, keypoints_2;
        //Mat descriptors_1 = new Mat(), descriptors_2 = new Mat();

        //detector.DetectAndCompute(img_1, new Mat(), out keypoints_1, descriptors_1);
        //detector.DetectAndCompute(img_2, new Mat(), out keypoints_2, descriptors_2);

        ////-- Step 2: Matching descriptor vectors using FLANN matcher
        //FlannBasedMatcher matcher = new FlannBasedMatcher();
        //DMatch[] matches;

        //matches = matcher.Match(descriptors_1, descriptors_2);
        //double max_dist = 0; double min_dist = 100;

        ////-- Quick calculation of max and min distances between keypoints
        //for (int i = 0; i < descriptors_1.Rows; i++)
        //{
        //    double dist = matches[i].Distance;
        //    if (dist < min_dist) min_dist = dist;
        //    if (dist > max_dist) max_dist = dist;
        //}
        //Console.WriteLine("-- Max dist : %f", max_dist);
        //Console.WriteLine("-- Min dist : %f", min_dist);

        ////-- Draw only "good" matches (i.e. whose distance is less than 2*min_dist,
        ////-- or a small arbitrary value ( 0.02 ) in the event that min_dist is very
        ////-- small)  //-- PS.- radiusMatch can also be used here.
        //List<DMatch> good_matches = new List<DMatch>();
        //for (int i = 0; i < descriptors_1.Rows; i++)
        //{
        //    if (matches[i].Distance <= Math.Max(2 * min_dist, 0.02))
        //    {
        //        good_matches.Add(matches[i]);
        //    }
        //}

        ////-- Draw only "good" matches
        //Mat img_matches = new Mat();
        //Cv2.DrawMatches(img_1, keypoints_1, img_2, keypoints_2,
        //    good_matches, img_matches, Scalar.All(-1), Scalar.All(-1),
        //    new List<byte>(), DrawMatchesFlags.NotDrawSinglePoints);

        ////-- Show detected matches  imshow( "Good Matches", img_matches );
        //for (int i = 0; i < (int)good_matches.Count; i++)
        //{
        //    Console.WriteLine("-- Good Match [%d] Keypoint 1: %d  -- Keypoint 2: %d", i,
        //        good_matches[i].QueryIdx, good_matches[i].TrainIdx);
        //}

        //Cv2.WaitKey(0);

#endregion

    }

    /***********************************************************************
    ************************** SUPPORT ROUTINES  ***************************
    ***********************************************************************/

    #region Support code for Get Corners on image region

    static Mat getEdges(Mat image, int threshold)
    {
        // Get the gradient image
        Mat result = new Mat();
        Cv2.MorphologyEx(image, result, MorphTypes.Gradient, new Mat());
        applyThreshold(result, threshold);

        return result;
    }

    static Mat createACross()
    {
        Mat cross = new Mat(5, 5, MatType.CV_8U, new Scalar(0));

        // creating the cross-shaped structuring element
        for (int i = 0; i < 5; i++)
        {
            cross.Set<byte>(2, i, 1);
            cross.Set<byte>(i, 2, 1);
        }

        return cross;
    }

    static Mat createADiamond()
    {
        Mat diamond = new Mat(5, 5, MatType.CV_8U, new Scalar(1));

        // Creating the diamond-shaped structuring element
        diamond.Set<byte>(0, 0, 0);
        diamond.Set<byte>(1, 0, 0);
        diamond.Set<byte>(3, 0, 0);
        diamond.Set<byte>(4, 0, 0);
        diamond.Set<byte>(0, 1, 0);
        diamond.Set<byte>(4, 1, 0);
        diamond.Set<byte>(0, 3, 0);
        diamond.Set<byte>(4, 3, 0);
        diamond.Set<byte>(4, 4, 0);
        diamond.Set<byte>(0, 4, 0);
        diamond.Set<byte>(1, 4, 0);
        diamond.Set<byte>(3, 4, 0);

        return diamond;
    }

    static Mat createASquare()
    {
        Mat Square = new Mat(5, 5, MatType.CV_8U, new Scalar(1));

        return Square;
    }

    static Mat createAXShape()
    {
        Mat x = new Mat(5, 5, MatType.CV_8U, new Scalar(0));

        // Creating the x-shaped structuring element
        for (int i = 0; i < 5; i++)
        {
            x.Set<byte>(i, i, 1);
            x.Set<byte>(4 - i, i, 1);
        }

        return x;
    }

    static void applyThreshold(Mat result, int threshold)
    {
        Cv2.Threshold(result, result, threshold, 255, ThresholdTypes.Binary);
    }

    static void IDTheCorners(Mat binary, Mat image)
    {
        for (int r = 0; r < binary.Rows; r++)
            for (int c = 0; c < binary.Cols; c++)
                if (binary.At<byte>(r, c) != 0)
                    Cv2.Circle(image, c, r, 5, new Scalar(255));
    }

    #endregion


    #region nearest palette color averaging

    /// <summary>
    ///
    /// </summary>
    /// <param name="src">input image</param>
    /// <param name="dst">output image</param>
    /// <param name="binSize">color channel size.
    /// i.e., Number of Red shades = floor(255/binSize),
    /// where binSize is between 1 and 128.</param>
    static void binImage(Mat src, out Mat dst, int binSize = 51)
    {
        dst = src.Clone();
        if (binSize <= 0 || binSize > 255) return;

        for (int r = 0; r<src.Rows; r++)
            for (int c = 0; c < src.Cols; c++)
            {
                Vec3b color = src.Get<Vec3b>(r, c);
                 int binCenter = binSize / 2;
                dst.Set<Vec3b>(r, c, new Vec3b(
                    // nearest color, note the size of the black and white
                    // bins are only half the size of the other bins.
                    // Note rounding colors to the nearest color palette
                    // can "sharply change" a gradient color in a region.
                    (byte)(((color[0] + binCenter) / binSize) * binSize),
                    (byte)(((color[1] + binCenter) / binSize) * binSize),
                    (byte)(((color[2] + binCenter) / binSize) * binSize)
                    ));
            }
    }

    #endregion
}

A Brief C++ to C# Walkthough

A full discussion on popular C++ API calls is available at opencvexamples.blogspot.com/p/learning-opencv-functions-step-by-step.html. The C# APIs are pretty much intuitive match to the C++ APIs. Also, see opencv home page for methods and classes discussion at: docs.opencv.org/3.1.0/ and (docs.opencv.org/2.4/ for backwards compatibility.)

The C# APIs namespace is at shimat.github.io/opencvsharp/, and github.com/shimat/opencvsharp/wiki.

The purpose of this walkthrough is to reflect the usage of the C# APIs when translating from C++ to C#. Note, we needed a CvTrackbar reference... because the C# CvTrackbar does not return an modified value within input arguments list...  whereas the C++ counter API does return a modified value in the input  parameter list. The intent of the walkthrough is not to explain the C++ library calls... but is to show the C# syntax required for translation.

C#
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using OpenCvSharp;
/*
    * Token from C++ example:
    * http://docs.opencv.org/master/d0/d2a/contours2_8cpp-example.html
    * C# APIs:
    * http://shimat.github.io/opencvsharp/html/1bb515b3-6278-49e4-9a33-1054bd279323.htm
    * Note to, IntelliSense will usually show the APIs arguments.
*/
class Program
{
    const int w = 500;
    static CvTrackbar Track;
    static Point[][] contours; // IntelliSense shows the vector array of vectors in C#
    static HierarchyIndex[] hierarchy;

    static void on_trackbar(int pos, object usedata) // The C# APIs doc shows void* is an object
    {
        int _levels = Track.Pos - 3;
        Mat cnt_img = Mat.Zeros(w, w, MatType.CV_8UC3);
        Cv2.DrawContours( cnt_img, contours, _levels <= 0 ? 3 : -1, Scalar.White,
            2, LineTypes.AntiAlias, hierarchy, Math.Abs(_levels) );
        Cv2.ImShow("contours", cnt_img);
    }

    static void Main()
    {
        Mat img = Mat.Zeros(w, w, MatType.CV_8UC1);

        //Draw "6" faces
        for( int i = 0; i < 6; i++ )
        {
            int dx = (i % 2) * 250 - 30;
            int dy = (i/2)*150;
            Scalar white = Scalar.White;
            Scalar black = Scalar.Black;

            if( i == 0 )
            {
                for( int j = 0; j <= 10; j++ )
                {
                    double angle = (j + 5) * Math.PI/21;
                    Cv2.Line(img,
                        new Point(Math.Round(dx + 100 + j * 10 - 80 * Math.Cos(angle), 0),
                            Math.Round(dy + 100 - 90 * Math.Sin(angle), 0)),
                        new Point(Math.Round(dx + 100 + j * 10 - 30 * Math.Cos(angle), 0),
                            Math.Round(dy + 100 - 30 * Math.Sin(angle), 0)),
                            white, 1);
                }
            }

            Cv2.Ellipse(img, new Point(dx + 150, dy + 100), new Size(100, 70), 0, 0, 360, white);
            Cv2.Ellipse(img, new Point(dx + 115, dy +  70), new Size( 30, 20), 0, 0, 360, black);
            Cv2.Ellipse(img, new Point(dx + 185, dy +  70), new Size( 30, 20), 0, 0, 360, black);
            Cv2.Ellipse(img, new Point(dx + 115, dy +  70), new Size( 15, 15), 0, 0, 360, white);
            Cv2.Ellipse(img, new Point(dx + 185, dy +  70), new Size( 15, 15), 0, 0, 360, white);
            Cv2.Ellipse(img, new Point(dx + 115, dy +  70), new Size(  5,  5), 0, 0, 360, black);
            Cv2.Ellipse(img, new Point(dx + 185, dy +  70), new Size(  5,  5), 0, 0, 360, black);
            Cv2.Ellipse(img, new Point(dx + 150, dy + 100), new Size( 10,  5), 0, 0, 360, black);
            Cv2.Ellipse(img, new Point(dx + 150, dy + 150), new Size( 40, 10), 0, 0, 360, black);
            Cv2.Ellipse(img, new Point(dx +  27, dy + 100), new Size( 20, 35), 0, 0, 360, white);
            Cv2.Ellipse(img, new Point(dx + 273, dy + 100), new Size( 20, 35), 0, 0, 360, white);
        }

        //show the faces
            Cv2.ImShow( "image", img );

        //Extract the contours
        Point[][] contours0;
        Cv2.FindContours( img, out contours0, out hierarchy,
        RetrievalModes.Tree, ContourApproximationModes.ApproxSimple);
        contours = new Point[contours0.Length][];

        for( int k = 0; k < contours0.Length; k++ )
            // Here the C# equivalent is of the C++ API is not a one-to-one match
            contours[k] = Cv2.ApproxPolyDP(contours0[k], 3, true);

        //show the contours and activate trackbar event control
        Cv2.NamedWindow("contours", WindowMode.AutoSize);
        Track = new CvTrackbar("levels+3", "contours", 4, 7, on_trackbar);
        on_trackbar(0, 0);

        Cv2.WaitKey();
    }
}

Another interesting C++ to C# code translation I noted is the threshold demo at docs.opencv.org/2.4/doc/tutorials/imgproc/threshold/threshold.html. The Threshold_Demo callback event required an alternative window definition specifying the window by name, causing a new thread or null pointer to execute inside the C++ Library.

C#
using OpenCvSharp;

class Program
{
    static void Main()
    {
        TestCode t = new TestCode();
        t.Function();
    }

// I did not want to use a lot of static objects so, I put the code
// in a user definite class. The code is derived from:
// http://docs.opencv.org/2.4/doc/tutorials/imgproc/threshold/threshold.html
// This code shows how to program multiple CvTrackbar controls.

    class TestCode
    {
        int max_BINARY_value = 255;
        CvTrackbar CvTrackbarValue;
        CvTrackbar CvTrackbarType;
        Mat src_gray = new Mat();
        Mat dst = new Mat();
        Window MyWindow;

        public void Function()
        {
            int threshold_value = 0;
            int threshold_type = 3;
            int max_value = 255;
            int max_type = 4;

            string trackbar_type = "Type: \n 0: Binary \n 1:
    Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted";
            string trackbar_value = "Value";

            // Load an image
            Mat src = new Mat("../../Images/cartoon-train.png", ImreadModes.Color);

            /// Convert the image to Gray
            Cv2.CvtColor(src, src_gray, ColorConversionCodes.BGR2GRAY);

            // Create output window
            MyWindow = new Window("Track", WindowMode.AutoSize);

            // Whenever the user changes the value of any of the Trackbars,
            // the function Threshold_Demo is called. I needed a common global
            // window object for Threshold_Demo and Function to prevent a
            // internal null reference.
            CvTrackbarType = MyWindow.CreateTrackbar(trackbar_type,
                            threshold_type, max_type, Threshold_Demo);

            CvTrackbarValue = MyWindow.CreateTrackbar(trackbar_value,
                            threshold_value, max_value, Threshold_Demo);

            /// Call the function to initialize
            Threshold_Demo(0, 0);

            /// Wait until user finishes program
            while (true)
            {
                int c;
                c = Cv2.WaitKey(20);
                if ((char)c == 27)
                { break; }
            }
        }

        void Threshold_Demo(int pos, object userdata)
        {
            /* 0: Binary
               1: Binary Inverted
               2: Threshold Truncated
               3: Threshold to Zero
               4: Threshold to Zero Inverted
             */

            Cv2.Threshold(src_gray, dst, CvTrackbarValue.Pos,
                max_BINARY_value, (ThresholdTypes)CvTrackbarType.Pos);

            MyWindow.Image = dst;
        }
    }
}

Points of Interest

I rewrote some of the more common C++ implementation demo code to show how it works in OpenCvSharp. I find using the Vision Studio Object Browser to view the OpenCvSharp3 API method parameters very helpful. I also recommend viewing the https://github.com/shimat/opencvsharp/releases code samples too. It illustrates additional API calling code snippets and implementation.

History

I added this article because I found no information on OpenCvSharp3 at this website.

In the first update: I added 2 additional code snippets for completeness.

In the second update: I fixed the problem with the Draw FillPoly code section.

In the third update: I only updated the article content. The downloadable code snippet section "nearest palette color averaging" works, but is slightly inferior to the code listed in the printout above. The download file doesn't allow for the bright colors. So, I added an offset value to the base colors. This makes the palette color closer to the real average and allow for pure white.

Also, because a nuget search on "opencvsharp" only shows version 2, I updated the text to reflect "opencvsharp3".

In the fourth update: I added 2 more complex examples to the code set.

In the fifth update: I added unsafe code to fix the float pointer translation in the Machine Learning snippet section.

In this update: I added a simple code walkthrough section.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)