I am personally interested in SIMPLE C++ solutions, but you can post your LabVIEW and Java fixes (or links to other threads with FULL working solutions after all the latest software/firmware/etc. updates).
I’ve seen 7 billion partial solutions and complete formerly working solutions for switching 2+ cams on one stream, but I’ve yet to see anything for C++ that represents a complete and current camera switching solution that doesn’t require a Computer Science degree to implement.
If anyone has had success switching cams using the latest WPILib and other software, please consider sharing your methods. Again, C++ is what I personally am most interested in.
I wish this was an option that was built in to WPILib. I believe I read that it would be something that will be worked on for future releases. If anyone has figured it out though, I think the community would benefit greatly from your insight.
The below examples use USB cameras 0 and 1, but the same technique can be used with other cameras such as Axis cameras.
If you’re interested in just switching what the driver sees, and are using SmartDashboard, the SmartDashboard CameraServer Stream Viewer has an option (“Selected Camera Path”) that reads the given NT key and changes the “Camera Choice” to that value (displaying that camera). The robot code then just needs to set the NT key to the correct camera name. Assuming “Selected Camera Path” is set to “CameraSelection”, the following code uses the joystick 1 trigger button state to show camera1 and camera2.
If you’re using some other dashboard, you can change the camera used by the camera server dynamically. If you open a stream viewer nominally to camera1, the robot code will change the stream contents to either camera1 or camera2 based on the joystick trigger.
The cscore library is pretty aggressive in turning off cameras not in use. What this means is that when you switch cameras, it may disconnect from the camera not in use, so switching back will have some delay as it reconnects to the camera. This is something I would like to find a better way of doing for next year, but there is a workaround. The idea is to hook up and enable a CvSink on each camera; even if you aren’t actually processing images, this fools cscore into keeping the camera connection open (as it sees multiple enabled sinks). The code for this is similar for both options above, but I’ll use the SetSource() method (option 2) for illustration purposes:
cs::UsbCamera camera1;
cs::UsbCamera camera2;
cs::CvSink cvsink1;
cs::CvSink cvsink2;
cs::VideoSink server;
frc::Joystick joy1{0};
bool prevTrigger = false;
void RobotInit() override {
camera1 = CameraServer::GetInstance()->StartAutomaticCapture(0);
camera2 = CameraServer::GetInstance()->StartAutomaticCapture(1);
server = CameraServer::GetInstance()->GetServer();
// dummy sinks to keep camera connections open
cvsink1 = new cs::CvSink("cam1cv");
cvsink1.SetSource(camera1);
cvsink1.SetEnabled(true);
cvsink2 = new cs::CvSink("cam2cv");
cvsink2.SetSource(camera2);
cvsink2.SetEnabled(true);
}
void TeleopPeriodic() override {
if (joy1.GetTrigger() && !prevTrigger) {
printf("Setting camera 2
");
server.SetSource(camera2);
} else if (!joy1.GetTrigger() && prevTrigger) {
printf("Setting camera 1
");
server.SetSource(camera1);
}
prevTrigger = joy1.GetTrigger();
}
Yes, as only one camera’s worth of data is consuming the radio bandwidth at a given time.
If both cameras are USB, it’s worth noting that you may run into USB bandwidth limitations with higher resolutions, as in all of these cases the roboRio is going to be streaming data from both cameras to the roboRio simultaneously (for a short period in options 1 and 2, and continuously in option 3). It is theoretically possible for the library to avoid this simultaneity in the option 2 case (only), but this is not currently implemented.
Unfortunately, this depends on the cameras. Different cameras report bandwidth usage differently. The library will tell you if you’re hitting the limit; you’ll get this error message: “could not start streaming due to USB bandwidth limitations; try a lower resolution or a different pixel format (VIDIOC_STREAMON: No space left on device)”. If you’re using Option 3 it will give you this error during RobotInit(). Thus I would recommend just trying your desired resolution and adjusting as necessary until you both don’t get that error and don’t exceed the radio bandwidth limitations.
Our team was looking for something like this ever since we decided to use cameras. Sadly our regional was this past weekend and had to go on the field with only 1 camera working. Thank you very much Peter, this is wonderful. I will get the students to test this code, and if it works we’ll add it to our library for future seasons.
The LV template code should already contain a server in RobotMain. It will be on port 1181 and will serve up any camera that is Open and Registered by name. The server will wait for a client request before beginning the stream.
The dashboard lists all registered cameras in the drop-down menus. You make a connection by choosing from the list.
If you want to dynamically switch based on a button, you need to update the value of the dashboard combo-box by writing to it. This is equivalent to picking from the list.
As Peter mentioned, there will be a bit of a delay as the streams are resolved, established, and the cameras come up.
I do understand what is going on here – C++ is kind of my native language, and my Masters Degree research project work was in computer vision.
Our programming students on the team feel more comfortable with Java than they do with C++, so that is what they program in. When I sat down with our lead programming student and helped him to translate this example to Java, it seemed that the switching code was always defaulting to camera1. The feed on the SmartDashboard never switched to camera0. Debugging this with our lead programmer, it seemed to us that the NetworkTable’s CameraSelection string was properly updating with the appropriate button presses on our XBox controllers, but the feed was still always set to /dev/video1 for some reason. This occurred regardless of which camera was plugged into which USB port – it always used the one in the “lower” USB port, for lack of better port notation. I have a bad feeling that there may be a problem with the upper USB port or with the /dev/video0 unix device file.
Our team’s other programming mentor says the switching problem is solved, but she still suspects a port issue. That fix probably happened at yesterday’s meeting, which I was not able to attend. I’ve got an e-mail out to her asking what they did to fix the problem yesterday, but I’d still like to understand potential places to look for code issues.
Is there any way to reduce the delay in Labview? We experience a 10-15 second delay when switching between two USB cameras, its gotten to the point that we prefer not to toggle between cameras anymore.
Yes, the delay is pretty annoying. The code for the camera server is all open and included, but it was rushed and is not well documented.
Off the top of my head, a reasonable portion of the delay was caused by the WPI_CameraIssue HTTP Request with Authentication. That was used as a quick way to connect, but it has a loop with stuff for three different Axis camera accounts. If you are not using Axis cameras, you could wire a 1 to N on the For loop and that will speed things up. There are other changes that will help, but that is a quick one.