Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

README.md

Save BMP on Trigger

General Concept

In many situations, the camera is required to take an image exactly at the time of an external event. In this scenario, the camera can be put into trigger mode. In trigger mode, the camera does not take images, but instead waits for a trigger signal. Multiple sources can be used as the trigger signal, e.g. a digital input, a software signal, or even a specifically crafted network packet.

A program using trigger mode usually wants to capture all images sent by the device, and requires notifications once an image is received. For this purpose, using a queue sink is the optimal solution. It maintains two queues: A free queue of image buffers yet to be filled, and an output queue of completed buffers ready to be processed. A callback function is called whenever a buffer was filled and added to the output queue.

In this example, we use a queue sink to be notified of incoming images and save them as bitmap files. For demonstration purposes (since not everyone has the hardware for digital input available) the program uses the camera's software trigger feature to issue trigger signals to the camera.

Practical Implementation

Step 0: Preparation

A video capture device is opened with the helper function from previous examples:

// Let the user select a device
auto device_list = ic4::DeviceEnum::enumDevices();
auto it = ic4_examples::console::select_from_list(device_list);
if (it == device_list.end())
{
return -1;
}
// Open the selected device
ic4::Error err;
ic4::Grabber grabber;
if (!grabber.deviceOpen(*it, err))
{
std::cerr << "Failed to open device: " << err.message() << std::endl;
return -2;
}

Step 1: Configure the Device

Next, the device needs to be configured. For simplicity, we just load a UserSet to restore all camera settings to factory defaults. TriggerSelector is set to FrameStart to trigger images. After that, trigger mode is enabled by setting TriggerMode to On:

auto map = grabber.devicePropertyMap(err);
if (err.isError())
{
std::cerr << "Failed to query device property map: " << err.message() << std::endl;
return -3;
}
// Reset all device settings to default
// Not all devices support this, so ignore possible errors
map.setValue(ic4::PropId::UserSetSelector, "Default", ic4::Error::Ignore());
map.executeCommand(ic4::PropId::UserSetLoad, ic4::Error::Ignore());
// Select FrameStart trigger (for cameras that support this)
map.setValue(ic4::PropId::TriggerSelector, "FrameStart", ic4::Error::Ignore());
// Enable trigger mode
if (!map.setValue(ic4::PropId::TriggerMode, "On", err))
{
std::cerr << "Failed to enable trigger mode: " << err.message() << std::endl;
return -4;
}

Step 2: Define a QueueSinkListener

To get notifications from a queue sink, a class has to be derived from QueueSinkListener, overriding the framesQueued method.

In the framesQueued function, image buffers are extracted from the sink's output queue using popOutputBuffer, and then saved using imageBufferSaveAsBitmap:

// Define QueueSinkListener-derived class that saves all received frames in bitmap files
class SaveAsBmpListener : public ic4::QueueSinkListener
{
private:
std::string path_base_;
int counter_;
public:
SaveAsBmpListener(std::string path_base)
: path_base_(std::move(path_base))
, counter_(0)
{
}
// Inherited via QueueSinkListener, called when there are frames available in the sink's output queue
void framesQueued(ic4::QueueSink& sink) override
{
ic4::Error err;
while (true)
{
// Remove a buffer from the sink's output queue
auto buffer = sink.popOutputBuffer(err);
if (buffer == nullptr)
{
// No more buffers available, return
return;
}
// Generate a file name for the bitmap file
auto file_name = path_base_ + std::to_string(counter_) + ".bmp";
// Save the image buffer in the bitmap file
if (!ic4::imageBufferSaveAsBitmap(*buffer, file_name, {}, err))
{
std::cerr << "Failed save buffer: " << err.message() << std::endl;
}
else
{
std::cout << "Saved image " << file_name << std::endl;
counter_ += 1;
}
}
}
};

Step 3: Start the Stream to the Sink

Next, we create a new QueueSink using QueueSink::create, passing a reference an instance of the QueueSinkListener defined above.

After that, the a data stream to the sink is started using Grabber::streamSetup:

// Create an instance of the listener type defined above, specifyin a partial file name
std::string path_base = "./test_image";
SaveAsBmpListener listener(path_base);
// Create a QueueSink to capture all images arriving from the video capture device
auto sink = ic4::QueueSink::create(listener, err);
if (!sink)
{
std::cerr << "Failed to create sink: " << err.message() << std::endl;
return -3;
}
// Start the video stream into the sink
if (!grabber.streamSetup(sink, ic4::StreamSetupOption::AcquisitionStart, err))
{
std::cerr << "Failed to setup stream: " << err.message() << std::endl;
return -4;
}

Step 4: Waiting for Images

We set up a loop to let the program run while waiting for images. Whenever a key is pressed, a TriggerSoftware command is executed to trigger an image:

while( true )
{
int ch = std::getchar();
if (ch == 'q')
break;
// Execute software trigger
if (!map.executeCommand(ic4::PropId::TriggerSoftware, err))
{
std::cerr << "Failed to perform software trigger: " << err.message() << std::endl;
continue;
}
}