-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implement and test camera prototype #16
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
project(camera) | ||
|
||
set(SOURCES camera_driver.cc) | ||
|
||
add_library(camera ${SOURCES}) | ||
|
||
target_link_libraries(camera ${OpenCV_LIBS} pthread) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
#include "camera_driver.h" | ||
#include <thread> | ||
#include <chrono> | ||
#include <iostream> | ||
#include <string> | ||
|
||
namespace camera { | ||
|
||
CameraBase::CameraBase(uint32_t n) { | ||
this->n = n; | ||
this->busy_index = 0; | ||
this->is_capturing = false; | ||
this->_buffer.resize(this->n); | ||
} | ||
|
||
void CameraBase::_start_capture(unsigned int sleep) { | ||
if (this->is_capturing) { | ||
// TODO: add legit exception handling | ||
std::cerr << "DONT CALL ME TWICE!!!!!\r\n"; | ||
} | ||
this->is_capturing = true; | ||
while (true) { | ||
this->_buffer[this->busy_index] = this->cam_read(); | ||
this->_index_lock.lock(); | ||
this->busy_index = (this->busy_index + 1) % this->n; | ||
this->_index_lock.unlock(); | ||
if (sleep) | ||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep)); | ||
} | ||
} | ||
|
||
void CameraBase::get_img(cv::Mat &dst) { | ||
this->_index_lock.lock(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider using modern C++ lock guard instead of this raw locking mechanism |
||
uint32_t ind_to_read = (this->busy_index + this->n - 1) % this->n; | ||
// this can be potentially improved by taking (diminishing) tradeoff with thread safety | ||
// but on Jetson TX2, this implementation already runs faster than 120fps | ||
// which is faster than almost every camera... | ||
this->_buffer[ind_to_read].copyTo(dst); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the circular buffer is big enough and the processing frequency is faster than the circular capture frequency, we could get rid of this copy. |
||
this->_index_lock.unlock(); | ||
} | ||
|
||
void CameraBase::start() { | ||
// fill the buffer | ||
for (uint32_t i = 0; i < this->n; ++i){ | ||
this->_buffer[i] = this->cam_read(); | ||
} | ||
std::thread t(&::camera::CameraBase::_start_capture, this, 0); | ||
this->camera_thread_handle = t.native_handle(); | ||
t.detach(); | ||
} | ||
|
||
void CameraBase::stop() { | ||
this->_index_lock.lock(); | ||
pthread_cancel(this->camera_thread_handle); | ||
this->_index_lock.unlock(); | ||
} | ||
|
||
/* ---------------------------- SimpleCVCam ---------------------------*/ | ||
|
||
SimpleCVCam::SimpleCVCam(uint32_t n) : CameraBase(n) { | ||
this->cap = cv::VideoCapture(0); // default camera | ||
} | ||
|
||
SimpleCVCam::SimpleCVCam(uint32_t n, unsigned short device_id) : CameraBase(n) { | ||
this->cap = cv::VideoCapture(device_id); | ||
} | ||
|
||
SimpleCVCam::~SimpleCVCam() { | ||
this->stop(); | ||
this->cap.release(); | ||
cv::destroyAllWindows(); | ||
} | ||
|
||
cv::Mat SimpleCVCam::cam_read() { | ||
cv::Mat frame; | ||
this->cap >> frame; | ||
return frame; | ||
} | ||
|
||
} // namespace camera |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,114 @@ | ||||||||||
#pragma once | ||||||||||
|
||||||||||
#include <mutex> | ||||||||||
#include <vector> | ||||||||||
#include <thread> | ||||||||||
#include <iostream> | ||||||||||
#include <cstdlib> | ||||||||||
#include <cstring> | ||||||||||
#include <unistd.h> | ||||||||||
#include <opencv2/opencv.hpp> | ||||||||||
#include <sys/ioctl.h> | ||||||||||
#include <sys/types.h> | ||||||||||
#include <sys/stat.h> | ||||||||||
#include <fcntl.h> | ||||||||||
|
||||||||||
using namespace std; | ||||||||||
|
||||||||||
namespace camera { | ||||||||||
/** | ||||||||||
* A base class for generalized camera level processing | ||||||||||
*/ | ||||||||||
class CameraBase { | ||||||||||
public: | ||||||||||
/** | ||||||||||
* @brief constructor for CameraBase class | ||||||||||
* | ||||||||||
* @param n size of circular frame buffer | ||||||||||
*/ | ||||||||||
CameraBase(uint32_t n); | ||||||||||
|
||||||||||
/** | ||||||||||
* @brief read an image and copy into src Mat | ||||||||||
* | ||||||||||
* @param dst destination place holder for the stored image | ||||||||||
* @return none | ||||||||||
*/ | ||||||||||
void get_img(cv::Mat &dst); | ||||||||||
|
||||||||||
/** | ||||||||||
* @brief start the camera in a seperate thread | ||||||||||
* | ||||||||||
* @return none | ||||||||||
*/ | ||||||||||
void start(); | ||||||||||
|
||||||||||
/** | ||||||||||
* @brief terminates the (already started) separate thread | ||||||||||
*/ | ||||||||||
void stop(); | ||||||||||
|
||||||||||
/** | ||||||||||
* @brief to be implemented according to the specs of a specific camera | ||||||||||
* | ||||||||||
* @return Mat object read directly from the camera | ||||||||||
*/ | ||||||||||
virtual cv::Mat cam_read() = 0; | ||||||||||
|
||||||||||
/** | ||||||||||
* @brief determine if the camera is dead or alive. Should only be of use for video feed | ||||||||||
* | ||||||||||
* @return alive flag | ||||||||||
*/ | ||||||||||
inline bool is_alive(void) { return this->is_capturing; } | ||||||||||
protected: | ||||||||||
/** | ||||||||||
* @brief Invoke a separate thread to get images | ||||||||||
* | ||||||||||
* @param sleep time (in ms) to sleep between each capture. Default to zero | ||||||||||
* @return none | ||||||||||
*/ | ||||||||||
void _start_capture(unsigned int sleep = 0); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
bool is_capturing; // maybe use a lock? | ||||||||||
uint32_t n; | ||||||||||
// index indicating frames of interest | ||||||||||
// increment by each capturing loop | ||||||||||
uint32_t busy_index; | ||||||||||
std::vector<cv::Mat> _buffer; | ||||||||||
mutex _index_lock; | ||||||||||
Comment on lines
+78
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
pthread_t camera_thread_handle; | ||||||||||
}; | ||||||||||
|
||||||||||
/** | ||||||||||
* @brief a generalized opencv camera class | ||||||||||
*/ | ||||||||||
class SimpleCVCam: public CameraBase { | ||||||||||
public: | ||||||||||
/** | ||||||||||
* @brief constructor for SimpleCVCam class | ||||||||||
* | ||||||||||
* @param n size of circular frame buffer | ||||||||||
*/ | ||||||||||
SimpleCVCam(uint32_t n); | ||||||||||
|
||||||||||
/** | ||||||||||
* @brief constructor for SimpleCVCam | ||||||||||
* | ||||||||||
* @param n size of the circular frame buffer | ||||||||||
* @param device_id device id as in camera index [default to 0] | ||||||||||
*/ | ||||||||||
SimpleCVCam(uint32_t n, unsigned short device_id); | ||||||||||
|
||||||||||
~SimpleCVCam(); | ||||||||||
|
||||||||||
/** | ||||||||||
* @brief virtual implementation of how to read images | ||||||||||
* | ||||||||||
* @return Mat object read directly from the camera | ||||||||||
*/ | ||||||||||
cv::Mat cam_read(); | ||||||||||
protected: | ||||||||||
cv::VideoCapture cap; | ||||||||||
}; | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
#include <iostream> | ||
#include <chrono> | ||
#include <opencv2/opencv.hpp> | ||
#include "utils/timing/tic_toc.h" | ||
#include "utils/camera/camera_driver.h" | ||
#include "utils/gtest_utils/test_base.h" | ||
|
||
/************************************ | ||
Camerat tests: | ||
- uses metrics to compare camera implementation and visually confirm results are correct | ||
|
||
Metrics | ||
- Latency (How long does each call to get img take)? | ||
- Throughput (In a given time interval e.g. 10sec). How many non-identical image can be obtained? | ||
|
||
************************************/ | ||
|
||
using namespace std; | ||
using namespace timing; | ||
using namespace camera; | ||
|
||
bool mat_equal(cv::Mat & a, cv::Mat & b) { | ||
// vectorized Op | ||
return cv::countNonZero(a != b) == 0; | ||
} | ||
|
||
class CameraTest : public TestBase { | ||
public: | ||
void OnePhotoTest() { | ||
SimpleCVCam my_cam(4, 0); | ||
my_cam.start(); | ||
cv::Mat test_frame; | ||
ASSERT_TRUE(test_frame.rows == 0); | ||
ASSERT_TRUE(test_frame.cols == 0); | ||
my_cam.get_img(test_frame); | ||
ASSERT_TRUE(test_frame.rows > 0); | ||
ASSERT_TRUE(test_frame.cols > 0); | ||
imwrite("./test.jpg", test_frame); | ||
} | ||
|
||
void BenchmarkTest() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this test necessary? It does not seem to verify the correctness of anything. |
||
int n = 100; // number of frames to get | ||
SimpleCVCam my_cam(4, 0); | ||
TicTocGlobalReset(); | ||
TicToc tictoc_baseline("Standard get_img"); | ||
TicToc tictoc_ours("Our get_img"); | ||
vector<cv::Mat> baseline_arr; | ||
vector<cv::Mat> novel_mat_arr; | ||
// baseline | ||
for (int i = 0; i < n; ++i) { | ||
tictoc_baseline.Tic(); | ||
cv::Mat my_img = my_cam.cam_read(); | ||
tictoc_baseline.Toc(); | ||
baseline_arr.push_back(my_img); | ||
} | ||
my_cam.start(); | ||
// ours | ||
for (int i = 0; i < n; ++i) { | ||
cv::Mat my_img; | ||
tictoc_ours.Tic(); | ||
my_cam.get_img(my_img); | ||
tictoc_ours.Toc(); | ||
novel_mat_arr.push_back(my_img); | ||
} | ||
std::cout << "[ TicTocTest ] Time Summary:" << std::endl; | ||
std::cout << TicTocGlobalSummary() << std::endl; | ||
ASSERT_TRUE(baseline_arr.size() == ((size_t)n)); | ||
ASSERT_TRUE(novel_mat_arr.size() == ((size_t)n)); | ||
for (int i = 0; i < n; ++i) { | ||
ASSERT_TRUE(baseline_arr[i].rows > 0); | ||
ASSERT_TRUE(baseline_arr[i].cols > 0); | ||
ASSERT_TRUE(novel_mat_arr[i].rows > 0); | ||
ASSERT_TRUE(novel_mat_arr[i].cols > 0); | ||
} | ||
// latency | ||
double baseline_time = TicTocStatsTime("Standard get_img"); | ||
double our_time = TicTocStatsTime("Our get_img"); | ||
ASSERT_TRUE(our_time < baseline_time); | ||
// throughput | ||
int baseline_valid_cnt = 0; | ||
int our_valid_cnt = 0; | ||
for (int i = 0; i < n - 1; ++i) { | ||
if (!mat_equal(baseline_arr[i], baseline_arr[i + 1])) | ||
baseline_valid_cnt++; | ||
if (!mat_equal(novel_mat_arr[i], novel_mat_arr[i + 1])) | ||
our_valid_cnt++; | ||
} | ||
std::cout << "Valid baseline mat cnt: " << baseline_valid_cnt << std::endl; | ||
std::cout << "Valid our mat cnt: " << our_valid_cnt << std::endl; | ||
double baseline_throughput = baseline_valid_cnt / baseline_time; | ||
double our_throughput = our_valid_cnt / our_time; | ||
std::cout << "Baseline throughput (fps): " << baseline_throughput << std::endl; | ||
std::cout << "Our throughput (fps): " << our_throughput << std::endl; | ||
} | ||
}; | ||
|
||
// for some unknown reason, releasing a videocapture object | ||
// and then reacquring it sometimes cause Illegal Hardware Instruction fault. | ||
// This should not be a problem on the robot. | ||
// Therefore, one test is commented out. | ||
// TEST_FM(CameraTest, OnePhotoTest); | ||
TEST_FM(CameraTest, BenchmarkTest); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,6 +53,16 @@ std::string TicTocGlobalSummary() { | |
return ss.str(); | ||
} | ||
|
||
double TicTocStatsTime(std::string query) { | ||
tictoc_t tictocs = global_tic_toc_bank.GetLCM(); | ||
// sort according to keys + find maximum channel name length | ||
for (tictoc_channel_t &tictoc: tictocs.tictoc_channels) { | ||
if (tictoc.name == query) | ||
return ((TicTocStats)(tictoc.tictoc_stats)).TotalTime(); | ||
} | ||
return -1; // not found | ||
} | ||
|
||
Comment on lines
+56
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make this a TicTocBank method, and for generalization purposes, return the entire stats for the channel. Key access should be hash mapped not queried. |
||
/*********************** | ||
* --- TicTocStats --- * | ||
***********************/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
include_directories
should not be used unless this is required for every source file. usetarget_include_directories
instead, for the module that actually depends on it.