How to implement circle detection in C#

In this lecture you will find detailed information on how to implement circle detection in C# with your camera application using the Ozeki Camera SDK. To achieve your aims, Ozeki Camera SDK has to be installed and a reference to OzekiSDK.dll has to be added to your Visual Studio project.

detected circles
Figure 1 - Detected circles

Important: you should study this article in order to find out how to setup your Windows Forms Application correctly.

Getting started

To get started it is recomended to Download and Install the latest version of Ozeki Camera SDK. After installation you can find the example code discussed in this page with full source code in the following location on your harddisk:

Download Ozeki Camera SDK: https://www.camera-sdk.com/https://ozeki-sms-gateway.com/p_595-how-to-configure-the-firewall-for-smpp.html
Windows forms version: C:\Users\user\Documents\Ozeki\Ozeki SDK\Examples\Camera\04_ComputerVision\03_CircleDetection\CircleDetection

To compile this example you will need Microsoft Visual Studio installed on your computer.

Corporate use of circle detection

The circle detection function can be used efficiently in several machine vision applications. The circle detection function is an important application for examining industrial parts or tools.

Circle detection can be a very effective way to make your organizing and storage more easier. For example, in a warehouse where a million items can be found, circle detection can serve your purpose well. With the help of circle detection you can easily detect the circle-shaped items in smaller or larger areas.

Implement circle detection in C#

Some computer vision possibilities which are supplied by the SDK require an extra ToolKit. This ToolKit is the OzekiComputerVision.dll to which a reference has to be added in the project, just like in the case of the OzekiSDK.dll. This dll is responsible for the algorithms and tools which are necessary for the CV (Computer Vision) actions.

Circle Detection

The ICircleDetector object of the SDK can be used for detecting circles. After an instance has been created with the help of the static ImageProcesserFactory class, we can detect on frames and on video as well.

In the case of frames the output image can be created by the Process() method of the instance. In the case of videos we have to use the ImageProcesserHandler mediahandler.

ImageProcesserHandler:

This is a mediahandler from a VideoHandler class (so it is VideoReceiver and VideoSender at the same time) which means that the input can be a VideoSender (for example WebCamera) and the output can also be created for a VideoReceiver. These connections can be achieved with the help of an instance of the MediaConnector class in the SDK.

No actions are executed on the input frames they are simply forwarded by default. However, the object which implement the IImageProcesser interface (for example the ICircleDetector) can be added with the help of the AddProcesser() method. More instance can be added which implement IImageProcesser, they will run one after the other using the image which is before them in the list.

Global Variables

  • WebCamera _webCamera;
  • Webcamera instance, with the help of this we can get the images

  • CameraURLBuilderWF _myCameraUrlBuilder;
  • Make access to all avaiable cameras, which you can select from afterwards.

  • MediaConnector _connector;
  • Using this we can connect the mediahandlers

  • ImageProcesserHandler _imageProcesserHandler;
  • This is a Mediahandler, which runs the IImageProcesser interface (this processes the images) on the incoming video

  • ICircleDetector _circleDetector;
  • This is an image processer interface which can detect circles, this implements the IImageProcesser interface

  • VideoViewerWF instances

    This is a GUI tool which is responsible for displaying the video for Windows Forms applications

  • DrawingImageProvider instances
  • Mediahandler which prepares the image which is sent by the mediahandlers from the VideoSender class for the VideoViewerWF instances.

