Skip to content

Commit

Permalink
Resolved Clean Exit of all thread,imagebuffer performance issue
Browse files Browse the repository at this point in the history
  • Loading branch information
inbangsa committed Jul 8, 2019
1 parent af849aa commit 2337cd6
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 116 deletions.
8 changes: 8 additions & 0 deletions multi_camera_parallel_grabber/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ set(CMAKE_CXX_EXTENSIONS OFF) # OFF -> -std=c++14, ON -> -std=gnu++14
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED true)

if(FORCE_OPENCV3)
find_package(OpenCV 3 REQUIRED)
elseif(FORCE_OPENCV2)
find_package(OpenCV 2.4 REQUIRED)
else()
find_package(OpenCV REQUIRED)
endif()

##############################################################
## finding the ifm3d lib.
Expand All @@ -24,4 +31,5 @@ target_link_libraries(ex-multi_camera_parallel_grabber
ifm3d::framegrabber
ifm3d::image
${CMAKE_THREAD_LIBS_INIT}
${OpenCV_LIBRARIES}
)
9 changes: 4 additions & 5 deletions multi_camera_parallel_grabber/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ ifm3d Multiple camera parallel grabber
This document explains the setup that is needed to use this code.
The setup is consist of multiple ifm o3d cameras out of which one is in
software trigger mode and others are configured as hardware trigger mode (Positive,Negative,Positive and Negative).
This example triggers the device in software trigger mode whose IP Address must be assigned to *CAMERA0* variable
[Line 44](ex-multi_camera_parallel_grabber.cpp#L44) in the code, rest all other camera IP can be added to the
*camera_ips* variable on [Line 49](ex-multi_camera_parallel_grabber.cpp#L49). After the CAMERA0 finished
the Image capture it further sends the hardware trigger to the one of the device e.g.CAMERA1.
When CAMERA1 device finishes its capture it further trigger CAMERA2 and this way all the devices are triggered.
This example triggers the device in software trigger mode whose IP Address must be at 0th location of the ```camera_ips``` array at.
After the CAMERA0 (0 index camera) finished the Image capture it further sends the hardware trigger to the one of the device
e.g.CAMERA1 (device at index 1 in ```camera_ips```array).When CAMERA1 device finishes its capture it further trigger CAMERA2
and this way all the devices are triggered.

Setup and pin connection
------------------------
Expand Down
306 changes: 195 additions & 111 deletions multi_camera_parallel_grabber/ex-multi_camera_parallel_grabber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,153 +30,237 @@
#include <chrono>
#include <vector>
#include <thread>
#include <atomic>
#include <mutex>
#include <signal.h>
#include <array>
#include <tuple>
#include <ifm3d/camera.h>
#include <ifm3d/fg.h>
#include <ifm3d/image.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui/highgui.hpp>

namespace
{ // enum for all the trigger types supported by o3d3xx device
enum class trigger_mode : int { FREE_RUN = 1, SW = 2, POSITIVE_EDGE = 3, NEGATIVE_EDGE = 4, POSITIVE_N_NEGATIVE = 5 };

//CHANGE IP addreses to those of camera's avaialible!!!!
const auto CAMERA0 = "192.168.0.70"; // Device in software trigger mode
const auto CAMERA1 = "192.168.0.71";
const auto CAMERA2 = "192.168.0.72";

//Add the IP of cameras to be used with hardware trigger
const std::vector<std::string> camera_ips = {CAMERA1,CAMERA2};
int num_cam_finished = camera_ips.size();

auto j_conf = json::parse(R"(
{
"ifm3d":
{
"Device":
{
// enum for all the trigger types supported by o3d3xx device
enum class trigger_mode : int { FREE_RUN = 1, SW = 2, POSITIVE_EDGE = 3, NEGATIVE_EDGE = 4, POSITIVE_N_NEGATIVE = 5 };

//flag for stoping the application
std::atomic<bool> start = true;

//Add the IP of cameras to be used for the complete stystem. Address at 0th location must be of camera with software trigger
/* to add device add device address at second last position in the camera_ips vector, increase NUMBER_OF_DEVICES too */
const auto NUMBER_OF_DEVICES = 3;
const std::array<std::string, NUMBER_OF_DEVICES> camera_ips = { "192.168.0.70", "192.168.0.71","192.168.0.72"};

auto j_conf = json::parse(R"(
{
"ifm3d":
{
"Device":
{
"ActiveApplication": "1"
},
"Apps":
[
{
"ActiveApplication": "1"
},
"Apps":
[
"Index": "1",
"TriggerMode":"2",
"LogicGraph": "{\"IOMap\": {\"OUT1\": \"RFT\",\"OUT2\": \"AQUFIN\"},\"blocks\": {\"B00001\": {\"pos\": {\"x\": 200,\"y\": 200},\"properties\": {},\"type\": \"PIN_EVENT_IMAGE_ACQUISITION_FINISHED\"},\"B00002\": {\"pos\": {\"x\": 200,\"y\": 75},\"properties\": {},\"type\": \"PIN_EVENT_READY_FOR_TRIGGER\"},\"B00003\": {\"pos\": {\"x\": 600,\"y\": 75},\"properties\": {\"pulse_duration\": 0},\"type\": \"DIGITAL_OUT1\"},\"B00005\": {\"pos\": {\"x\": 600,\"y\": 200},\"properties\": {\"pulse_duration\": 0},\"type\": \"DIGITAL_OUT2\"}},\"connectors\": {\"C00000\": {\"dst\": \"B00003\",\"dstEP\": 0,\"src\": \"B00002\",\"srcEP\": 0},\"C00001\": {\"dst\": \"B00005\",\"dstEP\": 0,\"src\": \"B00001\",\"srcEP\": 0}}}",
"Imager":
{
"Index": "1",
"TriggerMode":"2",
"LogicGraph": "{\"IOMap\": {\"OUT1\": \"RFT\",\"OUT2\": \"AQUFIN\"},\"blocks\": {\"B00001\": {\"pos\": {\"x\": 200,\"y\": 200},\"properties\": {},\"type\": \"PIN_EVENT_IMAGE_ACQUISITION_FINISHED\"},\"B00002\": {\"pos\": {\"x\": 200,\"y\": 75},\"properties\": {},\"type\": \"PIN_EVENT_READY_FOR_TRIGGER\"},\"B00003\": {\"pos\": {\"x\": 600,\"y\": 75},\"properties\": {\"pulse_duration\": 0},\"type\": \"DIGITAL_OUT1\"},\"B00005\": {\"pos\": {\"x\": 600,\"y\": 200},\"properties\": {\"pulse_duration\": 0},\"type\": \"DIGITAL_OUT2\"}},\"connectors\": {\"C00000\": {\"dst\": \"B00003\",\"dstEP\": 0,\"src\": \"B00002\",\"srcEP\": 0},\"C00001\": {\"dst\": \"B00005\",\"dstEP\": 0,\"src\": \"B00001\",\"srcEP\": 0}}}",
"Imager":
{
"ExposureTime": "1000",
"Type":"under5m_moderate",
"FrameRate":"20"
}
"ExposureTime": "1000",
"Type":"under5m_moderate",
"FrameRate":"20"
}
]
}
}
]
}
)");
}
)");

//for configuration of required trigger on camera
void configuration(ifm3d::Camera::Ptr& camera, trigger_mode type)
//for configuration of required trigger on camera
void configuration(ifm3d::Camera::Ptr& camera, trigger_mode type)
{
try
{
auto application_id = camera->ActiveApplication();
j_conf["ifm3d"]["Device"]["ActiveApplication"] = std::to_string(application_id);
j_conf["ifm3d"]["Apps"][0]["Index"] = std::to_string(application_id);
j_conf["ifm3d"]["Apps"][0]["TriggerMode"] = std::to_string((int)type);

camera->FromJSON(j_conf);
}
catch (std::exception &e)
{
std::cout << e.what();
}
}

// class to encapsulate all the camera related stuff and grabbing images
struct CameraObject
{
using Ptr = std::shared_ptr<CameraObject>;
using Callback = std::function<void(ifm3d::ImageBuffer::Ptr &image_buffer, bool in_time)>;

CameraObject(std::string ip_address, trigger_mode type)
{
try
{
auto application_id = camera->ActiveApplication();
j_conf["ifm3d"]["Device"]["ActiveApplication"] = std::to_string(application_id);
j_conf["ifm3d"]["Apps"][0]["Index"] = std::to_string(application_id);
j_conf["ifm3d"]["Apps"][0]["TriggerMode"] = std::to_string((int)type);
// initialize all the objects and configure the device with trigger type
try
{
//connecting to camera
camera = ifm3d::Camera::MakeShared(ip_address);
frame_grabber = std::make_shared<ifm3d::FrameGrabber>(camera, ifm3d::IMG_AMP);
image_buffer = std::make_shared<ifm3d::ImageBuffer>();
std::cout << "Connected to the device with IP Address " << ip_address << std::endl;

camera->FromJSON(j_conf);
}
catch (std::exception &e)
{
std::cout << e.what();
}
// configure the CAMERA acoording to the type
configuration(camera, type);
}
catch (std::exception &e)
{
std::cout << e.what();
}
}

// for grabbing data when its available
void grabimage(ifm3d::FrameGrabber::Ptr& frame_grabber,
std::function<void(ifm3d::ImageBuffer::Ptr image_buffer,
bool in_time)> callback)
ifm3d::Camera::Ptr camera;
ifm3d::ImageBuffer::Ptr image_buffer;
ifm3d::FrameGrabber::Ptr frame_grabber;
std::mutex mutex;

void grabimage(Callback callback)
{
while(true)
std::unique_lock<std::mutex> lock(mutex, std::defer_lock);
while (start)
{
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(1));
{
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(1));
ifm3d::ImageBuffer::Ptr image_buffer = std::make_shared<ifm3d::ImageBuffer>();
const bool in_time = frame_grabber->WaitForFrame(image_buffer.get(), 10000);
callback(image_buffer,in_time);
std::lock_guard<std::mutex> lock(mutex);
const bool in_time = frame_grabber->WaitForFrame(image_buffer.get(), 10000);
callback(image_buffer, in_time);
}
};
}
}
};
}; //end namespcase

