2

I'm working on a project that involves object detection with real-time video in C#. As part of the project, I use a BlockingCollection to manage a list of bounding boxes for detected objects, and the details for each bounding box are held in a tuple. However, I'm encountering an error CS1503 when trying to add new tuples to the collection. Here's how I defined the BlockingCollection:

BlockingCollection<Tuple<int, int, int, int, string, string, Scalar, DateTime>> ObjectDetectionBbox = new BlockingCollection<Tuple<int, int, int, int, string, string, Scalar, DateTime>>();

And here's the line where I'm trying to add a new tuple to the collection:

ObjectDetectionBbox.Add(Tuple.Create(
    topLeft.X,                        
    topLeft.Y,                        
    bottomRight.X - topLeft.X,        
    bottomRight.Y - topLeft.Y,        
    className,                        
    trackedName,                      
    classColor,                       
    bboxTimestamp                     
));

The error message is:

Error CS1503 Argument 1: cannot convert from 'System.Tuple<int, int, int, int, string, string, OpenCvSharp.Scalar, System.Tuple<System.DateTime>>' to 'System.Tuple<int, int, int, int, string, string, OpenCvSharp.Scalar, System.DateTime>'

The error message seems to suggest that my tuple has a Tuple as the last element, but as you can see in my code, the last element is a DateTime.

I've confirmed that bboxTimestamp is indeed a DateTime, and I'm not sure why this error is happening. enter image description here

enter image description here

Does anyone know why this might be happening and how I could fix it? Any help would be greatly appreciated.

HERE IS THE FULL CODE FOR REFERENCE:

