mvIMPACT Acquire SDK C++
ContinuousCaptureMultipleVideoSignals.cpp

The ContinuousCaptureMultipleVideoSignals program is based on the ContinuousCaptureMultipleInputs.cpp example. The sample is intended for devices with multiple inputs like the mvHYPERION-CLm. With the sample, it is possible to select the channels, which should be acquired. The sample also shows, how you can work with multiple settings and how you can reprogram a complete channel of the frame grabber by changing one parameter during the image acquisition.

Note
On Linux this example can only be built when using CMake with C++11 compliant compiler (e.g. gcc 5.0 or greater).
Program location
The source file ContinuousCaptureMultipleVideoSignals.cpp can be found under:
%INSTALLDIR%\apps\ContinuousCaptureMultipleVideoSignals\
Note
If you have installed the package without example applications, this file will not be available. On Windows the sample application can be installed or removed from the target system at any time by simply restarting the installation package.
ContinuousCaptureMultipleVideoSignals example:
  1. Opens a MATRIX VISION device.
  2. Snaps images continuously and switches between the input channels (without display using Linux).
How it works
The continuous acquisition is similar to the single capture. The only major difference is, that this sample starts a separate thread that continuously requests images from the device.

However, the general stuff (selection of a device etc.) are similar to the ContinuousCapture.cpp source example.

After getting the device from user input the sample opens the device (pDev->open()). The program lists only devices which support the sample. Then, the CaptureParameters class gets the max. video channels

videoChannelMax_ = connector.videoChannel.read( plMaxValue );
@ plMaxValue
Set/Get the maximum value for this mvIMPACT::acquire::Property.
Definition: mvIMPACT_acquire.h:2878

and creates a setting for each video channel.

for( int i = 0; i <= videoChannelMax_; i++ )
{
ostringstream oss;
oss << "VideoChannel" << i;
ComponentList setting;
// create a setting for this video channel (more efficient for switching)
int result = fi_.createSetting( oss.str(), "Base", &setting );
if( result == DMR_NO_ERROR )
{
// select a fixed video channel for each setting
Connector c( p, oss.str() );
c.videoChannel.write( i );
channelSpecificData_.push_back( make_shared<ChannelSpecificData>( setting ) );
}
else
{
cout << "ERROR while creating setting " << oss.str() << "(" << ImpactAcquireException::getErrorCodeAsString( result ) << ")" << endl;
assert( !"ERROR while creating setting" );
}
}
@ DMR_NO_ERROR
The function call was executed successfully.
Definition: mvDriverBaseEnums.h:2356

Afterwards, the sample creates display windows for the max. number of video channels.

vector<int>::const_iterator it = channelsUsed_.begin();
vector<int>::const_iterator itEND = channelsUsed_.end();
while( it != itEND )
{
ostringstream oss;
oss << "video channel " << *it;
#ifdef USE_DISPLAY
channelSpecificData_[*it]->pCaptureDisplay = unique_ptr<ImageDisplayWindow>( new ImageDisplayWindow( oss.str() ) );
#endif // #ifdef USE_DISPLAY
++it;
}

Now, the sample calls getCaptureSequenceFromUser(), where you can set the capture sequence:

getCaptureSequenceFromUser( params );

Then, the sample will check, if the device is a frame grabber. If this is true, the user can select a camera description (selectCameraDescriptionsFromUserInput()) for each channel that is part of the capture sequence:

if( pDev->deviceClass.read() == dcFrameGrabber )
{
// only frame grabbers support camera descriptions
selectCameraDescriptionsFromUserInput( params );
}
@ dcFrameGrabber
A frame grabber device.
Definition: mvDriverBaseEnums.h:1833

Afterwards, image request queues for all channels of the device are generated.

for( unsigned int i=0; i<requestCount; i++ )
{
// request frames in the order of the capture sequence by using the settings belonging to each channel
irc.setting.write( pThreadParameter->channelSpecificData()[pThreadParameter->captureSequence()[i%captureSequenceCount]]->setting.hObj() );
fi.imageRequestSingle( &irc );
}

The live thread updates the displays using the channelSpecificData() vector. This vector contains one element per channel, which includes the display handles. The request queue delivers the channel of the image.