int main(int argc, const char **argv)
{
//vectors for the objects to be used.
std::vector<std::thread> workers;

// Connecting to device with software trigger
auto cam_sw = ifm3d::Camera::MakeShared(CAMERA0);
auto framegrab_sw = std::make_shared<ifm3d::FrameGrabber>(cam_sw, ifm3d::IMG_AMP );
std::cout << "--------------IFM3D Multi Threaded Grabber Example-------------------- " << std::endl;
std::cout << std::endl << "CLOSE APPLICATION BY PRESSING CNTRL+C" << std::endl << std::endl << std::endl;

// Worker threads for each camera.
std::vector<std::thread> workers;
std::array<std::tuple<CameraObject::Ptr, CameraObject::Callback>, NUMBER_OF_DEVICES> camera_object_list;

// configure the CAMERA0 as software trigger
configuration(cam_sw, trigger_mode::SW);
// call back buffer for threads.user can write algorithms for each camera data
// e.g. we increase the brigthness of each image by scaling pixel values by 100

auto buf_callback =
[&](auto image_buffer, bool in_time) -> void
auto buf_callback =
[&](auto image_buffer, bool in_time) -> void
{
if (in_time)
{
if(in_time)
{
std::cout << "Frame received: "
<< image_buffer->TimeStamp().time_since_epoch().count()
<< std::endl;
}
else
{
std::cout << "Timeout occured" << std::endl;
}
};
auto trig_callback =
[&](auto image_buffer, bool in_time) -> void
//Scaling to higer value to make image looks brighter
image_buffer->AmplitudeImage() *= 100;
}
else
{
// Trigger the next frame
framegrab_sw->SWTrigger();
// execute the regular callback
buf_callback(image_buffer,in_time);
std::cout << "Timeout occured" << std::endl;
}
};

};
// this trigger the first camera and call buffer_callback
auto trig_callback =
[&](auto image_buffer, bool in_time) -> void
{
// Trigger the next frame
std::get<0>(camera_object_list[0])->frame_grabber->SWTrigger();
// execute the regular callback
buf_callback(image_buffer, in_time);

// queue the software triggered camera
workers.push_back(
std::thread(std::bind(grabimage,framegrab_sw,buf_callback))
);
};