public async Task Main(System.Windows.Controls.Image imageControl, CancellationToken token)
        {
            BlockingCollection<Tuple<int, int, int, int, string, string, Scalar, DateTime>> ObjectDetectionBbox = new BlockingCollection<Tuple<int, int, int, int, string, string, Scalar, DateTime>>();
            BlockingCollection<Tuple<Mat, DateTime>> frameBuffer = new BlockingCollection<Tuple<Mat, DateTime>>(); // Our new frame buffer

            string cameraUrl = "http://cam1.infolink.ru/mjpg/video.mjpg";
            //string cameraUrl = "http://hotel-seerose.dyndns.org:8001/cgi-bin/faststream.jpg?stream=full&fps=0";
            string URLreturnBbox = "http://localhost:8003/api/v1/camera/get_frame";  // Updated endpoint URL
            await initializeRunServer(cameraUrl);
            var idFaceDictionary = new ConcurrentDictionary<long, object>();
            var classNames = await GetClassNames();
            var colorList = InitColorList(classNames.Count);

            Task processFrameTask = Task.Run(() => ProcessTask(imageControl, token, URLreturnBbox, classNames, colorList,idFaceDictionary, ObjectDetectionBbox));
            

            // Create video source
            MJPEGStream stream = new MJPEGStream(cameraUrl);


            // New frame event handler
            stream.NewFrame += (sender, eventArgs) =>
            {
                // Convert AForge.NET's Bitmap to OpenCvSharp's Mat
                Bitmap bitmap = (Bitmap)eventArgs.Frame.Clone();
                Mat frame = OpenCvSharp.Extensions.BitmapConverter.ToMat(bitmap);
                
                // Get bbox info from ObjectDetectionBbox, if any
                while (ObjectDetectionBbox.TryTake(out var bbox)) // Replace if with while to draw all available bounding boxes
                {
                    // Ensure we only proceed if there is a detection and a valid class name.
                    if (!string.IsNullOrEmpty(bbox.Item5))
                    {
                        if (bbox.Item5 == "person" && bbox.Item6 != null) // Display if person is detected
                        {
                            DrawPerson(
                                frame,
                                bbox.Item1,
                                bbox.Item2,
                                bbox.Item3,
                                bbox.Item4,
                                bbox.Item6,
                                bbox.Item7
                            );
                        }
                        else
                        {
                            Cv2.Rectangle(frame, new OpenCvSharp.Point(bbox.Item1, bbox.Item2), new OpenCvSharp.Point(bbox.Item1 + bbox.Item3, bbox.Item2 + bbox.Item4), bbox.Item7, 1);
                            var caption = bbox.Item5;
                            Cv2.PutText(frame, caption, new OpenCvSharp.Point(bbox.Item1, bbox.Item2), HersheyFonts.HersheyComplex, 1, bbox.Item7, 1);
                        }
                    }
                }

                // Process and display the frame
                Application.Current.Dispatcher.Invoke(() =>
                {
                    UpdateDisplay(frame, imageControl);
                });
            };

            // Start video capture
            stream.Start();

            // Wait until cancellation is requested
            while (!token.IsCancellationRequested)
            {
                await Task.Delay(20);
            }

            // Stop video capture
            stream.SignalToStop();
            stream.WaitForStop();
        }

        public async Task ProcessTask(System.Windows.Controls.Image imageControl, CancellationToken token, string URLreturnBbox, IDictionary<int, string> classNames, List<Scalar> colorList, ConcurrentDictionary<long, object> idFaceDictionary, BlockingCollection<Tuple<int, int, int, int, string, string, Scalar, DateTime>> ObjectDetectionBbox)
        {
            while (!token.IsCancellationRequested)
            {
                if (RunOTModel && ObjectTracking)
                {
                    await GetObjectDetectionBbox(URLreturnBbox, classNames, colorList, idFaceDictionary, ObjectDetectionBbox);
                }
            }
        }




        private async Task GetObjectDetectionBbox(string boundingBoxesUrl, IDictionary<int, string> classNames, List<Scalar> colorList, ConcurrentDictionary<long, object> idFaceDictionary, BlockingCollection<Tuple<int, int, int, int, string, string, Scalar, DateTime>> ObjectDetectionBbox)
        {
            var resultsList = await RunYolo(boundingBoxesUrl);

            // New dictionary to keep track of names already assigned
            var assignedNames = new ConcurrentDictionary<string, object>();

            foreach (var results in resultsList)
            {
                if (!results.ContainsKey("class_")) continue;

                var classIndex = int.Parse(results["class_"].ToString());
                var className = classNames[classIndex];
                var classColor = colorList[classIndex];
                var topLeft = new OpenCvSharp.Point((long)results["x"], (long)results["y"]);
                var bottomRight = new OpenCvSharp.Point((long)results["w"], (long)results["h"]);
                string trackedName = null;
                long trackId = 0; // initialize with default value
                bool hasTrackId = results.ContainsKey("track_id");

                DateTime bboxTimestamp = DateTime.Parse(results["timestamp"].ToString());
                


                if (hasTrackId)
                {
                    trackId = (long)results["track_id"];
                }
                if (OTFaceRecognition && hasTrackId && className == "person" && !idFaceDictionary.ContainsKey(trackId))
                {
                    if (results.ContainsKey("result") && results["result"] != null)
                    {
                        var resultStr = results["result"].ToString();
                        // Check if this name has not been assigned already
                        if (!string.IsNullOrEmpty(resultStr) && !assignedNames.ContainsKey(resultStr))
                        {
                            // Remove any existing entries with the same value
                            foreach (var kvp in idFaceDictionary.Where(kvp => kvp.Value.Equals(resultStr)).ToList())
                            {
                                idFaceDictionary.TryRemove(kvp.Key, out _);
                            }
                            // Add the new entry
                            idFaceDictionary.TryAdd(trackId, resultStr);
                            // Add this name to the assigned names
                            assignedNames[resultStr] = new object();
                        }
                    }
                }
                if (OTFaceRecognition && hasTrackId)
                {
                    trackedName = idFaceDictionary.ContainsKey(trackId)
                        ? $"{idFaceDictionary[trackId]} - ID {results["track_id"]}"
                        : null;
                }

                ObjectDetectionBbox.Add(Tuple.Create(
                    topLeft.X,                        // int
                    topLeft.Y,                        // int
                    bottomRight.X - topLeft.X,        // int
                    bottomRight.Y - topLeft.Y,        // int
                    className,                        // string
                    trackedName,                      // string
                    classColor,                       // Scalar
                    bboxTimestamp                     // DateTime
                    ));
            }
        }
