Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to display camera streams on WinForm? #23

Open
Skullface9512 opened this issue Nov 5, 2024 · 23 comments
Open

How to display camera streams on WinForm? #23

Skullface9512 opened this issue Nov 5, 2024 · 23 comments
Labels
dotnet dotnet specific issue windows Windows specific issue

Comments

@Skullface9512
Copy link

I am developing a C# UI using IC4 camera. How can I display camera stream in real-time on the WinForm? Meanwhile, how to modify parameters such as resolution and exposure time for the camera and make it change in real-time? Is there any control that can be used?

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Nov 5, 2024

For display, just put a ic4.WinForms.Display on your form, as shown in the Windows Forms example applications: https://github.com/TheImagingSource/ic4-examples/tree/master/dotnet/winforms/DialogApp-net6/DialogApp-net6

The example program also shows how to display the pre-built property dialog that allows the user to adjust settings.

@Skullface9512
Copy link
Author

Recently i'm working on putting the camera video stream into a UI WinForm and show a cross in image, the code i modified like that:

namespace CameraTest.Forms
{
    public partial class Calibration : Form
    {
        public Calibration()
        {
            InitializeComponent();
            ic4.Library.Init(apiLogLevel: ic4.LogLevel.Info, logTargets: ic4.LogTarget.WinDebug);
        }

        private void btnOpen_Click(object sender, EventArgs e)
        {
            ic4.Grabber grabber = new ic4.Grabber();

            var selectedDeviceInfo = ic4.WinForms.Dialogs.ShowSelectDeviceDialog(this);
            if (selectedDeviceInfo == null)
                return;
            grabber.DeviceOpen(selectedDeviceInfo);
            var sink = new ic4.QueueSink(ic4.PixelFormat.BGR8, maxOutputBuffers: 1);
            sink.FramesQueued += (s, args) =>
            {
                // Get the new buffer from the sink
                // Buffer is disposed at the end of the using-block to allow re-queue
                using (var buffer = sink.PopOutputBuffer())
                {
                    // Create an OpenCVSharp view onto the buffer
                    // This view is only valid while the buffer itself exists,
                    // which is guaranteed by them both not being passed out of this function
                    var wrap = buffer.CreateOpenCvWrap();

                    int width = wrap.Width;
                    int height = wrap.Height;
                    //Console.WriteLine(width);
                    //Console.WriteLine(height);

                    OpenCvSharp.Point startPoint1 = new OpenCvSharp.Point(Convert.ToInt32(width / 2) - 500, Convert.ToInt32(height / 2));
                    OpenCvSharp.Point endPoint1 = new OpenCvSharp.Point(Convert.ToInt32(width / 2) + 500, Convert.ToInt32(height / 2)); 
                    OpenCvSharp.Point startPoint2 = new OpenCvSharp.Point(Convert.ToInt32(width / 2), Convert.ToInt32(height / 2) - 500);
                    OpenCvSharp.Point endPoint2 = new OpenCvSharp.Point(Convert.ToInt32(width / 2), Convert.ToInt32(height / 2) + 500);
                    Scalar color = new Scalar(0, 255, 0);
                    int thickness = 2;

                    Cv2.Line(wrap, startPoint1, endPoint1, color, thickness);
                    Cv2.Line(wrap, startPoint2, endPoint2, color, thickness);
                    display1.DisplayBuffer(buffer);
                }
            };

            // Start the stream
            grabber.StreamSetup(sink, display1,StreamSetupOption.AcquisitionStart);

            //grabber.StreamStop();
        }
    }
}

the running result is very strange, sometimes screen likely fix the first frame and don't change, sometimes it can show the video stream, but see no cross in image.

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Nov 7, 2024

The display should not be used manually and as stream target at the same time.

This line manually displays the modified image:

display1.DisplayBuffer(buffer);

This line automatically displays all (original) images:

grabber.StreamSetup(sink, display1,StreamSetupOption.AcquisitionStart);

Don't pass the display to StreamSetup and the cross should be displayed correctly.

@Skullface9512
Copy link
Author

Ok, it works, and if i want to close this window through a button, how can i add this function.

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Nov 7, 2024

"This Window"? You mean your Windows Form? Form.Close.

@Skullface9512
Copy link
Author

Just stop the video stream when click the button.

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Nov 7, 2024

To stop the video stream, call grabber.StreamStop()