// Create ifm3d objects of Camera, ImageBuffer and FrameGrabber for each of the camera devices in hardware trigger.
for(auto& camera_ip:camera_ips)
//system setup
// first device will have Software trigger and all other will have hardware trigger.
// The last device will trigger the first device hence a different trigger_call back for last device.
[&]()->void
{
// set the first device for SW trigger
camera_object_list[0] = std::make_tuple(std::make_shared<CameraObject>(camera_ips[0], trigger_mode::SW), buf_callback);

// all the devices after 1st Device must have hardware trigger.
for (int i = 1; i < NUMBER_OF_DEVICES - 1; i++)
{
auto cam = ifm3d::Camera::MakeShared(camera_ip);
configuration(cam, trigger_mode::POSITIVE_EDGE);
auto frame_grabber = std::make_shared<ifm3d::FrameGrabber>(cam, ifm3d::IMG_AMP );
if(camera_ip!=*(camera_ips.end() -1))
camera_object_list[i] = std::make_tuple(std::make_shared<CameraObject>(camera_ips[i], trigger_mode::POSITIVE_EDGE), buf_callback);
}
// last device setuped to call the trigger_callback.
camera_object_list[NUMBER_OF_DEVICES- 1] = std::make_tuple(std::make_shared<CameraObject>(camera_ips[NUMBER_OF_DEVICES - 1], trigger_mode::POSITIVE_EDGE), trig_callback);
}();