Miki
  • 40,887
  • 13
  • 123
  • 202
  • 1
    Look at the return type for `Tuple.Create` taking 8 parameters. – madreflection Jun 28 '23 at 21:06
  • @madreflection what do you mean? I can't really look at the return becauuse the error won't let me run the code. But it says its a tuple(datetime) which is really weird. I even tried chatgpt xD – Nicolantonio De Bari Jun 28 '23 at 21:12
  • You don't need to run the code to look at the return type. It's part of the method's signature. – madreflection Jun 28 '23 at 21:19
  • Or look at the generic arguments for this type that you're using: https://learn.microsoft.com/en-us/dotnet/api/system.tuple-8?view=net-7.0 (particularly the last argument) – Stephen Cleary Jun 28 '23 at 21:19
  • It's not weird. It's extensible. `Tuple<...>` only goes up to 8 type parameters and therefore 8 items, so the last item is a tuple containg all items after the 7th. You only have 1 `DateTime` item after the 7th so it's a 1-tuple of `DateTime`. The 8th type parameter is named `TRest` as in "the rest of the items". – madreflection Jun 28 '23 at 21:22
  • If you had 9 items, `TRest` would be a 2-tuple of `DateTime` and whetever the 9th item's type is. – madreflection Jun 28 '23 at 21:25
  • @madreflection thanks!!! I did not know this. I tried to solve this by: ObjectDetectionBbox.Add(new Tuple). Instead of using Tuple.Create<>, we use the new Tuple<>. I hope this is a good approach. – Nicolantonio De Bari Jun 28 '23 at 21:41
  • @madreflection But i dont think this is a good approach. What can i do then to be able to access this? – Nicolantonio De Bari Jun 28 '23 at 21:46
  • *"we use the new Tuple<>"* - No, that'st the *old* one. `ValueTuple<>` is the newer one. – madreflection Jun 29 '23 at 00:09
  • Couple issues with this code: You are nesting Ifs and inside a loop too, try to simplify this or chuck the logic in a logically named function(s) inside any if or loop. Each function should only do one thing. For your question either make a class or struct to hold the variables, tuples are supposed to be for small amounts of immutable data. This means every change to a tuple duplicates it and doubles your memory usage (for the tuple). Your functions are really long, this makes it had to read and bugfix down the track. – Slipoch Jul 03 '23 at 01:20

1 Answers1

3

Tuple.Create<T1,T2,T3,T4,T5,T6,T7,T8>(T1, T2, T3, T4, T5, T6, T7, T8) has the following return type:

Tuple<T1,T2,T3,T4,T5,T6,T7,Tuple<T8>>

So the last element is not the the type but tuple of single hence the error. Minimal reproducible example would be something like the following:

Tuple<int, int, int, int, string, string, double, DateTime> tup 
  = Tuple.Create(1, 1, 1, 1, "", "", 2.0, DateTime.UtcNow);

You can use the ctor for Tuple directly:

Tuple<int, int, int, int, string, string, double, DateTime> tup 
    = new Tuple<int, int, int, int, string, string, double, DateTime>(1, 1, 1, 1, "", "", 2.0, DateTime.UtcNow);

But in general I would recommend to avoid using tuples for such "big" number of items - just introduce a class/struct/record.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • 2
    Or introduce a struct as it is just a container and has none of the class overheads. Either way then things can be given descriptive names. – Slipoch Jul 03 '23 at 01:20
  • 2
    @Slipoch depending on the actual usage `struct` can be not the optimal choice though. Due to value semantics it can even result in performance degradation if value is passed around a lot. Or in case of closures and/or threading (which `BlockingCollection` heavily hints on). – Guru Stron Jul 03 '23 at 10:03
  • fascinating, thx for the info. – Slipoch Jul 24 '23 at 02:53