Skip to content

Commit 2b3ac07

Browse files
first version of DarkHelp_cam
1 parent 5f8d655 commit 2b3ac07

11 files changed

Lines changed: 840 additions & 11 deletions

‎CM_source.cmake‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
# MIT license applies. See "license.txt" for details.
44

55

6-
INCLUDE_DIRECTORIES ( src-lib )
7-
INCLUDE_DIRECTORIES ( src-tool )
6+
INCLUDE_DIRECTORIES (src-lib)
7+
INCLUDE_DIRECTORIES (src-tool)
88

9-
ADD_SUBDIRECTORY ( src-lib )
10-
ADD_SUBDIRECTORY ( src-tool )
11-
ADD_SUBDIRECTORY ( src-apps )
12-
ADD_SUBDIRECTORY ( src-doc )
9+
ADD_SUBDIRECTORY (src-lib)
10+
ADD_SUBDIRECTORY (src-tool)
11+
ADD_SUBDIRECTORY (src-apps)
12+
ADD_SUBDIRECTORY (src-cam)
13+
ADD_SUBDIRECTORY (src-doc)

‎src-cam/CMakeLists.txt‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# DarkHelp - C++ helper class for Darknet's C API.
2+
# Copyright 2019-2024 Stephane Charette <stephanecharette@gmail.com>
3+
# MIT license applies. See "license.txt" for details.
4+
5+
6+
# the library is C++11, but this app uses C++17
7+
SET (CMAKE_CXX_STANDARD 17)
8+
SET (CMAKE_CXX_STANDARD_REQUIRED ON)
9+
10+
FILE (GLOB SRC_CAM *.cpp)
11+
LIST (SORT SRC_CAM)
12+
13+
ADD_EXECUTABLE (DarkHelp_cam ${SRC_CAM})
14+
TARGET_LINK_LIBRARIES (DarkHelp_cam PRIVATE Threads::Threads dh ${Darknet} ${OpenCV_LIBS})
15+
INSTALL (TARGETS DarkHelp_cam DESTINATION bin)