// Starting the thread for each camera for grabbing data and calling respective callback function
for (auto &elem : camera_object_list)
{
workers.push_back(std::thread(std::bind(&CameraObject::grabimage, std::get<0>(elem), std::get<1>(elem))));
}

// Kickstart the loop by giving first camera a SW trigger
std::get<0>(camera_object_list[0])->frame_grabber->SWTrigger();

// Function where all the data from camera is available for display
auto display_all_images =
[](decltype(camera_object_list) &camera_object_list)
{
cv::Mat all_images;
cv::Mat image;
cv::Mat last_image;
int index = 0;
while (start)
{
index = 0;
for (auto &camera_object : camera_object_list)
{
{
std::lock_guard<std::mutex> lock(std::get<0>(camera_object)->mutex);
image = std::get<0>(camera_object)->image_buffer->AmplitudeImage();
}
if (index != 0)
{
workers.push_back(
std::thread(
std::bind(grabimage, frame_grabber,buf_callback))
);
cv::hconcat(image, last_image, all_images);
last_image = all_images;
}
else
{
workers.push_back(
std::thread(
std::bind(grabimage, frame_grabber,trig_callback))
);
all_images = image;
last_image = all_images;
}
index++;
}
cv::imshow("Display side by side", all_images);
cv::waitKey(2);
}
};

// Kickstart the loop
framegrab_sw->SWTrigger();
workers.push_back(
std::thread(
std::bind(display_all_images, camera_object_list))
);

//waiting for all threads to complete
for (auto &worker: workers)
{
if (worker.joinable())
worker.join();
}
return 0;
auto closeApplication = [](int signal)
{
start = false;
};

signal(SIGINT, closeApplication);

//waiting for all threads to complete
for (auto &worker : workers)
{
if (worker.joinable())
worker.join();
}
return 0;
}

0 comments on commit 2337cd6

Please sign in to comment.