This research project explores how computer vision can help save lives by detecting firearms in real time. I built and trained a specialized convolutional neural network with the YOLO/Darknet framework to identify active shooter scenarios with high precision, then integrated the model into a C++ application with computer vision for live video analysis and alerting. The goal is a practical, software-driven approach to faster threat detection. 1
Real-time firearm detection with 99% accuracy
Neural network training and validation results
Explore the C++ GUI built with FLTK framework for real-time firearm detection and security monitoring
How it works:
And yup, that works. But not without jumping through a few hoops:
#include opencv2/opencv.hpp #include opencv2/highgui/highgui.hpp
#include opencv2/imgproc/imgproc.hpp
#include iostream
#include opencv2/objdetect/objdetect.hpp
using namespace std;
using namespace cv;
#include
#include
#include
#include
#include
#include
#include "darknet.hpp"
#include "darknet_cfg_and_state.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace {
const Fl_Color kBg = fl_rgb_color(28, 28, 28);
const Fl_Color kPanel = fl_rgb_color(36, 36, 36);
const Fl_Color kBorder = fl_rgb_color(46, 204, 64);
const Fl_Color kTitleBar = fl_rgb_color(45, 45, 45);
const Fl_Color kBtnDark = fl_rgb_color(48, 48, 48);
const Fl_Color kCall911 = fl_rgb_color(200, 40, 40);
const Fl_Color kAlerts = fl_rgb_color(110, 130, 55);
const Fl_Color kSettings = fl_rgb_color(210, 175, 35);
const Fl_Color kRecord = fl_rgb_color(40, 170, 55);
const Fl_Color kWhite = fl_rgb_color(255, 255, 255);
void styleOpsButton(Fl_Button* btn, Fl_Color fill) {
btn->box(FL_FLAT_BOX);
btn->color(fill);
btn->labelcolor(kWhite);
btn->labelfont(FL_HELVETICA_BOLD);
btn->labelsize(14);
btn->clear_visible_focus();
}
} // namespace
class PlaybackWindow : public Fl_Window {
private:
Fl_Box* titleBox;
Fl_Button* closeButton;
Fl_Browser* recordingsList;
Fl_Button* playButton;
Fl_Button* deleteButton;
std::vector recordings;
void loadRecordings() {
recordings.clear();
recordingsList->clear();
for (const auto& entry : std::filesystem::directory_iterator(".")) {
const auto ext = entry.path().extension().string();
if (ext == ".mp4" || ext == ".avi") {
recordings.push_back(entry.path().string());
recordingsList->add(entry.path().filename().string().c_str());
}
}
}
static void playback_callback(Fl_Widget*, void* data) {
auto* win = static_cast(data);
const int selected = win->recordingsList->value();
if (selected > 0 && selected <= static_cast(win->recordings.size())) {
const std::string cmd = "xdg-open \"" + win->recordings[selected - 1] + "\"";
system(cmd.c_str());
}
}
static void delete_callback(Fl_Widget*, void* data) {
auto* win = static_cast(data);
const int selected = win->recordingsList->value();
if (selected > 0 && selected <= static_cast(win->recordings.size())) {
std::filesystem::remove(win->recordings[selected - 1]);
win->loadRecordings();
}
}
public:
PlaybackWindow(int w, int h, const char* title)
: Fl_Window(w, h, title) {
color(kBg);
box(FL_FLAT_BOX);
titleBox = new Fl_Box(0, 0, w, 30, "PLAYBACK");
titleBox->box(FL_FLAT_BOX);
titleBox->color(kTitleBar);
titleBox->labelcolor(kWhite);
titleBox->labelfont(FL_HELVETICA_BOLD);
titleBox->labelsize(14);
Fl_Box* accent = new Fl_Box(0, 30, w, 3);
accent->box(FL_FLAT_BOX);
accent->color(kBorder);
closeButton = new Fl_Button(w - 34, 3, 28, 24, "X");
closeButton->box(FL_FLAT_BOX);
closeButton->color(kCall911);
closeButton->labelcolor(kWhite);
closeButton->labelsize(14);
closeButton->callback([](Fl_Widget*, void* data) {
static_cast(data)->hide();
}, this);
recordingsList = new Fl_Browser(10, 44, w - 20, h - 100);
recordingsList->type(FL_HOLD_BROWSER);
recordingsList->textsize(14);
recordingsList->color(kPanel);
recordingsList->textcolor(kWhite);
playButton = new Fl_Button(10, h - 46, 100, 32, "PLAY");
styleOpsButton(playButton, fl_rgb_color(0, 140, 220));
playButton->callback(playback_callback, this);
deleteButton = new Fl_Button(120, h - 46, 100, 32, "DELETE");
styleOpsButton(deleteButton, kCall911);
deleteButton->callback(delete_callback, this);
loadRecordings();
end();
}
void refresh() { loadRecordings(); }
};
class CameraListWindow : public Fl_Window {
private:
Fl_Browser* cameraList;
Fl_Button* selectButton;
Fl_Button* closeButton;
std::vector cameraIndexes;
std::function onSelect;
void refreshCameras() {
cameraIndexes.clear();
cameraList->clear();
for (int i = 0; i < 10; ++i) {
cv::VideoCapture test(i, cv::CAP_V4L2);
if (test.isOpened()) {
cameraIndexes.push_back(i);
const std::string label = "/dev/video" + std::to_string(i);
cameraList->add(label.c_str());
test.release();
}
}
}
static void select_callback(Fl_Widget*, void* data) {
auto* win = static_cast(data);
const int selected = win->cameraList->value();
if (selected > 0 && selected <= static_cast(win->cameraIndexes.size()) && win->onSelect) {
win->onSelect(win->cameraIndexes[selected - 1]);
win->hide();
}
}
public:
CameraListWindow(int w, int h, std::function selectCb)
: Fl_Window(w, h, "Camera List"), onSelect(std::move(selectCb)) {
color(kBg);
Fl_Box* title = new Fl_Box(0, 0, w, 30, "CAMERA LIST");
title->box(FL_FLAT_BOX);
title->color(kTitleBar);
title->labelcolor(kWhite);
title->labelfont(FL_HELVETICA_BOLD);
title->labelsize(14);
Fl_Box* accent = new Fl_Box(0, 30, w, 3);
accent->box(FL_FLAT_BOX);
accent->color(kBorder);
closeButton = new Fl_Button(w - 34, 3, 28, 24, "X");
closeButton->box(FL_FLAT_BOX);
closeButton->color(kCall911);
closeButton->labelcolor(kWhite);
closeButton->callback([](Fl_Widget*, void* data) {
static_cast(data)->hide();
}, this);
cameraList = new Fl_Browser(10, 44, w - 20, h - 100);
cameraList->type(FL_HOLD_BROWSER);
cameraList->color(kPanel);
cameraList->textcolor(kWhite);
cameraList->textsize(14);
selectButton = new Fl_Button(10, h - 46, 120, 32, "SELECT");
styleOpsButton(selectButton, kRecord);
selectButton->callback(select_callback, this);
refreshCameras();
end();
}
void refresh() { refreshCameras(); }
};
class WebcamWindow : public Fl_Window {
private:
Fl_Box* borderBox;
Fl_Box* videoBox;
cv::VideoCapture cap;
cv::Mat frame, resizedFrame;
Fl_RGB_Image* img;
bool stopFlag;
cv::VideoWriter videoWriter;
bool isRecording;
std::string currentVideoFile;
PlaybackWindow* playbackPopup;
CameraListWindow* cameraListPopup;
int currentCameraIndex;
std::mutex frameMutex;
Darknet::NetworkPtr net;
double estimated_fps;
size_t frame_counter;
size_t total_objects_found;
std::chrono::high_resolution_clock::time_point timestamp_start;
Fl_Box* topBar;
Fl_Button* exitButton;
Fl_Button* actionButton;
Fl_Button* alertButton;
Fl_Button* settingsButton;
Fl_Button* recordButton;
Fl_Button* CameralistButton;
Fl_Button* viewButton;
Fl_Button* playbackButton;
const std::string MODEL_CFG = "/home/zeshan/src/darknet/src-examples/Shooter.cfg";
const std::string MODEL_NAMES = "/home/zeshan/src/darknet/src-examples/Shooter.names";
const std::string MODEL_WEIGHTS = "/home/zeshan/src/darknet/src-examples/Shooter_best.weights";
std::string getTimestamp() {
const auto now = std::chrono::system_clock::now();
const auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&in_time_t), "%Y%m%d_%H%M%S");
return ss.str();
}
void startRecording() {
currentVideoFile = "recording_" + getTimestamp() + ".avi";
const int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
double fps = estimated_fps > 1.0 ? estimated_fps : 20.0;
const cv::Size frameSize(
static_cast(cap.get(cv::CAP_PROP_FRAME_WIDTH)),
static_cast(cap.get(cv::CAP_PROP_FRAME_HEIGHT)));
if (videoWriter.open(currentVideoFile, fourcc, fps, frameSize)) {
isRecording = true;
recordButton->label("STOP");
recordButton->color(kCall911);
recordButton->redraw();
std::cout << "Recording started: ./" << currentVideoFile << std::endl;
} else {
std::cerr << "Failed to initialize video writer!" << std::endl;
}
}
void stopRecording() {
isRecording = false;
videoWriter.release();
recordButton->label("RECORD");
recordButton->color(kRecord);
recordButton->redraw();
std::cout << "Recording saved: ./" << currentVideoFile << std::endl;
}
static void record_button_callback(Fl_Widget*, void* data) {
auto* win = static_cast(data);
if (!win->isRecording) {
win->startRecording();
} else {
win->stopRecording();
}
}
bool openCamera(int index) {
if (cap.isOpened()) {
cap.release();
}
if (!cap.open(index, cv::CAP_V4L2)) {
std::cerr << "Error: Could not open webcam index " << index << std::endl;
return false;
}
cap.set(cv::CAP_PROP_FRAME_WIDTH, 640);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);
cap.set(cv::CAP_PROP_FPS, 30);
currentCameraIndex = index;
std::cout << "Switched to camera /dev/video" << index << std::endl;
return true;
}
public:
WebcamWindow(int w, int h, const char* title)
: Fl_Window(w, h, title),
img(nullptr),
stopFlag(false),
isRecording(false),
playbackPopup(nullptr),
cameraListPopup(nullptr),
currentCameraIndex(0),
frame_counter(0),
total_objects_found(0) {
Darknet::VStr args = {
MODEL_CFG,
MODEL_NAMES,
MODEL_WEIGHTS,
"--thresh", "0.5"
};
Darknet::Parms parms = Darknet::parse_arguments(args);
net = Darknet::load_neural_network(parms);
if (!openCamera(0)) {
throw std::runtime_error("webcam open failed");
}
estimated_fps = estimate_camera_fps(cap);
timestamp_start = std::chrono::high_resolution_clock::now();
Fl::scheme("gtk+");
color(kBg);
box(FL_FLAT_BOX);
const int border = 4;
const int topBarHeight = 36;
const int sidebarW = 220;
const int margin = 16;
const int btnH = 48;
const int btnGap = 14;
borderBox = new Fl_Box(0, 0, w, h);
borderBox->box(FL_BORDER_BOX);
borderBox->color(kBg);
borderBox->color2(kBorder);
// Green outline using thin boxes
Fl_Box* topEdge = new Fl_Box(0, 0, w, border);
topEdge->box(FL_FLAT_BOX);
topEdge->color(kBorder);
Fl_Box* bottomEdge = new Fl_Box(0, h - border, w, border);
bottomEdge->box(FL_FLAT_BOX);
bottomEdge->color(kBorder);
Fl_Box* leftEdge = new Fl_Box(0, 0, border, h);
leftEdge->box(FL_FLAT_BOX);
leftEdge->color(kBorder);
Fl_Box* rightEdge = new Fl_Box(w - border, 0, border, h);
rightEdge->box(FL_FLAT_BOX);
rightEdge->color(kBorder);
topBar = new Fl_Box(border, border, w - 2 * border, topBarHeight, "GUARDIANSAFE OPS CONSOLE v1.0.3");
topBar->box(FL_FLAT_BOX);
topBar->color(kTitleBar);
topBar->labelcolor(kWhite);
topBar->labelfont(FL_HELVETICA_BOLD);
topBar->labelsize(16);
topBar->align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE);
Fl_Box* accentLine = new Fl_Box(border, border + topBarHeight, w - 2 * border, 3);
accentLine->box(FL_FLAT_BOX);
accentLine->color(kBorder);
exitButton = new Fl_Button(w - border - 34, border + 5, 28, 26, "X");
exitButton->box(FL_FLAT_BOX);
exitButton->color(kCall911);
exitButton->labelcolor(kWhite);
exitButton->labelfont(FL_HELVETICA_BOLD);
exitButton->labelsize(14);
exitButton->callback([](Fl_Widget*, void* data) {
static_cast(data)->hide();
}, this);
const int contentTop = border + topBarHeight + 3 + margin;
const int contentH = h - contentTop - border - margin;
const int videoW = w - 2 * border - sidebarW - 3 * margin;
const int sidebarX = border + margin + videoW + margin;
videoBox = new Fl_Box(border + margin, contentTop, videoW, contentH);
videoBox->box(FL_FLAT_BOX);
videoBox->color(FL_BLACK);
resizedFrame = cv::Mat(videoBox->h(), videoBox->w(), CV_8UC3);
int by = contentTop;
const int bw = sidebarW;
actionButton = new Fl_Button(sidebarX, by, bw, btnH, "CALL 911");
styleOpsButton(actionButton, kCall911);
actionButton->callback(button_callback, this);
by += btnH + btnGap;
alertButton = new Fl_Button(sidebarX, by, bw, btnH, "ALERTS");
styleOpsButton(alertButton, kAlerts);
alertButton->callback([](Fl_Widget*, void*) {
std::cout << "Alerts panel opened" << std::endl;
});
by += btnH + btnGap;
settingsButton = new Fl_Button(sidebarX, by, bw, btnH, "SETTINGS");
styleOpsButton(settingsButton, kSettings);
settingsButton->callback([](Fl_Widget*, void*) {
std::cout << "Settings opened" << std::endl;
});
by += btnH + btnGap;
recordButton = new Fl_Button(sidebarX, by, bw, btnH, "RECORD");
styleOpsButton(recordButton, kRecord);
recordButton->callback(record_button_callback, this);
by += btnH + btnGap;
CameralistButton = new Fl_Button(sidebarX, by, bw, btnH, "CAMERA LIST");
styleOpsButton(CameralistButton, kBtnDark);
CameralistButton->callback(camera_list_callback, this);
by += btnH + btnGap;
viewButton = new Fl_Button(sidebarX, by, bw, btnH, "CAMERA VIEW");
styleOpsButton(viewButton, kBtnDark);
viewButton->callback([](Fl_Widget*, void* data) {
auto* win = static_cast(data);
std::cout << "Camera view: /dev/video" << win->currentCameraIndex << std::endl;
}, this);
by += btnH + btnGap;
playbackButton = new Fl_Button(sidebarX, by, bw, btnH, "PLAYBACK");
styleOpsButton(playbackButton, kBtnDark);
playbackButton->callback(playback_button_callback, this);
end();
}
double estimate_camera_fps(cv::VideoCapture& capture) {
std::cout << "Estimating FPS..." << std::endl;
cv::Mat mat;
for (int i = 0; i < 5; i++) {
capture >> mat;
}
size_t counted = 0;
const auto ts1 = std::chrono::high_resolution_clock::now();
for (int i = 0; capture.isOpened() && i < 5; i++) {
capture >> mat;
if (!mat.empty()) {
counted++;
}
}
const auto ts2 = std::chrono::high_resolution_clock::now();
const double ns = std::chrono::duration_cast(ts2 - ts1).count();
return static_cast(counted) / ns * 1000000000.0;
}
void drawAppleStyleText(cv::Mat& imgFrame, const std::string& text, cv::Point position, double fontScale, int thickness) {
int baseline = 0;
cv::Size textSize = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, fontScale, thickness, &baseline);
cv::Rect bgRect(
position.x,
position.y - textSize.height,
textSize.width + 8,
textSize.height + baseline + 6);
bgRect &= cv::Rect(0, 0, imgFrame.cols, imgFrame.rows);
if (bgRect.area() <= 0) {
return;
}
cv::Mat overlay = imgFrame.clone();
cv::rectangle(overlay, bgRect, cv::Scalar(0, 0, 0), cv::FILLED);
cv::addWeighted(overlay, 0.35, imgFrame, 0.65, 0, imgFrame);
cv::putText(imgFrame, text, position + cv::Point(2, 2), cv::FONT_HERSHEY_SIMPLEX, fontScale, cv::Scalar(0, 0, 0), thickness);
cv::putText(imgFrame, text, position, cv::FONT_HERSHEY_SIMPLEX, fontScale, cv::Scalar(80, 255, 120), thickness);
}
void updateFrame() {
cv::Mat local;
{
std::lock_guard lock(frameMutex);
if (!cap.read(local) || local.empty()) {
std::cerr << "Error: Failed to capture frame!" << std::endl;
return;
}
}
const auto results = Darknet::predict_and_annotate(net, local);
total_objects_found += results.size();
const auto now = std::chrono::high_resolution_clock::now();
const double elapsed_seconds = std::chrono::duration_cast(now - timestamp_start).count() / 1000000000.0;
const double current_fps = frame_counter > 0 ? frame_counter / elapsed_seconds : 0.0;
std::stringstream stats;
stats << "FPS: " << std::fixed << std::setprecision(1) << current_fps << "\n"
<< "Objects: " << total_objects_found << "\n"
<< "Elapsed seconds: " << static_cast(elapsed_seconds);
std::vector lines;
std::string line;
while (std::getline(stats, line, '\n')) {
lines.push_back(line);
}
int y_offset = 30;
for (const auto& text_line : lines) {
drawAppleStyleText(local, text_line, cv::Point(10, y_offset), 0.7, 2);
y_offset += 30;
}
if (isRecording && videoWriter.isOpened()) {
videoWriter.write(local);
}
cv::cvtColor(local, local, cv::COLOR_BGR2RGB);
cv::resize(local, resizedFrame, resizedFrame.size());
if (img) {
delete img;
}
img = new Fl_RGB_Image(resizedFrame.data, resizedFrame.cols, resizedFrame.rows, 3);
videoBox->image(img);
videoBox->redraw();
frame_counter++;
}
static void captureLoop(void* userdata) {
auto* win = static_cast(userdata);
while (!win->stopFlag) {
win->updateFrame();
Fl::check();
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 30));
}
}
void startCapture() {
std::thread(captureLoop, this).detach();
}
~WebcamWindow() {
stopFlag = true;
if (isRecording) {
stopRecording();
}
if (img) {
delete img;
}
delete playbackPopup;
delete cameraListPopup;
cap.release();
Darknet::free_neural_network(net);
}
static void button_callback(Fl_Widget*, void*) {
std::cout << "CALL 911 pressed" << std::endl;
}
static void playback_button_callback(Fl_Widget*, void* data) {
auto* win = static_cast(data);
if (!win->playbackPopup) {
win->playbackPopup = new PlaybackWindow(420, 520, "Playback");
}
win->playbackPopup->refresh();
win->playbackPopup->show();
}
static void camera_list_callback(Fl_Widget*, void* data) {
auto* win = static_cast(data);
if (!win->cameraListPopup) {
win->cameraListPopup = new CameraListWindow(360, 420, [win](int index) {
std::lock_guard lock(win->frameMutex);
win->openCamera(index);
win->estimated_fps = win->estimate_camera_fps(win->cap);
});
}
win->cameraListPopup->refresh();
win->cameraListPopup->show();
}
};
int main() {
WebcamWindow window(1320, 860, "GuardianSafe Ops Console");
window.show();
window.startCapture();
return Fl::run();
}
For now, lets build :D