Skip to content

Commit

Permalink
SUM Rewrite Old Python Version Code to C++
Browse files Browse the repository at this point in the history
-# Delete unnecessary architecture detector CMake file.
 : Because this project only supports x64 architecture.
*G Modify Git Ignore file.
 : Ignore large model file.
*M Modify main source file.
 : Add new functions.
*? Update wiki.
!- Ready to temporarily remove Linux support.
 : Link error using `lapack` on Linux.
 & microsoft/vcpkg#23109
 & microsoft/vcpkg#21479
*@ Modify workflows.
 : Add denpendencies installation step on Linux.
 : Modify target triplet.
  • Loading branch information
ren-yc committed Mar 26, 2023
1 parent 06de1ad commit 7ab038d
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 55 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on: [push, pull_request]

jobs:
build:
name: ${{ format('Build {0}-{1}', matrix.platform, matrix.config) }}
name: ${{ format('{0}-{1}', matrix.platform, matrix.config) }}
strategy:
fail-fast: false
matrix:
Expand All @@ -20,7 +20,7 @@ jobs:
platform: windows
triplet: x64-windows-static
env:
pkgs: fmt jsoncpp spdlog
pkgs: argparse dlib fmt jsoncpp opencv spdlog
VCPKG_ROOT: ${{ format('{0}/vcpkg', github.workspace) }}
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -29,6 +29,10 @@ jobs:
- name: Use MSVC CLI
uses: ilammy/msvc-dev-cmd@v1
if: ${{ matrix.platform == 'windows' }}
- name: Install APT dependencies
if: ${{ matrix.platform == 'linux' }}
run: |
sudo apt install autoconf bison gfortran gperf libdbus-1-dev libtool libgles2-mesa-dev libx11-dev libx11-xcb-dev libxcursor-dev libxdamage-dev libxext-dev libxft-dev libxi-dev libxinerama-dev libxkbcommon-x11-dev libxrandr-dev libxtst-dev
- name: Install CMake and Ninja
uses: lukka/get-cmake@latest
- name: Initialize vcpkg
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Initialize vcpkg
uses: johnwason/vcpkg-action@v4
with:
pkgs: fmt jsoncpp spdlog
pkgs: argparse dlib fmt jsoncpp opencv spdlog
triplet: x64-windows-static
token: ${{ github.token }}
- name: Initialize CodeQL
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/out/
/.vscode/
/.vs/
shape_predictor_68_face_landmarks.dat
14 changes: 12 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,21 @@ if(NOT DEFINED VCPKG_ROOT)
if(DEFINED ENV{VCPKG_ROOT})
set(VCPKG_ROOT ENV{VCPKG_ROOT})
else()
message(FATAL_ERROR "VCPKG_ROOT is not defined")
message(FATAL_ERROR "VCPKG_ROOT is not defined.")
endif()
endif()

include_directories("${VCPKG_ROOT}/installed/${VCPKG_TARGET_TRIPLET}/include")
find_package(argparse CONFIG REQUIRED)
find_package(dlib CONFIG REQUIRED)
find_package(fmt CONFIG REQUIRED)
find_package(jsoncpp CONFIG REQUIRED)
find_package(OpenCV CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
target_link_libraries(LF jsoncpp_static JsonCpp::JsonCpp spdlog::spdlog)
target_link_libraries(LF argparse::argparse dlib::dlib fmt::fmt jsoncpp_static JsonCpp::JsonCpp opencv_core opencv_highgui opencv_imgproc opencv_videoio spdlog::spdlog)

if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/shape_predictor_68_face_landmarks.dat)
message(FATAL_ERROR "Cannot find shape_predictor_68_face_landmarks.dat.\nDownload and decompress it from http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2.")
endif()

file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/shape_predictor_68_face_landmarks.dat DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
111 changes: 110 additions & 1 deletion LF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,116 @@ Contributors: ren-yc
#include "./modules/functions.hpp"
#include "./modules/init.hpp"

