-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.cpp
295 lines (264 loc) · 9.47 KB
/
main.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
#include <opencv2/opencv.hpp>
#include <boost/asio/thread_pool.hpp>
#include <boost/asio/post.hpp>
#include <vector>
#include <getopt.h>
#include <spdlog/spdlog.h>
#include <filesystem>
namespace {
constexpr int DEFAULT_RATE = 1;
constexpr int DEFAULT_BATCHES = 100;
constexpr int DEFAULT_WORKERS = 5;
constexpr bool DEFAULT_VERBOSE = false;
constexpr bool DEFAULT_TRANSFORM = false;
constexpr char DEFAULT_OUTPUT_FILE[] = "../barcode.png";
}
enum ReturnCode { SUCCESS = 0, FAILURE = 1 };
/*
* Batch Struct
* ------------
* The Batch structure maintains a vector of frames and a unique identifier.
* This abstraction helps process frames in easy-to-manage groups.
*/
struct Batch {
int id;
std::vector<cv::Mat> frames;
};
/*
* CommandLineArguments Struct
* ---------------------------
* The CommandLineArguments structure maintains all barcode options.
* Missing options are reasonably defaulted.
*/
struct CommandLineArguments {
std::string file;
int rate = DEFAULT_RATE;
int batches = DEFAULT_BATCHES;
int workers = DEFAULT_WORKERS;
bool verbose = DEFAULT_VERBOSE;
bool transform = DEFAULT_TRANSFORM;
std::string output = DEFAULT_OUTPUT_FILE;
};
/*
* printUsage()
* ------------
* This helper function displays all available barcode options.
*/
void printUsage(const char* programName) {
std::cout << "Usage: " << programName << " [options]\n"
<< "Options:\n"
<< " -f, --file <file> Movie file (required)\n"
<< " -r, --rate <rate> Sampling rate (default: 1)\n"
<< " -b, --batches <batches> Number of batches (default: 100)\n"
<< " -w, --workers <workers> Number of workers (default: 5)\n"
<< " -v, --verbose Verbose output (default: false)\n"
<< " -t, --transform Transform output (default: false)\n"
<< " -h, --help Display this help message\n";
}
/*
* parseArguments()
* ----------------
* This helper function parses the supplied command line arguments and updates the structure
* (passed by reference) defaults accordingly. Notably, it demands that a file is supplied.
*/
bool parseArguments(int argc, char* argv[], CommandLineArguments& arguments) {
int opt;
while ((opt = getopt(argc, argv, "f:r:b:w:o:vth")) != -1) {
try {
switch (opt) {
case 'f':
arguments.file = optarg;
break;
case 'r':
arguments.rate = std::stoi(optarg);
break;
case 'b':
arguments.batches = std::stoi(optarg);
break;
case 'w':
arguments.workers = std::stoi(optarg);
break;
case 'o':
arguments.output = optarg;
break;
case 'v':
arguments.verbose = true;
break;
case 't':
arguments.transform = true;
break;
case 'h':
default:
printUsage(argv[0]);
return FAILURE;
}
} catch (const std::invalid_argument& e) {
spdlog::error("Invalid argument for option: -{}", static_cast<char>(opt));
return FAILURE;
}
}
// Check that a video file is supplied
if (arguments.file.empty()) {
spdlog::error("A movie file is required.");
printUsage(argv[0]);
return FAILURE;
}
return SUCCESS;
}
/*
* checkArguments()
* ----------------
* This helper function returns TRUE if the arguments supplied appear valid and false otherwise.
* Errors are logged for convenience.
*/
bool checkArguments(const CommandLineArguments& arguments) {
// Check that the movie file is readable and, if so, extract the frame count
cv::VideoCapture cap(arguments.file);
if (!cap.isOpened()) {
spdlog::error("Unable to open the movie file: {}", arguments.file);
return FAILURE;
}
int totalFrames = cap.get(cv::CAP_PROP_FRAME_COUNT);
// Check that the arguments are positive and do not exceed extreme bounds
if (arguments.rate <= 0) {
spdlog::error("Sampling rate must be greater than 0.");
return FAILURE;
}
if (arguments.batches <= 0) {
spdlog::error("Number of batches must be greater than 0.");
return FAILURE;
}
if (arguments.workers <= 0) {
spdlog::error("Number of workers must be greater than 0.");
return FAILURE;
}
if (arguments.rate >= totalFrames) {
spdlog::error("Sampling rate must be less than the total number of frames.");
return FAILURE;
}
int usedFrames = totalFrames / arguments.rate;
if (arguments.batches >= usedFrames) {
spdlog::error("Number of batches must be less than the number of used frames.");
return FAILURE;
}
// Validate the output path
std::filesystem::path path = arguments.output;
if (!std::filesystem::exists(path.parent_path())) {
spdlog::error("Invalid output path: {}", arguments.output);
return FAILURE;
}
// Otherwise, release the capture and indicate success
cap.release();
return SUCCESS;
}
/*
* process()
* ---------
* This function processes a batch of frames by computing its row-wise average.
* The result is neatly placed in a results vector based on the batch's id.
*/
void process(const Batch& batch, std::vector<cv::Mat>& results) {
cv::Mat concatenation;
cv::Mat average;
cv::hconcat(batch.frames, concatenation);
cv::reduce(concatenation, average, 1, cv::REDUCE_AVG);
// Store the result
// thread-safe because the unique batch id ensures we never modify the same memory
results[batch.id] = average;
spdlog::info("Processed batch {}.", batch.id);
}
/*
* read()
* ------
* This function reads frames from the capture and batches them based on the
* sampling rate specified. Once packaged, batches are allocated to worker threads.
*/
void read(boost::asio::thread_pool& pool, std::vector<cv::Mat>& results, const CommandLineArguments& arguments) {
// Open the video file
cv::VideoCapture cap(arguments.file);
if (!cap.isOpened()) {
spdlog::error("Unable to open the movie file: {}", arguments.file);
return;
}
// Compute frames per batch
int totalFrames = cap.get(cv::CAP_PROP_FRAME_COUNT);
int usedFrames = totalFrames / arguments.rate;
int framesPerBatch = usedFrames / arguments.batches;
// Setup Counters and Current Batch
int frameCounter = 0;
int batchCounter = 0;
Batch currentBatch{batchCounter};
// While batches are remaining...
while (batchCounter < arguments.batches) {
// Grab frames from the capture
cap.grab();
// Retrieve frames based on the sampling rate
if (frameCounter % arguments.rate == 0) {
cv::Mat frame;
cap.retrieve(frame);
currentBatch.frames.push_back(frame);
// Allocate the current batch if its size threshold is met
if (currentBatch.frames.size() == framesPerBatch) {
boost::asio::post(pool, [currentBatch, &results]() { process(currentBatch, results); });
spdlog::info("Allocated batch {}.", currentBatch.id);
batchCounter++;
currentBatch = Batch{batchCounter};
}
}
frameCounter++;
}
spdlog::info("Finished reading frames.");
}
/*
* polarTransform()
* ----------------
* This helper function remaps the barcode (passed by reference) from cartesian to
* polar space in-place.
*/
void polarTransform(cv::Mat& barcode, int nBatches) {
// Prepare for transformation
cv::Mat flipped;
cv::Mat polarImage;
cv::rotate(barcode, flipped, cv::ROTATE_90_CLOCKWISE);
// Apply transformation
cv::Size dsize(nBatches, nBatches);
cv::Point2f center(nBatches / 2.0f, nBatches / 2.0f);
int maxRadius = nBatches / 2.0f;
cv::warpPolar(flipped, polarImage, dsize, center, maxRadius, cv::WARP_INVERSE_MAP);
// Create a circular mask to make pixels outside the circle transparent
cv::Mat mask = cv::Mat::zeros(polarImage.size(), CV_8UC1);
cv::circle(mask, center, maxRadius, cv::Scalar(255), -1);
// Apply the mask to make pixels outside the circle transparent
cv::cvtColor(polarImage, polarImage, cv::COLOR_BGR2BGRA);
polarImage.setTo(cv::Scalar(0, 0, 0, 0), mask == 0);
barcode = polarImage;
}
int main(int argc, char** argv) {
// Parse and Check arguments
CommandLineArguments arguments;
if (parseArguments(argc, argv, arguments) == FAILURE) {
return FAILURE;
}
if (checkArguments(arguments) == FAILURE) {
return FAILURE;
}
if (!arguments.verbose) {
spdlog::set_level(spdlog::level::off);
}
// Setup Results and ThreadPool
std::vector<cv::Mat> results(arguments.batches);
boost::asio::thread_pool pool(arguments.workers);
read(pool, results, arguments);
pool.join();
// Package the vector into a single matrix
cv::Mat barcode;
cv::hconcat(results, barcode);
// Apply polar transform if requested
if (arguments.transform) {
polarTransform(barcode, arguments.batches);
}
// Save the barcode image
cv::imwrite(arguments.output, barcode);
spdlog::info("Barcode saved as {}", arguments.output);
return SUCCESS;
}