268 lines
10 KiB
C++
268 lines
10 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;
|
|
|
|
uint32_t sensorHeight(CInstantCamera &camera) {
|
|
CIntegerParameter maxheight( camera.GetNodeMap(), "SensorHeight" );
|
|
return maxheight.GetValue();
|
|
}
|
|
|
|
uint32_t sensorWidth(CInstantCamera &camera) {
|
|
CIntegerParameter maxwidth( camera.GetNodeMap(), "SensorWidth" );
|
|
return maxwidth.IsValid() && maxwidth.IsReadable() ? maxwidth.GetValue() : 0;
|
|
}
|
|
|
|
//void exposureTime(double exposure_time);
|
|
|
|
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 xOffset = 0;
|
|
int yOffset = 0;
|
|
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 --xoffs|-x \t image x offset in pixel, defaults to 0\n"
|
|
<< "\t --yoffs|-y \t image y-offset in pixel, defaults to 0\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\n";
|
|
return 0;
|
|
}
|
|
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 == "--xoffs" || arg == "-x") && i + 1 < argc) {
|
|
xOffset = std::atoi(argv[++i]);
|
|
}
|
|
if ((arg == "--yoffs" || arg == "-y") && i + 1 < argc) {
|
|
yOffset = 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 {
|
|
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;
|
|
PylonTerminate();
|
|
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;
|
|
|
|
uint32_t mw = sensorWidth(cameras[i]);
|
|
uint32_t mh = sensorHeight(cameras[i]);
|
|
uint32_t maxXOffs = mw - cWidth;
|
|
uint32_t maxYOffs = mh - cHeight;
|
|
xOffset = xOffset <= maxXOffs ? xOffset : maxXOffs;
|
|
yOffset = yOffset <= maxYOffs ? yOffset : maxYOffs;
|
|
|
|
INodeMap& nodemap = cameras[i].GetNodeMap();
|
|
CIntegerParameter width( nodemap, "Width" );
|
|
CIntegerParameter height( nodemap, "Height" );
|
|
CIntegerParameter offsetX( nodemap, "OffsetX" );
|
|
CIntegerParameter offsetY( nodemap, "OffsetY" );
|
|
CEnumParameter pixelFormat( nodemap, "PixelFormat" );
|
|
CEnumParameter trigmode( nodemap, "TriggerMode");
|
|
CEnumParameter trigsource( nodemap, "TriggerSource");
|
|
|
|
trigmode.TrySetValue( "On" );
|
|
trigsource.TrySetValue( "Software" );
|
|
width.TrySetValue( cWidth, IntegerValueCorrection_Nearest );
|
|
height.TrySetValue( cHeight, IntegerValueCorrection_Nearest );
|
|
offsetX.TrySetValue( xOffset, IntegerValueCorrection_Nearest );
|
|
offsetY.TrySetValue( yOffset, 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;
|
|
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;
|
|
}
|
|
|