Methods

  • Init()
  • The initialization of the global variables is the task of the Init() method. The instance of the FrameCapture mediahandler are set here and the ICircleDetector instance is also created here with the help of the ImageProcesserFactory. This also can be added to the ImageProcesserHandler instance. The processing of images is handled by the DetectionOccurred event of the ICircleDetector class to which we can subscribe here.

  • SetVideoViewers()
  • The SetVideoViewer() creates and initialize the objects which are responsible for the displaying of the video. It defines the VideoViewerWF instances, configures their properties and assigns the appropiate DrawingImageProvider instances. Furthermore, the SetVideoViewers() adds these instances to the GUI.

  • InvokeGUIThread()
  • This method handles the GUI thread. It performs a specific method on the GUI thread in asynchronous mode.

  • InitDetectorFields()
  • This method fills the TextBox-es which can be found on the GUI with the actual settings of the ICircleDetector instance using the InvokeGUIThread() helper method.

  • ConnectCam()
  • This method connects the appropiate mediahandler instances with the help of the MediaConnector instance. One of the ImageProvider object recives the original image of the webcamera and the other object receives the processed image.

  • Start()
  • After the initializations the mediahandlers can start to operate which is the responsibility of the Start() method

Configurations:

  • AccumulatorResolution: detects the center of the circles. From this value we can get the accumulator's resolution
  • CannyThreshold: this vlaue will be used by the inner edge detector
  • CircleAccumulatorThreshold: the closer the detected circles are to this value the forwarder they are brought in the list of matches. If this value is smaller, the chance to detect false circles are bigger
  • MaximumRadius: the size of the radius of circles which can be detected by the detector
  • MinimumDistance: the minimum distance between the possibly detected circles. If this value is too small we can detect false circles. If this value is too big we can miss some detections
  • MinimumRadius: the minimum of the radius of the circle which we would like to detect with the detector

PostProcess configurations

After the detection we can configure the behavior of the output image and what changes are required. Examples for this kind of configurations:

  • ShowImage: declares whether the original image should be shown or only the detected shapes should be seen with black backround. It is important if there are more image processer algorithm are running after each other.
  • DrawColor: the color of the selection of the detected object can be provided here
  • DrawThickness: the thickness of the selection of the detected onject can be provided here

Detection:

After all image/frame processing a DetectionOccurred event is popped up. In the arguments of this event you can find the list of detected circles where the centers/radius and area of the circles can be queried.

C# code example for circle detection

Windows Form  

Windows forms version

MainForm.cs

using System;
using System.Windows.Forms;
using Ozeki.Camera;
using Ozeki.Media;
using Ozeki.Vision;

namespace CircleDetection_WF
{
    public partial class MainForm : Form
    {
        private OzekiCamera _camera;
        private MediaConnector _connector;
        private CameraURLBuilderWF _myCameraUrlBuilder;
        private ImageProcesserHandler _imageProcesserHandler;
        private ICircleDetector _circleDetector;
        private DrawingImageProvider _originalImageProvider;
        private DrawingImageProvider _processedImageProvider;

        public MainForm()
        {
            InitializeComponent();
        }

        void MainForm_Load(object sender, EventArgs e)
        {
            Init();

            SetVideoViewers();

            InitDetectorFields();
        }

        void Init()
        {
            _myCameraUrlBuilder = new CameraURLBuilderWF();
            _connector = new MediaConnector();
            _originalImageProvider = new DrawingImageProvider();
            _processedImageProvider = new DrawingImageProvider();

            _circleDetector = ImageProcesserFactory.CreateCircleDetector();
            _circleDetector.DetectionOccurred += _circleDetector_DetectionOccurred;

            _imageProcesserHandler = new ImageProcesserHandler();
            _imageProcesserHandler.AddProcesser(_circleDetector);

        }

        void SetVideoViewers()
        {
            OriginalViewer.SetImageProvider(_originalImageProvider);
            ProcessedViewer.SetImageProvider(_processedImageProvider);
        }

        void InitDetectorFields()
        {
            InvokeGuiThread(() =>
            {
                tb_CannyThreshold.Text = _circleDetector.CannyThreshold.ToString();
                tb_CannyThresholdLinking.Text = _circleDetector.CircleAccumulatorThreshold.ToString();
            });
        }

        void ConnectCam()
        {
            _connector.Connect(_camera.VideoChannel, _originalImageProvider);
            _connector.Connect(_camera.VideoChannel, _imageProcesserHandler);
            _connector.Connect(_imageProcesserHandler, _processedImageProvider);

        }