if( pChannelData )
{
++pChannelData->frameCnt;
#ifdef USE_DISPLAY
pChannelData->pCaptureDisplay->GetImageDisplay().SetImage( pRequest );
pChannelData->pCaptureDisplay->GetImageDisplay().Update();
#endif // #ifdef USE_DISPLAY
if( fi.isRequestNrValid( pThreadParameter->channelSpecificData()[videoChannel]->lastRequestNr ) )
{
fi.imageRequestUnlock( pThreadParameter->channelSpecificData()[videoChannel]->lastRequestNr );
}
pThreadParameter->channelSpecificData()[videoChannel]->lastRequestNr = requestNr;
}
else
{
cout << "ERROR: A request has been returned from channel " << videoChannel << " however no data structure has been allocated for this channel." << endl;
fi.imageRequestUnlock( requestNr );
}
Source code
#include <cassert>
#include <iostream>
#include <memory>
#include <thread>
#include <apps/Common/exampleHelper.h>
#ifdef _WIN32
# define USE_DISPLAY
#endif // #ifdef _WIN32
using namespace std;
using namespace mvIMPACT::acquire;
static bool s_boTerminated = false;
//-----------------------------------------------------------------------------
struct ChannelSpecificData
//-----------------------------------------------------------------------------
{
#ifdef USE_DISPLAY
unique_ptr<ImageDisplayWindow> pCaptureDisplay;
#endif // #ifdef USE_DISPLAY
ComponentList setting;
int lastRequestNr;
unsigned int frameCnt;
ChannelSpecificData( ComponentList s ) :
#ifdef USE_DISPLAY
pCaptureDisplay(),
#endif // #ifdef USE_DISPLAY
setting( s ), lastRequestNr( INVALID_ID ), frameCnt( 0 ) {}
ChannelSpecificData( const ChannelSpecificData& src ) = delete;
ChannelSpecificData& operator=( const ChannelSpecificData& rhs ) = delete;
};
using ChannelContainer = vector<shared_ptr<ChannelSpecificData>>;
//-----------------------------------------------------------------------------
class CaptureParameters
//-----------------------------------------------------------------------------
{
Device* pDev_;
FunctionInterface fi_;
ImageRequestControl irc_;
Statistics statistics_;
SystemSettings ss_;
ChannelContainer channelSpecificData_;
vector<int> captureSequence_;
vector<int> channelsUsed_;
int videoChannelMax_ = -1;
public:
explicit CaptureParameters( Device* p ) : pDev_( p ), fi_( p ), irc_( p ), statistics_( p ), ss_( p ),
channelSpecificData_(), captureSequence_(), channelsUsed_()
{
Connector connector( p );
if( connector.videoChannel.isValid() )
{
videoChannelMax_ = connector.videoChannel.read( plMaxValue );
}
for( int i = 0; i <= videoChannelMax_; i++ )
{
ostringstream oss;
oss << "VideoChannel" << i;
ComponentList setting;
// create a setting for this video channel (more efficient for switching)
int result = fi_.createSetting( oss.str(), "Base", &setting );
if( result == DMR_NO_ERROR )
{
// select a fixed video channel for each setting
Connector c( p, oss.str() );
c.videoChannel.write( i );
channelSpecificData_.push_back( make_shared<ChannelSpecificData>( setting ) );
}
else
{
cout << "ERROR while creating setting " << oss.str() << "(" << ImpactAcquireException::getErrorCodeAsString( result ) << ")" << endl;
assert( !"ERROR while creating setting" );
}
}
}
CaptureParameters( const CaptureParameters& src ) = delete;
CaptureParameters& operator=( const CaptureParameters& rhs ) = delete;
Device* device( void ) const
{
return pDev_;
}
FunctionInterface& functionInterface( void )
{
return fi_;
}
ImageRequestControl& imageRequestControl( void )
{
return irc_;
}
Statistics& statistics( void )
{
return statistics_;
}
SystemSettings& systemSettings( void )
{
return ss_;
}
const ChannelContainer& channelSpecificData( void )
{
return channelSpecificData_;
}
const vector<int>& captureSequence( void )
{
return captureSequence_;
}
const vector<int>& channelsUsed( void )
{
return channelsUsed_;
}
void setCaptureSequence( const vector<int>& c )
{
captureSequence_ = c;
// find out how many DIFFERENT channels are part of the sequence
channelsUsed_ = c;
sort( channelsUsed_.begin(), channelsUsed_.end() );
channelsUsed_.erase( unique( channelsUsed_.begin(), channelsUsed_.end() ), channelsUsed_.end() );
// make sure that each channel has at least 4 buffers to work with
ss_.requestCount.write( static_cast<int>( channelsUsed_.size() * 4 ) );
ChannelContainer::size_type channelDataCnt = channelSpecificData_.size();
for( ChannelContainer::size_type i = 0; i < channelDataCnt; i++ )
{
channelSpecificData_[i]->lastRequestNr = INVALID_ID;
channelSpecificData_[i]->frameCnt = 0;
}
// create a display for each channel that is part of the sequence
for( int channel : channelsUsed_ )
{
ostringstream oss;
oss << "video channel " << channel;
#ifdef USE_DISPLAY
channelSpecificData_[channel]->pCaptureDisplay = unique_ptr<ImageDisplayWindow>( new ImageDisplayWindow( oss.str() ) );
#endif // #ifdef USE_DISPLAY
}
}
int videoChannelMax( void ) const
{
return videoChannelMax_;
}
};
//-----------------------------------------------------------------------------
void getCaptureSequenceFromUser( CaptureParameters& params )
//-----------------------------------------------------------------------------
{
const int videoChannelMax = params.videoChannelMax();
cout << endl
<< "This " << params.device()->product.readS() << " has " << videoChannelMax + 1 << " separate video inputs(";
for( int i = 0; i <= videoChannelMax; i++ )
{
if( i > 0 )
{
cout << ", ";
}
cout << i;
}
cout << ").";
cout << endl
<< "Please enter the zero based sequence of channels to capture from and press [ENTER]: ";
static const size_t s_BUF_SIZE = {1024};
char buf[s_BUF_SIZE];
cin.getline( buf, s_BUF_SIZE, '\n' );
string captureSequenceString( buf );
// parse input
string::size_type start = 0;
vector<int> captureSequence;
while( ( start = captureSequenceString.find_first_of( "0123456789", start ) ) != string::npos )
{
const auto end = captureSequenceString.find_first_not_of( "0123456789", start + 1 );
const string channelString( captureSequenceString.substr( start, ( end != string::npos ) ? end - start : end ) );
const int channel = atoi( channelString.c_str() );
if( channel > videoChannelMax )
{
cout << "Channel " << channel << " is invalid for this device. Skipped!" << endl;
}
else
{
captureSequence.push_back( channel );
}
if( end == string::npos )
{
break;
}
start = end + 1;
}
params.setCaptureSequence( captureSequence );
}
//-----------------------------------------------------------------------------
// select a camera description from the list of available camera descriptions for
// each channel that is part of the capture sequence
void selectCameraDescriptionForChannelFromUserInput( CaptureParameters& params, int channel )
//-----------------------------------------------------------------------------
{
bool boRun = true;
while( boRun )
{
try
{
CameraSettingsFrameGrabber cs( params.device(), params.channelSpecificData()[channel]->setting.name() );
// display the name of every camera description available for this device.
// this might be less than the number of camera descriptions available on the system as e.g.
// an analog frame grabber can't use descriptions for digital cameras
vector<pair<string, int> > vAvailableDescriptions;
cs.type.getTranslationDict( vAvailableDescriptions );
cout << endl << "Available camera descriptions: " << vAvailableDescriptions.size() << endl
<< "----------------------------------" << endl;
for_each( vAvailableDescriptions.begin(), vAvailableDescriptions.end(), DisplayDictEntry<int>() );
cout << endl << "Please select a camera description to use for this sample (numerical value): ";
int cameraDescriptionToUse;
cin >> cameraDescriptionToUse;
// remove the '\n' from the stream
cin.get();
// wrong input will raise an exception
// The camera description contains all the parameters needed to capture the associated video
// signal. Camera descriptions for a certain camera can be obtained from MATRIX VISION. Experienced
// users can also create their own camera descriptions using wxPropView. The documentation will
// explain how to create and configure camera description files
cs.type.write( cameraDescriptionToUse );
boRun = false;
}
catch( const ImpactAcquireException& e )
{
cout << "ERROR while selecting camera description for channel " << channel << ": " << e.getErrorString() << endl;
}
}
}
//-----------------------------------------------------------------------------
void liveThread( CaptureParameters* pThreadParameter )
//-----------------------------------------------------------------------------
{
const unsigned int requestCount = pThreadParameter->functionInterface().requestCount();
const unsigned int captureSequenceCount = static_cast<unsigned int>( pThreadParameter->captureSequence().size() );
FunctionInterface& fi = pThreadParameter->functionInterface();
ImageRequestControl& irc = pThreadParameter->imageRequestControl();
chrono::high_resolution_clock::time_point startTime = chrono::high_resolution_clock::now();
for( unsigned int i = 0; i < requestCount; i++ )
{
// request frames in the order of the capture sequence by using the settings belonging to each channel
irc.setting.write( pThreadParameter->channelSpecificData()[pThreadParameter->captureSequence()[i % captureSequenceCount]]->setting.hObj() );
fi.imageRequestSingle( &irc );
}
manuallyStartAcquisitionIfNeeded( pThreadParameter->device(), fi );
// run thread loop
const Request* pRequest = nullptr;
const unsigned int timeout_ms = {500};
int requestNr = {INVALID_ID};
unsigned int cnt = {0};
// each display has to buffer the current frame as the display module might want to repaint the image, thus we
// cannot free it unless we have a assigned the display to a new buffer.
while( !s_boTerminated )
{
// wait for results from the default capture queue
requestNr = fi.imageRequestWaitFor( timeout_ms );
if( fi.isRequestNrValid( requestNr ) )
{
pRequest = fi.getRequest( requestNr );
const int videoChannel = pRequest->infoVideoChannel.read();
if( pRequest->isOK() )
{
++cnt;
shared_ptr<ChannelSpecificData> pChannelData = pThreadParameter->channelSpecificData()[videoChannel];
if( pChannelData )
{
++pChannelData->frameCnt;
#ifdef USE_DISPLAY
pChannelData->pCaptureDisplay->GetImageDisplay().SetImage( pRequest );
pChannelData->pCaptureDisplay->GetImageDisplay().Update();
#endif // #ifdef USE_DISPLAY
if( fi.isRequestNrValid( pThreadParameter->channelSpecificData()[videoChannel]->lastRequestNr ) )
{
fi.imageRequestUnlock( pThreadParameter->channelSpecificData()[videoChannel]->lastRequestNr );
}
pThreadParameter->channelSpecificData()[videoChannel]->lastRequestNr = requestNr;
}
else
{
cout << "ERROR: A request has been returned from channel " << videoChannel << " however no data structure has been allocated for this channel." << endl;
fi.imageRequestUnlock( requestNr );
}
// here we can display some statistical information every 100th image
if( cnt % 100 == 0 )
{
ostringstream oss;
for( auto channel : pThreadParameter->channelsUsed() )
{
if( !oss.str().empty() )
{
oss << ", ";
}
shared_ptr<ChannelSpecificData> pCD = pThreadParameter->channelSpecificData()[channel];
const double elapsedTime = chrono::duration_cast<chrono::duration<double>>( chrono::high_resolution_clock::now() - startTime ).count();
oss << "channel " << channel << " " << pCD->frameCnt << "(" << static_cast<double>( pCD->frameCnt ) / elapsedTime << "fps)";
}
cout << pThreadParameter->statistics().framesPerSecond.readS() << ", " << pThreadParameter->statistics().errorCount.readS() << ", frames captured: "
<< oss.str() << endl;
}
}
else
{
cout << "Error: " << pRequest->requestResult.readS() << endl;
fi.imageRequestUnlock( requestNr );
}
// request the next image with the same settings as the buffer that just became ready
irc.setting.write( pThreadParameter->channelSpecificData()[videoChannel]->setting.hObj() );
fi.imageRequestSingle( &irc );
}
//else
//{
// Please note that slow systems or interface technologies in combination with high resolution sensors
// might need more time to transmit an image than the timeout value which has been passed to imageRequestWaitFor().
// If this is the case simply wait multiple times OR increase the timeout(not recommended as usually not necessary
// and potentially makes the capture thread less responsive) and rebuild this application.
// Once the device is configured for triggered image acquisition and the timeout elapsed before
// the device has been triggered this might happen as well.
// The return code would be -2119(DEV_WAIT_FOR_REQUEST_FAILED) in that case, the documentation will provide
// additional information under TDMR_ERROR in the interface reference.
// If waiting with an infinite timeout(-1) it will be necessary to call 'imageRequestReset' from another thread
// to force 'imageRequestWaitFor' to return when no data is coming from the device/can be captured.
// cout << "imageRequestWaitFor failed (" << requestNr << ", " << ImpactAcquireException::getErrorCodeAsString( requestNr ) << ")"
// << ", timeout value too small?" << endl;
//}
}
manuallyStopAcquisitionIfNeeded( pThreadParameter->device(), fi );
for( const auto& pChannel : pThreadParameter->channelSpecificData() )
{
#ifdef USE_DISPLAY
ImageDisplayWindow* pCaptureDisplay = pChannel->pCaptureDisplay.get();
if( pCaptureDisplay )
{
// stop the display from showing freed memory
pCaptureDisplay->GetImageDisplay().RemoveImage();
}
#endif // #ifdef USE_DISPLAY
if( fi.isRequestNrValid( pChannel->lastRequestNr ) )
{
fi.imageRequestUnlock( pChannel->lastRequestNr );
pChannel->lastRequestNr = INVALID_ID;
}
}
// In this sample all the next lines are redundant as the device driver will be
// closed now, but in a real world application a thread like this might be started
// several times an then it becomes crucial to clean up correctly.
// clear all queues
fi.imageRequestReset( 0, 0 );
}
//-----------------------------------------------------------------------------
bool isDeviceSupportedBySample( const Device* const pDev )
//-----------------------------------------------------------------------------
{
return pDev->hasCapability( dcSelectableVideoInputs );
}
//-----------------------------------------------------------------------------
int main( void )
//-----------------------------------------------------------------------------
{
cout << "This sample is meant for devices that support multiple video channels only. Other devices might be installed" << endl
<< "but won't be recognized by the application." << endl
<< endl;
DeviceManager devMgr;
Device* pDev = getDeviceFromUserInput( devMgr, isDeviceSupportedBySample );
if( pDev == nullptr )
{
cout << "Unable to continue! Press [ENTER] to end the application" << endl;
cin.get();
return 1;
}
cout << "Initialising device " << pDev->serial.read() << ". This might take some time..." << endl;
try
{
pDev->open();
}
catch( const ImpactAcquireException& e )
{
// this e.g. might happen if the same device is already opened in another process...
cout << "An error occurred while opening the device(error code: " << e.getErrorCode() << ")." << endl
<< "Press [ENTER] to end the application" << endl;
cin.get();
return 1;
}
CaptureParameters params( pDev );
getCaptureSequenceFromUser( params );
if( params.captureSequence().empty() )
{
cout << "User defined capture sequence is empty. Unable to continue!" << endl
<< "Press [ENTER] to end the application" << endl;
cin.get();
return 1;
}
if( pDev->deviceClass.read() == dcFrameGrabber )
{
// only frame grabbers support camera descriptions
for( auto channel : params.channelsUsed() )
{
selectCameraDescriptionForChannelFromUserInput( params, channel );
}
}
// start the execution of the 'live' thread.
cout << "Press [ENTER] to end the application" << endl;
thread myThread( liveThread, &params );
cin.get();
s_boTerminated = true;
myThread.join();
return 0;
}
std::string getErrorCodeAsString(void) const
Returns a string representation of the error associated with the exception.
Definition: mvIMPACT_acquire.h:280
@ dcSelectableVideoInputs
This is a device, that has more than one video input channel.
Definition: mvDriverBaseEnums.h:1808
This namespace contains classes and functions that can be used to display images.
This namespace contains classes and functions belonging to the image acquisition module of this SDK.
const int INVALID_ID
A constant to check for an invalid ID returned from the property handling module.
Definition: mvPropHandlingDatatypes.h:58