mini_pylon/minimal_pylon.cpp
2025-07-07 17:49:31 +02:00

238 lines
9.0 KiB
C++

// Grab_MultipleCameras.cpp
/*
Note: Before getting started, Basler recommends reading the "Programmer's Guide" topic
in the pylon C++ API documentation delivered with pylon.
If you are upgrading to a higher major version of pylon, Basler also
strongly recommends reading the "Migrating from Previous Versions" topic in the pylon C++ API documentation.
This sample illustrates how to grab and process images from multiple cameras
using the CInstantCameraArray class. The CInstantCameraArray class represents
an array of instant camera objects. It provides almost the same interface
as the instant camera for grabbing.
The main purpose of the CInstantCameraArray is to simplify waiting for images and
camera events of multiple cameras in one thread. This is done by providing a single
RetrieveResult method for all cameras in the array.
Alternatively, the grabbing can be started using the internal grab loop threads
of all cameras in the CInstantCameraArray. The grabbed images can then be processed by one or more
image event handlers. Please note that this is not shown in this example.
*/
#include <pylon/PylonIncludes.h>
#include "stitchimage.h"
#include <chrono>
#include <cstdlib>
#include <unistd.h> // for read(), STDIN_FILENO
#include <termios.h> // for tcgetattr(), tcsetattr()
#include <sys/select.h> // for select()
using namespace Pylon;
using namespace GenApi;
using namespace std;
using namespace std::chrono;
typedef high_resolution_clock Time;
typedef milliseconds ms;
typedef duration<float> fsec;
int main( int argc, char* argv[] ) {
uint32_t frameCount = 100;
size_t maxCameras = 1;
int exitCode = 0;
bool stop_request = false;
int camCount = 0;
int framerate = 20;
uint32_t quality = 50;
int cWidth = 2000; //2592;
int cHeight = 2000; //2048;
int camIndex = 0;
string errorMessage = "";
String_t filename = "_TestVideo.mp4";
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if ((arg == "--help") || arg == "?"){
cout << "Expected args:\n"
<< "\t --fps|-f \t the framerate, defaults to 20 Hz\n"
<< "\t --help|? \t this help\n"
<< "\t --width|-w \t the image width in pixel, defaults to 2000\n"
<< "\t --height|-h \t the image height in pixel, defaults to 2000\n"
<< "\t --cameras|-c \t the desired number of cameras to grab at the same time, defaults to 1\n"
<< "\t --framecount|-n \t the number of frames, defaults to 100\n"
<< "\t --quality|-q \t the qualtiy of the compression (0 < q <= 100), defaults to 50\n"
<< "\t --index|-i \t the camera index, ignored if cameras > 1, defaults to 0\n"
<< "\t --outfile|-o \t the output filename";
}
if ((arg == "--framecount" || arg == "-n") && i + 1 < argc) {
frameCount = std::atoi(argv[++i]);
}
if ((arg == "--fps" || arg == "-f") && i + 1 < argc) {
framerate = std::atoi(argv[++i]);
}
if ((arg == "--cameras" || arg == "-c") && i + 1 < argc) {
maxCameras = std::atoi(argv[++i]);
}
if ((arg == "--width" || arg == "-w") && i + 1 < argc) {
cWidth = std::atoi(argv[++i]);
}
if ((arg == "--height" || arg == "-h") && i + 1 < argc) {
cHeight = std::atoi(argv[++i]);
}
if ((arg == "--index" || arg == "-i") && i + 1 < argc) {
camIndex = std::atoi(argv[++i]);
}
if ((arg == "--quality" || arg == "-q") && i + 1 < argc) {
quality = (uint32_t)std::atoi(argv[++i]);
if (quality < 1)
quality = 1;
if (quality > 100)
quality = 100;
}
if ((arg == "--outfile" || arg == "-o") && i + 1 < argc) {
filename = Pylon::String_t(argv[++i]);
}
}
PylonInitialize();
try {
// Check if CVideoWriter is supported and all DLLs are available.
if (!CVideoWriter::IsSupported()) {
cout << "VideoWriter is not supported at the moment. Please install the pylon Supplementary Package for MPEG-4 which is available on the Basler website." << endl;
// Releases all pylon resources.
PylonTerminate();
// Return with error code 1.
return 1;
}
CTlFactory& tlFactory = CTlFactory::GetInstance();
DeviceInfoList_t devices;
if (tlFactory.EnumerateDevices( devices ) == 0) {
throw RUNTIME_EXCEPTION( "No camera present." );
}
CInstantCameraArray cameras( min( devices.size(), maxCameras ) );
for (size_t i = 0; i < cameras.GetSize(); ++i) {
if ( maxCameras == 1 && devices.size() > 1 && camIndex < devices.size() ){
cameras[i].Attach( tlFactory.CreateDevice( devices[camIndex] ) );
} else {
cameras[i].Attach( tlFactory.CreateDevice( devices[i] ) );
}
camCount += 1;
cameras[i].Open();
cout << "Using device " << cameras[i].GetDeviceInfo().GetModelName() << endl;
CIntegerParameter width( cameras[i].GetNodeMap(), "Width" );
CIntegerParameter height( cameras[i].GetNodeMap(), "Height" );
CEnumParameter pixelFormat( cameras[i].GetNodeMap(), "PixelFormat" );
CEnumParameter trigmode( cameras[i].GetNodeMap(), "TriggerMode");
trigmode.TrySetValue( "On" );
CEnumParameter trigsource( cameras[i].GetNodeMap(), "TriggerSource");
trigsource.TrySetValue( "Software" );
width.TrySetValue( cWidth, IntegerValueCorrection_Nearest );
height.TrySetValue( cHeight, IntegerValueCorrection_Nearest );
}
CIntegerParameter width( cameras[0].GetNodeMap(), "Width" );
CIntegerParameter height( cameras[0].GetNodeMap(), "Height" );
CEnumParameter pixelFormat( cameras[0].GetNodeMap(), "PixelFormat" );
CPixelTypeMapper pixelTypeMapper( &pixelFormat );
EPixelType pixelType = pixelTypeMapper.GetPylonPixelTypeFromNodeValue( pixelFormat.GetIntValue() );
CGrabResultPtr frames[camCount];
for (int i =0; i < camCount; i++) {
CGrabResultPtr ptrGrabResult;
frames[i] = ptrGrabResult;
}
Pylon::CPylonImage leftImage;
Pylon::CPylonImage rightImage;
Pylon::CPylonImage stitchedImage;
// Create a video writer object. Set parameters before opening the video writer.
CVideoWriter videoWriter;
videoWriter.SetParameter( (uint32_t) width.GetValue() * camCount,
(uint32_t) height.GetValue(),
pixelType,
framerate,
quality );
// Open the video writer.
videoWriter.Open( filename );
// Starts grabbing for all cameras starting with index 0. The grabbing
// is started for one camera after the other. That's why the images of all
// cameras are not taken at the same time.
// However, a hardware trigger setup can be used to cause all cameras to grab images synchronously.
// According to their default configuration, the cameras are
// set up for free-running continuous acquisition.
cameras.StartGrabbing();
int counter = 0;
auto before = high_resolution_clock::now();
auto done = high_resolution_clock::now();
auto total_duration = duration_cast<microseconds>(done - before);
int expected_usecs = (int)(1./framerate * 1000000);
while ( cameras.IsGrabbing() && !stop_request && counter < frameCount) {
if (counter > 0) {
long delay = total_duration.count() - expected_usecs;
if (delay > 0) {
cout << "frame: " << counter-1 << " took " << delay << " usecs too long to record!" << endl;
} else {
auto now = high_resolution_clock::now();
while (duration_cast<microseconds>( now - before).count() < expected_usecs ) {
now = high_resolution_clock::now();
}
}
}
before = high_resolution_clock::now();
if ( camCount > 1 ) {
if ( cameras[0].WaitForFrameTriggerReady( 1000, Pylon::TimeoutHandling_ThrowException ) &&
cameras[1].WaitForFrameTriggerReady( 1000, Pylon::TimeoutHandling_ThrowException )) {
cameras[0].ExecuteSoftwareTrigger();
cameras[1].ExecuteSoftwareTrigger();
}
} else {
cameras[0].WaitForFrameTriggerReady( 1000, Pylon::TimeoutHandling_ThrowException );
cameras[0].ExecuteSoftwareTrigger();
}
for ( int i =0; i < camCount; ++i ) {
cameras[i].RetrieveResult( 5000, frames[i], TimeoutHandling_ThrowException );
}
// Image grabbed successfully?
bool success = true;
for ( int i =0; i < camCount; ++i ) {
success = success & frames[i]->GrabSucceeded();
}
if (success) {
if ( camCount > 1 ) {
leftImage.AttachGrabResultBuffer( frames[0] );
rightImage.AttachGrabResultBuffer( frames[1] );
StitchImage::StitchToRight(leftImage, rightImage, &stitchedImage, errorMessage);
videoWriter.Add( stitchedImage );
} else {
videoWriter.Add( frames[0] );
}
} else {
cout << "Error: " << std::hex << frames[0]->GetErrorCode() << std::dec << " " << frames[0]->GetErrorDescription() << endl;
}
counter += 1;
std::cout << "." << std::flush;
done = high_resolution_clock::now();
total_duration = duration_cast<microseconds>(done - before);
}
} catch (const GenericException& e) {
// Error handling
cerr << "An exception occurred." << endl << e.GetDescription() << endl;
exitCode = 1;
}
// Releases all pylon resources.
PylonTerminate();
return exitCode;
}