diff --git a/Makefile b/Makefile index 7719359..de64934 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ CXXFLAGS := \ LDFLAGS := \ -L$(ROOT)/lib \ - -Wl,-rpath,$(ROOT)/lib + -Wl,-rpath,'$$ORIGIN/../lib' LDLIBS := \ -lrknnrt \ diff --git a/README1.md b/README1.md new file mode 100644 index 0000000..cb5e661 --- /dev/null +++ b/README1.md @@ -0,0 +1,102 @@ +cd /home/nvme/dockerdata/prove/face-clustering-rk3588 + +# Files e dir necessarie per far funzionare + +prj_root/ +├── bin/ +│ └── face_scan +├── lib/ +│ └── librknnrt.so +├── models/ +│ ├── SCRFD_500M_KPS_640.rknn +│ └── face_recognition_sface_2021dec.rknn +└── test + └── test3f.jpg + +### Copia + +cd /home/nvme/dockerdata/prove/s121js || exit 1 + +mkdir -p bin lib models + +cp -f \ + /home/nvme/dockerdata/prove/face-clustering-rk3588/lib/librknnrt.so \ + lib/ + +cp -f \ + /home/nvme/dockerdata/prove/face-clustering-rk3588/models/SCRFD_500M_KPS_640.rknn \ + models/ + +cp -f \ + /home/nvme/dockerdata/prove/face-clustering-rk3588/models/face_recognition_sface_2021dec.rknn \ + models/ + + +### Compilazione + +make clean +make -j"$(nproc)" + +### Test delle quattro rotazioni + +for rotation in 0 90 180 270; do + ./bin/face_scan \ + --rotation "$rotation" \ + test/test3f.jpg \ + > "/tmp/rotation_${rotation}.json" \ + 2> "/tmp/rotation_${rotation}.stderr" + + status=$? + stderr_bytes=$(wc -c < "/tmp/rotation_${rotation}.stderr") + + echo \ + "rotation=$rotation" \ + "status=$status" \ + "stderr_bytes=$stderr_bytes" +done + +### Verifica dimensioni, rotazione e volti + +python3 - <<'PY' +import json +from pathlib import Path + +for rotation in (0, 90, 180, 270): + json_path = Path( + "/tmp/rotation_" + + str(rotation) + + ".json" + ) + + data = json.loads( + json_path.read_text(encoding="utf-8") + ) + + image = data.get("image") + faces = data.get("faces") + + if not isinstance(image, dict): + raise SystemExit( + "Campo image non valido per rotazione " + + str(rotation) + ) + + if not isinstance(faces, list): + raise SystemExit( + "Campo faces non valido per rotazione " + + str(rotation) + ) + + print( + "rotation:", + rotation, + "size:", + str(image.get("width")) + + "x" + + str(image.get("height")), + "json_rotation:", + image.get("rotation"), + "faces:", + len(faces), + ) +PY diff --git a/include/face_engine.h b/include/face_engine.h index b115d71..b646335 100644 --- a/include/face_engine.h +++ b/include/face_engine.h @@ -38,7 +38,8 @@ public: const std::string& image_path, std::vector& faces, int& image_width, - int& image_height); + int& image_height, + int rotation_degrees = 0); private: struct Impl; diff --git a/src/face_engine.cc b/src/face_engine.cc index 77a8f8b..46e43c0 100644 --- a/src/face_engine.cc +++ b/src/face_engine.cc @@ -132,6 +132,108 @@ struct Image { std::vector rgb; }; +static bool normalize_rotation( + int rotation_degrees, + int& normalized_rotation) +{ + int value = rotation_degrees % 360; + + if (value < 0) + value += 360; + + if ( + value != 0 + && value != 90 + && value != 180 + && value != 270 + ) { + return false; + } + + normalized_rotation = value; + return true; +} + +static Image rotate_image( + const Image& source, + int rotation_degrees) +{ + if (rotation_degrees == 0) + return source; + + Image destination; + + if ( + rotation_degrees == 90 + || rotation_degrees == 270 + ) { + destination.w = source.h; + destination.h = source.w; + } else { + destination.w = source.w; + destination.h = source.h; + } + + destination.rgb.resize( + static_cast(destination.w) + * static_cast(destination.h) + * 3); + + for (int source_y = 0; source_y < source.h; ++source_y) { + for (int source_x = 0; source_x < source.w; ++source_x) { + int destination_x = 0; + int destination_y = 0; + + switch (rotation_degrees) { + case 90: + destination_x = source.h - 1 - source_y; + destination_y = source_x; + break; + + case 180: + destination_x = source.w - 1 - source_x; + destination_y = source.h - 1 - source_y; + break; + + case 270: + destination_x = source_y; + destination_y = source.w - 1 - source_x; + break; + + default: + destination_x = source_x; + destination_y = source_y; + break; + } + + const std::size_t source_offset = + ( + static_cast(source_y) + * static_cast(source.w) + + static_cast(source_x) + ) * 3; + + const std::size_t destination_offset = + ( + static_cast(destination_y) + * static_cast(destination.w) + + static_cast(destination_x) + ) * 3; + + destination.rgb[destination_offset + 0] = + source.rgb[source_offset + 0]; + + destination.rgb[destination_offset + 1] = + source.rgb[source_offset + 1]; + + destination.rgb[destination_offset + 2] = + source.rgb[source_offset + 2]; + } + } + + return destination; +} + static bool load_image(const std::string& path, Image& img) { int c = 0; @@ -314,7 +416,6 @@ public: (const uint16_t*)outputs[6 + level].buf; int feat_w = SCRFD_SIZE / stride; - int feat_h = SCRFD_SIZE / stride; for (int i = 0; i < count; ++i) { float score = fp16_to_float(scores[i]); @@ -692,7 +793,8 @@ bool FaceEngine::process_image( const std::string& image_path, std::vector& faces, int& image_width, - int& image_height) + int& image_height, + int rotation_degrees) { faces.clear(); image_width = 0; @@ -703,16 +805,33 @@ bool FaceEngine::process_image( return false; } - Image image; + int normalized_rotation = 0; - if (!load_image(image_path, image)) + if (!normalize_rotation( + rotation_degrees, + normalized_rotation)) { + std::cerr + << "Rotazione non valida: " + << rotation_degrees + << ". Valori ammessi: 0, 90, 180, 270\n"; + + return false; + } + + Image source_image; + + if (!load_image(image_path, source_image)) return false; - image_width = image.w; - image_height = image.h; + Image oriented_image = rotate_image( + source_image, + normalized_rotation); + + image_width = oriented_image.w; + image_height = oriented_image.h; faces = ::process_image( - image, + oriented_image, impl_->detector, impl_->recognizer); diff --git a/src/face_scan.cc b/src/face_scan.cc index 43d91c3..08ddf04 100644 --- a/src/face_scan.cc +++ b/src/face_scan.cc @@ -1,15 +1,72 @@ #include #include +#include +#include #include #include +#include #include #include +#include + #include "face_engine.h" -#include "face_config.h" static constexpr std::size_t EMBEDDING_SIZE = 128; +static std::filesystem::path executable_path() +{ + std::vector buffer(4096); + + while (true) { + const ssize_t length = readlink( + "/proc/self/exe", + buffer.data(), + buffer.size() - 1); + + if (length < 0) { + throw std::runtime_error( + "Impossibile leggere /proc/self/exe"); + } + + const std::size_t path_length = + static_cast(length); + + if (path_length < buffer.size() - 1) { + buffer[path_length] = '\0'; + + return std::filesystem::path( + buffer.data()); + } + + buffer.resize(buffer.size() * 2); + } +} + +static std::filesystem::path application_root() +{ + /* + * Struttura prevista: + * + * root/ + * bin/face_scan + * lib/librknnrt.so + * models/.rknn + * + * executable_path(): + * root/bin/face_scan + * + * parent_path(): + * root/bin + * + * parent_path().parent_path(): + * root + */ + return executable_path() + .parent_path() + .parent_path(); +} + static void write_json_string(const std::string& value) { std::cout << '"'; @@ -165,23 +222,77 @@ static void write_face_json( int main(int argc, char** argv) { - if (argc != 2) { + int rotation_degrees = 0; + const char* image_path = nullptr; + + if (argc == 2) { + image_path = argv[1]; + } else if ( + argc == 4 + && std::string(argv[1]) == "--rotation" + ) { + char* parse_end = nullptr; + + const long parsed_rotation = std::strtol( + argv[2], + &parse_end, + 10); + + if ( + parse_end == argv[2] + || *parse_end != '\0' + || parsed_rotation < -360000 + || parsed_rotation > 360000 + ) { + std::cerr + << "Rotazione non valida: " + << argv[2] + << "\n"; + + return 2; + } + + rotation_degrees = + static_cast(parsed_rotation); + + image_path = argv[3]; + } else { std::cerr - << "Uso: " - << argv[0] - << " image.jpg\n"; + << "Uso:\n" + << " " << argv[0] + << " image.jpg\n" + << " " << argv[0] + << " --rotation 90 image.jpg\n"; return 2; } - const std::string root = - FACE_CLUSTERING_ROOT; + std::filesystem::path root; + + try { + root = application_root(); + } catch (const std::exception& error) { + std::cerr + << "Impossibile determinare la root applicativa: " + << error.what() + << "\n"; + + return 3; + } const std::string scrfd_model = - root + "/models/SCRFD_500M_KPS_640.rknn"; + ( + root + / "models" + / "SCRFD_500M_KPS_640.rknn" + ).string(); const std::string sface_model = - root + "/models/face_recognition_sface_2021dec.rknn"; + ( + root + / "models" + / "face_recognition_sface_2021dec.rknn" + ).string(); FaceEngine engine; @@ -197,13 +308,14 @@ int main(int argc, char** argv) int image_height = 0; if (!engine.process_image( - argv[1], + image_path, faces, image_width, - image_height)) { + image_height, + rotation_degrees)) { std::cerr << "Scansione fallita: " - << argv[1] + << image_path << "\n"; return 4; @@ -227,13 +339,15 @@ int main(int argc, char** argv) std::cout << "\"image\":{"; std::cout << "\"path\":"; - write_json_string(argv[1]); + write_json_string(image_path); std::cout << ",\"width\":" << image_width << ",\"height\":" - << image_height; + << image_height + << ",\"rotation\":" + << rotation_degrees; std::cout << "},";