-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpylon_camera.cpp
349 lines (280 loc) · 13.2 KB
/
pylon_camera.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
// Copyright 2019, The Jackson Laboratory, Bar Harbor, Maine - all rights reserved
#include <chrono>
#include <ctime>
#include <exception>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <thread>
#include "pylon_camera.h"
#include "video_writer.h"
namespace chrono = std::chrono;
using namespace Pylon;
using namespace GenApi;
void PylonCameraController::RecordVideo(const RecordingSessionConfig &config)
{
// reset the CameraController err_state_
// this is set to let the controlling thread know that we encountered an error
err_state_ = 0;
std::string filename; // output video filename
std::string output_dir; // output directory
// if config.fragment_by_hour is true, current hour == next_hour triggers
// rolling over to a new file
int next_hour = 0;
//TODO move current_frame into VideoWriter
size_t current_frame = 0; // frame number in the current file
size_t frames_captured = 0; // total number of frames captured in session
uint64_t first_click = 0; // timestamp of first frame captured
uint64_t last_click = 0; // timestamp of last frame captured
double current_fps; // current acquisition framerate
// setup the output directory
try {
output_dir = MakeOutputDir(chrono::system_clock::now());
} catch (const std::runtime_error &e) {
// couldn't setup the file path. set error string and return
recording_ = false;
err_msg_ = "unable to setup output dir: " + std::string(e.what());
err_state_ = 1;
return;
}
// setup filenames for timestamp files
// file for storing timestamp of each frame
std::string timestamp_filename = output_dir + config.file_prefix() + "timestamps.txt";
// file for storing timestamp of recording session start
std::string timestamp_start_filename = output_dir + config.file_prefix() + "start_timestamp.txt";
// open files
std::ofstream timestamp_file (timestamp_filename, std::ofstream::out);
std::ofstream timestamp_start_file (timestamp_start_filename, std::ofstream::out);
// terminate recording session if we were unable to open either file
if (!timestamp_file || ! timestamp_start_file) {
err_state_ = 1;
err_msg_ = "error opening timestamp files";
recording_ = false;
return;
}
// set format for floating point output to the timestamp file
timestamp_file << std::fixed << std::setprecision(6);
// attach and configure the camera
PylonAutoInitTerm autoInitTerm;
CImageFormatConverter img_converter;
CGrabResultPtr ptrGrabResult;
CBaslerGigEInstantCamera camera;
try {
camera.Attach(CTlFactory::GetInstance().CreateFirstDevice());
// customConfig will be managed by the Basler API so we are not using a smart pointer
CameraConfiguration *customConfig = new CameraConfiguration(frame_width_, frame_height_,
config.target_fps(), config.pixel_format(), false);
camera.RegisterConfiguration(customConfig, RegistrationMode_ReplaceAll, Cleanup_Delete);
camera.MaxNumBuffer = 15;
camera.Open();
} catch (const GenericException &e) {
// couldn't attach to or configure the camera. set error string and return
recording_ = false;
err_msg_ = "unable to configure camera: " + std::string(e.what());
err_state_ = 1;
return;
}
// done configuring camera
// save the start time of the recording session
auto start_time = chrono::system_clock::now();
std::time_t t = chrono::system_clock::to_time_t(start_time);
session_start_.store(start_time.time_since_epoch());
timestamp_start_file << "Recording started at Local Time: " << std::ctime(&t);
timestamp_start_file.close();
if (config.fragment_by_hour()) {
next_hour = (GetCurrentHour(start_time) + 1) % 24;
filename = output_dir + config.file_prefix() + timestamp(start_time);
} else {
filename = output_dir + config.file_prefix();
}
VideoWriter video_writer(filename, rtmp_uri_, frame_width_, frame_height_, config);
// camera is configured and we're ready to start capturing video
// start grabbing frames
camera.StartGrabbing(GrabStrategy_OneByOne);
capturing_ = true;
// main recording loop
while(1) {
auto elapsed = chrono::duration_cast<chrono::seconds>(
chrono::system_clock::now().time_since_epoch() - session_start_.load());
// check to see if we've completed the specified duration or we've been told
// to terminate early
if (this->stop_recording_ || elapsed >= config.duration()) {
break;
}
// Wait for an image and then retrieve it. A timeout of 5000 ms is used.
try {
camera.RetrieveResult(5000, ptrGrabResult, TimeoutHandling_ThrowException);
} catch (const GenericException &e) {
// bailing out -- should we retry?
err_msg_ = "Timeout retrieving frame: " + std::string(e.what());
err_state_ = 1;
break;
}
if (!ptrGrabResult->GrabSucceeded()) {
std::cerr << "Error: " << ptrGrabResult->GetErrorCode()
<< " " << ptrGrabResult->GetErrorDescription() << std::endl;
continue;
}
// got a frame from the camera
// get a pointer to the image buffer and
auto pImageBuffer = (uint8_t *)ptrGrabResult->GetBuffer();
// get the timestamp of the frame
auto frame_timestamp = ptrGrabResult->GetTimeStamp();
// record timestamp of first frame
if (frames_captured == 0) {
first_click = frame_timestamp;
}
// calculate current framerate and add to the vector used for computing
// the moving average
current_fps = 1.0/((frame_timestamp - last_click)/125000000.0);
{
std::lock_guard<std::mutex> lock(mutex_);
if (moving_avg_.size() == moving_avg_.capacity())
moving_avg_.pop_back();
moving_avg_.insert(moving_avg_.begin(), current_fps);
last_click = frame_timestamp;
}
// record timestamp of current frame, as an offset from the first timestamp
// NOTE: Camera tick is measured at 125MHz, so divide by 125,000,000 to get the value in seconds
timestamp_file << (frame_timestamp - first_click) / 125000000.0 << std::endl;
// send frame to the encoder
video_writer.EncodeFrame(pImageBuffer, current_frame, live_stream_);
current_frame++;
frames_captured++;
// check to see if we need to roll over to a new file
if (config.fragment_by_hour() && GetCurrentHour() == next_hour) {
start_time = chrono::system_clock::now();
filename = output_dir + config.file_prefix() + timestamp(start_time);
// setup a VideoWriter to the new filename:
video_writer = VideoWriter(filename, rtmp_uri_, frame_width_, frame_height_, config);
next_hour = (GetCurrentHour(start_time) + 1) % 24;
current_frame = 0;
}
ptrGrabResult.Release();
}
elapsed_time_ = chrono::duration_cast<chrono::seconds>(
chrono::system_clock::now().time_since_epoch() - session_start_.load());
// out of acquisition loop, stop grabbing frames and shutdown the camera
camera.StopGrabbing();
camera.Close();
capturing_ = false;
recording_ = false;
}
PylonCameraController::CameraConfiguration::CameraConfiguration(
int frame_width, int frame_height, int target_fps,
const std::string& pixel_format, bool enable_pgi)
{
frame_width_ = frame_width;
frame_height_ = frame_height;
target_fps_ = target_fps;
// for YUV420P we get Mono8 off the camera
if (pixel_format == pixel_types::YUV420P) {
pixel_format_ = pixel_types::MONO8;
} else {
// pixel_format should have been validated by the time we get here
pixel_format_ = pixel_format;
}
enable_pgi_ = enable_pgi;
}
void PylonCameraController::CameraConfiguration::OnOpened(CInstantCamera &camera)
{
try {
// Get the camera control object.
INodeMap &control = camera.GetNodeMap();
if (!camera.IsGigE()) {
throw RUNTIME_EXCEPTION("Could not apply configuration: Only GigE cameras are currently supported.");
}
CBaslerGigEInstantCamera *gige_camera = dynamic_cast<CBaslerGigEInstantCamera *>(&camera);
// Get the parameters for setting the image area of interest (Image AOI).
const CIntegerPtr width = control.GetNode("Width");
const CIntegerPtr height = control.GetNode("Height");
const CIntegerPtr offset_x = control.GetNode("OffsetX");
const CIntegerPtr offset_y = control.GetNode("OffsetY");
const CIntegerPtr auto_roi_width = control.GetNode("AutoFunctionAOIWidth");
const CIntegerPtr auto_roi_height = control.GetNode("AutoFunctionAOIHeight");
const CIntegerPtr auto_roi_offset_x = control.GetNode("AutoFunctionAOIOffsetX");
const CIntegerPtr auto_roi_offset_y = control.GetNode("AutoFunctionAOIOffsetY");
// Set the AOI.
if (IsWritable(offset_x)) {
offset_x->SetValue(offset_x->GetMin());
}
if (IsWritable(offset_y)) {
offset_y->SetValue(offset_y->GetMin());
}
// Assign frame width/height
width->SetValue(frame_width_);
height->SetValue(frame_height_);
// Re-center, the GetMax is already shifted given the width/height
if (IsWritable(offset_x)) {
offset_x->SetValue(int(offset_x->GetMax() / 2));
}
if (IsWritable(offset_y)) {
offset_y->SetValue(int(offset_y->GetMax() / 2));
}
// Set the pixel data format.
CEnumerationPtr(control.GetNode("PixelFormat"))->FromString(pixel_format_.c_str());
CEnumerationPtr(control.GetNode("ShutterMode"))->FromString("Global");
gige_camera->AutoFunctionAOISelector.SetValue(Basler_GigECameraParams::AutoFunctionAOISelector_AOI1);
// Assign the auto-gain to only look at the RoI assigned for exposure balancing
//camera.AutoFunctionAOISelector.SetValue(AutoFunctionAOISelector_AOI1);
if (IsWritable(auto_roi_offset_x)) {
auto_roi_offset_x->SetValue(auto_roi_offset_x->GetMin());
}
if (IsWritable(auto_roi_offset_y)) {
auto_roi_offset_y->SetValue(auto_roi_offset_y->GetMin());
}
// Assign frame width/height
auto_roi_width->SetValue(frame_width_);
auto_roi_height->SetValue(frame_height_);
// Re-center, the GetMax is already shifted given the width/height
if (IsWritable(auto_roi_offset_x)) {
auto_roi_offset_x->SetValue(int(auto_roi_offset_x->GetMax() / 2));
}
if (IsWritable(auto_roi_offset_y)) {
auto_roi_offset_y->SetValue(int(auto_roi_offset_y->GetMax() / 2));
}
gige_camera->AutoFunctionAOISelector.SetValue(Basler_GigECameraParams::AutoFunctionAOISelector_AOI2);
// Assign the auto-gain to only look at the RoI assigned for white balancing
if (IsWritable(auto_roi_offset_x)) {
auto_roi_offset_x->SetValue(auto_roi_offset_x->GetMin());
}
if (IsWritable(auto_roi_offset_y)) {
auto_roi_offset_y->SetValue(auto_roi_offset_y->GetMin());
}
// Assign frame width/height
auto_roi_width->SetValue(frame_width_);
auto_roi_height->SetValue(frame_height_);
// Re-center, the GetMax is already shifted given the width/height
if (IsWritable(auto_roi_offset_x)) {
auto_roi_offset_x->SetValue(int(auto_roi_offset_x->GetMax() / 2));
}
if (IsWritable(auto_roi_offset_y)) {
auto_roi_offset_y->SetValue(int(auto_roi_offset_y->GetMax() / 2));
}
// Enforce a 15ms exposure time manually
gige_camera->ExposureTimeRaw.SetValue(15000);
// Set autogain
gige_camera->ExposureAuto.SetValue(Basler_GigECameraParams::ExposureAuto_Off);
gige_camera->GainAuto.SetValue( Basler_GigECameraParams::GainAuto_Once);
// Stream parameters (for more efficient gige communication)
gige_camera->GevStreamChannelSelector.SetValue(Basler_GigECameraParams::GevStreamChannelSelector_StreamChannel0);
gige_camera->GevSCPD.SetValue(0);
gige_camera->GevSCFTD.SetValue(0);
gige_camera->GevSCBWR.SetValue(5);
gige_camera->GevSCBWRA.SetValue(2);
gige_camera->GevSCPSPacketSize.SetValue(9000);
// PGI mode stuff
if (enable_pgi_) {
gige_camera->PgiMode.SetValue(Basler_GigECameraParams::PgiMode_On);
gige_camera->NoiseReductionRaw.SetValue(10);
gige_camera->SharpnessEnhancementRaw.SetValue(100);
}
// Framerate items
CBooleanPtr(control.GetNode("AcquisitionFrameRateEnable"))->SetValue(true);
CFloatPtr(control.GetNode("AcquisitionFrameRateAbs"))->SetValue(target_fps_);
}
catch (const GenericException &e) {
throw RUNTIME_EXCEPTION(
"Could not apply configuration. const GenericException caught in OnOpened method msg=%hs", e.what());
}
}