int32_t main([[maybe_unused]] int32_t _argc, [[maybe_unused]] char* _argv[]) {
int32_t main(int32_t _argc, char* _argv[]) {
LF_Init();
try {
ARG_parser.parse_args(_argc, _argv);
} catch (...) {
SPDLOG_CRITICAL("Invalid command line arguments");
std::cout << "Invalid command line arguments." << std::endl;
std::cout << ARG_parser << std::endl;
std::exit(0);
}
LF_Log_Args();
cv::VideoCapture cap;
cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));
cap.open(ARG_parser.get<int32_t>("-c"), cv::CAP_DSHOW);
dlib::sleep(1000);
if (!cap.isOpened()) {
SPDLOG_CRITICAL("Cannot open camera");
std::cout << "Cannot open camera." << std::endl;
std::exit(0);
}
if (!std::filesystem::is_regular_file(fmt::format("{}/shape_predictor_68_face_landmarks.dat", GetExecDir()))) {
SPDLOG_CRITICAL("Cannot find shape_predictor_68_face_landmarks.dat");
std::cout << "Cannot find shape_predictor_68_face_landmarks.dat." << std::endl;
std::cout << "Download and decompress it from http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2." << std::endl;
std::exit(0);
}
dlib::frontal_face_detector detector = dlib::get_frontal_face_detector();
dlib::shape_predictor predictor;
dlib::deserialize("shape_predictor_68_face_landmarks.dat") >> predictor;
dlib::correlation_tracker tracker;
bool status = false;
int32_t totalgood = 0;
int32_t totalbad = 0;
time_t timestart = 0;
time_t timeend = 0;
cv::namedWindow("Face", cv::WINDOW_AUTOSIZE);
while (true) {
time(&timestart);
cv::Mat frame;
cap >> frame;
if (frame.empty()) {
break;
}
std::vector<dlib::rectangle> facelist = detector(dlib::cv_image<dlib::bgr_pixel>(frame));
if (facelist.size() == 0) {
status = true;
} else {
dlib::rectangle face = facelist[0];
tracker.start_track(dlib::cv_image<dlib::bgr_pixel>(frame), face);
tracker.update(dlib::cv_image<dlib::bgr_pixel>(frame));
dlib::drectangle pos = tracker.get_position();
cv::rectangle(frame, cv::Point(pos.left(), pos.top()), cv::Point(pos.right(), pos.bottom()), (status ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0)), 2);
status = false;
dlib::full_object_detection landmarks = predictor(dlib::cv_image<dlib::bgr_pixel>(frame), face);
if (!status && 1.5 * (landmarks.part(37).y() - landmarks.part(19).y()) < (landmarks.part(8).y() - landmarks.part(57).y())) {
int32_t axisx = landmarks.part(40).x() - 1;
int32_t axisy = landmarks.part(40).y() - 3;
if (axisy > frame.rows || axisx > frame.cols) {
status = true;
} else {
cv::Vec3b pixel = frame.at<cv::Vec3b>(axisy, axisx);
if (0.11 * pixel[0] + 0.59 * pixel[1] + 0.3 * pixel[2] >= 130) {
status = true;
}
}
}
if (!status && 2 * (landmarks.part(29).x() - landmarks.part(1).x()) < (landmarks.part(15).x() - landmarks.part(29).x())) {
int32_t axisx = landmarks.part(42).x() + 6;
int32_t axisy = landmarks.part(42).y() - 3;
if (axisy > frame.rows || axisx > frame.cols) {
status = true;
} else {
cv::Vec3b pixel = frame.at<cv::Vec3b>(axisy, axisx);
if (0.11 * pixel[0] + 0.59 * pixel[1] + 0.3 * pixel[2] >= 100) {
status = true;
}
}
}
if (!status && (landmarks.part(29).x() - landmarks.part(1).x()) > 2 * (landmarks.part(15).x() - landmarks.part(29).x())) {
int32_t axisx = landmarks.part(42).x() + 4;
int32_t axisy = landmarks.part(39).y() - 3;
if (axisy > frame.rows || axisx > frame.cols) {
status = true;
} else {
cv::Vec3b pixel = frame.at<cv::Vec3b>(axisy, axisx);
if (0.11 * pixel[0] + 0.59 * pixel[1] + 0.3 * pixel[2] <= 100) {
status = true;
}
}
}
if (ARG_parser.get<bool>("-d")) {
for (int32_t i = 0; i < 68; i++) {
cv::circle(frame, cv::Point(landmarks.part(i).x(), landmarks.part(i).y()), 2, cv::Scalar(255, 255, 255), -1);
}
}
}
time(&timeend);
if (!status) {
totalbad += difftime(timeend, timestart);
} else {
totalgood += difftime(timeend, timestart);
}
cv::imshow("Face", frame);
if (cv::waitKey(1) == 27) {
break;
}
}
cap.release();
cv::destroyAllWindows();
std::cout << fmt::format("Total good studying time (Minute): {:.5f}", static_cast<double>(totalgood) / 60) << std::endl;
std::cout << fmt::format("Total bad studying time (Minute): {:.5f}", static_cast<double>(totalbad) / 60) << std::endl;
return 0;
}
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
<img src="https://github.com/class-tools/Learning-Focus/actions/workflows/wiki.yml/badge.svg">
<img src="https://img.shields.io/github/v/release/class-tools/Learning-Focus.svg?logo=iCloud">
<img src="https://img.shields.io/github/downloads/class-tools/Learning-Focus/total?label=GitHub%20Downloads">
<img src="https://img.shields.io/badge/support-Windows%208%20%2F%208.1%20%2F%2010%20%2F%2011-blue?logo=Windows">
<img src="https://img.shields.io/badge/support-Linux-blue?logo=Linux">
<img src="https://img.shields.io/badge/support-Windows%20x64-blue?logo=Windows">
<img src="https://img.shields.io/badge/support-Linux%20x64-blue?logo=Linux">
</p>