        void Start()
        {
            OriginalViewer.Start();
            ProcessedViewer.Start();
            _camera.Start();
            _imageProcesserHandler.Start();
        }

        void btn_HighlightSet_Click(object sender, EventArgs e)
        {
            InvokeGuiThread(() =>
            {
                _circleDetector.CannyThreshold = Double.Parse(tb_CannyThreshold.Text);
                _circleDetector.CircleAccumulatorThreshold = Double.Parse(tb_CannyThresholdLinking.Text);
            });
        }

        void _circleDetector_DetectionOccurred(object sender, CircleDetectedEventArgs e)
        {
            InvokeGuiThread(() =>
            {
                lb_Detection.Items.Clear();

                foreach (var info in e.Info)
                {
                    lb_Detection.Items.Add(info);
                }
            });
        }

        void InvokeGuiThread(Action action)
        {
            BeginInvoke(action);
        }

        private void button_Compose_Click(object sender, EventArgs e)
        {
            var result = _myCameraUrlBuilder.ShowDialog();

            if (result != DialogResult.OK) return;

            tb_cameraUrl.Text = _myCameraUrlBuilder.CameraURL;

            button_Connect.Enabled = true;
        }

        private void button_Connect_Click(object sender, EventArgs e)
        {
            if (_camera != null)
            {
                _camera.CameraStateChanged -= _camera_CameraStateChanged;
                _camera.Disconnect();
                _connector.Disconnect(_camera.VideoChannel, _processedImageProvider);
                _connector.Disconnect(_camera.VideoChannel, _originalImageProvider);
                _camera.Dispose();
                _camera = null;
            }

            _camera = new OzekiCamera(_myCameraUrlBuilder.CameraURL);
            _camera.CameraStateChanged += _camera_CameraStateChanged;
            ConnectCam();
            Start();
        }

        private void _camera_CameraStateChanged(object sender, CameraStateEventArgs e)
        {
            InvokeGuiThread(() =>
            {
                if (e.State == CameraState.Connecting)
                    button_Connect.Enabled = false;
                if (e.State == CameraState.Streaming)
                    button_Disconnect.Enabled = true;
                if (e.State == CameraState.Disconnected)
                {
                    button_Disconnect.Enabled = false;
                    button_Connect.Enabled = true;
                }
            });

            InvokeGuiThread(() =>
            {
                stateLabel.Text = e.State.ToString();
            });
        }

        private void button_Disconnect_Click(object sender, EventArgs e)
        {
            
            _camera.Disconnect();
            _connector.Disconnect(_camera.VideoChannel, _processedImageProvider);
            _connector.Disconnect(_camera.VideoChannel, _originalImageProvider);
            _camera = null;
        }
    }
}

		

Code 1 - Circle detection example code in C#

GUI

After you have downloaded the Ozeki Camera SDK software you can find the GUI code in the Example folder.

graphical user interface
Figure 2 - The graphical user interface of your application

Windows forms version

MainForm.Designer.cs