@TIS-Tim TIS-Tim added dotnet dotnet specific issue windows Windows specific issue labels Nov 7, 2024
@Skullface9512
Copy link
Author

Where should I put this function in the above code and set relative button?

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Nov 7, 2024

I am very sorry, but Windows Forms programming really is outside the scope of this forum.

@Skullface9512
Copy link
Author

Rencently i tried to compile C# program in Win32, but come across some problems like that:
System.NotSupportedException:“IC Imaging Control 4 .NET does not support x86 Windows applications. Change your project settings to x64, or, when the project is set to 'Any CPU' disable 'Prefer 32-bit' in the build options.”
How can i solve this problem, thanks.

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Nov 25, 2024

You do exactly as the error message says. ic4 is not supported in 32-bit applications.

@Skullface9512
Copy link
Author

Hello, I want to take 4 pictures and display them in real-time on display 1, but I found that using sink TryPopOutputBuffer can only capture 3 images. Do you have any good methods?

 var sink = new ic4.QueueSink(ic4.PixelFormat.BGR8, maxOutputBuffers: 10);

 int save_num = 0;
 string file_name = $"{picture_dir}/MTF";
 if (!Directory.Exists(file_name))
 {
     Directory.CreateDirectory(file_name);
 }
 Console.WriteLine("file_name" + file_name);
 List<double> value = new List<double>();
 value.Add(0.0);
 value.Add(500.0);
 value.Add(1000.0);
 value.Add(1920.0);


 this.grabberL.StreamStop();
 this.grabberL.DeviceClose();
 var grabber2 = new ic4.Grabber();
 var selectedDeviceInfo2 = selectedDeviceInfoL;
 if (selectedDeviceInfo2 == null) { Console.WriteLine("无法读到设备"); return; }
 grabber2.DeviceOpen(selectedDeviceInfo2);


 Console.WriteLine("程序已启动,参数已传递。");
 sink.FramesQueued += (s, args) =>
 {
     Console.WriteLine("value.Count:"+ value.Count);
     if (sink.TryPopOutputBuffer(out ic4.ImageBuffer buffer) && save_num < value.Count+10)
     {

         try
         {
             string zoomingValue = value[save_num].ToString();
             Process.Start(motorFilePath, zoomingValue);
             buffer.SaveAsPng($"{file_name}/{save_num}.png");
             Console.WriteLine($"Saved image {$"{file_name}/{save_num}.png"}");
             display1.DisplayBuffer(buffer);
             //AnalyseMTFLeft(save_num);


         }
         catch (IC4Exception err)
         {
             Console.WriteLine($"Failed to save buffer: {err.Message}");
         }
         save_num++;
     }
 };
 Console.WriteLine("22222");
 // Start the stream
 grabber2.StreamSetup(sink, StreamSetupOption.AcquisitionStart);
 //this.grabberL = grabber2;
 Console.WriteLine("333333");```

and logcat is as follows:

```file_nameE:\WorkProject\Agent\CameraTest\bin\x64\Debug\picture/MTF
程序已启动,参数已传递。
22222
333333
value.Count:4
Saved image E:\WorkProject\Agent\CameraTest\bin\x64\Debug\picture/MTF/0.png
value.Count:4
Saved image E:\WorkProject\Agent\CameraTest\bin\x64\Debug\picture/MTF/1.png
value.Count:4
Saved image E:\WorkProject\Agent\CameraTest\bin\x64\Debug\picture/MTF/2.png```

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Nov 27, 2024

When posting code, please try to create a Minimal, Reproducible Example.

Your code does not show what happens after you call StreamSetup, so for all I know your program may just exit.

One wild guess: You do not dispose the buffer objects you get in the callback. Since garbage collection takes time, your program owns all buffers and no buffers are available for new images to be received.

Add a finally block to your try statement and call buffer.Dispose() to make sure the buffer is returned to the driver.

@Skullface9512
Copy link
Author

Hi,I tried method you advised before, and the modified code is:

{

    string motorFilePath = @"E:\WorkProject\Agent\CameraTest\bin\x64\Debug\exe\CH347Demo.exe";
    var sink = new ic4.QueueSink(ic4.PixelFormat.BGR8, maxOutputBuffers: 1);

    int save_num = 0;
    string file_name = $"{picture_dir}/VID";
    if (!Directory.Exists(file_name))
    {
        Directory.CreateDirectory(file_name);
    }
    Console.WriteLine("file_name" + file_name);
    List<double> value = new List<double>();
    value.Add(120.0);
    value.Add(28.0);
    value.Add(26.0);
    value.Add(25.3);
    value.Add(24.9);
    value.Add(23.5);
    value.Add(22.0);
    value.Add(21.0);
    value.Add(0.0);


    this.grabberL.StreamStop();
    this.grabberL.DeviceClose();
    var grabber2 = new ic4.Grabber();
    var selectedDeviceInfo2 = selectedDeviceInfoL;
    if (selectedDeviceInfo2 == null) { Console.WriteLine("can't read the device"); return; }
    grabber2.DeviceOpen(selectedDeviceInfo2);

    sink.FramesQueued += (s, args) =>
    {
        Console.WriteLine("value.Count:" + value.Count);

        if (sink.TryPopOutputBuffer(out ic4.ImageBuffer buffer)&& save_num< value.Count)
        {

            try
            {
                string zoomingValue = value[save_num].ToString();
                Console.WriteLine("zoomingValue:" + zoomingValue);
                Process.Start(motorFilePath, zoomingValue);
                Console.WriteLine("----start----");
                Thread.Sleep(30);
                Console.WriteLine("----stop----");
                buffer.SaveAsPng($"{file_name}/{save_num}.png");
                Console.WriteLine($"Saved image {$"{file_name}/{save_num}.png"}");
                display1.DisplayBuffer(buffer);
                //AnalyseMTFLeft(save_num);

            }
            catch (IC4Exception err)
            {
                Console.WriteLine($"Failed to save buffer: {err.Message}");
            }
            finally
            {
                buffer.Dispose();
            }
            save_num++;
        }

    };
    Console.WriteLine("22222");
    // Start the stream
    grabber2.StreamSetup(sink, StreamSetupOption.AcquisitionStart);
    //this.grabberL = grabber2;
    Console.WriteLine("333333");
}```