‎src-cam/Cam.cpp‎

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
/* DarkHelp - C++ helper class for Darknet's C API.
2+
* Copyright 2019-2024 Stephane Charette <stephanecharette@gmail.com>
3+
* MIT license applies. See "license.txt" for details.
4+
*/
5+
6+
7+
#include "DarkHelp.hpp"
8+
#include "CamOptions.hpp"
9+
10+
11+
int main(int argc, char * argv[])
12+
{
13+
int rc = 1;
14+
15+
try
16+
{
17+
#ifndef HAVE_OPENCV_HIGHGUI
18+
...do something here
19+
#endif
20+
21+
DarkHelp::Config config;
22+
DarkHelp::CamOptions options;
23+
24+
DarkHelp::parse(options, config, argc, argv);
25+
DarkHelp::NN nn(config);
26+
27+
cv::VideoCapture cap;
28+
cap.setExceptionMode(true);
29+
if (options.device_index >= 0)
30+
{
31+
std::cout << "-> opening camera device index #" << options.device_index << std::endl;
32+
DarkHelp::VInt v =
33+
{
34+
cv::CAP_PROP_BUFFERSIZE, 5,
35+
cv::CAP_PROP_HW_ACCELERATION, cv::VIDEO_ACCELERATION_ANY,
36+
cv::CAP_PROP_FRAME_WIDTH, options.size_request.width,
37+
cv::CAP_PROP_FRAME_HEIGHT, options.size_request.height,
38+
cv::CAP_PROP_FPS, static_cast<int>(std::round(options.fps_request)),
39+
};
40+
cap.open(options.device_index, options.device_backend, v);
41+
if (not cap.isOpened())
42+
{
43+
throw std::runtime_error("failed to open camera index #" + std::to_string(options.device_index));
44+
}
45+
}
46+
else
47+
{
48+
std::cout << "-> opening filename \"" << options.device_filename << "\"" << std::endl;
49+
cap.open(options.device_filename, options.device_backend);
50+
if (not cap.isOpened())
51+
{
52+
throw std::runtime_error("failed to open \"" + options.device_filename + "\"");
53+
}
54+
}
55+
56+
std::cout << "-> video backend API: " << cap.getBackendName() << std::endl;
57+
58+
if (options.fps_request > 0.0)
59+
{
60+
std::cout << "-> attempting to set the video device to " << options.fps_request << " FPS" << std::endl;
61+
cap.set(cv::CAP_PROP_FPS, options.fps_request);
62+
}
63+
64+
if (options.size_request.width > 0 and options.size_request.height > 0)
65+
{
66+
std::cout << "-> attempting to set the video dimensions to " << options.size_request.width << "x" << options.size_request.height << std::endl;
67+
cap.set(cv::CAP_PROP_FRAME_WIDTH, options.size_request.width);
68+
cap.set(cv::CAP_PROP_FRAME_HEIGHT, options.size_request.height);
69+
}
70+
71+
int milliseconds_to_wait = 10;
72+
cv::Mat first_frame;
73+
cap >> first_frame;
74+
if (first_frame.empty())
75+
{
76+
std::cout << "-> failed to read video frame" << std::endl;
77+
78+
// looks like things are about to fail, but still make an attempt
79+
options.fps_actual = options.fps_request;
80+
options.size_actual = options.size_request;
81+
}
82+
else
83+
{
84+
options.fps_actual = cap.get(cv::CAP_PROP_FPS);
85+
options.size_actual.width = first_frame.cols;
86+
options.size_actual.height = first_frame.rows;
87+
88+
std::cout << "-> input video claims to be " << first_frame.cols << "x" << first_frame.rows << " @ " << options.fps_actual << " FPS" << std::endl;
89+
90+
// see if we can confirm the FPS since either V4L2 or OpenCV seems to get that wrong most of the time
91+
bool ok = true;
92+
int read_this_many_frames = std::max(10.0, std::ceil(options.fps_actual));
93+
const auto timestamp_start = std::chrono::high_resolution_clock::now();
94+
for (int idx = 0; idx < read_this_many_frames; idx ++)
95+
{
96+
cv::Mat mat;
97+
cap >> mat;
98+
if (mat.size() != first_frame.size())
99+
{
100+
ok = false;
101+
break;
102+
}
103+
}
104+
const auto timestamp_end = std::chrono::high_resolution_clock::now();
105+
if (not ok)
106+
{
107+
std::cout << "-> failed to read initial video frames" << std::endl;
108+
}
109+
else
110+
{
111+
// 1 second = 1,000,000,000 nanoseconds
112+
const auto duration = timestamp_end - timestamp_start;
113+
const double length_in_nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count();
114+
const double real_fps = 1000000000.0 / length_in_nanoseconds * read_this_many_frames;
115+
116+
std::cout << "-> took " << DarkHelp::duration_string(duration) << " to read " << read_this_many_frames << " frames, giving us " << real_fps << " FPS" << std::endl;
117+
const auto ratio = options.fps_actual / real_fps;
118+
if (ratio < 0.9 or ratio > 1.1)
119+
{
120+
std::cout << "-> modfying input video from " << options.fps_actual << " FPS to " << real_fps << " FPS" << std::endl;
121+
options.fps_actual = real_fps;
122+
}
123+
}
124+
milliseconds_to_wait = std::max(5.0, std::min(10.0, std::round(1000.0 / 2.0 / options.fps_actual)));
125+
std::cout << "-> HighGUI event timeout is set to " << milliseconds_to_wait << " milliseconds which is good up to " << std::floor(1000.0 / milliseconds_to_wait) << " FPS" << std::endl;
126+
}
127+
128+
if (options.fps_actual <= 0)
129+
{
130+
std::cout << "-> " << options.fps_actual << " FPS seems to be invalid" << std::endl;
131+
options.fps_actual = 10.0;
132+
}
133+
134+
if (options.size_actual.width < 10 or options.size_actual.height < 10)
135+
{
136+
std::cout << "-> video dimensions of " << options.size_actual.width << "x" << options.size_actual.height << " seems to be invalid" << std::endl;
137+
options.size_actual = cv::Size(640, 480);
138+
}
139+
140+
cv::Size final_size(options.size_actual);
141+
bool resize_before = false;
142+
bool resize_after = false;
143+
size_t errors = 0;
144+
145+
if (options.resize_before.width > 0 and options.resize_before.height > 0)
146+
{
147+
resize_before = true;
148+
if (not first_frame.empty())
149+
{
150+
// the size requested may need to be adjusted to keep the aspect ratio the same
151+
auto tmp = DarkHelp::resize_keeping_aspect_ratio(first_frame, options.resize_before);
152+
options.resize_before = tmp.size();
153+
}
154+
std::cout << "-> resizing video frames before inference to " << options.resize_before.width << "x" << options.resize_before.height << std::endl;
155+
final_size = options.resize_before;
156+
}
157+
if (options.resize_after.width > 0 and options.resize_after.height > 0)
158+
{
159+
resize_after = true;
160+
if (not first_frame.empty())
161+
{
162+
// the size requested may need to be adjusted to keep the aspect ratio the same
163+
auto tmp = DarkHelp::resize_keeping_aspect_ratio(first_frame, options.resize_after);
164+
options.resize_after = tmp.size();
165+
}
166+
std::cout << "-> resizing video frames after annotation to " << options.resize_after.width << "x" << options.resize_after.height << std::endl;
167+
final_size = options.resize_after;
168+
}
169+
170+
cv::VideoWriter output("output.mp4", cv::VideoWriter::fourcc('m', 'p', '4', 'v'), options.fps_actual, final_size);
171+
if (not output.isOpened())
172+
{
173+
std::cout << "-> failed to open output.mp4 (video will not be saved!)" << std::endl;
174+
}
175+
else
176+
{
177+
std::cout << "-> output video will be " << final_size.width << "x" << final_size.height << " @ " << options.fps_actual << " FPS" << std::endl;
178+
}
179+
180+
size_t max_frame_counter = 0;
181+
if (options.capture_seconds > 0)
182+
{
183+
max_frame_counter = options.fps_actual * options.capture_seconds;
184+
std::cout << "-> max frame counter is set to " << max_frame_counter << " (" << (max_frame_counter / options.fps_actual) << " seconds)" << std::endl;
185+
}
186+
187+
// if we got here, then assume that everything is good
188+
rc = 0;
189+
const int fps_rounded = std::round(options.fps_actual);
190+
if (options.show_gui)
191+
{
192+
std::cout << "-> press ESC to stop" << std::endl;
193+
}
194+
195+
// Create a map where we'll track which objects were seen when. The key is the object name, the value is the frame index.
196+
std::map<std::string, size_t> m;
197+
std::string previously_seen_objects;
198+
199+
size_t frame_counter = 0;
200+
while (cap.isOpened() and errors < 5)
201+
{
202+
cv::Mat frame;
203+
cap >> frame;
204+
if (frame.empty())
205+
{
206+
// Was the camera disconnected? Or a bad frame? End of the video?
207+
errors ++;
208+
continue;
209+
}
210+
errors = 0;
211+
212+
if ((frame_counter % fps_rounded) == 0)
213+
{
214+
std::cout << "\rframe #" << frame_counter << " " << std::flush;
215+
}
216+
frame_counter ++;
217+
218+
if (resize_before)
219+
{
220+
frame = DarkHelp::resize_keeping_aspect_ratio(frame, options.resize_before);
221+
}
222+
223+
const auto results = nn.predict(frame);
224+
frame = nn.annotate();
225+
226+
// update the map that tracks when an object was last seen
227+
for (const auto & pred : results)
228+
{
229+
const auto & key = nn.names[pred.best_class];
230+
#if 0
231+
if (m.count(key) == 0 or // object was previously undetected...
232+
m[key] + fps_rounded * 2 < frame_counter) // ...or was last seen more than 2 seconds ago
233+
{
234+
new_object_found = true;
235+
}
236+
#endif
237+
m[key] = frame_counter;
238+
}
239+
240+
// come up with a new string of recently seen objects
241+
std::string str;
242+
for (const auto & [key, val] : m)
243+
{
244+
if (val + fps_rounded * 4 >= frame_counter)
245+
{
246+
// we're recently seen this object
247+
if (not str.empty())
248+
{
249+
str += ", ";
250+
}
251+
str += key;
252+
}
253+
}
254+
if (str != previously_seen_objects)
255+
{
256+
std::cout << "\rframe #" << frame_counter << ": " << str << std::endl;
257+
previously_seen_objects = str;
258+
}
259+
260+
if (resize_after)
261+
{
262+
frame = DarkHelp::resize_keeping_aspect_ratio(frame, options.resize_after);
263+
}
264+
265+
if (output.isOpened())
266+
{
267+
output.write(frame);
268+
}
269+
270+
if (max_frame_counter > 0 and frame_counter > max_frame_counter)
271+
{
272+
std::cout << std::endl << "Exiting!" << std::endl;
273+
break;
274+
}
275+
276+
if (options.show_gui)
277+
{
278+
cv::imshow("DarkHelp Camera Output", frame);
279+
const auto key = cv::waitKey(milliseconds_to_wait);
280+
if (key == 27)
281+
{
282+
std::cout << std::endl << "ESC detected -- exiting!" << std::endl;
283+
break;
284+
}
285+
}
286+
}
287+
}
288+
catch (const std::exception & e)
289+
{
290+
std::cout << e.what() << std::endl;
291+
rc = 1;
292+
}
293+
294+
return rc;
295+
}

0 commit comments

Comments
 (0)