namespace CircleDetection_WF
{
    partial class MainForm
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        private void InitializeComponent()
        {
            this.label1 = new System.Windows.Forms.Label();
            this.label2 = new System.Windows.Forms.Label();
            this.tb_CannyThreshold = new System.Windows.Forms.TextBox();
            this.tb_CannyThresholdLinking = new System.Windows.Forms.TextBox();
            this.label5 = new System.Windows.Forms.Label();
            this.label4 = new System.Windows.Forms.Label();
            this.groupBox2 = new System.Windows.Forms.GroupBox();
            this.btn_HighlightSet = new System.Windows.Forms.Button();
            this.lb_Detection = new System.Windows.Forms.ListBox();
            this.label12 = new System.Windows.Forms.Label();
            this.OriginalViewer = new Ozeki.Media.VideoViewerWF();
            this.ProcessedViewer = new Ozeki.Media.VideoViewerWF();
            this.groupBox5 = new System.Windows.Forms.GroupBox();
            this.stateLabel = new System.Windows.Forms.Label();
            this.label14 = new System.Windows.Forms.Label();
            this.button_Connect = new System.Windows.Forms.Button();
            this.button_Disconnect = new System.Windows.Forms.Button();
            this.tb_cameraUrl = new System.Windows.Forms.TextBox();
            this.label13 = new System.Windows.Forms.Label();
            this.button_Compose = new System.Windows.Forms.Button();
            this.groupBox2.SuspendLayout();
            this.groupBox5.SuspendLayout();
            this.SuspendLayout();

            this.label1.AutoSize = true;
            this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
            this.label1.Location = new System.Drawing.Point(7, 357);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(87, 13);
            this.label1.TabIndex = 0;
            this.label1.Text = "Original image";

            this.label2.AutoSize = true;
            this.label2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
            this.label2.Location = new System.Drawing.Point(348, 357);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(103, 13);
            this.label2.TabIndex = 1;
            this.label2.Text = "Processed image";

            this.tb_CannyThreshold.Location = new System.Drawing.Point(148, 26);
            this.tb_CannyThreshold.Name = "tb_CannyThreshold";
            this.tb_CannyThreshold.Size = new System.Drawing.Size(87, 20);
            this.tb_CannyThreshold.TabIndex = 4;

            this.tb_CannyThresholdLinking.Location = new System.Drawing.Point(148, 57);
            this.tb_CannyThresholdLinking.Name = "tb_CannyThresholdLinking";
            this.tb_CannyThresholdLinking.Size = new System.Drawing.Size(87, 20);
            this.tb_CannyThresholdLinking.TabIndex = 5;

            this.label5.AutoSize = true;
            this.label5.Location = new System.Drawing.Point(6, 60);
            this.label5.Name = "label5";
            this.label5.Size = new System.Drawing.Size(136, 13);
            this.label5.TabIndex = 12;
            this.label5.Text = "CircleAccumulatorTreshold:";

            this.label4.AutoSize = true;
            this.label4.Location = new System.Drawing.Point(6, 26);
            this.label4.Name = "label4";
            this.label4.Size = new System.Drawing.Size(87, 13);
            this.label4.TabIndex = 11;
            this.label4.Text = "CannyThreshold:";

            this.groupBox2.Controls.Add(this.label5);
            this.groupBox2.Controls.Add(this.tb_CannyThresholdLinking);
            this.groupBox2.Controls.Add(this.btn_HighlightSet);
            this.groupBox2.Controls.Add(this.label4);
            this.groupBox2.Controls.Add(this.tb_CannyThreshold);
            this.groupBox2.Location = new System.Drawing.Point(358, 12);
            this.groupBox2.Name = "groupBox2";
            this.groupBox2.Size = new System.Drawing.Size(315, 89);
            this.groupBox2.TabIndex = 13;
            this.groupBox2.TabStop = false;
            this.groupBox2.Text = "Highlight";

            this.btn_HighlightSet.Location = new System.Drawing.Point(251, 54);
            this.btn_HighlightSet.Name = "btn_HighlightSet";
            this.btn_HighlightSet.Size = new System.Drawing.Size(58, 23);
            this.btn_HighlightSet.TabIndex = 19;
            this.btn_HighlightSet.Text = "Set";
            this.btn_HighlightSet.UseVisualStyleBackColor = true;
            this.btn_HighlightSet.Click += new System.EventHandler(this.btn_HighlightSet_Click);

            this.lb_Detection.FormattingEnabled = true;
            this.lb_Detection.Location = new System.Drawing.Point(10, 390);
            this.lb_Detection.Name = "lb_Detection";
            this.lb_Detection.Size = new System.Drawing.Size(657, 108);
            this.lb_Detection.TabIndex = 14;

            this.label12.AutoSize = true;
            this.label12.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
            this.label12.Location = new System.Drawing.Point(7, 374);
            this.label12.Name = "label12";
            this.label12.Size = new System.Drawing.Size(91, 13);
            this.label12.TabIndex = 15;
            this.label12.Text = "Detection info:";

            this.OriginalViewer.BackColor = System.Drawing.Color.Black;
            this.OriginalViewer.FlipMode = Ozeki.Media.FlipMode.None;
            this.OriginalViewer.FrameStretch = Ozeki.Media.FrameStretch.Uniform;
            this.OriginalViewer.FullScreenEnabled = true;
            this.OriginalViewer.Location = new System.Drawing.Point(10, 110);
            this.OriginalViewer.Name = "OriginalViewer";
            this.OriginalViewer.RotateAngle = 0;
            this.OriginalViewer.Size = new System.Drawing.Size(320, 240);
            this.OriginalViewer.TabIndex = 17;
            this.OriginalViewer.Text = "videoViewerWF1";

            this.ProcessedViewer.BackColor = System.Drawing.Color.Black;
            this.ProcessedViewer.FlipMode = Ozeki.Media.FlipMode.None;
            this.ProcessedViewer.FrameStretch = Ozeki.Media.FrameStretch.Uniform;
            this.ProcessedViewer.FullScreenEnabled = true;
            this.ProcessedViewer.Location = new System.Drawing.Point(351, 110);
            this.ProcessedViewer.Name = "ProcessedViewer";
            this.ProcessedViewer.RotateAngle = 0;
            this.ProcessedViewer.Size = new System.Drawing.Size(320, 240);
            this.ProcessedViewer.TabIndex = 18;
            this.ProcessedViewer.Text = "videoViewerWF1";
 
            this.groupBox5.Controls.Add(this.stateLabel);
            this.groupBox5.Controls.Add(this.label14);
            this.groupBox5.Controls.Add(this.button_Connect);
            this.groupBox5.Controls.Add(this.button_Disconnect);
            this.groupBox5.Controls.Add(this.tb_cameraUrl);
            this.groupBox5.Controls.Add(this.label13);
            this.groupBox5.Controls.Add(this.button_Compose);
            this.groupBox5.Location = new System.Drawing.Point(12, 12);
            this.groupBox5.Name = "groupBox5";
            this.groupBox5.Size = new System.Drawing.Size(340, 89);
            this.groupBox5.TabIndex = 21;
            this.groupBox5.TabStop = false;
            this.groupBox5.Text = "Connect";

            this.stateLabel.AutoSize = true;
            this.stateLabel.Location = new System.Drawing.Point(80, 72);
            this.stateLabel.Name = "stateLabel";
            this.stateLabel.Size = new System.Drawing.Size(0, 13);
            this.stateLabel.TabIndex = 24;

            this.label14.AutoSize = true;
            this.label14.Location = new System.Drawing.Point(39, 72);
            this.label14.Name = "label14";
            this.label14.Size = new System.Drawing.Size(35, 13);
            this.label14.TabIndex = 23;
            this.label14.Text = "State:";

            this.button_Connect.Enabled = false;
            this.button_Connect.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
            this.button_Connect.ForeColor = System.Drawing.Color.Black;
            this.button_Connect.Location = new System.Drawing.Point(83, 39);
            this.button_Connect.Name = "button_Connect";
            this.button_Connect.Size = new System.Drawing.Size(80, 23);
            this.button_Connect.TabIndex = 18;
            this.button_Connect.Text = "Connect";
            this.button_Connect.UseVisualStyleBackColor = true;
            this.button_Connect.Click += new System.EventHandler(this.button_Connect_Click);
 
            this.button_Disconnect.Enabled = false;
            this.button_Disconnect.Location = new System.Drawing.Point(169, 39);
            this.button_Disconnect.Name = "button_Disconnect";
            this.button_Disconnect.Size = new System.Drawing.Size(80, 23);
            this.button_Disconnect.TabIndex = 22;
            this.button_Disconnect.Text = "Disconnect";
            this.button_Disconnect.UseVisualStyleBackColor = true;
            this.button_Disconnect.Click += new System.EventHandler(this.button_Disconnect_Click);

            this.tb_cameraUrl.Location = new System.Drawing.Point(83, 13);
            this.tb_cameraUrl.Name = "tb_cameraUrl";
            this.tb_cameraUrl.ReadOnly = true;
            this.tb_cameraUrl.Size = new System.Drawing.Size(166, 20);
            this.tb_cameraUrl.TabIndex = 21;

            this.label13.AutoSize = true;
            this.label13.Location = new System.Drawing.Point(6, 16);
            this.label13.Name = "label13";
            this.label13.Size = new System.Drawing.Size(71, 13);
            this.label13.TabIndex = 20;
            this.label13.Text = "Camera URL:";

            this.button_Compose.Location = new System.Drawing.Point(255, 11);
            this.button_Compose.Name = "button_Compose";
            this.button_Compose.Size = new System.Drawing.Size(80, 23);
            this.button_Compose.TabIndex = 19;
            this.button_Compose.Text = "Compose";
            this.button_Compose.UseVisualStyleBackColor = true;
            this.button_Compose.Click += new System.EventHandler(this.button_Compose_Click);

            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(680, 507);
            this.Controls.Add(this.groupBox5);
            this.Controls.Add(this.ProcessedViewer);
            this.Controls.Add(this.OriginalViewer);
            this.Controls.Add(this.label12);
            this.Controls.Add(this.lb_Detection);
            this.Controls.Add(this.groupBox2);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.label1);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.MaximizeBox = false;
            this.Name = "MainForm";
            this.Text = "Circle Detection";
            this.Load += new System.EventHandler(this.MainForm_Load);
            this.groupBox2.ResumeLayout(false);
            this.groupBox2.PerformLayout();
            this.groupBox5.ResumeLayout(false);
            this.groupBox5.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();
        }

        #endregion

        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.TextBox tb_CannyThreshold;
        private System.Windows.Forms.TextBox tb_CannyThresholdLinking;
        private System.Windows.Forms.Label label5;
        private System.Windows.Forms.Label label4;
        private System.Windows.Forms.GroupBox groupBox2;
        private System.Windows.Forms.ListBox lb_Detection;
        private System.Windows.Forms.Button btn_HighlightSet;
        private System.Windows.Forms.Label label12;
        private Ozeki.Media.VideoViewerWF OriginalViewer;
        private Ozeki.Media.VideoViewerWF ProcessedViewer;
        private System.Windows.Forms.GroupBox groupBox5;
        private System.Windows.Forms.Button button_Connect;
        private System.Windows.Forms.Button button_Disconnect;
        private System.Windows.Forms.TextBox tb_cameraUrl;
        private System.Windows.Forms.Label label13;
        private System.Windows.Forms.Button button_Compose;
        private System.Windows.Forms.Label stateLabel;
        private System.Windows.Forms.Label label14;
    }
}
		

Code 2 - Circle detection GUI example in C#

Conclusion

Based on the information that you can find on this webpage you will be able to efficiently implement the circle detection function with your C# camera application using the Ozeki Camera SDK.

Related pages

FAQ

Below you can find the answers for the most frequently asked questions related to this topic:

  1. Is there a maximum of circles that can be detected with the help of the circle detection function of the Ozeki Camera SDK?

    No, there is no limit. You can detect all circles with one function using the Ozeki Camera SDK.

  2. Am I able to detect an exact amount of circles? For istance, can I filter which size of circle would I like to detect?

    Yes, you are able to detect only those circles of which you provided the size previously.

  3. I have not managed to build the solution. How to solve it?

    • Please set the Target framework property of the project to .NET 4.0.
    • You should add the OzekiSDK.dll to the references of the solution.
    • Please import the missing classes.

More information