diff --git a/CMakeLists.txt b/CMakeLists.txt
index dc272e3..678b9c7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
# +-----------------------------------------+
# | License: MIT |
# +-----------------------------------------+
-# | Copyright (c) 2023 |
+# | Copyright (c) 2024 |
# | Author: Gleb Trufanov (aka Glebchansky) |
# +-----------------------------------------+
@@ -9,68 +9,54 @@ cmake_minimum_required(VERSION 3.21)
project(ISS-Driver-Drowsiness-Detector)
+include(cmake/CheckSystem.cmake)
+
set(MAIN_TARGET DDDetector)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
-set(CMAKE_CXX_STANDARD 17)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_SOURCE_DIR}/src/ui)
-if (CMAKE_SIZEOF_VOID_P EQUAL 8)
- if (WIN32)
- message(STATUS "Windows operating system detected")
-
- if (NOT MSVC)
- message(FATAL_ERROR "The project is designed to work with the MSVC compiler when compiling on a Windows operating system")
- endif ()
-
- message(STATUS "Disabling the console at program start-up")
- add_link_options(/SUBSYSTEM:windows /ENTRY:mainCRTStartup) # To hide the console window at program start-up on Windows
- elseif (UNIX AND NOT APPLE)
- message(STATUS "Linux kernel operating system detected")
- else ()
- message(FATAL_ERROR "At this point, the project is only designed to run on Windows or Linux operating systems")
- endif ()
-else ()
- message(FATAL_ERROR "The project is designed to work with an operating system that supports 64-bit extensions")
+check_system()
+
+set(EXECUTABLE_DESTINATION_PATH ${CMAKE_BINARY_DIR})
+
+if (UNIX)
+ set(EXECUTABLE_DESTINATION_PATH ${CMAKE_BINARY_DIR}/bin)
endif ()
-set(RESOURCE_CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/resource)
-set(RESOURCE_CMAKE_BINARY_DIR ${CMAKE_BINARY_DIR}/resource)
+message(STATUS "Set the output directory for the executable to " ${EXECUTABLE_DESTINATION_PATH})
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${EXECUTABLE_DESTINATION_PATH})
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${EXECUTABLE_DESTINATION_PATH})
-message(STATUS "Set the output directory for the executable to " ${CMAKE_BINARY_DIR})
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR})
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR})
+set(RESOURCE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/resource)
+set(RESOURCE_BINARY_DIR ${CMAKE_BINARY_DIR}/resource)
# Project sources
-set(TS_FILES ${RESOURCE_CMAKE_SOURCE_DIR}/translation/${PROJECT_NAME}_ru_RU.ts)
-set(RC_FILES ${RESOURCE_CMAKE_SOURCE_DIR}/resource.qrc ${RESOURCE_CMAKE_SOURCE_DIR}/executable_icon_resource.rc)
+set(TS_FILES ${RESOURCE_SOURCE_DIR}/translation/${PROJECT_NAME}_ru_RU.ts)
+set(RC_FILES ${RESOURCE_SOURCE_DIR}/resource.qrc ${RESOURCE_SOURCE_DIR}/executable_icon_resource.rc)
set(PROJECT_SOURCES
${RC_FILES}
+ ${CMAKE_SOURCE_DIR}/src/ui/mainwindow.ui
${CMAKE_SOURCE_DIR}/src/main.cpp
- ${CMAKE_SOURCE_DIR}/src/MainWindow.h
${CMAKE_SOURCE_DIR}/src/MainWindow.cpp
- ${CMAKE_SOURCE_DIR}/src/Camera.h
${CMAKE_SOURCE_DIR}/src/Camera.cpp
- ${CMAKE_SOURCE_DIR}/src/ImageRecognizer.h
${CMAKE_SOURCE_DIR}/src/ImageRecognizer.cpp
- ${CMAKE_SOURCE_DIR}/src/utils/Utils.h
${CMAKE_SOURCE_DIR}/src/utils/Utils.cpp
- ${CMAKE_SOURCE_DIR}/src/utils/RecognitionFrameCounter.h
- ${CMAKE_SOURCE_DIR}/src/utils/TextBrowserLogger.h
${CMAKE_SOURCE_DIR}/src/utils/TextBrowserLogger.cpp
- ${CMAKE_SOURCE_DIR}/src/ui/mainwindow.ui
)
#####################################################################################
# Find packages
-find_package(Qt6 COMPONENTS REQUIRED
+find_package(Qt6 6.3 REQUIRED COMPONENTS
LinguistTools
Widgets
Multimedia
@@ -79,18 +65,14 @@ find_package(Qt6 COMPONENTS REQUIRED
find_package(OpenCV 4.5.4 REQUIRED) # 4.5.4 is the minimum version
#####################################################################################
-# Uncomment this to create a QM file based on the TS file.
-# A deprecated but working way to update .ts files based on new translations in the source code
-# qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
-
add_executable(${MAIN_TARGET} ${PROJECT_SOURCES})
# Copying the neural network model and warning sound to the folder with the executable file
-# It only works when running cmake command, not during actual build time
-file(COPY ${RESOURCE_CMAKE_SOURCE_DIR}/neural_network_model DESTINATION ${RESOURCE_CMAKE_BINARY_DIR})
-file(COPY ${RESOURCE_CMAKE_SOURCE_DIR}/sound DESTINATION ${RESOURCE_CMAKE_BINARY_DIR})
+# It only works during CMake configuration (build generation), not during the actual build
+file(COPY ${RESOURCE_SOURCE_DIR}/neural_network_model DESTINATION ${RESOURCE_BINARY_DIR})
+file(COPY ${RESOURCE_SOURCE_DIR}/sound DESTINATION ${RESOURCE_BINARY_DIR})
-set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION ${RESOURCE_CMAKE_BINARY_DIR}/translation)
+set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION ${RESOURCE_BINARY_DIR}/translation)
qt6_add_translations(${MAIN_TARGET} TS_FILES ${TS_FILES})
target_include_directories(${MAIN_TARGET} PRIVATE
@@ -99,6 +81,19 @@ target_include_directories(${MAIN_TARGET} PRIVATE
${OpenCV_INCLUDE_DIRS}
)
+if (UNIX)
+ set(RPATH_LIBRARY_DIRECTORIES Qt6 opencv cuda)
+
+ foreach (RPATH_DIRECTORY ${RPATH_LIBRARY_DIRECTORIES})
+ list(APPEND RPATH_LIST "$ORIGIN/../lib64/${RPATH_DIRECTORY}")
+ endforeach ()
+
+ list(JOIN RPATH_LIST ":" RPATH_LIST)
+
+ # Lots of problems with escaping $ORIGIN in CMake (target_link_options especially), but thanks https://stackoverflow.com/a/75790542
+ set_target_properties(${MAIN_TARGET} PROPERTIES LINK_FLAGS "-Wl,--enable-new-dtags,-rpath,'${RPATH_LIST}'")
+endif ()
+
target_link_libraries(${MAIN_TARGET} PRIVATE
Qt6::Widgets
Qt6::Multimedia
diff --git a/LICENSE.md b/LICENSE.md
index d6987c1..e7d4b11 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2023 Gleb Trufanov (aka Glebchansky)
+Copyright (c) 2024 Gleb Trufanov (aka Glebchansky)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 49c3ed1..fcbebab 100644
--- a/README.md
+++ b/README.md
@@ -35,14 +35,14 @@ using facial video surveillance.
- 🎮 Interactive interaction with the prototype:
- Start/stop the recognition system
- Interaction with a video camera
- - Eye and gesture recognition ([V gesture ✌](#v-gesture), [Fist gesture ✊](#fist-gesture), [Palm gesture ✋](#palm-gesture))
+ - Eye and gesture recognition ([V gesture ✌️](#v-gesture), [Fist gesture ✊](#fist-gesture), [Palm gesture ✋](#palm-gesture))
- Warning of a potential emergency situation using warning sound
- 🚀 Recognition (drowsiness, gestures, etc.) takes approximately one second
- ⚙️ Multi-language user interface
- ⚙️ Cross-platform (Windows/Linux)
- ⚙️ Multithreaded application
-## Dependencies
+## Dependencies (Ninja) TODO
- [C++17](https://en.cppreference.com/w/cpp/17):
- 64-bit MSVC since version 19.15 on Windows
- 64-bit GCC since version 11.2 on Linux
diff --git a/build.py b/build.py
index 001bd60..5b92969 100644
--- a/build.py
+++ b/build.py
@@ -1,7 +1,7 @@
# +-----------------------------------------+
# | License: MIT |
# +-----------------------------------------+
-# | Copyright (c) 2023 |
+# | Copyright (c) 2024 |
# | Author: Gleb Trufanov (aka Glebchansky) |
# +-----------------------------------------+
@@ -12,27 +12,30 @@
import subprocess
argParser = argparse.ArgumentParser(prog="Driver drowsiness detector")
+argParser.add_argument("--qt-cmake-prefix-path", required=True, help="Qt CMake prefix path")
+argParser.add_argument("--opencv-dir", required=True, help="Directory containing a CMake configuration file for OpenCV")
+argParser.add_argument("-j", "--jobs", default=4, help="Number of threads used when building")
+
+args = argParser.parse_args()
-qtCmakePrefixPathDefaultValue = "C:\\Qt\\6.5.2\\msvc2019_64\\lib\\cmake" # Hardcoded default value
-opencvDirDefaultValue = "C:\\opencv-4.8.0\\build\\install" # Hardcoded default value
cmakeConfigureArgs = ["cmake"]
+cmakeBuildArgs = ["cmake", "--build", "."]
if platform.system() == "Linux":
+ needShell = False
+
# On Windows, the generator is not explicitly specified so that the required Visual Studio version of the
# generator is detected automatically
- cmakeConfigureArgs.append("-G Ninja") # TODO: Build with Ninja in Linux
-
- qtCmakePrefixPath = "qt_pass" # Hardcoded default value # TODO: Workability in Linux
- opencvDir = "opencv_pass" # Hardcoded default value # TODO: Workability in Linux
-elif platform.system() != "Windows":
+ cmakeConfigureArgs.append("-G=Ninja")
+ cmakeConfigureArgs.append("-DCMAKE_BUILD_TYPE=Release")
+elif platform.system() == "Windows":
+ needShell = True
+
+ cmakeConfigureArgs.append("-DCMAKE_CONFIGURATION_TYPES=Release")
+ cmakeBuildArgs.append("--config=Release")
+else:
raise Exception(f"Unexpected operating system. Got {platform.system()}, but expected Windows or Linux")
-argParser.add_argument("--qt-cmake-prefix-path", default=qtCmakePrefixPathDefaultValue, help="Qt CMake prefix path")
-argParser.add_argument("--opencv-dir", default=opencvDirDefaultValue, help="Directory containing a CMake "
- "configuration file for OpenCV")
-argParser.add_argument("-j", "--jobs", default=4, help="Number of threads used when building")
-args = argParser.parse_args()
-
shutil.rmtree("build", ignore_errors=True)
os.mkdir("build")
os.chdir("build")
@@ -41,5 +44,7 @@
cmakeConfigureArgs.append(f"-DOpenCV_DIR={args.opencv_dir}")
cmakeConfigureArgs.append("..")
-subprocess.run(cmakeConfigureArgs, shell=True, check=True) # CMake configure
-subprocess.run(["cmake", "--build", ".", "--config", "Release", "-j", str(args.jobs)], shell=True, check=True) # Build
+cmakeBuildArgs.append(f"-j={str(args.jobs)}")
+
+subprocess.run(cmakeConfigureArgs, shell=needShell, check=True) # CMake configure
+subprocess.run(cmakeBuildArgs, shell=needShell, check=True) # Build
diff --git a/cmake/CheckSystem.cmake b/cmake/CheckSystem.cmake
new file mode 100644
index 0000000..4649e38
--- /dev/null
+++ b/cmake/CheckSystem.cmake
@@ -0,0 +1,24 @@
+function(check_system)
+ if (CMAKE_SIZEOF_VOID_P EQUAL 8)
+ if (WIN32)
+ message(STATUS "Windows operating system detected")
+
+ if (NOT MSVC)
+ message(FATAL_ERROR "The project is designed to work with the MSVC compiler when compiling on Windows operating system")
+ endif ()
+
+ message(STATUS "Disabling the console at program start-up")
+ add_link_options(/SUBSYSTEM:windows /ENTRY:mainCRTStartup) # To hide the console window at program start-up on Windows
+ elseif (UNIX AND NOT APPLE)
+ message(STATUS "Linux kernel operating system detected")
+
+ if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ message(FATAL_ERROR "The project is designed to work with the GNU compiler (GCC) when compiling on Linux operating system")
+ endif ()
+ else ()
+ message(FATAL_ERROR "At this point, the project is only designed to run on Windows or Linux operating systems")
+ endif ()
+ else ()
+ message(FATAL_ERROR "The project is designed to work with an operating system that supports 64-bit extensions")
+ endif ()
+endfunction()
\ No newline at end of file
diff --git a/resource/resource.qrc b/resource/resource.qrc
index 46ab028..f2ae27f 100644
--- a/resource/resource.qrc
+++ b/resource/resource.qrc
@@ -2,6 +2,6 @@
images/Flag_of_Russia.svg
images/Flag_of_the_United_Kingdom.svg
- images/Main_Window_Icon.png
+ images/Main_Window_Icon.png
diff --git a/resource/translation/ISS-Driver-Drowsiness-Detector_ru_RU.ts b/resource/translation/ISS-Driver-Drowsiness-Detector_ru_RU.ts
index 9f7da26..017ad88 100644
--- a/resource/translation/ISS-Driver-Drowsiness-Detector_ru_RU.ts
+++ b/resource/translation/ISS-Driver-Drowsiness-Detector_ru_RU.ts
@@ -25,8 +25,7 @@
-
-
+
Запуск
@@ -36,22 +35,22 @@
Доступные камеры
-
+
Системная информация
-
+
ГП:
-
+
ЦП:
-
+
Операционная система:
@@ -71,29 +70,28 @@
Очистить логгер
-
+
Логи появятся здесь...
-
+
Язык интерфейса
-
+
-
+
-
- Ошибка при инициализации распознавателя
+ Ошибка при инициализации распознавателя
@@ -104,83 +102,88 @@
Не найден файл предупредительного звукового сигнала по адресу "
-
+
Камера "
-
+
" выбрана
-
+
Нет доступных камер
-
-
+
+
Стоп
-
+
Возникла проблема с камерой
-
+
Сбой камеры
-
+
Камера запущена
-
+
Камера остановлена
-
+
Распознан жест "Кулак"
-
+
Перезапуск системы распознавания
-
+
+
+
+
+
+
Распознан жест "Ладонь"
-
+
Ввод системы распознавания сонливости в режим оповещения
-
+
Распознан "V-жест"
-
+
Ввод системы распознавания сонливости в спящий режим
-
+
Распознана сонливость
-
+
Оповещение о сонливости при помощи предупредительного звукового сигнала
@@ -196,27 +199,27 @@
Стоп
-
+
Внимательный глаз
-
+
Соннный глаз
-
+
Жест "Кулак"
-
+
Жест "Ладонь"
-
+
"V-жест"
diff --git a/src/Camera.cpp b/src/Camera.cpp
index 4606f3d..c503a13 100644
--- a/src/Camera.cpp
+++ b/src/Camera.cpp
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+
@@ -29,12 +29,25 @@ bool Camera::IsAvailable() const {
}
void Camera::SetCameraDevice(int cameraDeviceIndex) {
- // Very fine-tuning, perhaps not all systems will be suitable for this combination (with MJPEG) to get
- // high FPS when capturing frames from the camera
+#ifdef _WIN64 // The Camera class is Qt independent, so no Q_OS_* type macros are used here
_isAvailable = _videoCapture.open(cameraDeviceIndex, cv::CAP_DSHOW);
- // If possible, this resolution will be used
- _videoCapture.set(cv::CAP_PROP_FRAME_WIDTH, _preferredResolution.width);
- _videoCapture.set(cv::CAP_PROP_FRAME_HEIGHT, _preferredResolution.height);
- _videoCapture.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
+ if (!_isAvailable)
+ _isAvailable = _videoCapture.open(cameraDeviceIndex, cv::CAP_MSMF);
+#else
+ _isAvailable = _videoCapture.open(cameraDeviceIndex, cv::CAP_V4L2);
+#endif
+
+ if (_isAvailable) {
+ // If possible, this resolution will be used
+ _videoCapture.set(cv::CAP_PROP_FRAME_WIDTH, _preferredResolution.width);
+ _videoCapture.set(cv::CAP_PROP_FRAME_HEIGHT, _preferredResolution.height);
+
+ auto const cameraBackendName = _videoCapture.getBackendName();
+
+ // Very fine-tuning, perhaps not all systems will be suitable for this combination (DSHOW/V4L2 with MJPEG) to get
+ // high FPS when capturing frames from the camera
+ if (cameraBackendName == "DSHOW" || cameraBackendName == "V4L2")
+ _videoCapture.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
+ }
}
diff --git a/src/Camera.h b/src/Camera.h
index ac0ec17..780122c 100644
--- a/src/Camera.h
+++ b/src/Camera.h
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+
diff --git a/src/ImageRecognizer.cpp b/src/ImageRecognizer.cpp
index 1e365eb..c0775fb 100644
--- a/src/ImageRecognizer.cpp
+++ b/src/ImageRecognizer.cpp
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+
diff --git a/src/ImageRecognizer.h b/src/ImageRecognizer.h
index 8d0e4fe..05bb665 100644
--- a/src/ImageRecognizer.h
+++ b/src/ImageRecognizer.h
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 51b13e6..4c5d80e 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -22,9 +23,7 @@
using namespace std;
using namespace cv;
-namespace {
-
-constexpr int CAMERA_CONTENT_HINT_LABEL_PAGE = 0;
+constexpr int CAMERA_CONTENT_HINT_LABEL_PAGE = 0;
constexpr int VIDEO_FRAME_PAGE = 1;
constexpr int RESTART_RECOGNITION_SYSTEM_PAGE = 2;
@@ -37,6 +36,16 @@ constexpr QColor CRAIOLA_PERIWINKLE = {197, 208, 230};
constexpr QColor DEEP_CARMINE_PINK = {239, 48, 56};
constexpr QColor WISTERIA = {201, 160, 20};
+#ifdef Q_OS_WIN64
+constexpr QLatin1StringView NEURAL_NETWORK_MODEL_PATH{"/resource/neural_network_model/neural_network_model.onnx"};
+constexpr QLatin1StringView WARNING_SOUND_PATH{"/resource/sound/warning_sound.wav"};
+constexpr QLatin1StringView TRANSLATION_PATH{"/resource/translation/ISS-Driver-Drowsiness-Detector_ru_RU.qm"};
+#else
+constexpr QLatin1StringView NEURAL_NETWORK_MODEL_PATH{"/../resource/neural_network_model/neural_network_model.onnx"};
+constexpr QLatin1StringView WARNING_SOUND_PATH{"/../resource/sound/warning_sound.wav"};
+constexpr QLatin1StringView TRANSLATION_PATH{"/../resource/translation/ISS-Driver-Drowsiness-Detector_ru_RU.qm"};
+#endif
+
string ToClassName(RecognitionType recognitionType) {
switch (recognitionType) {
case AttentiveEye: return QObject::tr("Attentive Eye").toStdString();
@@ -62,7 +71,7 @@ Scalar ToRecognitionColor(RecognitionType recognitionType) {
void DrawLabel(Mat& image, const string& text, int left, int top, const Scalar& lineRectangleColor) {
int baseLine;
- Size labelSize = getTextSize(text, FONT_HERSHEY_COMPLEX, 0.5, 1, &baseLine);
+ const auto labelSize = getTextSize(text, FONT_HERSHEY_COMPLEX, 0.5, 1, &baseLine);
top = max(top, labelSize.height);
Point cornerLineTopRight(left + 15, top - 20);
@@ -80,29 +89,9 @@ void DrawLabel(Mat& image, const string& text, int left, int top, const Scalar&
putText(image, text, cornerLineTopRight, FONT_HERSHEY_COMPLEX, 0.5, Scalar(255, 255, 255), 1, LINE_AA); // BGR format
}
-} // namespace
-
-MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow),
- _imageRecognizer(QDir::currentPath().append("/resource/neural_network_model/neural_network_model.onnx").toStdString())
+MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), _ui(new Ui::MainWindow),
+ _imageRecognizer(QCoreApplication::applicationDirPath().append(NEURAL_NETWORK_MODEL_PATH).toStdString())
{
- auto warningSoundPath = QDir::currentPath().append("/resource/sound/warning_sound.wav");
-
- if (!QFileInfo::exists(warningSoundPath)) {
- QMessageBox::critical(nullptr, "Error when initialising the warning sound",
- QString("No warning sound file found at \"").append(warningSoundPath).append("\""));
- _hasErrors = true;
- return;
- }
-
- if (!_imageRecognizer.Error().empty()) {
- QMessageBox::critical(nullptr, tr("Error when initialising the recogniser"), tr(_imageRecognizer.Error().c_str()));
- _hasErrors = true;
- return;
- }
-
- _warningSound.setSource(QUrl::fromLocalFile(warningSoundPath));
-
- ui->setupUi(this);
Init();
}
@@ -112,19 +101,19 @@ bool MainWindow::Errors() const {
void MainWindow::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
- ui->retranslateUi(this);
+ _ui->retranslateUi(this);
- // Explicitly setting text is needed because calling "ui->retranslateUi(this)" resets the text to the one
+ // Explicitly setting text is needed because calling "_ui->retranslateUi(this)" resets the text to the one
// originally set for the button in the GUI (Qt Designer). This is because retranslate only calls
// QCoreApplication::translate for the GUI caption (in this case the "Run" caption)
- if (_isCameraRun) ui->runStop->setText(tr("Stop"));
+ if (_isCameraRun) _ui->runStop->setText(tr("Stop"));
}
- QWidget::changeEvent(event);
+ QMainWindow::changeEvent(event);
}
void MainWindow::OnAvailableCamerasActivated(int index) {
- auto selectedCameraName = ui->availableCameras->itemText(index);
+ auto selectedCameraName = _ui->availableCameras->itemText(index);
if (selectedCameraName == _currentCameraName) // To avoid re-setting the same camera device
return;
@@ -139,14 +128,14 @@ void MainWindow::OnAvailableCamerasActivated(int index) {
isNeedToResumeVideoStream = true;
}
- ui->videoFrame->clear();
+ _ui->videoFrame->clear();
_currentCameraName = std::move(selectedCameraName);
_camera.SetCameraDevice(index);
const auto newCameraResolution = _camera.GetResolution();
- ui->cameraContentStackedWidget->setFixedSize(newCameraResolution.width, newCameraResolution.height);
+ _ui->cameraContentStackedWidget->setFixedSize(newCameraResolution.width, newCameraResolution.height);
- TextBrowserLogger::Log(ui->logger, tr("The camera \"").append(_currentCameraName).append(tr("\" is selected")), CRAIOLA_PERIWINKLE);
+ TextBrowserLogger::Log(_ui->logger, tr("The camera \"").append(_currentCameraName).append(tr("\" is selected")), CRAIOLA_PERIWINKLE);
if (isNeedToResumeVideoStream)
ResumeVideoStreamThread();
@@ -159,26 +148,26 @@ void MainWindow::OnRunStopClicked() {
}
_isCameraRun = !_isCameraRun;
- ui->runStop->setText(_isCameraRun ? tr("Stop") : tr("Run"));
+ _ui->runStop->setText(_isCameraRun ? tr("Stop") : tr("Run"));
_isCameraRun ? StartCamera() : StopCamera();
}
void MainWindow::OnClearLoggerClicked() {
- TextBrowserLogger::Clear(ui->logger);
+ TextBrowserLogger::Clear(_ui->logger);
}
void MainWindow::OnVideoInputsChanged() {
- const QList cameras = _mediaDevices.videoInputs();
+ const QList cameras = QMediaDevices::videoInputs();
QList existingCameras; // There may be a situation where there are cameras with the same names (descriptions)
- ui->availableCameras->clear();
+ _ui->availableCameras->clear();
for (const auto& camera : cameras) {
const auto camerasCount = existingCameras.count(camera.description());
const QString cameraName = camera.description().append(camerasCount > 0 ? QString::number(camerasCount) : "");
- ui->availableCameras->addItem(cameraName);
+ _ui->availableCameras->addItem(cameraName);
existingCameras.emplace_back(camera.description());
}
@@ -188,7 +177,7 @@ void MainWindow::OnVideoInputsChanged() {
const auto errorMessage = tr("There is a problem with the camera ").append(_currentCameraName);
OnRunStopClicked();
- TextBrowserLogger::Log(ui->logger, errorMessage, DEEP_CARMINE_PINK);
+ TextBrowserLogger::Log(_ui->logger, errorMessage, DEEP_CARMINE_PINK);
QMessageBox::warning(nullptr, tr("Camera failure"), errorMessage);
}
@@ -198,58 +187,86 @@ void MainWindow::OnVideoInputsChanged() {
else if (!_camera.IsAvailable() || !isCurrentCameraDeviceAvailable) {
_camera.SetCameraDevice(0); // If there were no cameras available before or the current camera device has been deactivated
const auto cameraResolution = _camera.GetResolution();
- ui->cameraContentStackedWidget->setFixedSize(cameraResolution.width, cameraResolution.height);
+ _ui->cameraContentStackedWidget->setFixedSize(cameraResolution.width, cameraResolution.height);
_currentCameraName = existingCameras[0];
- ui->availableCameras->setCurrentIndex(0);
+ _ui->availableCameras->setCurrentIndex(0);
}
else {
- ui->availableCameras->setCurrentText(_currentCameraName);
+ _ui->availableCameras->setCurrentText(_currentCameraName);
}
}
void MainWindow::OnRecognitionSystemRestarted() {
- ui->cameraContentStackedWidget->setCurrentIndex(RESTART_RECOGNITION_SYSTEM_PAGE);
- ui->cameraContentStackedWidget->repaint();
+ TextBrowserLogger::Log(_ui->logger, tr("The Fist gesture is recognized"), WISTERIA);
+ TextBrowserLogger::Log(_ui->logger, tr("Restart the recognition system"), WISTERIA);
+
+ _ui->cameraContentStackedWidget->setCurrentIndex(RESTART_RECOGNITION_SYSTEM_PAGE);
+ _ui->cameraContentStackedWidget->repaint();
- _frameCounterWithRecognition.Reset(FistGesture);
DestroyVideoStreamThread();
_imageRecognizer.Restart();
- QThread::msleep(RESTART_SLEEP_TIME_MS); // A pause for a sense of duration in restarting the recognition system
+ QThread::msleep(RESTART_SLEEP_TIME_MS); // Pause to simulate the restart duration of the recognition system
StartCamera();
}
void MainWindow::Init() {
+ if (!_imageRecognizer.Error().empty()) {
+ QMessageBox::critical(nullptr, tr("Error when initializing the recogniser"), tr(_imageRecognizer.Error().c_str()));
+ _hasErrors = true;
+ return;
+ }
+
+ const auto warningSoundPath = QCoreApplication::applicationDirPath().append(WARNING_SOUND_PATH);
+
+ if (!QFileInfo::exists(warningSoundPath)) {
+ QMessageBox::critical(nullptr, "Error when initializing the warning sound",
+ QString("No warning sound file found at \"").append(warningSoundPath).append("\""));
+ _hasErrors = true;
+ return;
+ }
+
+ _warningSound.setAudioDevice(QMediaDevices::defaultAudioOutput());
+ _warningSound.setSource(QUrl::fromLocalFile(warningSoundPath));
+
+ _ui->setupUi(this);
+
SetSystemInformation();
- ui->russianLanguage->setIcon(QIcon(":/icons/images/Flag_of_Russia.svg"));
- ui->englishLanguage->setIcon(QIcon(":/icons/images/Flag_of_the_United_Kingdom.svg"));
+ _ui->russianLanguage->setIcon(QIcon(":/icons/images/Flag_of_Russia.svg"));
+ _ui->englishLanguage->setIcon(QIcon(":/icons/images/Flag_of_the_United_Kingdom.svg"));
OnVideoInputsChanged(); // For fill combobox on init
InitConnects();
- if (ui->availableCameras->count())
- _currentCameraName = ui->availableCameras->itemText(0);
-
- showMaximized();
+ if (_ui->availableCameras->count())
+ _currentCameraName = _ui->availableCameras->itemText(0);
- const auto translationFilePath = QDir::currentPath().append("/resource/translation/ISS-Driver-Drowsiness-Detector_ru_RU.qm");
+ const auto translationFilePath = QCoreApplication::applicationDirPath().append(TRANSLATION_PATH);
const auto translationFilePathErrorMessage = QString("Error when loading the translation file \"%1\". "
"The Russian translation will not be available during the work of the application.").arg(translationFilePath);
if (!_uiRussianTranslator.load(translationFilePath)) {
- ui->russianLanguage->setDisabled(true);
+ _ui->russianLanguage->setDisabled(true);
QMessageBox::warning(nullptr, "Error when loading the translation file", translationFilePathErrorMessage);
}
+
+ // A workaround for the next very strange problem:
+ // Because the cameraContentStackedWidget is set to a fixed size in OnVideoInputsChanged, which is called above,
+ // this breaks QMainWindow's showMaximized and prevents the window from expanding fully (even the maximize window
+ // size button is removed). This is somehow fixed when the whole application is translated for the first time.
+ QApplication::installTranslator(&_uiRussianTranslator);
+ _ui->retranslateUi(this);
+ QApplication::removeTranslator(&_uiRussianTranslator);
}
void MainWindow::InitConnects() {
// Connect for update available cameras if the list of cameras in the system has changed
connect(&_mediaDevices, &QMediaDevices::videoInputsChanged, this, &MainWindow::OnVideoInputsChanged);
- for (const auto* retranslateUiButton : {ui->russianLanguage, ui->englishLanguage}) {
+ for (const auto* retranslateUiButton : {_ui->russianLanguage, _ui->englishLanguage}) {
connect(retranslateUiButton, &QAction::triggered, this, [&, retranslateUiButton]() {
- if (retranslateUiButton == ui->russianLanguage) {
+ if (retranslateUiButton == _ui->russianLanguage) {
QApplication::installTranslator(&_uiRussianTranslator);
}
else {
@@ -258,40 +275,66 @@ void MainWindow::InitConnects() {
});
}
- connect(ui->availableCameras, &QComboBox::activated, this, &MainWindow::OnAvailableCamerasActivated);
- connect(ui->runStop, &QPushButton::clicked, this, &MainWindow::OnRunStopClicked);
- connect(ui->clearLogger, &QPushButton::clicked, this, &MainWindow::OnClearLoggerClicked);
+ connect(_ui->availableCameras, &QComboBox::activated, this, &MainWindow::OnAvailableCamerasActivated);
+ connect(_ui->runStop, &QPushButton::clicked, this, &MainWindow::OnRunStopClicked);
+ connect(_ui->clearLogger, &QPushButton::clicked, this, &MainWindow::OnClearLoggerClicked);
+
+ // The connections below are used for safe interaction of the secondary thread with GUI elements via the main thread
+
connect(this, &MainWindow::RecognitionSystemRestarted, this, &MainWindow::OnRecognitionSystemRestarted);
connect(this, &MainWindow::PlayWarningSignal, this, [this]() { _warningSound.play(); });
+
+ connect(this, &MainWindow::LogAsync, this, [this](const QString& message, const QColor& color) {
+ TextBrowserLogger::Log(_ui->logger, message, color);
+ });
+
+ connect(this, &MainWindow::UpdateVideoFrame, this, [this](const QPixmap& videoFrame) {
+ _ui->videoFrame->setPixmap(videoFrame);
+ });
}
void MainWindow::SetSystemInformation() {
QProcess systemProcess;
- QString cpuName, gpuName;
+ QString cpuName = "Cannot be detected";
+ QString gpuName = "Cannot be detected"; // If there are multiple active GPUs (SLI), they will concatenate into a single text (at least on Linux :) )
#ifdef Q_OS_WIN64
systemProcess.startCommand("wmic cpu get name");
- systemProcess.waitForFinished();
- cpuName = systemProcess.readAllStandardOutput();
- cpuName.remove(0, cpuName.indexOf('\n') + 1);
- cpuName = cpuName.trimmed();
+
+ if (systemProcess.waitForFinished()) {
+ cpuName = systemProcess.readAllStandardOutput();
+ cpuName = cpuName.remove(0, cpuName.indexOf('\n') + 1).trimmed();
+ }
systemProcess.startCommand("wmic PATH Win32_videocontroller get VideoProcessor");
- systemProcess.waitForFinished();
- gpuName = systemProcess.readAllStandardOutput();
- gpuName.remove(0, gpuName.indexOf('\n') + 1);
- gpuName = gpuName.trimmed();
-#endif
-#ifdef Q_OS_LINUX
- systemProcess.start("\"cat /proc/cpuinfo | grep 'model name' | uniq\"");
- systemProcess.waitForFinished();
- cpuName = systemProcess.readAllStandardOutput();
- // TODO System Information in Linux
+
+ if (systemProcess.waitForFinished()) {
+ gpuName = systemProcess.readAllStandardOutput();
+ gpuName = gpuName.remove(0, gpuName.indexOf('\n') + 1).trimmed();
+ }
+#else
+ static constexpr QUtf8StringView getCpuCommand{R"(lscpu | grep "Имя модели\|Model name" | awk -F ':' '{print $2}')"};
+
+ static constexpr QLatin1StringView getGpusCommand{
+ R"(
+ lspci -vnnn |
+ perl -lne 'print if /^\d+\:.+(\[\S+\:\S+\])/' |
+ grep VGA |
+ awk -F ':' '{print $3,":",$4}' |
+ cut -d '(' -f 1 |
+ awk '{gsub("[[:blank:]]+:[[:blank:]]+", ":"); print}'
+ )"};
+
+ systemProcess.start("sh", QStringList() << "-c" << getCpuCommand.toString());
+ if (systemProcess.waitForFinished()) cpuName = systemProcess.readAllStandardOutput().trimmed();
+
+ systemProcess.start("sh", QStringList() << "-c" << getGpusCommand);
+ if (systemProcess.waitForFinished()) gpuName = systemProcess.readAllStandardOutput().trimmed();
#endif
- ui->operatingSystem->setText(QSysInfo::prettyProductName());
- ui->cpuName->setText(cpuName);
- ui->gpuName->setText(gpuName);
+ _ui->operatingSystem->setText(QSysInfo::prettyProductName());
+ _ui->cpuName->setText(cpuName);
+ _ui->gpuName->setText(gpuName);
}
void MainWindow::StartCamera() {
@@ -304,20 +347,20 @@ void MainWindow::StartCamera() {
ResumeVideoStreamThread();
}
- TextBrowserLogger::Log(ui->logger, tr("The camera is running"), PALE_YELLOW);
- ui->cameraContentStackedWidget->setCurrentIndex(VIDEO_FRAME_PAGE);
+ TextBrowserLogger::Log(_ui->logger, tr("The camera is running"), PALE_YELLOW);
+ _ui->cameraContentStackedWidget->setCurrentIndex(VIDEO_FRAME_PAGE);
}
void MainWindow::StopCamera() {
- ui->videoFrame->clear();
- ui->cameraContentStackedWidget->setCurrentIndex(CAMERA_CONTENT_HINT_LABEL_PAGE);
+ _ui->videoFrame->clear();
+ _ui->cameraContentStackedWidget->setCurrentIndex(CAMERA_CONTENT_HINT_LABEL_PAGE);
SuspendVideoStreamThread(50);
- TextBrowserLogger::Log(ui->logger, tr("The camera is stopped"), PALE_YELLOW);
+ TextBrowserLogger::Log(_ui->logger, tr("The camera is stopped"), PALE_YELLOW);
}
void MainWindow::VideoStream() {
while (_isVideoStreamThreadRun) {
- QMutexLocker locker(&_mutex);
+ QMutexLocker locker(&_mutex);
while (!_isCameraRun)
_videoStreamStopper.wait(&_mutex);
@@ -332,7 +375,7 @@ void MainWindow::VideoStream() {
const auto recognitions = _imageRecognizer.Recognize(flippedVideoFrame);
HandleRecognitions(recognitions, flippedVideoFrame);
- ui->videoFrame->setPixmap(QPixmap::fromImage(ToQImage(flippedVideoFrame)));
+ emit UpdateVideoFrame(QPixmap::fromImage(ToQImage(flippedVideoFrame)));
}
// When repeatedly stopping and starting the camera using the GUI without this explicit unlock call, the thread may stall
@@ -341,7 +384,7 @@ void MainWindow::VideoStream() {
}
}
-void MainWindow::HandleRecognitions(const std::vector& recognitions, cv::Mat& videoFrame) {
+void MainWindow::HandleRecognitions(const vector& recognitions, cv::Mat& videoFrame) {
static char confidenceBuffer[10];
int16_t drowsyEyeCount = 0;
@@ -354,8 +397,9 @@ void MainWindow::HandleRecognitions(const std::vector& recognit
};
for (const auto& recognitionInfo : recognitions) {
- if (ui->showDetections->isChecked()) {
+ if (_ui->showDetections->isChecked()) {
const auto recognitionColor = ToRecognitionColor(recognitionInfo.recognitionType);
+
snprintf(confidenceBuffer, sizeof(confidenceBuffer), " %.2f %%", recognitionInfo.confidence * 100);
rectangle(videoFrame, recognitionInfo.boundingBox, recognitionColor, 2); // Bounding Box
@@ -364,7 +408,7 @@ void MainWindow::HandleRecognitions(const std::vector& recognit
}
// There is no specific handling for the attentive eye
- if (!ui->debugMode->isChecked() && recognitionInfo.recognitionType != AttentiveEye) {
+ if (!_ui->debugMode->isChecked() && recognitionInfo.recognitionType != AttentiveEye) {
if (recognitionInfo.recognitionType == DrowsyEye) {
// The frame counter with drowsy eye recognition is increased only if 2 drowsy eyes are recognized
if (!_isSleepMode && ++drowsyEyeCount == 2) {
@@ -401,31 +445,31 @@ void MainWindow::HandleRecognitions(const std::vector& recognit
}
void MainWindow::RestartRecognitionSystem() {
- TextBrowserLogger::Log(ui->logger, tr("The Fist gesture is recognized"), WISTERIA);
- TextBrowserLogger::Log(ui->logger, tr("Restart the recognition system"), WISTERIA);
-
+ // Restart is performed in the main thread, so resetting the counter is necessary in this thread
+ // so that the next iteration of the VideoStream loop does not have a second restart at once
+ _frameCounterWithRecognition.Reset(FistGesture);
emit RecognitionSystemRestarted();
}
void MainWindow::WakeUpDrowsinessRecognitionSystem() {
- TextBrowserLogger::Log(ui->logger, tr("The Palm gesture is recognized"), WISTERIA);
- TextBrowserLogger::Log(ui->logger, tr("Waking up the drowsiness recognition system"), WISTERIA);
+ emit LogAsync(tr("The Palm gesture is recognized"), WISTERIA);
+ emit LogAsync(tr("Waking up the drowsiness recognition system"), WISTERIA);
_isSleepMode = false;
_frameCounterWithRecognition.Reset(PalmGesture);
}
void MainWindow::PutDrowsinessRecognitionSystemIntoSleepMode() {
- TextBrowserLogger::Log(ui->logger, tr("The V gesture is recognized"), WISTERIA);
- TextBrowserLogger::Log(ui->logger, tr("Putting the drowsiness recognition system into sleep mode"), WISTERIA);
+ emit LogAsync(tr("The V gesture is recognized"), WISTERIA);
+ emit LogAsync(tr("Putting the drowsiness recognition system into sleep mode"), WISTERIA);
_isSleepMode = true;
_frameCounterWithRecognition.Reset(VGesture);
}
void MainWindow::DrowsinessAlert() {
- TextBrowserLogger::Log(ui->logger, tr("Drowsiness is recognized"), WISTERIA);
- TextBrowserLogger::Log(ui->logger, tr("Drowsiness alert with a warning sound"), WISTERIA);
+ emit LogAsync(tr("Drowsiness is recognized"), WISTERIA);
+ emit LogAsync(tr("Drowsiness alert with a warning sound"), WISTERIA);
emit PlayWarningSignal();
@@ -463,5 +507,5 @@ void MainWindow::DestroyVideoStreamThread() {
MainWindow::~MainWindow() {
DestroyVideoStreamThread();
- delete ui;
+ delete _ui;
}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 011c87b..5471b63 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+
@@ -32,8 +32,10 @@ class MainWindow : public QMainWindow {
bool Errors() const;
signals:
+ void LogAsync(const QString& message, const QColor& color); // Signal for the main thread to insert text into QTextBrowser
void RecognitionSystemRestarted();
void PlayWarningSignal();
+ void UpdateVideoFrame(const QPixmap& videoFrame);
protected:
void changeEvent(QEvent* event) override;
@@ -46,7 +48,7 @@ private slots:
void OnRecognitionSystemRestarted();
private:
- Ui::MainWindow* ui;
+ Ui::MainWindow* _ui;
bool _hasErrors = false;
QTranslator _uiRussianTranslator;
@@ -83,6 +85,8 @@ private slots:
void StartCamera();
void StopCamera();
+
+ ///// Executed not in the main thread
void VideoStream();
void HandleRecognitions(const std::vector& recognitions, cv::Mat& videoFrame);
@@ -90,6 +94,7 @@ private slots:
void WakeUpDrowsinessRecognitionSystem();
void PutDrowsinessRecognitionSystemIntoSleepMode();
void DrowsinessAlert();
+ //////////
void ResumeVideoStreamThread();
void SuspendVideoStreamThread(uint16_t msDelay);
diff --git a/src/main.cpp b/src/main.cpp
index 094e79a..de7c59a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+
@@ -9,9 +9,11 @@
#include
int main(int argc, char** argv) {
+#ifdef Q_OS_WIN64
// https://github.com/opencv/opencv/issues/17687#issuecomment-872291073
- // _putenv_s("OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS", "0"); // So there is no delay when opening a camera with MSMF backend
-
+ _putenv_s("OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS", "0"); // So there is no delay when opening a camera with MSMF backend
+#endif
+ // TODO: Readme
QApplication application(argc, argv);
QTranslator translator;
MainWindow mainWindow;
@@ -19,6 +21,6 @@ int main(int argc, char** argv) {
if (mainWindow.Errors())
return 1;
- mainWindow.show();
+ mainWindow.showMaximized();
return QApplication::exec();
}
diff --git a/src/ui/mainwindow.ui b/src/ui/mainwindow.ui
index c0f9bb7..b9b58d9 100644
--- a/src/ui/mainwindow.ui
+++ b/src/ui/mainwindow.ui
@@ -71,7 +71,7 @@ QScrollBar::handle:vertical:pressed {
background-color: #8b8787;
}
-/* BTN TOP - SCROLLBAR */
+/* BUTTON TOP - SCROLLBAR */
QScrollBar::sub-line:vertical {
border: none;
background-color: #4e4e4e;
@@ -88,7 +88,7 @@ QScrollBar::sub-line:vertical:pressed {
background-color: #9797a5;
}
-/* BTN BOTTOM - SCROLLBAR */
+/* BUTTON BOTTOM - SCROLLBAR */
QScrollBar::add-line:vertical {
border: none;
background-color: #4e4e4e;
@@ -172,7 +172,7 @@ QLabel {
0
-
-
+
0
@@ -556,8 +556,8 @@ p, li { white-space: pre-wrap; }
hr { height: 1px; border-width: 0; }
li.unchecked::marker { content: "\2610"; }
li.checked::marker { content: "\2612"; }
-</style></head><body style=" font-family:'Segoe UI'; font-size:16pt; font-weight:400; font-style:normal;">
-<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html>
+</style></head><body style=" font-family:'Ubuntu'; font-size:16pt; font-weight:400; font-style:normal;">
+<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Segoe UI';"><br /></p></body></html>
Logs will appear here...
@@ -763,8 +763,8 @@ li.checked::marker { content: "\2612"; }
0
0
- 805
- 31
+ 4095
+ 27
diff --git a/src/utils/RecognitionFrameCounter.h b/src/utils/RecognitionFrameCounter.h
index b6a7eb2..c77ea44 100644
--- a/src/utils/RecognitionFrameCounter.h
+++ b/src/utils/RecognitionFrameCounter.h
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+
diff --git a/src/utils/TextBrowserLogger.cpp b/src/utils/TextBrowserLogger.cpp
index 5a6d58e..e2bc917 100644
--- a/src/utils/TextBrowserLogger.cpp
+++ b/src/utils/TextBrowserLogger.cpp
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+
diff --git a/src/utils/TextBrowserLogger.h b/src/utils/TextBrowserLogger.h
index b6f6633..0670b03 100644
--- a/src/utils/TextBrowserLogger.h
+++ b/src/utils/TextBrowserLogger.h
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+
diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp
index 4b58c62..a5375a5 100644
--- a/src/utils/Utils.cpp
+++ b/src/utils/Utils.cpp
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+
diff --git a/src/utils/Utils.h b/src/utils/Utils.h
index 80b5df7..ef7e187 100644
--- a/src/utils/Utils.h
+++ b/src/utils/Utils.h
@@ -1,7 +1,7 @@
// +-----------------------------------------+
// | License: MIT |
// +-----------------------------------------+
-// | Copyright (c) 2023 |
+// | Copyright (c) 2024 |
// | Author: Gleb Trufanov (aka Glebchansky) |
// +-----------------------------------------+