In the code 'Process.Start(motorFilePath, zoomingValue);' means adjust camera's focal length, The result is very strange, the first time it take photos, the image taken is the same as focusing before,do you why hanppened this, thank you so much for giving me so many recommendations.

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Nov 28, 2024

You do realize that *PopOutputBuffer does not really synchronize to the camera? So you controlling some external motor depending on that function returning will not magically delay the next image being taken, and most images you receive are older than your motor movement. Additionally, are you sure that the external application is really done after 30ms?

The correct way to take images at a specific time on application request is the software trigger function.

@Skullface9512
Copy link
Author

what is software trigger function? I found https://www.theimagingsource.com/en-us/documentation/ic4dotnet/articles/grabbing-an-image.html using SnapSink.SnapSingle to grab the single image, can it meet my demand?

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Nov 29, 2024

There currently is no simple example showing software trigger. But a complex one: EventExposureEnd

But you might get the gist of it (which is enable TriggerMode and execute TriggerSoftware, still using QueueSink).

SnapSingle grab an image from the free-running stream of images, which by definition always looks a little into the past.

@janeshuu
Copy link

janeshuu commented Dec 2, 2024

Hi, I want to use two cameras to display the camera streams on different display at the same time, but now I found that I have to turn off one of the cameras for the other camera to work. How should I solve this problem?
The code is as follow:

 private void btnOpenLeft_Click(object sender, EventArgs e)
        {            
            var selectedDeviceInfoL = ic4.WinForms.Dialogs.ShowSelectDeviceDialog(this);
            if (selectedDeviceInfoL == null) { return; }
            this.selectedDeviceInfoL = selectedDeviceInfoL;
            Resources.CameraParameter.selectedDeviceInfoL = selectedDeviceInfoL;
            ic4.Grabber grabber = new Grabber();
            var selectedDeviceInfo2 = this.selectedDeviceInfoL;
            grabber.DeviceOpen(selectedDeviceInfo2);
            Console.WriteLine($"Opened device {grabber.DeviceInfo.ModelName}");

            var sink = new ic4.QueueSink(ic4.PixelFormat.BGR8, maxOutputBuffers: 1);
            sink.FramesQueued += (s, args) =>
            {
                using (var buffer = sink.PopOutputBuffer())
                {
                    var wrap = buffer.CreateOpenCvWrap();

                    this.width = wrap.Width;
                    this.height = wrap.Height;


                    OpenCvSharp.Point startPoint1 = new OpenCvSharp.Point(Convert.ToInt32(width / 2) - 500, Convert.ToInt32(height / 2));
                    OpenCvSharp.Point endPoint1 = new OpenCvSharp.Point(Convert.ToInt32(width / 2) + 500, Convert.ToInt32(height / 2));
                    OpenCvSharp.Point startPoint2 = new OpenCvSharp.Point(Convert.ToInt32(width / 2), Convert.ToInt32(height / 2) - 500);
                    OpenCvSharp.Point endPoint2 = new OpenCvSharp.Point(Convert.ToInt32(width / 2), Convert.ToInt32(height / 2) + 500);

                    Cv2.Line(wrap, startPoint1, endPoint1, color, thickness);
                    Cv2.Line(wrap, startPoint2, endPoint2, color, thickness);

                    display1.DisplayBuffer(buffer);
                }
            };

            grabber.StreamSetup(sink, StreamSetupOption.AcquisitionStart);
            this.grabberL = grabber;
}
        private void btnOpenRight_Click(object sender, EventArgs e)
        {            
            var selectedDeviceInfoR = ic4.WinForms.Dialogs.ShowSelectDeviceDialog(this);
            if (selectedDeviceInfoR == null) { return; }
            this.selectedDeviceInfoR = selectedDeviceInfoR;
            Resources.CameraParameter.selectedDeviceInfoR = selectedDeviceInfoR;
            ic4.Grabber grabber = new Grabber();
            var selectedDeviceInfo2 = this.selectedDeviceInfoR;
            grabber.DeviceOpen(selectedDeviceInfo2);
            Console.WriteLine($"Opened device {grabber.DeviceInfo.ModelName}");

            var sink = new ic4.QueueSink(ic4.PixelFormat.BGR8, maxOutputBuffers: 1);
            sink.FramesQueued += (s, args) =>
            {
                using (var buffer = sink.PopOutputBuffer())
                {
                    var wrap = buffer.CreateOpenCvWrap();

                    this.width = wrap.Width;
                    this.height = wrap.Height;


                    OpenCvSharp.Point startPoint1 = new OpenCvSharp.Point(Convert.ToInt32(width / 2) - 500, Convert.ToInt32(height / 2)); 
                    OpenCvSharp.Point endPoint1 = new OpenCvSharp.Point(Convert.ToInt32(width / 2) + 500, Convert.ToInt32(height / 2));  
                    OpenCvSharp.Point startPoint2 = new OpenCvSharp.Point(Convert.ToInt32(width / 2), Convert.ToInt32(height / 2) - 500);
                    OpenCvSharp.Point endPoint2 = new OpenCvSharp.Point(Convert.ToInt32(width / 2), Convert.ToInt32(height / 2) + 500);

                    Cv2.Line(wrap, startPoint1, endPoint1, color, thickness);
                    Cv2.Line(wrap, startPoint2, endPoint2, color, thickness);
                    display2.DisplayBuffer(buffer);
                }
            };

            grabber.StreamSetup(sink, StreamSetupOption.AcquisitionStart);
            this.grabberR = grabber;
}

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Dec 2, 2024

Which cameras are you using?
Do the two cameras work at the same time with the same settings in 2 instances of ic4-demoapp?

If you operate two cameras on the same network adapter or USB controllers, you have to take bandwidth into consideration. Set AcquisitionFrameRate to half the maximum for both cameras if you only have one interface.

@janeshuu
Copy link

janeshuu commented Dec 2, 2024

Two cameras both named DFM 37UX178-ML
Where can I set AcquisitionFrameRate?

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Dec 2, 2024

Please have a look at the documentation: Configuring a Video Capture Device

(It does not explicitly set AcquisitionFrameRate, but you should get the idea.)

@janeshuu
Copy link

janeshuu commented Dec 2, 2024

It worked, thank you.
By the way, is there an interface to obtain the maximum frame rate? I can only check it through IC capture2.5 software

@TIS-Tim
Copy link
Contributor

TIS-Tim commented Dec 2, 2024

The documentation describes how to access and query information about the different types of device properties.

(You could also use the device property dialog in ic4-demoapp instead of IC Capture.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dotnet dotnet specific issue windows Windows specific issue
Projects
None yet
Development

No branches or pull requests

3 participants