An AI application that helps you focus on learning.
Expand Down
4 changes: 2 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
<img src="https://github.com/class-tools/Learning-Focus/actions/workflows/wiki.yml/badge.svg">
<img src="https://img.shields.io/github/v/release/class-tools/Learning-Focus.svg?logo=iCloud">
<img src="https://img.shields.io/github/downloads/class-tools/Learning-Focus/total?label=GitHub%20Downloads">
<img src="https://img.shields.io/badge/support-Windows%208%20%2F%208.1%20%2F%2010%20%2F%2011-blue?logo=Windows">
<img src="https://img.shields.io/badge/support-Linux-blue?logo=Linux">
<img src="https://img.shields.io/badge/support-Windows%20x64-blue?logo=Windows">
<img src="https://img.shields.io/badge/support-Linux%20x64-blue?logo=Linux">
</p>

一个能够让你专注于学习的 AI 程序。
Expand Down
43 changes: 0 additions & 43 deletions archdetect.cmake

This file was deleted.

21 changes: 21 additions & 0 deletions modules/apis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,24 @@ timestamp GetTimestamp() {
NowTime.Second = ptminfo.tm_sec;
return NowTime;
}

std::string GetExecDir() {
// Get program executable directory.
#ifdef _WIN32
std::string _res = _pgmptr;
char _spilt = '\\';
#elif linux
char _path[4096] = {};
readlink("/proc/self/exe", _path, 4096);
std::string _res = _path;
char _spilt = '/';
#endif
int32_t _pos = 0;
for (int32_t i = static_cast<int32_t>(_res.size() - 1); i >= 0; i--) {
if (_res[i] == _spilt) {
_pos = i;
break;
}
}
return _res.substr(0, _pos);
}
10 changes: 10 additions & 0 deletions modules/basic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,17 @@ Contributors: ren-yc
#include <iostream>

#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
#include <argparse/argparse.hpp>
#include <dlib/image_processing.h>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/misc_api.h>
#include <dlib/opencv.h>
#include <fmt/format.h>
#include <json/json.h>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>

Expand All @@ -35,6 +44,7 @@ const std::string LF_path_data = fmt::format("{}/.class-tools/LF", getenv("HOME"
std::string LF_version;
std::ifstream fin;
std::ofstream fout;
argparse::ArgumentParser ARG_parser("Learning Focus", LF_VER_MAIN, argparse::default_arguments::all);
Json::Reader JSON_Reader;
Json::StreamWriterBuilder JSON_SWB;
std::unique_ptr<Json::StreamWriter> JSON_SW(JSON_SWB.newStreamWriter());
Expand Down
6 changes: 6 additions & 0 deletions modules/functions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ void LF_exit() {
SPDLOG_INFO(fmt::format("Exiting \"Learning Focus {}\"", LF_version));
_exit(0);
}

void LF_Log_Args() {
// Print command line arguments into log file.
SPDLOG_INFO(fmt::format("Camera id: {}", ARG_parser.get<int32_t>("-c")));
SPDLOG_INFO(fmt::format("Debug mode: {}", ARG_parser.get<bool>("-d") ? "Enabled" : "Disabled"));
}
2 changes: 2 additions & 0 deletions modules/init.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ void LF_Init_Dir() {

void LF_Init_Bind() {
// Initialize bindings.
ARG_parser.add_argument("-c", "--camera").default_value<int32_t>(0).nargs(1).help("specify the camera id of the video capturing device to use").scan<'i', int32_t>();
ARG_parser.add_argument("-d", "--debug").default_value<bool>(false).implicit_value(true).nargs(0).help("enable debug mode to view more output");
JSON_SWB.settings_ = []() {
Json::Value def;
Json::StreamWriterBuilder::setDefaults(&def);
Expand Down
8 changes: 6 additions & 2 deletions wiki/Build.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
**此页面适合开发人员 / 贡献者阅读。**

_**请注意,由于 `dlib` 的依赖库 `lapack` 在 vcpkg 中存在兼容性问题,故本项目暂时不能在 Linux 平台下编译,仅支持 Windows 平台。**_

### 环境

* Windows 10 / 11
Expand All @@ -13,10 +15,10 @@
vcpkg 依赖库安装流程如下:

```
vcpkg install fmt:${arch}-${os} jsoncpp:${arch}-${os} spdlog:${arch}-${os}
vcpkg install fmt:x64-${os} jsoncpp:x64-${os} spdlog:x64-${os}
```

其中 `${arch}` 为构建的程序架构,可为 `x86` / `x64``${os}` 为构建的平台名称,可为 `windows-static` / `linux`
其中 `${os}` 为构建的平台名称,可为 `windows-static` / `linux`

设置环境变量 `VCPKG_ROOT` 指定 vcpkg 路径,便于编译。

Expand All @@ -30,6 +32,8 @@ vcpkg install fmt:${arch}-${os} jsoncpp:${arch}-${os} spdlog:${arch}-${os}

构建完毕会自动运行可执行文件,之后还可直接双击运行构建目录中的可执行文件(**程序运行不受运行目录影响,但程序所在目录下必须有所需的依赖文件**)。

运行前务必下载 [DLib 人脸关键点检测模型](http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2),解压后放入程序所在目录。

### 调试

在相应位置标记断点后选择 Visual Studio 工具顶栏的调试器即可。
Expand Down

0 comments on commit 7ab038d

Please sign in to comment.