diff --git a/deploy/cpp_shitu/CMakeLists.txt b/deploy/cpp_shitu/CMakeLists.txt new file mode 100755 index 0000000000000000000000000000000000000000..f412654d030c4bf922bce430055673d93b783262 --- /dev/null +++ b/deploy/cpp_shitu/CMakeLists.txt @@ -0,0 +1,249 @@ +project(pp_shitu CXX C) +cmake_minimum_required(VERSION 3.14) + +option(WITH_MKL "Compile demo with MKL/OpenBlas support, default use MKL." ON) +option(WITH_GPU "Compile demo with GPU/CPU, default use CPU." OFF) +option(WITH_STATIC_LIB "Compile demo with static/shared library, default use static." ON) +option(WITH_TENSORRT "Compile demo with TensorRT." OFF) +option(FAISS_WITH_MKL "Faiss Compile demo with MKL." OFF) + +SET(PADDLE_LIB "" CACHE PATH "Location of libraries") +SET(OPENCV_DIR "" CACHE PATH "Location of libraries") +SET(CUDA_LIB "" CACHE PATH "Location of libraries") +SET(CUDNN_LIB "" CACHE PATH "Location of libraries") +SET(TENSORRT_DIR "" CACHE PATH "Compile demo with TensorRT") +SET(FAISS_DIR "" CACHE PATH "Location of libraries") + +set(DEMO_NAME "pp_shitu") + +if (FAISS_WITH_MKL) + SET(BLAS_NAME "mklml_intel") +else() + SET(BLAS_NAME "openblas") +endif() + +include(external-cmake/yaml-cpp.cmake) +include_directories("${CMAKE_SOURCE_DIR}/") +include_directories("${CMAKE_CURRENT_BINARY_DIR}/ext/yaml-cpp/src/ext-yaml-cpp/include") +link_directories("${CMAKE_CURRENT_BINARY_DIR}/ext/yaml-cpp/lib") + +macro(safe_set_static_flag) + foreach(flag_var + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if(${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif(${flag_var} MATCHES "/MD") + endforeach(flag_var) +endmacro() + + +if (WITH_MKL) + ADD_DEFINITIONS(-DUSE_MKL) +endif() + +if(NOT DEFINED FAISS_DIR) + message(FATAL_ERROR "please set FAISS_DIR with -DFAISS_DIR=/path/faiss") +endif() + +if(NOT DEFINED PADDLE_LIB) + message(FATAL_ERROR "please set PADDLE_LIB with -DPADDLE_LIB=/path/paddle/lib") +endif() + +if(NOT DEFINED OPENCV_DIR) + message(FATAL_ERROR "please set OPENCV_DIR with -DOPENCV_DIR=/path/opencv") +endif() + +if (WIN32) + include_directories("${FAISS_DIR}/include") + link_directories("${FAISS_DIR}/lib") + find_package(faiss REQUIRED PATHS ${FAISS_DIR}/share/faiss/ NO_DEFAULT_PATH) +else() + find_package(faiss REQUIRED PATHS ${FAISS_DIR}/share/faiss NO_DEFAULT_PATH) + include_directories("${FAISS_DIR}/include") + link_directories("${FAISS_DIR}/lib") +endif() + +if (WIN32) + include_directories("${PADDLE_LIB}/paddle/fluid/inference") + include_directories("${PADDLE_LIB}/paddle/include") + link_directories("${PADDLE_LIB}/paddle/fluid/inference") + find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/build/ NO_DEFAULT_PATH) + +else () + find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/share/OpenCV NO_DEFAULT_PATH) + include_directories("${PADDLE_LIB}/paddle/include") + link_directories("${PADDLE_LIB}/paddle/lib") +endif () +include_directories(${OpenCV_INCLUDE_DIRS}) + +if (WIN32) + add_definitions("/DGOOGLE_GLOG_DLL_DECL=") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /bigobj /MTd") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /bigobj /MT") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj /MTd") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /bigobj /MT") + if (WITH_STATIC_LIB) + safe_set_static_flag() + add_definitions(-DSTATIC_LIB) + endif() +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3 -std=c++11") + set(CMAKE_STATIC_LIBRARY_PREFIX "") +endif() +message("flags" ${CMAKE_CXX_FLAGS}) + + +if (WITH_GPU) + if (NOT DEFINED CUDA_LIB OR ${CUDA_LIB} STREQUAL "") + message(FATAL_ERROR "please set CUDA_LIB with -DCUDA_LIB=/path/cuda-8.0/lib64") + endif() + if (NOT WIN32) + if (NOT DEFINED CUDNN_LIB) + message(FATAL_ERROR "please set CUDNN_LIB with -DCUDNN_LIB=/path/cudnn_v7.4/cuda/lib64") + endif() + endif(NOT WIN32) +endif() + +include_directories("${PADDLE_LIB}/third_party/install/protobuf/include") +include_directories("${PADDLE_LIB}/third_party/install/glog/include") +include_directories("${PADDLE_LIB}/third_party/install/gflags/include") +include_directories("${PADDLE_LIB}/third_party/install/xxhash/include") +include_directories("${PADDLE_LIB}/third_party/install/zlib/include") +include_directories("${PADDLE_LIB}/third_party/boost") +include_directories("${PADDLE_LIB}/third_party/eigen3") + +include_directories("${CMAKE_SOURCE_DIR}/") + +if (NOT WIN32) + if (WITH_TENSORRT AND WITH_GPU) + include_directories("${TENSORRT_DIR}/include") + link_directories("${TENSORRT_DIR}/lib") + endif() +endif(NOT WIN32) + +link_directories("${PADDLE_LIB}/third_party/install/zlib/lib") + +link_directories("${PADDLE_LIB}/third_party/install/protobuf/lib") +link_directories("${PADDLE_LIB}/third_party/install/glog/lib") +link_directories("${PADDLE_LIB}/third_party/install/gflags/lib") +link_directories("${PADDLE_LIB}/third_party/install/xxhash/lib") +link_directories("${PADDLE_LIB}/paddle/lib") + + +if(WITH_MKL) + include_directories("${PADDLE_LIB}/third_party/install/mklml/include") + if (WIN32) + set(MATH_LIB ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.lib + ${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.lib) + else () + set(MATH_LIB ${PADDLE_LIB}/third_party/install/mklml/lib/libmklml_intel${CMAKE_SHARED_LIBRARY_SUFFIX} + ${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5${CMAKE_SHARED_LIBRARY_SUFFIX}) + execute_process(COMMAND cp -r ${PADDLE_LIB}/third_party/install/mklml/lib/libmklml_intel${CMAKE_SHARED_LIBRARY_SUFFIX} /usr/lib) + endif () + set(MKLDNN_PATH "${PADDLE_LIB}/third_party/install/mkldnn") + if(EXISTS ${MKLDNN_PATH}) + include_directories("${MKLDNN_PATH}/include") + if (WIN32) + set(MKLDNN_LIB ${MKLDNN_PATH}/lib/mkldnn.lib) + else () + set(MKLDNN_LIB ${MKLDNN_PATH}/lib/libmkldnn.so.0) + endif () + endif() +else() + if (WIN32) + set(MATH_LIB ${PADDLE_LIB}/third_party/install/openblas/lib/openblas${CMAKE_STATIC_LIBRARY_SUFFIX}) + else () + set(MATH_LIB ${PADDLE_LIB}/third_party/install/openblas/lib/libopenblas${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif () +endif() + +# Note: libpaddle_inference_api.so/a must put before libpaddle_fluid.so/a +if(WITH_STATIC_LIB) + if(WIN32) + set(DEPS + ${PADDLE_LIB}/paddle/lib/paddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX}) + else() + set(DEPS + ${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() +else() + if(WIN32) + set(DEPS + ${PADDLE_LIB}/paddle/lib/paddle_inference${CMAKE_SHARED_LIBRARY_SUFFIX}) + else() + set(DEPS + ${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_SHARED_LIBRARY_SUFFIX}) + endif() +endif(WITH_STATIC_LIB) + +if (NOT WIN32) + set(DEPS ${DEPS} + ${MATH_LIB} ${MKLDNN_LIB} + glog gflags protobuf z xxhash yaml-cpp + ) + if(EXISTS "${PADDLE_LIB}/third_party/install/snappystream/lib") + set(DEPS ${DEPS} snappystream) + endif() + if (EXISTS "${PADDLE_LIB}/third_party/install/snappy/lib") + set(DEPS ${DEPS} snappy) + endif() +else() + set(DEPS ${DEPS} + ${MATH_LIB} ${MKLDNN_LIB} + glog gflags_static libprotobuf xxhash libyaml-cppmt) + set(DEPS ${DEPS} libcmt shlwapi) + if (EXISTS "${PADDLE_LIB}/third_party/install/snappy/lib") + set(DEPS ${DEPS} snappy) + endif() + if(EXISTS "${PADDLE_LIB}/third_party/install/snappystream/lib") + set(DEPS ${DEPS} snappystream) + endif() +endif(NOT WIN32) + + +if(WITH_GPU) + if(NOT WIN32) + if (WITH_TENSORRT) + set(DEPS ${DEPS} ${TENSORRT_DIR}/lib/libnvinfer${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(DEPS ${DEPS} ${TENSORRT_DIR}/lib/libnvinfer_plugin${CMAKE_SHARED_LIBRARY_SUFFIX}) + endif() + set(DEPS ${DEPS} ${CUDA_LIB}/libcudart${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(DEPS ${DEPS} ${CUDNN_LIB}/libcudnn${CMAKE_SHARED_LIBRARY_SUFFIX}) + else() + set(DEPS ${DEPS} ${CUDA_LIB}/cudart${CMAKE_STATIC_LIBRARY_SUFFIX} ) + set(DEPS ${DEPS} ${CUDA_LIB}/cublas${CMAKE_STATIC_LIBRARY_SUFFIX} ) + set(DEPS ${DEPS} ${CUDNN_LIB}/cudnn${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() +endif() + + +if (NOT WIN32) + set(EXTERNAL_LIB "-ldl -lrt -lgomp -lz -lm -lpthread") + set(DEPS ${DEPS} ${EXTERNAL_LIB}) +endif() + +set(DEPS ${DEPS} ${OpenCV_LIBS}) + +include(FetchContent) +include(external-cmake/auto-log.cmake) +include_directories(${FETCHCONTENT_BASE_DIR}/extern_autolog-src) + + +AUX_SOURCE_DIRECTORY(./src SRCS) +add_executable(${DEMO_NAME} ${SRCS}) +ADD_DEPENDENCIES(${DEMO_NAME} ext-yaml-cpp) +target_link_libraries(${DEMO_NAME} ${DEPS}) +target_link_libraries(${DEMO_NAME} ${FAISS_DIR}/lib/libfaiss${CMAKE_STATIC_LIBRARY_SUFFIX}) +target_link_libraries(${DEMO_NAME} ${BLAS_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}) + +if (WIN32 AND WITH_MKL) + add_custom_command(TARGET ${DEMO_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.dll ./mklml.dll + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.dll ./libiomp5md.dll + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mkldnn/lib/mkldnn.dll ./mkldnn.dll + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.dll ./release/mklml.dll + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.dll ./release/libiomp5md.dll + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mkldnn/lib/mkldnn.dll ./release/mkldnn.dll + ) +endif() diff --git a/deploy/cpp_shitu/external-cmake/auto-log.cmake b/deploy/cpp_shitu/external-cmake/auto-log.cmake new file mode 100644 index 0000000000000000000000000000000000000000..9be9c2fb3b6d61207fad0d9bf216f4b9ad715fe3 --- /dev/null +++ b/deploy/cpp_shitu/external-cmake/auto-log.cmake @@ -0,0 +1,12 @@ +find_package(Git REQUIRED) +include(FetchContent) + +set(FETCHCONTENT_BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}/third-party") + +FetchContent_Declare( + extern_Autolog + PREFIX autolog + GIT_REPOSITORY https://github.com/LDOUBLEV/AutoLog.git + GIT_TAG main +) +FetchContent_MakeAvailable(extern_Autolog) diff --git a/deploy/cpp_shitu/external-cmake/yaml-cpp.cmake b/deploy/cpp_shitu/external-cmake/yaml-cpp.cmake new file mode 100644 index 0000000000000000000000000000000000000000..7bc7f34d476d69d57336940bcf6c8c55311b8112 --- /dev/null +++ b/deploy/cpp_shitu/external-cmake/yaml-cpp.cmake @@ -0,0 +1,30 @@ + +find_package(Git REQUIRED) + +include(ExternalProject) + +message("${CMAKE_BUILD_TYPE}") + +ExternalProject_Add( + ext-yaml-cpp + URL https://bj.bcebos.com/paddlex/deploy/deps/yaml-cpp.zip + URL_MD5 9542d6de397d1fbd649ed468cb5850e6 + CMAKE_ARGS + -DYAML_CPP_BUILD_TESTS=OFF + -DYAML_CPP_BUILD_TOOLS=OFF + -DYAML_CPP_INSTALL=OFF + -DYAML_CPP_BUILD_CONTRIB=OFF + -DMSVC_SHARED_RT=OFF + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} + -DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} + -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} + -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${CMAKE_BINARY_DIR}/ext/yaml-cpp/lib + -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=${CMAKE_BINARY_DIR}/ext/yaml-cpp/lib + PREFIX "${CMAKE_BINARY_DIR}/ext/yaml-cpp" + # Disable install step + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_BUILD 1 +) diff --git a/deploy/cpp_shitu/include/feature_extracter.h b/deploy/cpp_shitu/include/feature_extracter.h new file mode 100644 index 0000000000000000000000000000000000000000..e83caee73ed3ec69cc3864794b3c1f691bb38c34 --- /dev/null +++ b/deploy/cpp_shitu/include/feature_extracter.h @@ -0,0 +1,110 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "opencv2/core.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include "paddle_inference_api.h" +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +using namespace paddle_infer; + +namespace Feature { + + class FeatureExtracter { + public: + explicit FeatureExtracter(const YAML::Node &config_file) { + this->use_gpu_ = config_file["Global"]["use_gpu"].as(); + if (config_file["Global"]["gpu_id"].IsDefined()) + this->gpu_id_ = config_file["Global"]["gpu_id"].as(); + else + this->gpu_id_ = 0; + this->gpu_mem_ = config_file["Global"]["gpu_mem"].as(); + this->cpu_math_library_num_threads_ = + config_file["Global"]["cpu_num_threads"].as(); + this->use_mkldnn_ = config_file["Global"]["enable_mkldnn"].as(); + this->use_tensorrt_ = config_file["Global"]["use_tensorrt"].as(); + this->use_fp16_ = config_file["Global"]["use_fp16"].as(); + + this->cls_model_path_ = + config_file["Global"]["rec_inference_model_dir"].as() + + OS_PATH_SEP + "inference.pdmodel"; + this->cls_params_path_ = + config_file["Global"]["rec_inference_model_dir"].as() + + OS_PATH_SEP + "inference.pdiparams"; + this->resize_size_ = + config_file["RecPreProcess"]["transform_ops"][0]["ResizeImage"]["size"] + .as(); + this->scale_ = config_file["RecPreProcess"]["transform_ops"][1]["NormalizeImage"]["scale"].as(); + this->mean_ = config_file["RecPreProcess"]["transform_ops"][1] + ["NormalizeImage"]["mean"] + .as < std::vector < float >> (); + this->std_ = config_file["RecPreProcess"]["transform_ops"][1] + ["NormalizeImage"]["std"] + .as < std::vector < float >> (); + if (config_file["Global"]["rec_feature_normlize"].IsDefined()) + this->feature_norm = + config_file["Global"]["rec_feature_normlize"].as(); + + LoadModel(cls_model_path_, cls_params_path_); + } + + // Load Paddle inference model + void LoadModel(const std::string &model_path, const std::string ¶ms_path); + + // Run predictor + void Run(cv::Mat &img, std::vector &out_data, + std::vector ×); + + void FeatureNorm(std::vector &feature); + + std::shared_ptr predictor_; + + private: + bool use_gpu_ = false; + int gpu_id_ = 0; + int gpu_mem_ = 4000; + int cpu_math_library_num_threads_ = 4; + bool use_mkldnn_ = false; + bool use_tensorrt_ = false; + bool feature_norm = true; + bool use_fp16_ = false; + std::vector mean_ = {0.485f, 0.456f, 0.406f}; + std::vector std_ = {0.229f, 0.224f, 0.225f}; + float scale_ = 0.00392157; + int resize_size_ = 224; + int resize_short_ = 224; + std::string cls_model_path_; + std::string cls_params_path_; + + // pre-process + ResizeImg resize_op_; + Normalize normalize_op_; + Permute permute_op_; + }; + +} // namespace Feature diff --git a/deploy/cpp_shitu/include/nms.h b/deploy/cpp_shitu/include/nms.h new file mode 100644 index 0000000000000000000000000000000000000000..02956246af237e6d6b9674d4af8abdf15b01f923 --- /dev/null +++ b/deploy/cpp_shitu/include/nms.h @@ -0,0 +1,85 @@ +// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This code is adpated from opencv(https://github.com/opencv/opencv) + +#include +#include + +template +static inline bool SortScorePairDescend(const std::pair &pair1, + const std::pair &pair2) { + return pair1.first > pair2.first; +} + +float RectOverlap(const Detection::ObjectResult &a, + const Detection::ObjectResult &b) { + float Aa = (a.rect[2] - a.rect[0] + 1) * (a.rect[3] - a.rect[1] + 1); + float Ab = (b.rect[2] - b.rect[0] + 1) * (b.rect[3] - b.rect[1] + 1); + + int iou_w = max(min(a.rect[2], b.rect[2]) - max(a.rect[0], b.rect[0]) + 1, 0); + int iou_h = max(min(a.rect[3], b.rect[3]) - max(a.rect[1], b.rect[1]) + 1, 0); + float Aab = iou_w * iou_h; + return Aab / (Aa + Ab - Aab); +} + +// Get max scores with corresponding indices. +// scores: a set of scores. +// threshold: only consider scores higher than the threshold. +// top_k: if -1, keep all; otherwise, keep at most top_k. +// score_index_vec: store the sorted (score, index) pair. +inline void +GetMaxScoreIndex(const std::vector &det_result, + const float threshold, + std::vector > &score_index_vec) { + // Generate index score pairs. + for (size_t i = 0; i < det_result.size(); ++i) { + if (det_result[i].confidence > threshold) { + score_index_vec.push_back(std::make_pair(det_result[i].confidence, i)); + } + } + + // Sort the score pair according to the scores in descending order + std::stable_sort(score_index_vec.begin(), score_index_vec.end(), + SortScorePairDescend); + + // // Keep top_k scores if needed. + // if (top_k > 0 && top_k < (int)score_index_vec.size()) + // { + // score_index_vec.resize(top_k); + // } +} + +void NMSBoxes(const std::vector det_result, + const float score_threshold, const float nms_threshold, + std::vector &indices) { + int a = 1; + // Get top_k scores (with corresponding indices). + std::vector > score_index_vec; + GetMaxScoreIndex(det_result, score_threshold, score_index_vec); + + // Do nms + indices.clear(); + for (size_t i = 0; i < score_index_vec.size(); ++i) { + const int idx = score_index_vec[i].second; + bool keep = true; + for (int k = 0; k < (int) indices.size() && keep; ++k) { + const int kept_idx = indices[k]; + float overlap = RectOverlap(det_result[idx], det_result[kept_idx]); + keep = overlap <= nms_threshold; + } + if (keep) + indices.push_back(idx); + } +} diff --git a/deploy/cpp_shitu/include/object_detector.h b/deploy/cpp_shitu/include/object_detector.h new file mode 100644 index 0000000000000000000000000000000000000000..5bfc56253b1845a50f3b6b093db314e97505cfef --- /dev/null +++ b/deploy/cpp_shitu/include/object_detector.h @@ -0,0 +1,138 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "paddle_inference_api.h" // NOLINT + +#include "include/preprocess_op_det.h" +#include "include/yaml_config.h" + +using namespace paddle_infer; + +namespace Detection { +// Object Detection Result + struct ObjectResult { + // Rectangle coordinates of detected object: left, right, top, down + std::vector rect; + // Class id of detected object + int class_id; + // Confidence of detected object + float confidence; + }; + +// Generate visualization colormap for each class + std::vector GenerateColorMap(int num_class); + +// Visualiztion Detection Result + cv::Mat VisualizeResult(const cv::Mat &img, + const std::vector &results, + const std::vector &lables, + const std::vector &colormap, const bool is_rbox); + + class ObjectDetector { + public: + explicit ObjectDetector(const YAML::Node &config_file) { + this->use_gpu_ = config_file["Global"]["use_gpu"].as(); + if (config_file["Global"]["gpu_id"].IsDefined()) + this->gpu_id_ = config_file["Global"]["gpu_id"].as(); + this->gpu_mem_ = config_file["Global"]["gpu_mem"].as(); + this->cpu_math_library_num_threads_ = + config_file["Global"]["cpu_num_threads"].as(); + this->use_mkldnn_ = config_file["Global"]["enable_mkldnn"].as(); + this->use_tensorrt_ = config_file["Global"]["use_tensorrt"].as(); + this->use_fp16_ = config_file["Global"]["use_fp16"].as(); + this->model_dir_ = + config_file["Global"]["det_inference_model_dir"].as(); + this->threshold_ = config_file["Global"]["threshold"].as(); + this->max_det_results_ = config_file["Global"]["max_det_results"].as(); + this->image_shape_ = + config_file["Global"]["image_shape"].as < std::vector < int >> (); + this->label_list_ = + config_file["Global"]["labe_list"].as < std::vector < std::string >> (); + this->ir_optim_ = config_file["Global"]["ir_optim"].as(); + this->batch_size_ = config_file["Global"]["batch_size"].as(); + + preprocessor_.Init(config_file["DetPreProcess"]["transform_ops"]); + LoadModel(model_dir_, batch_size_, run_mode); + } + + // Load Paddle inference model + void LoadModel(const std::string &model_dir, const int batch_size = 1, + const std::string &run_mode = "fluid"); + + // Run predictor + void Predict(const std::vector imgs, const int warmup = 0, + const int repeats = 1, + std::vector *result = nullptr, + std::vector *bbox_num = nullptr, + std::vector *times = nullptr); + + const std::vector &GetLabelList() const { + return this->label_list_; + } + + const float &GetThreshold() const { return this->threshold_; } + + private: + bool use_gpu_ = true; + int gpu_id_ = 0; + int gpu_mem_ = 800; + int cpu_math_library_num_threads_ = 6; + std::string run_mode = "fluid"; + bool use_mkldnn_ = false; + bool use_tensorrt_ = false; + bool batch_size_ = 1; + bool use_fp16_ = false; + std::string model_dir_; + float threshold_ = 0.5; + float max_det_results_ = 5; + std::vector image_shape_ = {3, 640, 640}; + std::vector label_list_; + bool ir_optim_ = true; + bool det_permute_ = true; + bool det_postprocess_ = true; + int min_subgraph_size_ = 30; + bool use_dynamic_shape_ = false; + int trt_min_shape_ = 1; + int trt_max_shape_ = 1280; + int trt_opt_shape_ = 640; + bool trt_calib_mode_ = false; + + // Preprocess image and copy data to input buffer + void Preprocess(const cv::Mat &image_mat); + + // Postprocess result + void Postprocess(const std::vector mats, + std::vector *result, std::vector bbox_num, + bool is_rbox); + + std::shared_ptr predictor_; + Preprocessor preprocessor_; + ImageBlob inputs_; + std::vector output_data_; + std::vector out_bbox_num_data_; + }; + +} // namespace Detection diff --git a/deploy/cpp_shitu/include/preprocess_op.h b/deploy/cpp_shitu/include/preprocess_op.h new file mode 100644 index 0000000000000000000000000000000000000000..db4c6b2c5aa072abcd665ec0c9428bfe821cb842 --- /dev/null +++ b/deploy/cpp_shitu/include/preprocess_op.h @@ -0,0 +1,57 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "opencv2/core.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace std; + +namespace Feature { + + class Normalize { + public: + virtual void Run(cv::Mat *im, const std::vector &mean, + const std::vector &std, float scale); + }; + +// RGB -> CHW + class Permute { + public: + virtual void Run(const cv::Mat *im, float *data); + }; + + class CenterCropImg { + public: + virtual void Run(cv::Mat &im, const int crop_size = 224); + }; + + class ResizeImg { + public: + virtual void Run(const cv::Mat &img, cv::Mat &resize_img, int max_size_len, + int size = 0); + }; + +} // namespace Feature diff --git a/deploy/cpp_shitu/include/preprocess_op_det.h b/deploy/cpp_shitu/include/preprocess_op_det.h new file mode 100644 index 0000000000000000000000000000000000000000..cd663409caf8334cbd8d120d2a6fd453f7045004 --- /dev/null +++ b/deploy/cpp_shitu/include/preprocess_op_det.h @@ -0,0 +1,158 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Detection { + +// Object for storing all preprocessed data + class ImageBlob { + public: + // image width and height + std::vector im_shape_; + // Buffer for image data after preprocessing + std::vector im_data_; + // in net data shape(after pad) + std::vector in_net_shape_; + // Evaluation image width and height + // std::vector eval_im_size_f_; + // Scale factor for image size to origin image size + std::vector scale_factor_; + }; + +// Abstraction of preprocessing opration class + class PreprocessOp { + public: + virtual void Init(const YAML::Node &item) = 0; + + virtual void Run(cv::Mat *im, ImageBlob *data) = 0; + }; + + class InitInfo : public PreprocessOp { + public: + virtual void Init(const YAML::Node &item) {} + + virtual void Run(cv::Mat *im, ImageBlob *data); + }; + + class NormalizeImage : public PreprocessOp { + public: + virtual void Init(const YAML::Node &item) { + mean_ = item["mean"].as < std::vector < float >> (); + scale_ = item["std"].as < std::vector < float >> (); + is_scale_ = item["is_scale"].as(); + } + + virtual void Run(cv::Mat *im, ImageBlob *data); + + private: + // CHW or HWC + std::vector mean_; + std::vector scale_; + bool is_scale_; + }; + + class Permute : public PreprocessOp { + public: + virtual void Init(const YAML::Node &item) {} + + virtual void Run(cv::Mat *im, ImageBlob *data); + }; + + class Resize : public PreprocessOp { + public: + virtual void Init(const YAML::Node &item) { + interp_ = item["interp"].as(); + // max_size_ = item["target_size"].as(); + keep_ratio_ = item["keep_ratio"].as(); + target_size_ = item["target_size"].as < std::vector < int >> (); + } + + // Compute best resize scale for x-dimension, y-dimension + std::pair GenerateScale(const cv::Mat &im); + + virtual void Run(cv::Mat *im, ImageBlob *data); + + private: + int interp_ = 2; + bool keep_ratio_; + std::vector target_size_; + std::vector in_net_shape_; + }; + +// Models with FPN need input shape % stride == 0 + class PadStride : public PreprocessOp { + public: + virtual void Init(const YAML::Node &item) { + stride_ = item["stride"].as(); + } + + virtual void Run(cv::Mat *im, ImageBlob *data); + + private: + int stride_; + }; + + class Preprocessor { + public: + void Init(const YAML::Node &config_node) { + // initialize image info at first + ops_["InitInfo"] = std::make_shared(); + for (int i = 0; i < config_node.size(); ++i) { + if (config_node[i]["DetResize"].IsDefined()) { + ops_["Resize"] = std::make_shared(); + ops_["Resize"]->Init(config_node[i]["DetResize"]); + } + + if (config_node[i]["DetNormalizeImage"].IsDefined()) { + ops_["NormalizeImage"] = std::make_shared(); + ops_["NormalizeImage"]->Init(config_node[i]["DetNormalizeImage"]); + } + + if (config_node[i]["DetPermute"].IsDefined()) { + ops_["Permute"] = std::make_shared(); + ops_["Permute"]->Init(config_node[i]["DetPermute"]); + } + + if (config_node[i]["DetPadStrid"].IsDefined()) { + ops_["PadStride"] = std::make_shared(); + ops_["PadStride"]->Init(config_node[i]["DetPadStrid"]); + } + } + } + + void Run(cv::Mat *im, ImageBlob *data); + + public: + static const std::vector RUN_ORDER; + + private: + std::unordered_map > ops_; + }; + +} // namespace Detection diff --git a/deploy/cpp_shitu/include/vector_search.h b/deploy/cpp_shitu/include/vector_search.h new file mode 100644 index 0000000000000000000000000000000000000000..4b354f78d10a16db3ab642deb614028bdc0b17e1 --- /dev/null +++ b/deploy/cpp_shitu/include/vector_search.h @@ -0,0 +1,70 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#ifdef WIN32 +#define OS_PATH_SEP "\\" +#else +#define OS_PATH_SEP "/" +#endif + +#include "yaml-cpp/yaml.h" +#include +#include +#include +#include + +struct SearchResult { + std::vector I; + std::vector D; + int return_k; +}; + +class VectorSearch { +public: + explicit VectorSearch(const YAML::Node &config_file) { + // IndexProcess + this->index_dir = + config_file["IndexProcess"]["index_dir"].as(); + this->return_k = config_file["IndexProcess"]["return_k"].as(); + this->score_thres = config_file["IndexProcess"]["score_thres"].as(); + this->max_query_number = + config_file["Global"]["max_det_results"].as() + 1; + LoadIdMap(); + LoadIndexFile(); + this->I.resize(this->return_k * this->max_query_number); + this->D.resize(this->return_k * this->max_query_number); + }; + + void LoadIdMap(); + + void LoadIndexFile(); + + const SearchResult &Search(float *feature, int query_number); + + const std::string &GetLabel(faiss::Index::idx_t ind); + + const float &GetThreshold() { return this->score_thres; } + +private: + std::string index_dir; + int return_k = 5; + float score_thres = 0.5; + std::map id_map; + faiss::Index *index; + int max_query_number = 6; + std::vector D; + std::vector I; + SearchResult sr; +}; diff --git a/deploy/cpp_shitu/include/yaml_config.h b/deploy/cpp_shitu/include/yaml_config.h new file mode 100644 index 0000000000000000000000000000000000000000..78243e2fee5fa3f739e8921d992ac4fe76bf1246 --- /dev/null +++ b/deploy/cpp_shitu/include/yaml_config.h @@ -0,0 +1,58 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifdef WIN32 +#define OS_PATH_SEP "\\" +#else +#define OS_PATH_SEP "/" +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "opencv2/core.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include "yaml-cpp/yaml.h" + + +class YamlConfig { +public: + explicit YamlConfig(const std::string &path) { + config_file = ReadYamlConfig(path); + } + + static std::vector ReadDict(const std::string &path); + + static std::map ReadIndexId(const std::string &path); + + static YAML::Node ReadYamlConfig(const std::string &path); + + void PrintConfigInfo(); + + YAML::Node config_file; +}; diff --git a/deploy/cpp_shitu/readme.md b/deploy/cpp_shitu/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..22c3b6eb22e7b94dbc0b5f21470bd87359262812 --- /dev/null +++ b/deploy/cpp_shitu/readme.md @@ -0,0 +1,308 @@ +# 服务器端C++预测 + +本教程将介绍在服务器端部署PP-ShiTU的详细步骤。 + + +## 1. 准备环境 + +### 运行准备 +- Linux环境,推荐使用ubuntu docker。 + +### 1.1 升级cmake + +由于依赖库编译需要较高版本的cmake,因此,第一步首先将cmake升级。 + +- 下载最新版本cmake + +```shell +# 当前版本最新为3.22.0,根据实际情况自行下载,建议最新版本 +wget https://github.com/Kitware/CMake/releases/download/v3.22.0/cmake-3.22.0.tar.gz +tar xf cmake-3.22.0.tar.gz +``` + +最终可以在当前目录下看到`cmake-3.22.0/`的文件夹。 + +- 编译cmake,首先设置came源码路径(`root_path`)以及安装路径(`install_path`),`root_path`为下载的came源码路径,`install_path`为came的安装路径。在本例中,源码路径即为当前目录下的`cmake-3.22.0/`。 + +```shell +cd ./cmake-3.22.0 +export root_path=$PWD +export install_path=${root_path}/cmake +``` + +- 然后在cmake源码路径下,按照下面的方式进行编译 + +```shell +./bootstrap --prefix=${install_path} +make -j +make install +``` + +- 设置环境变量 + +```shell +export PATH=${install_path}/bin:$PATH +#检查是否正常使用 +cmake --version +``` + +此时,cmake就可以使用了 + +### 1.2 编译opencv库 + +* 首先需要从opencv官网上下载在Linux环境下源码编译的包,以3.4.7版本为例,下载及解压缩命令如下: + +``` +wget https://github.com/opencv/opencv/archive/3.4.7.tar.gz +tar -xvf 3.4.7.tar.gz +``` + +最终可以在当前目录下看到`opencv-3.4.7/`的文件夹。 + +* 编译opencv,首先设置opencv源码路径(`root_path`)以及安装路径(`install_path`),`root_path`为下载的opencv源码路径,`install_path`为opencv的安装路径。在本例中,源码路径即为当前目录下的`opencv-3.4.7/`。 + +```shell +cd ./opencv-3.4.7 +export root_path=$PWD +export install_path=${root_path}/opencv3 +``` + +* 然后在opencv源码路径下,按照下面的方式进行编译。 + +```shell +rm -rf build +mkdir build +cd build + +cmake .. \ + -DCMAKE_INSTALL_PREFIX=${install_path} \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DWITH_IPP=OFF \ + -DBUILD_IPP_IW=OFF \ + -DWITH_LAPACK=OFF \ + -DWITH_EIGEN=OFF \ + -DCMAKE_INSTALL_LIBDIR=lib64 \ + -DWITH_ZLIB=ON \ + -DBUILD_ZLIB=ON \ + -DWITH_JPEG=ON \ + -DBUILD_JPEG=ON \ + -DWITH_PNG=ON \ + -DBUILD_PNG=ON \ + -DWITH_TIFF=ON \ + -DBUILD_TIFF=ON + +make -j +make install +``` + +* `make install`完成之后,会在该文件夹下生成opencv头文件和库文件,用于后面的PaddleClas代码编译。 + +以opencv3.4.7版本为例,最终在安装路径下的文件结构如下所示。**注意**:不同的opencv版本,下述的文件结构可能不同。 + +``` +opencv3/ +|-- bin +|-- include +|-- lib64 +|-- share +``` + +### 1.3 下载或者编译Paddle预测库 + +* 有2种方式获取Paddle预测库,下面进行详细介绍。 + +#### 1.3.1 预测库源码编译 +* 如果希望获取最新预测库特性,可以从Paddle github上克隆最新代码,源码编译预测库。 +* 可以参考[Paddle预测库官网](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/05_inference_deployment/inference/build_and_install_lib_cn.html#id16)的说明,从github上获取Paddle代码,然后进行编译,生成最新的预测库。使用git获取代码方法如下。 + +```shell +git clone https://github.com/PaddlePaddle/Paddle.git +``` + +* 进入Paddle目录后,使用如下方法编译。 + +```shell +rm -rf build +mkdir build +cd build + +cmake .. \ + -DWITH_CONTRIB=OFF \ + -DWITH_MKL=ON \ + -DWITH_MKLDNN=ON \ + -DWITH_TESTING=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DWITH_INFERENCE_API_TEST=OFF \ + -DON_INFER=ON \ + -DWITH_PYTHON=ON +make -j +make inference_lib_dist +``` + +更多编译参数选项可以参考[Paddle C++预测库官网](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/05_inference_deployment/inference/build_and_install_lib_cn.html#id16)。 + + +* 编译完成之后,可以在`build/paddle_inference_install_dir/`文件下看到生成了以下文件及文件夹。 + +``` +build/paddle_inference_install_dir/ +|-- CMakeCache.txt +|-- paddle +|-- third_party +|-- version.txt +``` + +其中`paddle`就是之后进行C++预测时所需的Paddle库,`version.txt`中包含当前预测库的版本信息。 + +#### 1.3.2 直接下载安装 + +* [Paddle预测库官网](https://paddle-inference.readthedocs.io/en/latest/user_guides/download_lib.html)上提供了不同cuda版本的Linux预测库,可以在官网查看并选择合适的预测库版本,注意必须选择`develop`版本。 + + 以`https://paddle-inference-lib.bj.bcebos.com/2.1.1-gpu-cuda10.2-cudnn8.1-mkl-gcc8.2/paddle_inference.tgz`的`develop`版本为例,使用下述命令下载并解压: + + +```shell +wget https://paddle-inference-lib.bj.bcebos.com/2.1.1-gpu-cuda10.2-cudnn8.1-mkl-gcc8.2/paddle_inference.tgz + +tar -xvf paddle_inference.tgz +``` + + +最终会在当前的文件夹中生成`paddle_inference/`的子文件夹。 + +### 1.4 安装faiss库 + +```shell + # 下载 faiss + git clone https://github.com/facebookresearch/faiss.git + cd faiss + export faiss_install_path=$PWD/faiss_install + cmake -B build . -DFAISS_ENABLE_PYTHON=OFF -DCMAKE_INSTALL_PREFIX=${faiss_install_path} + make -C build -j faiss + make -C build install +``` + +在安装`faiss`前,请安装`openblas`,`ubuntu`系统中安装命令如下: + +```shell +apt-get install libopenblas-dev +``` + +注意本教程以安装faiss cpu版本为例,安装时请参考[faiss](https://github.com/facebookresearch/faiss)官网文档,根据需求自行安装。 + +## 2 代码编译 + +### 2.2 编译PaddleClas C++预测demo + +编译命令如下,其中Paddle C++预测库、opencv等其他依赖库的地址需要换成自己机器上的实际地址。同时,编译过程中需要下载编译`yaml-cpp`等C++库,请保持联网环境。 + + +```shell +sh tools/build.sh +``` + +具体地,`tools/build.sh`中内容如下,请根据具体路径修改。 + +```shell +OPENCV_DIR=${opencv_install_dir} +LIB_DIR=${paddle_inference_dir} +CUDA_LIB_DIR=/usr/local/cuda/lib64 +CUDNN_LIB_DIR=/usr/lib/x86_64-linux-gnu/ +FAISS_DIR=${faiss_install_dir} +FAISS_WITH_MKL=OFF + +BUILD_DIR=build +rm -rf ${BUILD_DIR} +mkdir ${BUILD_DIR} +cd ${BUILD_DIR} +cmake .. \ + -DPADDLE_LIB=${LIB_DIR} \ + -DWITH_MKL=ON \ + -DWITH_GPU=OFF \ + -DWITH_STATIC_LIB=OFF \ + -DUSE_TENSORRT=OFF \ + -DOPENCV_DIR=${OPENCV_DIR} \ + -DCUDNN_LIB=${CUDNN_LIB_DIR} \ + -DCUDA_LIB=${CUDA_LIB_DIR} \ + -DFAISS_DIR=${FAISS_DIR} \ + -DFAISS_WITH_MKL=${FAISS_WITH_MKL} + +make -j +``` + +上述命令中, + +* `OPENCV_DIR`为opencv编译安装的地址(本例中为`opencv-3.4.7/opencv3`文件夹的路径); +* `LIB_DIR`为下载的Paddle预测库(`paddle_inference`文件夹),或编译生成的Paddle预测库(`build/paddle_inference_install_dir`文件夹)的路径; +* `CUDA_LIB_DIR`为cuda库文件地址,在docker中为`/usr/local/cuda/lib64`; +* `CUDNN_LIB_DIR`为cudnn库文件地址,在docker中为`/usr/lib/x86_64-linux-gnu/`。 +* `TENSORRT_DIR`是tensorrt库文件地址,在dokcer中为`/usr/local/TensorRT6-cuda10.0-cudnn7/`,TensorRT需要结合GPU使用。 +* `FAISS_DIR`是faiss的安装地址 + * `FAISS_WITH_MKL`是指在编译faiss的过程中,是否使用了mkldnn,本文档中编译faiss,没有使用,而使用了openblas,故设置为`OFF`,若使用了mkldnn,则为`ON`. + + +在执行上述命令,编译完成之后,会在当前路径下生成`build`文件夹,其中生成一个名为`pp_shitu`的可执行文件。 + +## 3 运行demo + +- 请参考[识别快速开始文档](../../docs/zh_CN/quick_start/quick_start_recognition.md),下载好相应的 轻量级通用主体检测模型、轻量级通用识别模型及瓶装饮料测试数据并解压。 + + ```shell + mkdir models + cd models + wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar + tar -xf picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar + wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar + tar -xf general_PPLCNet_x2_5_lite_v1.0_infer.tar + cd .. + + mkdir data + cd data + wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v1.0.tar + tar -xf drink_dataset_v1.0.tar + cd .. + ``` + +- 将相应的yaml文件拷到`test`文件夹下 + + ```shell + cp ../configs/inference_drink.yaml . + ``` + +- 将`inference_drink.yaml`中的相对路径,改成基于本目录的路径或者绝对路径。涉及到的参数有 + + - Global.infer_imgs :此参数可以是具体的图像地址,也可以是图像集所在的目录 + - Global.det_inference_model_dir : 检测模型存储目录 + - Global.rec_inference_model_dir : 识别模型存储目录 + - IndexProcess.index_dir : 检索库的存储目录,在示例中,检索库在下载的demo数据中。 + +- 字典转换 + + 由于python的检索库的字典,使用`pickle`进行的序列化存储,导致C++不方便读取,因此进行转换 + + ```shell + python tools/transform_id_map.py -c inference_drink.yaml + ``` + + 转换成功后,在`IndexProcess.index_dir`目录下生成`id_map.txt`,方便c++ 读取。 + +- 执行程序 + + ```shell + ./build/pp_shitu -c inference_drink.yaml + # or + ./build/pp_shitu -config inference_drink.yaml + ``` + + 若对图像集进行检索,则可能得到,如下结果。注意,此结果只做展示,具体以实际运行结果为准。 + + 同时,需注意的是,由于opencv 版本问题,会导致图像在预处理的过程中,resize产生细微差别,导致python 和c++结果,轻微不同,如bbox相差几个像素,检索结果小数点后3位diff等。但不会改变最终检索label。 + + ![](../../docs/images/quick_start/shitu_c++_result.png) + +## 4 使用自己模型 + +使用自己训练的模型,可以参考[模型导出](../../docs/zh_CN/inference_deployment/export_model.md),导出`inference model`,用于模型预测。 + +同时注意修改`yaml`文件中具体参数。 diff --git a/deploy/cpp_shitu/src/feature_extracter.cpp b/deploy/cpp_shitu/src/feature_extracter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9588ff57ede8f23824957ea8375b2ad2ef8d83ae --- /dev/null +++ b/deploy/cpp_shitu/src/feature_extracter.cpp @@ -0,0 +1,119 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +namespace Feature { + + void FeatureExtracter::LoadModel(const std::string &model_path, + const std::string ¶ms_path) { + paddle_infer::Config config; + config.SetModel(model_path, params_path); + + if (this->use_gpu_) { + config.EnableUseGpu(this->gpu_mem_, this->gpu_id_); + if (this->use_tensorrt_) { + config.EnableTensorRtEngine( + 1 << 20, 1, 3, + this->use_fp16_ ? paddle_infer::Config::Precision::kHalf + : paddle_infer::Config::Precision::kFloat32, + false, false); + } + } else { + config.DisableGpu(); + if (this->use_mkldnn_) { + config.EnableMKLDNN(); + // cache 10 different shapes for mkldnn to avoid memory leak + config.SetMkldnnCacheCapacity(10); + } + config.SetCpuMathLibraryNumThreads(this->cpu_math_library_num_threads_); + } + + config.SwitchUseFeedFetchOps(false); + // true for multiple input + config.SwitchSpecifyInputNames(true); + + config.SwitchIrOptim(true); + + config.EnableMemoryOptim(); + config.DisableGlogInfo(); + + this->predictor_ = CreatePredictor(config); + } + + void FeatureExtracter::Run(cv::Mat &img, std::vector &out_data, + std::vector ×) { + cv::Mat resize_img; + std::vector time; + + auto preprocess_start = std::chrono::system_clock::now(); + this->resize_op_.Run(img, resize_img, this->resize_short_, + this->resize_size_); + + this->normalize_op_.Run(&resize_img, this->mean_, this->std_, this->scale_); + std::vector input(1 * 3 * resize_img.rows * resize_img.cols, 0.0f); + this->permute_op_.Run(&resize_img, input.data()); + + auto input_names = this->predictor_->GetInputNames(); + auto input_t = this->predictor_->GetInputHandle(input_names[0]); + input_t->Reshape({1, 3, resize_img.rows, resize_img.cols}); + auto preprocess_end = std::chrono::system_clock::now(); + + auto infer_start = std::chrono::system_clock::now(); + input_t->CopyFromCpu(input.data()); + this->predictor_->Run(); + + auto output_names = this->predictor_->GetOutputNames(); + auto output_t = this->predictor_->GetOutputHandle(output_names[0]); + std::vector output_shape = output_t->shape(); + int out_num = std::accumulate(output_shape.begin(), output_shape.end(), 1, + std::multiplies()); + + out_data.resize(out_num); + output_t->CopyToCpu(out_data.data()); + auto infer_end = std::chrono::system_clock::now(); + + auto postprocess_start = std::chrono::system_clock::now(); + if (this->feature_norm) + FeatureNorm(out_data); + auto postprocess_end = std::chrono::system_clock::now(); + + std::chrono::duration preprocess_diff = + preprocess_end - preprocess_start; + time.push_back(double(preprocess_diff.count())); + std::chrono::duration inference_diff = infer_end - infer_start; + double inference_cost_time = double(inference_diff.count()); + time.push_back(inference_cost_time); + // std::chrono::duration postprocess_diff = + // postprocess_end - postprocess_start; + time.push_back(0); + + // std::cout << "result: " << std::endl; + // std::cout << "\tclass id: " << maxPosition << std::endl; + // std::cout << std::fixed << std::setprecision(10) + // << "\tscore: " << double(out_data[maxPosition]) << std::endl; + times[0] += time[0]; + times[1] += time[1]; + times[2] += time[2]; + } + + void FeatureExtracter::FeatureNorm(std::vector &featuer) { + float featuer_sqrt = std::sqrt(std::inner_product( + featuer.begin(), featuer.end(), featuer.begin(), 0.0f)); + for (int i = 0; i < featuer.size(); ++i) + featuer[i] /= featuer_sqrt; + } +} // namespace Feature diff --git a/deploy/cpp_shitu/src/main.cpp b/deploy/cpp_shitu/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d89ba14d5426776683f43c5bc77f85e166f0168d --- /dev/null +++ b/deploy/cpp_shitu/src/main.cpp @@ -0,0 +1,298 @@ +// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opencv2/core.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace cv; + +DEFINE_string(config, +"", "Path of yaml file"); +DEFINE_string(c, +"", "Path of yaml file"); + +void DetPredictImage(const std::vector &batch_imgs, + const std::vector &all_img_paths, + const int batch_size, Detection::ObjectDetector *det, + std::vector &im_result, + std::vector &im_bbox_num, std::vector &det_t, + const bool visual_det = false, + const bool run_benchmark = false, + const std::string &output_dir = "output") { + int steps = ceil(float(all_img_paths.size()) / batch_size); + // printf("total images = %d, batch_size = %d, total steps = %d\n", + // all_img_paths.size(), batch_size, steps); + for (int idx = 0; idx < steps; idx++) { + int left_image_cnt = all_img_paths.size() - idx * batch_size; + if (left_image_cnt > batch_size) { + left_image_cnt = batch_size; + } + // for (int bs = 0; bs < left_image_cnt; bs++) { + // std::string image_file_path = all_img_paths.at(idx * batch_size+bs); + // cv::Mat im = cv::imread(image_file_path, 1); + // batch_imgs.insert(batch_imgs.end(), im); + // } + + // Store all detected result + std::vector result; + std::vector bbox_num; + std::vector det_times; + bool is_rbox = false; + if (run_benchmark) { + det->Predict(batch_imgs, 10, 10, &result, &bbox_num, &det_times); + } else { + det->Predict(batch_imgs, 0, 1, &result, &bbox_num, &det_times); + // get labels and colormap + auto labels = det->GetLabelList(); + auto colormap = Detection::GenerateColorMap(labels.size()); + + int item_start_idx = 0; + for (int i = 0; i < left_image_cnt; i++) { + cv::Mat im = batch_imgs[i]; + int detect_num = 0; + + for (int j = 0; j < bbox_num[i]; j++) { + Detection::ObjectResult item = result[item_start_idx + j]; + if (item.confidence < det->GetThreshold() || item.class_id == -1) { + continue; + } + detect_num += 1; + im_result.push_back(item); + if (visual_det) { + if (item.rect.size() > 6) { + is_rbox = true; + printf( + "class=%d confidence=%.4f rect=[%d %d %d %d %d %d %d %d]\n", + item.class_id, item.confidence, item.rect[0], item.rect[1], + item.rect[2], item.rect[3], item.rect[4], item.rect[5], + item.rect[6], item.rect[7]); + } else { + printf("class=%d confidence=%.4f rect=[%d %d %d %d]\n", + item.class_id, item.confidence, item.rect[0], item.rect[1], + item.rect[2], item.rect[3]); + } + } + } + im_bbox_num.push_back(detect_num); + item_start_idx = item_start_idx + bbox_num[i]; + + // Visualization result + if (visual_det) { + std::cout << all_img_paths.at(idx * batch_size + i) + << " The number of detected box: " << detect_num + << std::endl; + cv::Mat vis_img = Detection::VisualizeResult(im, im_result, labels, + colormap, is_rbox); + std::vector compression_params; + compression_params.push_back(CV_IMWRITE_JPEG_QUALITY); + compression_params.push_back(95); + std::string output_path(output_dir); + if (output_dir.rfind(OS_PATH_SEP) != output_dir.size() - 1) { + output_path += OS_PATH_SEP; + } + std::string image_file_path = all_img_paths.at(idx * batch_size + i); + output_path += + image_file_path.substr(image_file_path.find_last_of('/') + 1); + cv::imwrite(output_path, vis_img, compression_params); + printf("Visualized output saved as %s\n", output_path.c_str()); + } + } + } + det_t[0] += det_times[0]; + det_t[1] += det_times[1]; + det_t[2] += det_times[2]; + } +} + +void PrintResult(std::string &img_path, + std::vector &det_result, + std::vector &indeices, VectorSearch &vector_search, + SearchResult &search_result) { + printf("%s:\n", img_path.c_str()); + for (int i = 0; i < indeices.size(); ++i) { + int t = indeices[i]; + printf("\tresult%d: bbox[%d, %d, %d, %d], score: %f, label: %s\n", i, + det_result[t].rect[0], det_result[t].rect[1], det_result[t].rect[2], + det_result[t].rect[3], det_result[t].confidence, + vector_search.GetLabel(search_result.I[search_result.return_k * t]) + .c_str()); + } +} + +int main(int argc, char **argv) { + google::ParseCommandLineFlags(&argc, &argv, true); + std::string yaml_path = ""; + if (FLAGS_config == "" && FLAGS_c == "") { + std::cerr << "[ERROR] usage: " << std::endl + << argv[0] << " -c $yaml_path" << std::endl + << "or:" << std::endl + << argv[0] << " -config $yaml_path" << std::endl; + exit(1); + } else if (FLAGS_config != "") { + yaml_path = FLAGS_config; + } else { + yaml_path = FLAGS_c; + } + + YamlConfig config(yaml_path); + config.PrintConfigInfo(); + + // initialize detector, rec_Model, vector_search + Feature::FeatureExtracter feature_extracter(config.config_file); + Detection::ObjectDetector detector(config.config_file); + VectorSearch searcher(config.config_file); + + // config + const int batch_size = config.config_file["Global"]["batch_size"].as(); + bool visual_det = false; + if (config.config_file["Global"]["visual_det"].IsDefined()) { + visual_det = config.config_file["Global"]["visual_det"].as(); + } + bool run_benchmark = false; + if (config.config_file["Global"]["benchmark"].IsDefined()) { + run_benchmark = config.config_file["Global"]["benchmark"].as(); + } + int max_det_results = 5; + if (config.config_file["Global"]["max_det_results"].IsDefined()) { + max_det_results = config.config_file["Global"]["max_det_results"].as(); + } + float rec_nms_thresold = 0.05; + if (config.config_file["Global"]["rec_nms_thresold"].IsDefined()) { + rec_nms_thresold = + config.config_file["Global"]["rec_nms_thresold"].as(); + } + + // load image_file_path + std::string path = + config.config_file["Global"]["infer_imgs"].as(); + std::vector img_files_list; + if (cv::utils::fs::isDirectory(path)) { + std::vector filenames; + cv::glob(path, filenames); + for (auto f : filenames) { + img_files_list.push_back(f); + } + } else { + img_files_list.push_back(path); + } + std::cout << "img_file_list length: " << img_files_list.size() << std::endl; + // for time log + std::vector cls_times = {0, 0, 0}; + std::vector det_times = {0, 0, 0}; + // for read images + std::vector batch_imgs; + std::vector img_paths; + // for detection + std::vector det_result; + std::vector det_bbox_num; + // for vector search + std::vector features; + std::vector feature; + // for nms + std::vector indeices; + + int warmup_iter = img_files_list.size() > 5 ? 5 : 0; + for (int idx = 0; idx < img_files_list.size(); ++idx) { + std::string img_path = img_files_list[idx]; + cv::Mat srcimg = cv::imread(img_path, cv::IMREAD_COLOR); + if (!srcimg.data) { + std::cerr << "[ERROR] image read failed! image path: " << img_path + << "\n"; + exit(-1); + } + cv::cvtColor(srcimg, srcimg, cv::COLOR_BGR2RGB); + + batch_imgs.push_back(srcimg); + img_paths.push_back(img_path); + + // step1: get all detection results + DetPredictImage(batch_imgs, img_paths, batch_size, &detector, det_result, + det_bbox_num, det_times, visual_det, run_benchmark); + + // select max_det_results bbox + if (det_result.size() > max_det_results) { + det_result.resize(max_det_results); + } + // step2: add the whole image for recognition to improve recall + Detection::ObjectResult result_whole_img = { + {0, 0, srcimg.cols - 1, srcimg.rows - 1}, 0, 1.0}; + det_result.push_back(result_whole_img); + det_bbox_num[0] = det_result.size() + 1; + + // step3: extract feature for all boxes in an inmage + SearchResult search_result; + for (int j = 0; j < det_result.size(); ++j) { + int w = det_result[j].rect[2] - det_result[j].rect[0]; + int h = det_result[j].rect[3] - det_result[j].rect[1]; + cv::Rect rect(det_result[j].rect[0], det_result[j].rect[1], w, h); + cv::Mat crop_img = srcimg(rect); + feature_extracter.Run(crop_img, feature, cls_times); + features.insert(features.end(), feature.begin(), feature.end()); + } + + // step4: get search result + search_result = searcher.Search(features.data(), det_result.size()); + + // nms for search result + for (int i = 0; i < det_result.size(); ++i) { + det_result[i].confidence = search_result.D[search_result.return_k * i]; + } + NMSBoxes(det_result, searcher.GetThreshold(), rec_nms_thresold, indeices); + + // print result + PrintResult(img_path, det_result, indeices, searcher, search_result); + + // for postprocess + batch_imgs.clear(); + img_paths.clear(); + det_bbox_num.clear(); + det_result.clear(); + feature.clear(); + features.clear(); + indeices.clear(); + } + + std::string presion = "fp32"; + + // if (config.use_fp16) + // presion = "fp16"; + // if (config.benchmark) { + // AutoLogger autolog("Classification", config.use_gpu, config.use_tensorrt, + // config.use_mkldnn, config.cpu_threads, 1, + // "1, 3, 224, 224", presion, cls_times, + // img_files_list.size()); + // autolog.report(); + // } + return 0; +} diff --git a/deploy/cpp_shitu/src/object_detector.cpp b/deploy/cpp_shitu/src/object_detector.cpp new file mode 100644 index 0000000000000000000000000000000000000000..03b02b973076a8d442e106c1ac58f83ee0877cf3 --- /dev/null +++ b/deploy/cpp_shitu/src/object_detector.cpp @@ -0,0 +1,365 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +// for setprecision +#include "include/object_detector.h" +#include +#include + +using namespace paddle_infer; + +namespace Detection { + +// Load Model and create model predictor + void ObjectDetector::LoadModel(const std::string &model_dir, + const int batch_size, + const std::string &run_mode) { + paddle_infer::Config config; + std::string prog_file = model_dir + OS_PATH_SEP + "inference.pdmodel"; + std::string params_file = model_dir + OS_PATH_SEP + "inference.pdiparams"; + config.SetModel(prog_file, params_file); + if (this->use_gpu_) { + config.EnableUseGpu(this->gpu_mem_, this->gpu_id_); + config.SwitchIrOptim(this->ir_optim_); + // // use tensorrt + // if (run_mode != "fluid") { + // auto precision = paddle_infer::Config::Precision::kFloat32; + // if (run_mode == "trt_fp32") { + // precision = paddle_infer::Config::Precision::kFloat32; + // } + // else if (run_mode == "trt_fp16") { + // precision = paddle_infer::Config::Precision::kHalf; + // } + // else if (run_mode == "trt_int8") { + // precision = paddle_infer::Config::Precision::kInt8; + // } else { + // printf("run_mode should be 'fluid', 'trt_fp32', 'trt_fp16' or + // 'trt_int8'"); + // } + // set tensorrt + + if (this->use_tensorrt_) { + config.EnableTensorRtEngine( + 1 << 30, batch_size, this->min_subgraph_size_, + this->use_fp16_ ? paddle_infer::Config::Precision::kHalf + : paddle_infer::Config::Precision::kFloat32, + false, this->trt_calib_mode_); + // set use dynamic shape + if (this->use_dynamic_shape_) { + // set DynamicShsape for image tensor + const std::vector min_input_shape = {1, 3, this->trt_min_shape_, + this->trt_min_shape_}; + const std::vector max_input_shape = {1, 3, this->trt_max_shape_, + this->trt_max_shape_}; + const std::vector opt_input_shape = {1, 3, this->trt_opt_shape_, + this->trt_opt_shape_}; + const std::map > map_min_input_shape = { + {"image", min_input_shape}}; + const std::map > map_max_input_shape = { + {"image", max_input_shape}}; + const std::map > map_opt_input_shape = { + {"image", opt_input_shape}}; + + config.SetTRTDynamicShapeInfo(map_min_input_shape, map_max_input_shape, + map_opt_input_shape); + std::cout << "TensorRT dynamic shape enabled" << std::endl; + } + } + + // } else if (this->device_ == "XPU"){ + // config.EnableXpu(10*1024*1024); + } else { + config.DisableGpu(); + if (this->use_mkldnn_) { + config.EnableMKLDNN(); + // cache 10 different shapes for mkldnn to avoid memory leak + config.SetMkldnnCacheCapacity(10); + } + config.SetCpuMathLibraryNumThreads(this->cpu_math_library_num_threads_); + } + config.SwitchUseFeedFetchOps(false); + config.SwitchIrOptim(this->ir_optim_); + config.DisableGlogInfo(); + // Memory optimization + config.EnableMemoryOptim(); + predictor_ = std::move(CreatePredictor(config)); + } + +// Visualiztion MaskDetector results + cv::Mat VisualizeResult(const cv::Mat &img, + const std::vector &results, + const std::vector &lables, + const std::vector &colormap, + const bool is_rbox = false) { + cv::Mat vis_img = img.clone(); + for (int i = 0; i < results.size(); ++i) { + // Configure color and text size + std::ostringstream oss; + oss << std::setiosflags(std::ios::fixed) << std::setprecision(4); + oss << lables[results[i].class_id] << " "; + oss << results[i].confidence; + std::string text = oss.str(); + int c1 = colormap[3 * results[i].class_id + 0]; + int c2 = colormap[3 * results[i].class_id + 1]; + int c3 = colormap[3 * results[i].class_id + 2]; + cv::Scalar roi_color = cv::Scalar(c1, c2, c3); + int font_face = cv::FONT_HERSHEY_COMPLEX_SMALL; + double font_scale = 0.5f; + float thickness = 0.5; + cv::Size text_size = + cv::getTextSize(text, font_face, font_scale, thickness, nullptr); + cv::Point origin; + + if (is_rbox) { + // Draw object, text, and background + for (int k = 0; k < 4; k++) { + cv::Point pt1 = cv::Point(results[i].rect[(k * 2) % 8], + results[i].rect[(k * 2 + 1) % 8]); + cv::Point pt2 = cv::Point(results[i].rect[(k * 2 + 2) % 8], + results[i].rect[(k * 2 + 3) % 8]); + cv::line(vis_img, pt1, pt2, roi_color, 2); + } + } else { + int w = results[i].rect[2] - results[i].rect[0]; + int h = results[i].rect[3] - results[i].rect[1]; + cv::Rect roi = cv::Rect(results[i].rect[0], results[i].rect[1], w, h); + // Draw roi object, text, and background + cv::rectangle(vis_img, roi, roi_color, 2); + } + + origin.x = results[i].rect[0]; + origin.y = results[i].rect[1]; + + // Configure text background + cv::Rect text_back = + cv::Rect(results[i].rect[0], results[i].rect[1] - text_size.height, + text_size.width, text_size.height); + // Draw text, and background + cv::rectangle(vis_img, text_back, roi_color, -1); + cv::putText(vis_img, text, origin, font_face, font_scale, + cv::Scalar(255, 255, 255), thickness); + } + return vis_img; + } + + void ObjectDetector::Preprocess(const cv::Mat &ori_im) { + // Clone the image : keep the original mat for postprocess + cv::Mat im = ori_im.clone(); + cv::cvtColor(im, im, cv::COLOR_BGR2RGB); + preprocessor_.Run(&im, &inputs_); + } + + void ObjectDetector::Postprocess(const std::vector mats, + std::vector *result, + std::vector bbox_num, + bool is_rbox = false) { + result->clear(); + int start_idx = 0; + for (int im_id = 0; im_id < mats.size(); im_id++) { + cv::Mat raw_mat = mats[im_id]; + int rh = 1; + int rw = 1; + // if (config_.arch_ == "Face") { + // rh = raw_mat.rows; + // rw = raw_mat.cols; + // } + for (int j = start_idx; j < start_idx + bbox_num[im_id]; j++) { + if (is_rbox) { + // Class id + int class_id = static_cast(round(output_data_[0 + j * 10])); + // Confidence score + float score = output_data_[1 + j * 10]; + int x1 = (output_data_[2 + j * 10] * rw); + int y1 = (output_data_[3 + j * 10] * rh); + int x2 = (output_data_[4 + j * 10] * rw); + int y2 = (output_data_[5 + j * 10] * rh); + int x3 = (output_data_[6 + j * 10] * rw); + int y3 = (output_data_[7 + j * 10] * rh); + int x4 = (output_data_[8 + j * 10] * rw); + int y4 = (output_data_[9 + j * 10] * rh); + + ObjectResult result_item; + result_item.rect = {x1, y1, x2, y2, x3, y3, x4, y4}; + result_item.class_id = class_id; + result_item.confidence = score; + result->push_back(result_item); + } else { + // Class id + int class_id = static_cast(round(output_data_[0 + j * 6])); + // Confidence score + float score = output_data_[1 + j * 6]; + int xmin = (output_data_[2 + j * 6] * rw); + int ymin = (output_data_[3 + j * 6] * rh); + int xmax = (output_data_[4 + j * 6] * rw); + int ymax = (output_data_[5 + j * 6] * rh); + int wd = xmax - xmin; + int hd = ymax - ymin; + + ObjectResult result_item; + result_item.rect = {xmin, ymin, xmax, ymax}; + result_item.class_id = class_id; + result_item.confidence = score; + result->push_back(result_item); + } + } + start_idx += bbox_num[im_id]; + } + } + + void ObjectDetector::Predict(const std::vector imgs, const int warmup, + const int repeats, + std::vector *result, + std::vector *bbox_num, + std::vector *times) { + auto preprocess_start = std::chrono::steady_clock::now(); + int batch_size = imgs.size(); + + // in_data_batch + std::vector in_data_all; + std::vector im_shape_all(batch_size * 2); + std::vector scale_factor_all(batch_size * 2); + + // Preprocess image + for (int bs_idx = 0; bs_idx < batch_size; bs_idx++) { + cv::Mat im = imgs.at(bs_idx); + Preprocess(im); + im_shape_all[bs_idx * 2] = inputs_.im_shape_[0]; + im_shape_all[bs_idx * 2 + 1] = inputs_.im_shape_[1]; + + scale_factor_all[bs_idx * 2] = inputs_.scale_factor_[0]; + scale_factor_all[bs_idx * 2 + 1] = inputs_.scale_factor_[1]; + + // TODO: reduce cost time + in_data_all.insert(in_data_all.end(), inputs_.im_data_.begin(), + inputs_.im_data_.end()); + } + + // Prepare input tensor + auto input_names = predictor_->GetInputNames(); + for (const auto &tensor_name : input_names) { + auto in_tensor = predictor_->GetInputHandle(tensor_name); + if (tensor_name == "image") { + int rh = inputs_.in_net_shape_[0]; + int rw = inputs_.in_net_shape_[1]; + in_tensor->Reshape({batch_size, 3, rh, rw}); + in_tensor->CopyFromCpu(in_data_all.data()); + } else if (tensor_name == "im_shape") { + in_tensor->Reshape({batch_size, 2}); + in_tensor->CopyFromCpu(im_shape_all.data()); + } else if (tensor_name == "scale_factor") { + in_tensor->Reshape({batch_size, 2}); + in_tensor->CopyFromCpu(scale_factor_all.data()); + } + } + + auto preprocess_end = std::chrono::steady_clock::now(); + // Run predictor + // warmup + for (int i = 0; i < warmup; i++) { + predictor_->Run(); + // Get output tensor + auto output_names = predictor_->GetOutputNames(); + auto out_tensor = predictor_->GetOutputHandle(output_names[0]); + std::vector output_shape = out_tensor->shape(); + auto out_bbox_num = predictor_->GetOutputHandle(output_names[1]); + std::vector out_bbox_num_shape = out_bbox_num->shape(); + // Calculate output length + int output_size = 1; + for (int j = 0; j < output_shape.size(); ++j) { + output_size *= output_shape[j]; + } + + if (output_size < 6) { + std::cerr << "[WARNING] No object detected." << std::endl; + } + output_data_.resize(output_size); + out_tensor->CopyToCpu(output_data_.data()); + + int out_bbox_num_size = 1; + for (int j = 0; j < out_bbox_num_shape.size(); ++j) { + out_bbox_num_size *= out_bbox_num_shape[j]; + } + out_bbox_num_data_.resize(out_bbox_num_size); + out_bbox_num->CopyToCpu(out_bbox_num_data_.data()); + } + + bool is_rbox = false; + auto inference_start = std::chrono::steady_clock::now(); + for (int i = 0; i < repeats; i++) { + predictor_->Run(); + // Get output tensor + auto output_names = predictor_->GetOutputNames(); + auto out_tensor = predictor_->GetOutputHandle(output_names[0]); + std::vector output_shape = out_tensor->shape(); + auto out_bbox_num = predictor_->GetOutputHandle(output_names[1]); + std::vector out_bbox_num_shape = out_bbox_num->shape(); + // Calculate output length + int output_size = 1; + for (int j = 0; j < output_shape.size(); ++j) { + output_size *= output_shape[j]; + } + is_rbox = output_shape[output_shape.size() - 1] % 10 == 0; + + if (output_size < 6) { + std::cerr << "[WARNING] No object detected." << std::endl; + } + output_data_.resize(output_size); + out_tensor->CopyToCpu(output_data_.data()); + + int out_bbox_num_size = 1; + for (int j = 0; j < out_bbox_num_shape.size(); ++j) { + out_bbox_num_size *= out_bbox_num_shape[j]; + } + out_bbox_num_data_.resize(out_bbox_num_size); + out_bbox_num->CopyToCpu(out_bbox_num_data_.data()); + } + auto inference_end = std::chrono::steady_clock::now(); + auto postprocess_start = std::chrono::steady_clock::now(); + // Postprocessing result + result->clear(); + Postprocess(imgs, result, out_bbox_num_data_, is_rbox); + bbox_num->clear(); + for (int k = 0; k < out_bbox_num_data_.size(); k++) { + int tmp = out_bbox_num_data_[k]; + bbox_num->push_back(tmp); + } + auto postprocess_end = std::chrono::steady_clock::now(); + + std::chrono::duration preprocess_diff = + preprocess_end - preprocess_start; + times->push_back(double(preprocess_diff.count() * 1000)); + std::chrono::duration inference_diff = inference_end - inference_start; + times->push_back(double(inference_diff.count() / repeats * 1000)); + std::chrono::duration postprocess_diff = + postprocess_end - postprocess_start; + times->push_back(double(postprocess_diff.count() * 1000)); + } + + std::vector GenerateColorMap(int num_class) { + auto colormap = std::vector(3 * num_class, 0); + for (int i = 0; i < num_class; ++i) { + int j = 0; + int lab = i; + while (lab) { + colormap[i * 3] |= (((lab >> 0) & 1) << (7 - j)); + colormap[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j)); + colormap[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j)); + ++j; + lab >>= 3; + } + } + return colormap; + } + +} // namespace Detection diff --git a/deploy/cpp_shitu/src/preprocess_op.cpp b/deploy/cpp_shitu/src/preprocess_op.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6bf1ec91fcee02699c39a667a0b36998ec6ad04c --- /dev/null +++ b/deploy/cpp_shitu/src/preprocess_op.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opencv2/core.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include "paddle_api.h" +#include "paddle_inference_api.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace Feature { + + void Permute::Run(const cv::Mat *im, float *data) { + int rh = im->rows; + int rw = im->cols; + int rc = im->channels(); + for (int i = 0; i < rc; ++i) { + cv::extractChannel(*im, cv::Mat(rh, rw, CV_32FC1, data + i * rh * rw), i); + } + } + + void Normalize::Run(cv::Mat *im, const std::vector &mean, + const std::vector &std, float scale) { + (*im).convertTo(*im, CV_32FC3, scale); + for (int h = 0; h < im->rows; h++) { + for (int w = 0; w < im->cols; w++) { + im->at(h, w)[0] = + (im->at(h, w)[0] - mean[0]) / std[0]; + im->at(h, w)[1] = + (im->at(h, w)[1] - mean[1]) / std[1]; + im->at(h, w)[2] = + (im->at(h, w)[2] - mean[2]) / std[2]; + } + } + } + + void CenterCropImg::Run(cv::Mat &img, const int crop_size) { + int resize_w = img.cols; + int resize_h = img.rows; + int w_start = int((resize_w - crop_size) / 2); + int h_start = int((resize_h - crop_size) / 2); + cv::Rect rect(w_start, h_start, crop_size, crop_size); + img = img(rect); + } + + void ResizeImg::Run(const cv::Mat &img, cv::Mat &resize_img, + int resize_short_size, int size) { + int resize_h = 0; + int resize_w = 0; + if (size > 0) { + resize_h = size; + resize_w = size; + } else { + int w = img.cols; + int h = img.rows; + + float ratio = 1.f; + if (h < w) { + ratio = float(resize_short_size) / float(h); + } else { + ratio = float(resize_short_size) / float(w); + } + resize_h = round(float(h) * ratio); + resize_w = round(float(w) * ratio); + } + cv::resize(img, resize_img, cv::Size(resize_w, resize_h)); + } + +} // namespace Feature diff --git a/deploy/cpp_shitu/src/preprocess_op_det.cpp b/deploy/cpp_shitu/src/preprocess_op_det.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e32b03a47595245cc64a889c0bad38241ac73683 --- /dev/null +++ b/deploy/cpp_shitu/src/preprocess_op_det.cpp @@ -0,0 +1,130 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "include/preprocess_op_det.h" + +namespace Detection { + + void InitInfo::Run(cv::Mat *im, ImageBlob *data) { + data->im_shape_ = {static_cast(im->rows), + static_cast(im->cols)}; + data->scale_factor_ = {1., 1.}; + data->in_net_shape_ = {static_cast(im->rows), + static_cast(im->cols)}; + } + + void NormalizeImage::Run(cv::Mat *im, ImageBlob *data) { + double e = 1.0; + if (is_scale_) { + e /= 255.0; + } + (*im).convertTo(*im, CV_32FC3, e); + for (int h = 0; h < im->rows; h++) { + for (int w = 0; w < im->cols; w++) { + im->at(h, w)[0] = + (im->at(h, w)[0] - mean_[0]) / scale_[0]; + im->at(h, w)[1] = + (im->at(h, w)[1] - mean_[1]) / scale_[1]; + im->at(h, w)[2] = + (im->at(h, w)[2] - mean_[2]) / scale_[2]; + } + } + } + + void Permute::Run(cv::Mat *im, ImageBlob *data) { + int rh = im->rows; + int rw = im->cols; + int rc = im->channels(); + (data->im_data_).resize(rc * rh * rw); + float *base = (data->im_data_).data(); + for (int i = 0; i < rc; ++i) { + cv::extractChannel(*im, cv::Mat(rh, rw, CV_32FC1, base + i * rh * rw), i); + } + } + + void Resize::Run(cv::Mat *im, ImageBlob *data) { + auto resize_scale = GenerateScale(*im); + data->im_shape_ = {static_cast(im->cols * resize_scale.first), + static_cast(im->rows * resize_scale.second)}; + data->in_net_shape_ = {static_cast(im->cols * resize_scale.first), + static_cast(im->rows * resize_scale.second)}; + cv::resize(*im, *im, cv::Size(), resize_scale.first, resize_scale.second, + interp_); + data->im_shape_ = { + static_cast(im->rows), static_cast(im->cols), + }; + data->scale_factor_ = { + resize_scale.second, resize_scale.first, + }; + } + + std::pair Resize::GenerateScale(const cv::Mat &im) { + std::pair resize_scale; + int origin_w = im.cols; + int origin_h = im.rows; + + if (keep_ratio_) { + int im_size_max = std::max(origin_w, origin_h); + int im_size_min = std::min(origin_w, origin_h); + int target_size_max = + *std::max_element(target_size_.begin(), target_size_.end()); + int target_size_min = + *std::min_element(target_size_.begin(), target_size_.end()); + double scale_min = + static_cast(target_size_min) / static_cast(im_size_min); + double scale_max = + static_cast(target_size_max) / static_cast(im_size_max); + double scale_ratio = std::min(scale_min, scale_max); + resize_scale = {scale_ratio, scale_ratio}; + } else { + resize_scale.first = + static_cast(target_size_[1]) / static_cast(origin_w); + resize_scale.second = + static_cast(target_size_[0]) / static_cast(origin_h); + } + return resize_scale; + } + + void PadStride::Run(cv::Mat *im, ImageBlob *data) { + if (stride_ <= 0) { + return; + } + int rc = im->channels(); + int rh = im->rows; + int rw = im->cols; + int nh = (rh / stride_) * stride_ + (rh % stride_ != 0) * stride_; + int nw = (rw / stride_) * stride_ + (rw % stride_ != 0) * stride_; + cv::copyMakeBorder(*im, *im, 0, nh - rh, 0, nw - rw, cv::BORDER_CONSTANT, + cv::Scalar(0)); + data->in_net_shape_ = { + static_cast(im->rows), static_cast(im->cols), + }; + } + +// Preprocessor op running order + const std::vector Preprocessor::RUN_ORDER = { + "InitInfo", "Resize", "NormalizeImage", "PadStride", "Permute"}; + + void Preprocessor::Run(cv::Mat *im, ImageBlob *data) { + for (const auto &name : RUN_ORDER) { + if (ops_.find(name) != ops_.end()) { + ops_[name]->Run(im, data); + } + } + } + +} // namespace Detection diff --git a/deploy/cpp_shitu/src/vector_search.cpp b/deploy/cpp_shitu/src/vector_search.cpp new file mode 100644 index 0000000000000000000000000000000000000000..85c487a771bb9bb6d84a799f5d464a09ee883b74 --- /dev/null +++ b/deploy/cpp_shitu/src/vector_search.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "include/vector_search.h" +#include +#include +#include +#include +#include +#include + +void VectorSearch::LoadIndexFile() { + std::string file_path = this->index_dir + OS_PATH_SEP + "vector.index"; + const char *fname = file_path.c_str(); + this->index = faiss::read_index(fname, 0); +} + +void VectorSearch::LoadIdMap() { + std::string file_path = this->index_dir + OS_PATH_SEP + "id_map.txt"; + std::ifstream in(file_path); + std::string line; + std::vector m_vec; + if (in) { + while (getline(in, line)) { + std::regex ws_re("\\s+"); + std::vector v( + std::sregex_token_iterator(line.begin(), line.end(), ws_re, -1), + std::sregex_token_iterator()); + if (v.size() != 2) { + std::cout << "The number of element for each line in : " << file_path + << "must be 2, exit the program..." << std::endl; + exit(1); + } else + this->id_map.insert(std::pair( + std::stol(v[0], nullptr, 10), v[1])); + } + } +} + +const SearchResult &VectorSearch::Search(float *feature, int query_number) { + this->D.resize(this->return_k * query_number); + this->I.resize(this->return_k * query_number); + this->index->search(query_number, feature, return_k, D.data(), I.data()); + this->sr.return_k = this->return_k; + this->sr.D = this->D; + this->sr.I = this->I; + return this->sr; +} + +const std::string &VectorSearch::GetLabel(faiss::Index::idx_t ind) { + return this->id_map.at(ind); +} diff --git a/deploy/cpp_shitu/src/yaml_config.cpp b/deploy/cpp_shitu/src/yaml_config.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b20fcde9ea137c293d366a13d36d3b9e04f34064 --- /dev/null +++ b/deploy/cpp_shitu/src/yaml_config.cpp @@ -0,0 +1,78 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include + + +std::vector YamlConfig::ReadDict(const std::string &path) { + std::ifstream in(path); + std::string line; + std::vector m_vec; + if (in) { + while (getline(in, line)) { + m_vec.push_back(line); + } + } else { + std::cout << "no such label file: " << path << ", exit the program..." + << std::endl; + exit(1); + } + return m_vec; +} + +std::map YamlConfig::ReadIndexId(const std::string &path) { + std::ifstream in(path); + std::string line; + std::map m_vec; + if (in) { + while (getline(in, line)) { + std::regex ws_re("\\s+"); + std::vector v( + std::sregex_token_iterator(line.begin(), line.end(), ws_re, -1), + std::sregex_token_iterator()); + if (v.size() != 3) { + std::cout << "The number of element for each line in : " << path + << "must be 3, exit the program..." << std::endl; + exit(1); + } else + m_vec.insert(std::pair(stoi(v[0]), v[2])); + } + } + return m_vec; +} + +YAML::Node YamlConfig::ReadYamlConfig(const std::string &path) { + YAML::Node config; + try { + config = YAML::LoadFile(path); + } catch (YAML::BadFile &e) { + std::cout << "Something wrong in yaml file, please check yaml file" + << std::endl; + exit(1); + } + return config; +} + +void YamlConfig::PrintConfigInfo() { + std::cout << this->config_file << std::endl; + // for (YAML::const_iterator + // it=config_file.begin();it!=config_file.end();++it) + // { + // std::cout << it->as() << "\n"; + // } +} diff --git a/deploy/cpp_shitu/tools/build.sh b/deploy/cpp_shitu/tools/build.sh new file mode 100755 index 0000000000000000000000000000000000000000..5307af06143fc5857503bc5fbd0f0dd4caf24384 --- /dev/null +++ b/deploy/cpp_shitu/tools/build.sh @@ -0,0 +1,24 @@ +OPENCV_DIR=${opencv_install_dir} +LIB_DIR=${paddle_inference_dir} +CUDA_LIB_DIR=/usr/local/cuda/lib64 +CUDNN_LIB_DIR=/usr/lib/x86_64-linux-gnu/ +FAISS_DIR=${faiss_install_dir} +FAISS_WITH_MKL=OFF + +BUILD_DIR=build +rm -rf ${BUILD_DIR} +mkdir ${BUILD_DIR} +cd ${BUILD_DIR} +cmake .. \ + -DPADDLE_LIB=${LIB_DIR} \ + -DWITH_MKL=ON \ + -DWITH_GPU=OFF \ + -DWITH_STATIC_LIB=OFF \ + -DUSE_TENSORRT=OFF \ + -DOPENCV_DIR=${OPENCV_DIR} \ + -DCUDNN_LIB=${CUDNN_LIB_DIR} \ + -DCUDA_LIB=${CUDA_LIB_DIR} \ + -DFAISS_DIR=${FAISS_DIR} \ + -DFAISS_WITH_MKL=${FAISS_WITH_MKL} + +make -j \ No newline at end of file diff --git a/deploy/cpp_shitu/tools/transform_id_map.py b/deploy/cpp_shitu/tools/transform_id_map.py new file mode 100644 index 0000000000000000000000000000000000000000..02fe0813ec754877d12a18afb2378b297b333f33 --- /dev/null +++ b/deploy/cpp_shitu/tools/transform_id_map.py @@ -0,0 +1,38 @@ +import argparse +import os +import pickle + +import yaml + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config', type=str, required=True) + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + with open(args.config)as fd: + config = yaml.load(fd) + index_dir = "" + try: + index_dir = config["IndexProcess"]["index_dir"] + except Exception as e: + print("The IndexProcess.index_dir in config_file dose not exist") + exit(1) + id_map_path = os.path.join(index_dir, "id_map.pkl") + assert os.path.exists(id_map_path), "The id_map file dose not exist: {}".format(id_map_path) + + with open(id_map_path, "rb")as fd: + ids = pickle.load(fd) + with open(os.path.join(index_dir, "id_map.txt"), "w")as fd: + for k, v in ids.items(): + v = v.split("\t")[1] + fd.write(str(k) + " " + v + "\n") + print('Transform id_map sucess') + + +if __name__ == "__main__": + main() diff --git a/docs/images/quick_start/shitu_c++_result.png b/docs/images/quick_start/shitu_c++_result.png new file mode 100644 index 0000000000000000000000000000000000000000..f2cbee5ef2af0f1e8941564b5430247ef0919d92 Binary files /dev/null and b/docs/images/quick_start/shitu_c++_result.png differ