diff --git a/deploy/cpp/CMakeLists.txt b/deploy/cpp/CMakeLists.txt index bd13a46713e1239380891e25c3ee7cb68f0f8d1e..ceaa448253f18bb8ea55423ed323aeb3cb459fdc 100644 --- a/deploy/cpp/CMakeLists.txt +++ b/deploy/cpp/CMakeLists.txt @@ -73,7 +73,11 @@ endif() if(EXISTS "${PADDLE_DIR}/third_party/install/snappystream/include") include_directories("${PADDLE_DIR}/third_party/install/snappystream/include") endif() -include_directories("${PADDLE_DIR}/third_party/install/zlib/include") +# zlib does not exist in 1.8.1 +if (EXISTS "${PADDLE_DIR}/third_party/install/zlib/include") + include_directories("${PADDLE_DIR}/third_party/install/zlib/include") +endif() + include_directories("${PADDLE_DIR}/third_party/boost") include_directories("${PADDLE_DIR}/third_party/eigen3") @@ -84,7 +88,10 @@ if(EXISTS "${PADDLE_DIR}/third_party/install/snappystream/lib") link_directories("${PADDLE_DIR}/third_party/install/snappystream/lib") endif() -link_directories("${PADDLE_DIR}/third_party/install/zlib/lib") +if (EXISTS "${PADDLE_DIR}/third_party/install/zlib/lib") + link_directories("${PADDLE_DIR}/third_party/install/zlib/lib") +endif() + link_directories("${PADDLE_DIR}/third_party/install/protobuf/lib") link_directories("${PADDLE_DIR}/third_party/install/glog/lib") link_directories("${PADDLE_DIR}/third_party/install/gflags/lib") @@ -107,6 +114,14 @@ include_directories(${OpenCV_INCLUDE_DIRS}) if (WIN32) add_definitions("/DGOOGLE_GLOG_DLL_DECL=") + find_package(OpenMP REQUIRED) + if (OPENMP_FOUND) + message("OPENMP FOUND") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${OpenMP_C_FLAGS}") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${OpenMP_C_FLAGS}") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${OpenMP_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${OpenMP_CXX_FLAGS}") + endif() 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") @@ -186,8 +201,13 @@ if(WITH_STATIC_LIB) set(DEPS ${PADDLE_DIR}/paddle/lib/libpaddle_fluid${CMAKE_STATIC_LIBRARY_SUFFIX}) else() - set(DEPS - ${PADDLE_DIR}/paddle/lib/libpaddle_fluid${CMAKE_SHARED_LIBRARY_SUFFIX}) + if (NOT WIN32) + set(DEPS + ${PADDLE_DIR}/paddle/lib/libpaddle_fluid${CMAKE_SHARED_LIBRARY_SUFFIX}) + else() + set(DEPS + ${PADDLE_DIR}/paddle/lib/paddle_fluid${CMAKE_SHARED_LIBRARY_SUFFIX}) + endif() endif() if (NOT WIN32) @@ -204,13 +224,16 @@ if (NOT WIN32) else() set(DEPS ${DEPS} ${MATH_LIB} ${MKLDNN_LIB} - glog gflags_static libprotobuf zlibstatic xxhash libyaml-cppmt) + glog gflags_static libprotobuf xxhash libyaml-cppmt) + if (EXISTS "${PADDLE_DIR}/third_party/install/zlib/lib") + set(DEPS ${DEPS} zlibstatic) + endif() set(DEPS ${DEPS} libcmt shlwapi) if (EXISTS "${PADDLE_DIR}/third_party/install/snappy/lib") set(DEPS ${DEPS} snappy) endif() - if(EXISTS "${PADDLE_DIR}/third_party/install/snappystream/lib") + if (EXISTS "${PADDLE_DIR}/third_party/install/snappystream/lib") set(DEPS ${DEPS} snappystream) endif() endif(NOT WIN32) @@ -236,7 +259,9 @@ if(WITH_ENCRYPTION) link_directories("${ENCRYPTION_DIR}/lib") set(DEPS ${DEPS} ${ENCRYPTION_DIR}/lib/libpmodel-decrypt${CMAKE_SHARED_LIBRARY_SUFFIX}) else() - message(FATAL_ERROR "Encryption Tool don't support WINDOWS") + include_directories("${ENCRYPTION_DIR}/include") + link_directories("${ENCRYPTION_DIR}/lib") + set(DEPS ${DEPS} ${ENCRYPTION_DIR}/lib/pmodel-decrypt${CMAKE_STATIC_LIBRARY_SUFFIX}) endif() endif() @@ -284,10 +309,23 @@ if (WIN32 AND WITH_MKL) COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mkldnn/lib/mkldnn.dll ./mkldnn.dll COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mklml/lib/mklml.dll ./release/mklml.dll COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mklml/lib/libiomp5md.dll ./release/libiomp5md.dll - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mkldnn/lib/mkldnn.dll ./release/mkldnn.dll ) - + # for encryption + if (EXISTS "${ENCRYPTION_DIR}/lib/pmodel-decrypt.dll") + add_custom_command(TARGET classifier POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${ENCRYPTION_DIR}/lib/pmodel-decrypt.dll ./pmodel-decrypt.dll + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${ENCRYPTION_DIR}/lib/pmodel-decrypt.dll ./release/pmodel-decrypt.dll + ) + add_custom_command(TARGET detector POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${ENCRYPTION_DIR}/lib/pmodel-decrypt.dll ./pmodel-decrypt.dll + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${ENCRYPTION_DIR}/lib/pmodel-decrypt.dll ./release/pmodel-decrypt.dll + ) + add_custom_command(TARGET segmenter POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${ENCRYPTION_DIR}/lib/pmodel-decrypt.dll ./pmodel-decrypt.dll + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${ENCRYPTION_DIR}/lib/pmodel-decrypt.dll ./release/pmodel-decrypt.dll + ) + endif() endif() file(COPY "${CMAKE_SOURCE_DIR}/include/paddlex/visualize.h" diff --git a/deploy/cpp/CMakeSettings.json b/deploy/cpp/CMakeSettings.json index 860ca7a61e222d84e5cc7e9b3447bdc8397a8c40..535ff1a8b8aac2ddfc70e0f1c2a25a3b910976d4 100644 --- a/deploy/cpp/CMakeSettings.json +++ b/deploy/cpp/CMakeSettings.json @@ -22,9 +22,9 @@ "type": "PATH" }, { - "name": "CMAKE_BUILD_TYPE", - "value": "Release", - "type": "STRING" + "name": "CUDA_LIB", + "value": "", + "type": "PATH" }, { "name": "WITH_STATIC_LIB", @@ -40,8 +40,18 @@ "name": "WITH_GPU", "value": "False", "type": "BOOL" + }, + { + "name": "WITH_ENCRYPTION", + "value": "False", + "type": "BOOL" + }, + { + "name": "ENCRYPTION_DIR", + "value": "", + "type": "PATH" } ] } ] -} \ No newline at end of file +} diff --git a/deploy/cpp/cmake/yaml-cpp.cmake b/deploy/cpp/cmake/yaml-cpp.cmake index caa8be513bcaaf7ff73c12c268b8137e5582672c..a4b527d38a1676db9c40bd20b6c803b13f597eb2 100644 --- a/deploy/cpp/cmake/yaml-cpp.cmake +++ b/deploy/cpp/cmake/yaml-cpp.cmake @@ -1,5 +1,3 @@ -find_package(Git REQUIRED) - include(ExternalProject) message("${CMAKE_BUILD_TYPE}") diff --git a/deploy/cpp/demo/classifier.cpp b/deploy/cpp/demo/classifier.cpp index badb835132418098d332014a590d2dbb7a1e43fd..6fd354d3f9cb6a366f0efb0b31e7ae073a90b4ad 100644 --- a/deploy/cpp/demo/classifier.cpp +++ b/deploy/cpp/demo/classifier.cpp @@ -13,14 +13,19 @@ // limitations under the License. #include +#include +#include +#include // NOLINT #include #include #include #include - +#include #include "include/paddlex/paddlex.h" +using namespace std::chrono; // NOLINT + DEFINE_string(model_dir, "", "Path of inference model"); DEFINE_bool(use_gpu, false, "Infering with GPU or CPU"); DEFINE_bool(use_trt, false, "Infering with TensorRT"); @@ -28,6 +33,11 @@ DEFINE_int32(gpu_id, 0, "GPU card id"); DEFINE_string(key, "", "key of encryption"); DEFINE_string(image, "", "Path of test image file"); DEFINE_string(image_list, "", "Path of test image list file"); +DEFINE_int32(batch_size, 1, "Batch size of infering"); +DEFINE_int32(thread_num, + omp_get_num_procs(), + "Number of preprocessing threads"); +DEFINE_bool(use_ir_optim, true, "use ir optimization"); int main(int argc, char** argv) { // Parsing command-line @@ -44,32 +54,81 @@ int main(int argc, char** argv) { // 加载模型 PaddleX::Model model; - model.Init(FLAGS_model_dir, FLAGS_use_gpu, FLAGS_use_trt, FLAGS_gpu_id, FLAGS_key); + model.Init(FLAGS_model_dir, + FLAGS_use_gpu, + FLAGS_use_trt, + FLAGS_gpu_id, + FLAGS_key, + FLAGS_use_ir_optim); // 进行预测 + double total_running_time_s = 0.0; + double total_imread_time_s = 0.0; + int imgs = 1; if (FLAGS_image_list != "") { std::ifstream inf(FLAGS_image_list); if (!inf) { std::cerr << "Fail to open file " << FLAGS_image_list << std::endl; return -1; } + // 多batch预测 std::string image_path; + std::vector image_paths; while (getline(inf, image_path)) { - PaddleX::ClsResult result; - cv::Mat im = cv::imread(image_path, 1); - model.predict(im, &result); - std::cout << "Predict label: " << result.category - << ", label_id:" << result.category_id - << ", score: " << result.score << std::endl; + image_paths.push_back(image_path); + } + imgs = image_paths.size(); + for (int i = 0; i < image_paths.size(); i += FLAGS_batch_size) { + auto start = system_clock::now(); + // 读图像 + int im_vec_size = + std::min(static_cast(image_paths.size()), i + FLAGS_batch_size); + std::vector im_vec(im_vec_size - i); + std::vector results(im_vec_size - i, + PaddleX::ClsResult()); + int thread_num = std::min(FLAGS_thread_num, im_vec_size - i); + #pragma omp parallel for num_threads(thread_num) + for (int j = i; j < im_vec_size; ++j) { + im_vec[j - i] = std::move(cv::imread(image_paths[j], 1)); + } + auto imread_end = system_clock::now(); + model.predict(im_vec, &results, thread_num); + + auto imread_duration = duration_cast(imread_end - start); + total_imread_time_s += static_cast(imread_duration.count()) * + microseconds::period::num / + microseconds::period::den; + + auto end = system_clock::now(); + auto duration = duration_cast(end - start); + total_running_time_s += static_cast(duration.count()) * + microseconds::period::num / + microseconds::period::den; + for (int j = i; j < im_vec_size; ++j) { + std::cout << "Path:" << image_paths[j] + << ", predict label: " << results[j - i].category + << ", label_id:" << results[j - i].category_id + << ", score: " << results[j - i].score << std::endl; + } } } else { + auto start = system_clock::now(); PaddleX::ClsResult result; cv::Mat im = cv::imread(FLAGS_image, 1); model.predict(im, &result); + auto end = system_clock::now(); + auto duration = duration_cast(end - start); + total_running_time_s += static_cast(duration.count()) * + microseconds::period::num / + microseconds::period::den; std::cout << "Predict label: " << result.category << ", label_id:" << result.category_id << ", score: " << result.score << std::endl; } - + std::cout << "Total running time: " << total_running_time_s + << " s, average running time: " << total_running_time_s / imgs + << " s/img, total read img time: " << total_imread_time_s + << " s, average read time: " << total_imread_time_s / imgs + << " s/img, batch_size = " << FLAGS_batch_size << std::endl; return 0; } diff --git a/deploy/cpp/demo/detector.cpp b/deploy/cpp/demo/detector.cpp index e42288fbccd434ef5953c606696af623323aa80d..54f93d2995fa24af73bba2855b6b26466129fa20 100644 --- a/deploy/cpp/demo/detector.cpp +++ b/deploy/cpp/demo/detector.cpp @@ -13,15 +13,21 @@ // limitations under the License. #include +#include +#include +#include // NOLINT #include #include #include #include +#include #include "include/paddlex/paddlex.h" #include "include/paddlex/visualize.h" +using namespace std::chrono; // NOLINT + DEFINE_string(model_dir, "", "Path of inference model"); DEFINE_bool(use_gpu, false, "Infering with GPU or CPU"); DEFINE_bool(use_trt, false, "Infering with TensorRT"); @@ -30,6 +36,14 @@ DEFINE_string(key, "", "key of encryption"); DEFINE_string(image, "", "Path of test image file"); DEFINE_string(image_list, "", "Path of test image list file"); DEFINE_string(save_dir, "output", "Path to save visualized image"); +DEFINE_int32(batch_size, 1, "Batch size of infering"); +DEFINE_double(threshold, + 0.5, + "The minimum scores of target boxes which are shown"); +DEFINE_int32(thread_num, + omp_get_num_procs(), + "Number of preprocessing threads"); +DEFINE_bool(use_ir_optim, true, "use ir optimization"); int main(int argc, char** argv) { // 解析命令行参数 @@ -43,11 +57,18 @@ int main(int argc, char** argv) { std::cerr << "--image or --image_list need to be defined" << std::endl; return -1; } - // 加载模型 PaddleX::Model model; - model.Init(FLAGS_model_dir, FLAGS_use_gpu, FLAGS_use_trt, FLAGS_gpu_id, FLAGS_key); + model.Init(FLAGS_model_dir, + FLAGS_use_gpu, + FLAGS_use_trt, + FLAGS_gpu_id, + FLAGS_key, + FLAGS_use_ir_optim); + double total_running_time_s = 0.0; + double total_imread_time_s = 0.0; + int imgs = 1; auto colormap = PaddleX::GenerateColorMap(model.labels.size()); std::string save_dir = "output"; // 进行预测 @@ -58,47 +79,83 @@ int main(int argc, char** argv) { return -1; } std::string image_path; + std::vector image_paths; while (getline(inf, image_path)) { - PaddleX::DetResult result; - cv::Mat im = cv::imread(image_path, 1); - model.predict(im, &result); - for (int i = 0; i < result.boxes.size(); ++i) { - std::cout << "image file: " << image_path - << ", predict label: " << result.boxes[i].category - << ", label_id:" << result.boxes[i].category_id - << ", score: " << result.boxes[i].score << ", box(xmin, ymin, w, h):(" - << result.boxes[i].coordinate[0] << ", " - << result.boxes[i].coordinate[1] << ", " - << result.boxes[i].coordinate[2] << ", " - << result.boxes[i].coordinate[3] << ")" << std::endl; + image_paths.push_back(image_path); + } + imgs = image_paths.size(); + for (int i = 0; i < image_paths.size(); i += FLAGS_batch_size) { + auto start = system_clock::now(); + int im_vec_size = + std::min(static_cast(image_paths.size()), i + FLAGS_batch_size); + std::vector im_vec(im_vec_size - i); + std::vector results(im_vec_size - i, + PaddleX::DetResult()); + int thread_num = std::min(FLAGS_thread_num, im_vec_size - i); + #pragma omp parallel for num_threads(thread_num) + for (int j = i; j < im_vec_size; ++j) { + im_vec[j - i] = std::move(cv::imread(image_paths[j], 1)); + } + auto imread_end = system_clock::now(); + model.predict(im_vec, &results, thread_num); + auto imread_duration = duration_cast(imread_end - start); + total_imread_time_s += static_cast(imread_duration.count()) * + microseconds::period::num / + microseconds::period::den; + auto end = system_clock::now(); + auto duration = duration_cast(end - start); + total_running_time_s += static_cast(duration.count()) * + microseconds::period::num / + microseconds::period::den; + // 输出结果目标框 + for (int j = 0; j < im_vec_size - i; ++j) { + for (int k = 0; k < results[j].boxes.size(); ++k) { + std::cout << "image file: " << image_paths[i + j] << ", "; + std::cout << "predict label: " << results[j].boxes[k].category + << ", label_id:" << results[j].boxes[k].category_id + << ", score: " << results[j].boxes[k].score + << ", box(xmin, ymin, w, h):(" + << results[j].boxes[k].coordinate[0] << ", " + << results[j].boxes[k].coordinate[1] << ", " + << results[j].boxes[k].coordinate[2] << ", " + << results[j].boxes[k].coordinate[3] << ")" << std::endl; + } } - // 可视化 - cv::Mat vis_img = - PaddleX::Visualize(im, result, model.labels, colormap, 0.5); - std::string save_path = - PaddleX::generate_save_path(FLAGS_save_dir, image_path); - cv::imwrite(save_path, vis_img); - result.clear(); - std::cout << "Visualized output saved as " << save_path << std::endl; + for (int j = 0; j < im_vec_size - i; ++j) { + cv::Mat vis_img = PaddleX::Visualize( + im_vec[j], results[j], model.labels, colormap, FLAGS_threshold); + std::string save_path = + PaddleX::generate_save_path(FLAGS_save_dir, image_paths[i + j]); + cv::imwrite(save_path, vis_img); + std::cout << "Visualized output saved as " << save_path << std::endl; + } } } else { + auto start = system_clock::now(); PaddleX::DetResult result; cv::Mat im = cv::imread(FLAGS_image, 1); model.predict(im, &result); + auto end = system_clock::now(); + auto duration = duration_cast(end - start); + total_running_time_s += static_cast(duration.count()) * + microseconds::period::num / + microseconds::period::den; + // 输出结果目标框 for (int i = 0; i < result.boxes.size(); ++i) { + std::cout << "image file: " << FLAGS_image << std::endl; std::cout << ", predict label: " << result.boxes[i].category << ", label_id:" << result.boxes[i].category_id - << ", score: " << result.boxes[i].score << ", box(xmin, ymin, w, h):(" - << result.boxes[i].coordinate[0] << ", " - << result.boxes[i].coordinate[1] << ", " + << ", score: " << result.boxes[i].score + << ", box(xmin, ymin, w, h):(" << result.boxes[i].coordinate[0] + << ", " << result.boxes[i].coordinate[1] << ", " << result.boxes[i].coordinate[2] << ", " << result.boxes[i].coordinate[3] << ")" << std::endl; } // 可视化 cv::Mat vis_img = - PaddleX::Visualize(im, result, model.labels, colormap, 0.5); + PaddleX::Visualize(im, result, model.labels, colormap, FLAGS_threshold); std::string save_path = PaddleX::generate_save_path(FLAGS_save_dir, FLAGS_image); cv::imwrite(save_path, vis_img); @@ -106,5 +163,11 @@ int main(int argc, char** argv) { std::cout << "Visualized output saved as " << save_path << std::endl; } + std::cout << "Total running time: " << total_running_time_s + << " s, average running time: " << total_running_time_s / imgs + << " s/img, total read img time: " << total_imread_time_s + << " s, average read img time: " << total_imread_time_s / imgs + << " s, batch_size = " << FLAGS_batch_size << std::endl; + return 0; } diff --git a/deploy/cpp/demo/segmenter.cpp b/deploy/cpp/demo/segmenter.cpp index 0492ef803e15268022d869eb8b8e84969b1c8fad..90adb5aea860bf5ad9f6cb9019990a188c37f871 100644 --- a/deploy/cpp/demo/segmenter.cpp +++ b/deploy/cpp/demo/segmenter.cpp @@ -13,15 +13,20 @@ // limitations under the License. #include +#include +#include +#include // NOLINT #include #include #include #include - +#include #include "include/paddlex/paddlex.h" #include "include/paddlex/visualize.h" +using namespace std::chrono; // NOLINT + DEFINE_string(model_dir, "", "Path of inference model"); DEFINE_bool(use_gpu, false, "Infering with GPU or CPU"); DEFINE_bool(use_trt, false, "Infering with TensorRT"); @@ -30,6 +35,11 @@ DEFINE_string(key, "", "key of encryption"); DEFINE_string(image, "", "Path of test image file"); DEFINE_string(image_list, "", "Path of test image list file"); DEFINE_string(save_dir, "output", "Path to save visualized image"); +DEFINE_int32(batch_size, 1, "Batch size of infering"); +DEFINE_int32(thread_num, + omp_get_num_procs(), + "Number of preprocessing threads"); +DEFINE_bool(use_ir_optim, false, "use ir optimization"); int main(int argc, char** argv) { // 解析命令行参数 @@ -46,8 +56,16 @@ int main(int argc, char** argv) { // 加载模型 PaddleX::Model model; - model.Init(FLAGS_model_dir, FLAGS_use_gpu, FLAGS_use_trt, FLAGS_gpu_id, FLAGS_key); + model.Init(FLAGS_model_dir, + FLAGS_use_gpu, + FLAGS_use_trt, + FLAGS_gpu_id, + FLAGS_key, + FLAGS_use_ir_optim); + double total_running_time_s = 0.0; + double total_imread_time_s = 0.0; + int imgs = 1; auto colormap = PaddleX::GenerateColorMap(model.labels.size()); // 进行预测 if (FLAGS_image_list != "") { @@ -57,23 +75,54 @@ int main(int argc, char** argv) { return -1; } std::string image_path; + std::vector image_paths; while (getline(inf, image_path)) { - PaddleX::SegResult result; - cv::Mat im = cv::imread(image_path, 1); - model.predict(im, &result); + image_paths.push_back(image_path); + } + imgs = image_paths.size(); + for (int i = 0; i < image_paths.size(); i += FLAGS_batch_size) { + auto start = system_clock::now(); + int im_vec_size = + std::min(static_cast(image_paths.size()), i + FLAGS_batch_size); + std::vector im_vec(im_vec_size - i); + std::vector results(im_vec_size - i, + PaddleX::SegResult()); + int thread_num = std::min(FLAGS_thread_num, im_vec_size - i); + #pragma omp parallel for num_threads(thread_num) + for (int j = i; j < im_vec_size; ++j) { + im_vec[j - i] = std::move(cv::imread(image_paths[j], 1)); + } + auto imread_end = system_clock::now(); + model.predict(im_vec, &results, thread_num); + auto imread_duration = duration_cast(imread_end - start); + total_imread_time_s += static_cast(imread_duration.count()) * + microseconds::period::num / + microseconds::period::den; + auto end = system_clock::now(); + auto duration = duration_cast(end - start); + total_running_time_s += static_cast(duration.count()) * + microseconds::period::num / + microseconds::period::den; // 可视化 - cv::Mat vis_img = - PaddleX::Visualize(im, result, model.labels, colormap); - std::string save_path = - PaddleX::generate_save_path(FLAGS_save_dir, image_path); - cv::imwrite(save_path, vis_img); - result.clear(); - std::cout << "Visualized output saved as " << save_path << std::endl; + for (int j = 0; j < im_vec_size - i; ++j) { + cv::Mat vis_img = + PaddleX::Visualize(im_vec[j], results[j], model.labels, colormap); + std::string save_path = + PaddleX::generate_save_path(FLAGS_save_dir, image_paths[i + j]); + cv::imwrite(save_path, vis_img); + std::cout << "Visualized output saved as " << save_path << std::endl; + } } } else { + auto start = system_clock::now(); PaddleX::SegResult result; cv::Mat im = cv::imread(FLAGS_image, 1); model.predict(im, &result); + auto end = system_clock::now(); + auto duration = duration_cast(end - start); + total_running_time_s += static_cast(duration.count()) * + microseconds::period::num / + microseconds::period::den; // 可视化 cv::Mat vis_img = PaddleX::Visualize(im, result, model.labels, colormap); std::string save_path = @@ -82,6 +131,11 @@ int main(int argc, char** argv) { result.clear(); std::cout << "Visualized output saved as " << save_path << std::endl; } + std::cout << "Total running time: " << total_running_time_s + << " s, average running time: " << total_running_time_s / imgs + << " s/img, total read img time: " << total_imread_time_s + << " s, average read img time: " << total_imread_time_s / imgs + << " s, batch_size = " << FLAGS_batch_size << std::endl; return 0; } diff --git a/deploy/cpp/include/paddlex/config_parser.h b/deploy/cpp/include/paddlex/config_parser.h index 5303e4da7ac0eb3de73bc57059617d361065f136..850e46656d9efdb5374e3086757cb5350f0457b2 100644 --- a/deploy/cpp/include/paddlex/config_parser.h +++ b/deploy/cpp/include/paddlex/config_parser.h @@ -54,4 +54,4 @@ class ConfigPaser { YAML::Node Transforms_; }; -} // namespace PaddleDetection +} // namespace PaddleX diff --git a/deploy/cpp/include/paddlex/paddlex.h b/deploy/cpp/include/paddlex/paddlex.h index d000728c763666e46271d4602b0e42c41dc130f1..e0d0569341198d0a0b2a8c6d0637c3f5a61e1f3f 100644 --- a/deploy/cpp/include/paddlex/paddlex.h +++ b/deploy/cpp/include/paddlex/paddlex.h @@ -16,8 +16,11 @@ #include #include +#include +#include #include - +#include +#include #include "yaml-cpp/yaml.h" #ifdef _WIN32 @@ -28,53 +31,193 @@ #include "paddle_inference_api.h" // NOLINT -#include "config_parser.h" -#include "results.h" -#include "transforms.h" +#include "config_parser.h" // NOLINT +#include "results.h" // NOLINT +#include "transforms.h" // NOLINT #ifdef WITH_ENCRYPTION -#include "paddle_model_decrypt.h" -#include "model_code.h" +#include "paddle_model_decrypt.h" // NOLINT +#include "model_code.h" // NOLINT #endif namespace PaddleX { +/* + * @brief + * This class encapsulates all necessary proccess steps of model infering, which + * include image matrix preprocessing, model predicting and results postprocessing. + * The entire process of model infering can be simplified as below: + * 1. preprocess image matrix (resize, padding, ......) + * 2. model infer + * 3. postprocess the results which generated from model infering + * + * @example + * PaddleX::Model cls_model; + * // initialize model configuration + * cls_model.Init(cls_model_dir, use_gpu, use_trt, gpu_id, encryption_key); + * // define a Classification result object + * PaddleX::ClsResult cls_result; + * // get image matrix from image file + * cv::Mat im = cv::imread(image_file_path, 1); + * cls_model.predict(im, &cls_result); + * */ class Model { public: + /* + * @brief + * This method aims to initialize the model configuration + * + * @param model_dir: the directory which contains model.yml + * @param use_gpu: use gpu or not when infering + * @param use_trt: use Tensor RT or not when infering + * @param gpu_id: the id of gpu when infering with using gpu + * @param key: the key of encryption when using encrypted model + * @param use_ir_optim: use ir optimization when infering + * */ void Init(const std::string& model_dir, bool use_gpu = false, bool use_trt = false, int gpu_id = 0, - std::string key = "") { - create_predictor(model_dir, use_gpu, use_trt, gpu_id, key); + std::string key = "", + bool use_ir_optim = true) { + create_predictor(model_dir, use_gpu, use_trt, gpu_id, key, use_ir_optim); } void create_predictor(const std::string& model_dir, bool use_gpu = false, bool use_trt = false, int gpu_id = 0, - std::string key = ""); - - bool load_config(const std::string& model_dir); - + std::string key = "", + bool use_ir_optim = true); + + /* + * @brief + * This method aims to load model configurations which include + * transform steps and label list + * + * @param yaml_input: model configuration string + * @return true if load configuration successfully + * */ + bool load_config(const std::string& yaml_input); + + /* + * @brief + * This method aims to transform single image matrix, the result will be + * returned at second parameter. + * + * @param input_im: single image matrix to be transformed + * @param blob: the raw data of single image matrix after transformed + * @return true if preprocess image matrix successfully + * */ bool preprocess(const cv::Mat& input_im, ImageBlob* blob); + /* + * @brief + * This method aims to transform mutiple image matrixs, the result will be + * returned at second parameter. + * + * @param input_im_batch: a batch of image matrixs to be transformed + * @param blob_blob: raw data of a batch of image matrixs after transformed + * @param thread_num: the number of preprocessing threads, + * each thread run preprocess on single image matrix + * @return true if preprocess a batch of image matrixs successfully + * */ + bool preprocess(const std::vector &input_im_batch, + std::vector *blob_batch, + int thread_num = 1); + + /* + * @brief + * This method aims to execute classification model prediction on single image matrix, + * the result will be returned at second parameter. + * + * @param im: single image matrix to be predicted + * @param result: classification prediction result data after postprocessed + * @return true if predict successfully + * */ bool predict(const cv::Mat& im, ClsResult* result); + /* + * @brief + * This method aims to execute classification model prediction on a batch of image matrixs, + * the result will be returned at second parameter. + * + * @param im: a batch of image matrixs to be predicted + * @param results: a batch of classification prediction result data after postprocessed + * @param thread_num: the number of predicting threads, each thread run prediction + * on single image matrix + * @return true if predict successfully + * */ + bool predict(const std::vector &im_batch, + std::vector *results, + int thread_num = 1); + + /* + * @brief + * This method aims to execute detection or instance segmentation model prediction + * on single image matrix, the result will be returned at second parameter. + * + * @param im: single image matrix to be predicted + * @param result: detection or instance segmentation prediction result data after postprocessed + * @return true if predict successfully + * */ bool predict(const cv::Mat& im, DetResult* result); + /* + * @brief + * This method aims to execute detection or instance segmentation model prediction + * on a batch of image matrixs, the result will be returned at second parameter. + * + * @param im: a batch of image matrix to be predicted + * @param result: detection or instance segmentation prediction result data after postprocessed + * @param thread_num: the number of predicting threads, each thread run prediction + * on single image matrix + * @return true if predict successfully + * */ + bool predict(const std::vector &im_batch, + std::vector *result, + int thread_num = 1); + + /* + * @brief + * This method aims to execute segmentation model prediction on single image matrix, + * the result will be returned at second parameter. + * + * @param im: single image matrix to be predicted + * @param result: segmentation prediction result data after postprocessed + * @return true if predict successfully + * */ bool predict(const cv::Mat& im, SegResult* result); - bool postprocess(SegResult* result); - - bool postprocess(DetResult* result); - + /* + * @brief + * This method aims to execute segmentation model prediction on a batch of image matrix, + * the result will be returned at second parameter. + * + * @param im: a batch of image matrix to be predicted + * @param result: segmentation prediction result data after postprocessed + * @param thread_num: the number of predicting threads, each thread run prediction + * on single image matrix + * @return true if predict successfully + * */ + bool predict(const std::vector &im_batch, + std::vector *result, + int thread_num = 1); + + // model type, include 3 type: classifier, detector, segmenter std::string type; + // model name, such as FasterRCNN, YOLOV3 and so on. std::string name; std::map labels; + // transform(preprocessing) pipeline manager Transforms transforms_; + // single input preprocessed data ImageBlob inputs_; + // batch input preprocessed data + std::vector inputs_batch_; + // raw data of predicting results std::vector outputs_; + // a predictor which run the model predicting std::unique_ptr predictor_; }; -} // namespce of PaddleX +} // namespace PaddleX diff --git a/deploy/cpp/include/paddlex/results.h b/deploy/cpp/include/paddlex/results.h index 1643c9249e8e8e993017c7702d1d490352c2d9a8..72caa1f5d4f78275ca9c4de55aa89bc22edd02e5 100644 --- a/deploy/cpp/include/paddlex/results.h +++ b/deploy/cpp/include/paddlex/results.h @@ -20,9 +20,15 @@ namespace PaddleX { +/* + * @brief + * This class represents mask in instance segmentation tasks. + * */ template struct Mask { + // raw data of mask std::vector data; + // the shape of mask std::vector shape; void clear() { data.clear(); @@ -30,19 +36,34 @@ struct Mask { } }; +/* + * @brief + * This class represents target box in detection or instance segmentation tasks. + * */ struct Box { int category_id; + // category label this box belongs to std::string category; + // confidence score float score; std::vector coordinate; Mask mask; }; +/* + * @brief + * This class is prediction result based class. + * */ class BaseResult { public: + // model type std::string type = "base"; }; +/* + * @brief + * This class represent classification result. + * */ class ClsResult : public BaseResult { public: int category_id; @@ -51,17 +72,28 @@ class ClsResult : public BaseResult { std::string type = "cls"; }; +/* + * @brief + * This class represent detection or instance segmentation result. + * */ class DetResult : public BaseResult { public: + // target boxes std::vector boxes; int mask_resolution; std::string type = "det"; void clear() { boxes.clear(); } }; +/* + * @brief + * This class represent segmentation result. + * */ class SegResult : public BaseResult { public: + // represent label of each pixel on image matrix Mask label_map; + // represent score of each pixel on image matrix Mask score_map; std::string type = "seg"; void clear() { diff --git a/deploy/cpp/include/paddlex/transforms.h b/deploy/cpp/include/paddlex/transforms.h index f8265db447f693d084c5a789504bc4b0ccc14d28..c1ffd7e1de8a28f88a571e7b9d029585806cf59d 100644 --- a/deploy/cpp/include/paddlex/transforms.h +++ b/deploy/cpp/include/paddlex/transforms.h @@ -28,7 +28,10 @@ namespace PaddleX { -// Object for storing all preprocessed data +/* + * @brief + * This class represents object for storing all preprocessed data + * */ class ImageBlob { public: // Original image height and width @@ -45,21 +48,34 @@ class ImageBlob { std::vector im_data_; void clear() { - ori_im_size_.clear(); - new_im_size_.clear(); im_size_before_resize_.clear(); reshape_order_.clear(); im_data_.clear(); } }; -// Abstraction of preprocessing opration class +/* + * @brief + * Abstraction of preprocessing operation class + * */ class Transform { public: virtual void Init(const YAML::Node& item) = 0; + /* + * @brief + * This method executes preprocessing operation on image matrix, + * result will be returned at second parameter. + * @param im: single image matrix to be preprocessed + * @param data: the raw data of single image matrix after preprocessed + * @return true if transform successfully + * */ virtual bool Run(cv::Mat* im, ImageBlob* data) = 0; }; +/* + * @brief + * This class execute normalization operation on image matrix + * */ class Normalize : public Transform { public: virtual void Init(const YAML::Node& item) { @@ -74,6 +90,14 @@ class Normalize : public Transform { std::vector std_; }; +/* + * @brief + * This class execute resize by short operation on image matrix. At first, it resizes + * the short side of image matrix to specified length. Accordingly, the long side + * will be resized in the same proportion. If new length of long side exceeds max + * size, the long size will be resized to max size, and the short size will be + * resized in the same proportion + * */ class ResizeByShort : public Transform { public: virtual void Init(const YAML::Node& item) { @@ -92,6 +116,12 @@ class ResizeByShort : public Transform { int max_size_; }; +/* + * @brief + * This class execute resize by long operation on image matrix. At first, it resizes + * the long side of image matrix to specified length. Accordingly, the short side + * will be resized in the same proportion. + * */ class ResizeByLong : public Transform { public: virtual void Init(const YAML::Node& item) { @@ -103,13 +133,20 @@ class ResizeByLong : public Transform { int long_size_; }; +/* + * @brief + * This class execute resize operation on image matrix. It resizes width and height + * to specified length. + * */ class Resize : public Transform { public: virtual void Init(const YAML::Node& item) { + if (item["interp"].IsDefined()) { + interp_ = item["interp"].as(); + } if (item["target_size"].IsScalar()) { height_ = item["target_size"].as(); width_ = item["target_size"].as(); - interp_ = item["interp"].as(); } else if (item["target_size"].IsSequence()) { std::vector target_size = item["target_size"].as>(); width_ = target_size[0]; @@ -128,6 +165,11 @@ class Resize : public Transform { std::string interp_; }; +/* + * @brief + * This class execute center crop operation on image matrix. It crops the center + * of image matrix accroding to specified size. + * */ class CenterCrop : public Transform { public: virtual void Init(const YAML::Node& item) { @@ -147,6 +189,11 @@ class CenterCrop : public Transform { int width_; }; +/* + * @brief + * This class execute padding operation on image matrix. It makes border on edge + * of image matrix. + * */ class Padding : public Transform { public: virtual void Init(const YAML::Node& item) { @@ -175,7 +222,11 @@ class Padding : public Transform { int width_ = 0; int height_ = 0; }; - +/* + * @brief + * This class is transform operations manager. It stores all neccessary + * transform operations and run them in correct order. + * */ class Transforms { public: void Init(const YAML::Node& node, bool to_rgb = true); diff --git a/deploy/cpp/include/paddlex/visualize.h b/deploy/cpp/include/paddlex/visualize.h index 7a71f474d028795aa1dec3cd993f5480c0906ced..9ddba5387b427c60645db7c96a54bcba76fa9898 100644 --- a/deploy/cpp/include/paddlex/visualize.h +++ b/deploy/cpp/include/paddlex/visualize.h @@ -22,9 +22,14 @@ #include #else // Linux/Unix #include -#include +// #include +#ifdef __arm__ // for arm +#include +#include +#else #include #include +#endif #include #endif #include @@ -43,20 +48,55 @@ namespace PaddleX { -// Generate visualization colormap for each class +/* + * @brief + * Generate visualization colormap for each class + * + * @param number of class + * @return color map, the size of vector is 3 * num_class + * */ std::vector GenerateColorMap(int num_class); + +/* + * @brief + * Visualize the detection result + * + * @param img: initial image matrix + * @param results: the detection result + * @param labels: label map + * @param colormap: visualization color map + * @return visualized image matrix + * */ cv::Mat Visualize(const cv::Mat& img, const DetResult& results, const std::map& labels, const std::vector& colormap, float threshold = 0.5); +/* + * @brief + * Visualize the segmentation result + * + * @param img: initial image matrix + * @param results: the detection result + * @param labels: label map + * @param colormap: visualization color map + * @return visualized image matrix + * */ cv::Mat Visualize(const cv::Mat& img, const SegResult& result, const std::map& labels, const std::vector& colormap); +/* + * @brief + * generate save path for visualized image matrix + * + * @param save_dir: directory for saving visualized image matrix + * @param file_path: sourcen image file path + * @return path of saving visualized result + * */ std::string generate_save_path(const std::string& save_dir, const std::string& file_path); -} // namespce of PaddleX +} // namespace PaddleX diff --git a/deploy/cpp/scripts/build.sh b/deploy/cpp/scripts/build.sh index 74ab96a32466b6351def8f9abc3275954cdc1e06..e87d7bf4797f1833d88379df0587733958639b06 100644 --- a/deploy/cpp/scripts/build.sh +++ b/deploy/cpp/scripts/build.sh @@ -4,10 +4,10 @@ WITH_GPU=OFF WITH_MKL=ON # 是否集成 TensorRT(仅WITH_GPU=ON 有效) WITH_TENSORRT=OFF -# TensorRT 的路径 -TENSORRT_DIR=/path/to/TensorRT/ -# Paddle 预测库路径 -PADDLE_DIR=/docker/jiangjiajun/PaddleDetection/deploy/cpp/fluid_inference +# TensorRT 的路径,如果需要集成TensorRT,需修改为您实际安装的TensorRT路径 +TENSORRT_DIR=/root/projects/TensorRT/ +# Paddle 预测库路径, 请修改为您实际安装的预测库路径 +PADDLE_DIR=/root/projects/fluid_inference # Paddle 的预测库是否使用静态库来编译 # 使用TensorRT时,Paddle的预测库通常为动态库 WITH_STATIC_LIB=OFF @@ -16,7 +16,7 @@ CUDA_LIB=/usr/local/cuda/lib64 # CUDNN 的 lib 路径 CUDNN_LIB=/usr/local/cuda/lib64 -# 是否加载加密后的模型 +# 是否加载加密后的模型 WITH_ENCRYPTION=ON # 加密工具的路径, 如果使用自带预编译版本可不修改 sh $(pwd)/scripts/bootstrap.sh # 下载预编译版本的加密工具 diff --git a/deploy/cpp/src/paddlex.cpp b/deploy/cpp/src/paddlex.cpp index 6512d9b8dbd3458827821e61e673cb1c2f5706df..23a265b8c64a50f9e2dee3224eccd90232b203ad 100644 --- a/deploy/cpp/src/paddlex.cpp +++ b/deploy/cpp/src/paddlex.cpp @@ -11,32 +11,49 @@ // 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 #include "include/paddlex/paddlex.h" - namespace PaddleX { void Model::create_predictor(const std::string& model_dir, bool use_gpu, bool use_trt, int gpu_id, - std::string key) { - // 读取配置文件 - if (!load_config(model_dir)) { - std::cerr << "Parse file 'model.yml' failed!" << std::endl; - exit(-1); - } + std::string key, + bool use_ir_optim) { paddle::AnalysisConfig config; std::string model_file = model_dir + OS_PATH_SEP + "__model__"; std::string params_file = model_dir + OS_PATH_SEP + "__params__"; + std::string yaml_file = model_dir + OS_PATH_SEP + "model.yml"; + std::string yaml_input = ""; #ifdef WITH_ENCRYPTION if (key != "") { model_file = model_dir + OS_PATH_SEP + "__model__.encrypted"; params_file = model_dir + OS_PATH_SEP + "__params__.encrypted"; + yaml_file = model_dir + OS_PATH_SEP + "model.yml.encrypted"; paddle_security_load_model( - &config, key.c_str(), model_file.c_str(), params_file.c_str()); + &config, key.c_str(), model_file.c_str(), params_file.c_str()); + yaml_input = decrypt_file(yaml_file.c_str(), key.c_str()); } #endif + if (yaml_input == "") { + // 读取配置文件 + std::ifstream yaml_fin(yaml_file); + yaml_fin.seekg(0, std::ios::end); + size_t yaml_file_size = yaml_fin.tellg(); + yaml_input.assign(yaml_file_size, ' '); + yaml_fin.seekg(0); + yaml_fin.read(&yaml_input[0], yaml_file_size); + } + // 读取配置文件内容 + if (!load_config(yaml_input)) { + std::cerr << "Parse file 'model.yml' failed!" << std::endl; + exit(-1); + } + if (key == "") { config.SetModel(model_file, params_file); } @@ -47,6 +64,8 @@ void Model::create_predictor(const std::string& model_dir, } config.SwitchUseFeedFetchOps(false); config.SwitchSpecifyInputNames(true); + // 开启图优化 + config.SwitchIrOptim(use_ir_optim); // 开启内存优化 config.EnableMemoryOptim(); if (use_trt) { @@ -61,19 +80,17 @@ void Model::create_predictor(const std::string& model_dir, predictor_ = std::move(CreatePaddlePredictor(config)); } -bool Model::load_config(const std::string& model_dir) { - std::string yaml_file = model_dir + OS_PATH_SEP + "model.yml"; - YAML::Node config = YAML::LoadFile(yaml_file); +bool Model::load_config(const std::string& yaml_input) { + YAML::Node config = YAML::Load(yaml_input); type = config["_Attributes"]["model_type"].as(); name = config["Model"].as(); std::string version = config["version"].as(); if (version[0] == '0') { - std::cerr << "[Init] Version of the loaded model is lower than 1.0.0," - << " deployment cannot be done, please refer to " - << "https://github.com/PaddlePaddle/PaddleX/blob/develop/" - << "docs/tutorials/deploy/upgrade_version.md " - << "to transfer version." - << std::endl; + std::cerr << "[Init] Version of the loaded model is lower than 1.0.0, " + << "deployment cannot be done, please refer to " + << "https://github.com/PaddlePaddle/PaddleX/blob/develop/docs" + << "/tutorials/deploy/upgrade_version.md " + << "to transfer version." << std::endl; return false; } bool to_rgb = true; @@ -106,17 +123,29 @@ bool Model::preprocess(const cv::Mat& input_im, ImageBlob* blob) { return true; } +// use openmp +bool Model::preprocess(const std::vector& input_im_batch, + std::vector* blob_batch, + int thread_num) { + int batch_size = input_im_batch.size(); + bool success = true; + thread_num = std::min(thread_num, batch_size); + #pragma omp parallel for num_threads(thread_num) + for (int i = 0; i < input_im_batch.size(); ++i) { + cv::Mat im = input_im_batch[i].clone(); + if (!transforms_.Run(&im, &(*blob_batch)[i])) { + success = false; + } + } + return success; +} + bool Model::predict(const cv::Mat& im, ClsResult* result) { inputs_.clear(); if (type == "detector") { std::cerr << "Loading model is a 'detector', DetResult should be passed to " "function predict()!" - << std::endl; - return false; - } else if (type == "segmenter") { - std::cerr << "Loading model is a 'segmenter', SegResult should be passed " - "to function predict()!" - << std::endl; + "to function predict()!" << std::endl; return false; } // 处理输入图像 @@ -146,20 +175,80 @@ bool Model::predict(const cv::Mat& im, ClsResult* result) { result->category_id = std::distance(std::begin(outputs_), ptr); result->score = *ptr; result->category = labels[result->category_id]; + return true; +} + +bool Model::predict(const std::vector& im_batch, + std::vector* results, + int thread_num) { + for (auto& inputs : inputs_batch_) { + inputs.clear(); + } + if (type == "detector") { + std::cerr << "Loading model is a 'detector', DetResult should be passed to " + "function predict()!" << std::endl; + return false; + } else if (type == "segmenter") { + std::cerr << "Loading model is a 'segmenter', SegResult should be passed " + "to function predict()!" << std::endl; + return false; + } + inputs_batch_.assign(im_batch.size(), ImageBlob()); + // 处理输入图像 + if (!preprocess(im_batch, &inputs_batch_, thread_num)) { + std::cerr << "Preprocess failed!" << std::endl; + return false; + } + // 使用加载的模型进行预测 + int batch_size = im_batch.size(); + auto in_tensor = predictor_->GetInputTensor("image"); + int h = inputs_batch_[0].new_im_size_[0]; + int w = inputs_batch_[0].new_im_size_[1]; + in_tensor->Reshape({batch_size, 3, h, w}); + std::vector inputs_data(batch_size * 3 * h * w); + for (int i = 0; i < batch_size; ++i) { + std::copy(inputs_batch_[i].im_data_.begin(), + inputs_batch_[i].im_data_.end(), + inputs_data.begin() + i * 3 * h * w); + } + in_tensor->copy_from_cpu(inputs_data.data()); + // in_tensor->copy_from_cpu(inputs_.im_data_.data()); + predictor_->ZeroCopyRun(); + // 取出模型的输出结果 + auto output_names = predictor_->GetOutputNames(); + auto output_tensor = predictor_->GetOutputTensor(output_names[0]); + std::vector output_shape = output_tensor->shape(); + int size = 1; + for (const auto& i : output_shape) { + size *= i; + } + outputs_.resize(size); + output_tensor->copy_to_cpu(outputs_.data()); + // 对模型输出结果进行后处理 + int single_batch_size = size / batch_size; + for (int i = 0; i < batch_size; ++i) { + auto start_ptr = std::begin(outputs_); + auto end_ptr = std::begin(outputs_); + std::advance(start_ptr, i * single_batch_size); + std::advance(end_ptr, (i + 1) * single_batch_size); + auto ptr = std::max_element(start_ptr, end_ptr); + (*results)[i].category_id = std::distance(start_ptr, ptr); + (*results)[i].score = *ptr; + (*results)[i].category = labels[(*results)[i].category_id]; + } + return true; } bool Model::predict(const cv::Mat& im, DetResult* result) { - result->clear(); inputs_.clear(); + result->clear(); if (type == "classifier") { std::cerr << "Loading model is a 'classifier', ClsResult should be passed " - "to function predict()!" - << std::endl; + "to function predict()!" << std::endl; return false; } else if (type == "segmenter") { std::cerr << "Loading model is a 'segmenter', SegResult should be passed " - "to function predict()!" - << std::endl; + "to function predict()!" << std::endl; return false; } @@ -174,6 +263,7 @@ bool Model::predict(const cv::Mat& im, DetResult* result) { auto im_tensor = predictor_->GetInputTensor("image"); im_tensor->Reshape({1, 3, h, w}); im_tensor->copy_from_cpu(inputs_.im_data_.data()); + if (name == "YOLOv3") { auto im_size_tensor = predictor_->GetInputTensor("im_size"); im_size_tensor->Reshape({1, 2}); @@ -249,6 +339,181 @@ bool Model::predict(const cv::Mat& im, DetResult* result) { static_cast(box->coordinate[3])}; } } + return true; +} + +bool Model::predict(const std::vector& im_batch, + std::vector* result, + int thread_num) { + for (auto& inputs : inputs_batch_) { + inputs.clear(); + } + if (type == "classifier") { + std::cerr << "Loading model is a 'classifier', ClsResult should be passed " + "to function predict()!" << std::endl; + return false; + } else if (type == "segmenter") { + std::cerr << "Loading model is a 'segmenter', SegResult should be passed " + "to function predict()!" << std::endl; + return false; + } + + inputs_batch_.assign(im_batch.size(), ImageBlob()); + int batch_size = im_batch.size(); + // 处理输入图像 + if (!preprocess(im_batch, &inputs_batch_, thread_num)) { + std::cerr << "Preprocess failed!" << std::endl; + return false; + } + // 对RCNN类模型做批量padding + if (batch_size > 1) { + if (name == "FasterRCNN" || name == "MaskRCNN") { + int max_h = -1; + int max_w = -1; + for (int i = 0; i < batch_size; ++i) { + max_h = std::max(max_h, inputs_batch_[i].new_im_size_[0]); + max_w = std::max(max_w, inputs_batch_[i].new_im_size_[1]); + // std::cout << "(" << inputs_batch_[i].new_im_size_[0] + // << ", " << inputs_batch_[i].new_im_size_[1] + // << ")" << std::endl; + } + thread_num = std::min(thread_num, batch_size); + #pragma omp parallel for num_threads(thread_num) + for (int i = 0; i < batch_size; ++i) { + int h = inputs_batch_[i].new_im_size_[0]; + int w = inputs_batch_[i].new_im_size_[1]; + int c = im_batch[i].channels(); + if (max_h != h || max_w != w) { + std::vector temp_buffer(c * max_h * max_w); + float* temp_ptr = temp_buffer.data(); + float* ptr = inputs_batch_[i].im_data_.data(); + for (int cur_channel = c - 1; cur_channel >= 0; --cur_channel) { + int ori_pos = cur_channel * h * w + (h - 1) * w; + int des_pos = cur_channel * max_h * max_w + (h - 1) * max_w; + int last_pos = cur_channel * h * w; + for (; ori_pos >= last_pos; ori_pos -= w, des_pos -= max_w) { + memcpy(temp_ptr + des_pos, ptr + ori_pos, w * sizeof(float)); + } + } + inputs_batch_[i].im_data_.swap(temp_buffer); + inputs_batch_[i].new_im_size_[0] = max_h; + inputs_batch_[i].new_im_size_[1] = max_w; + } + } + } + } + int h = inputs_batch_[0].new_im_size_[0]; + int w = inputs_batch_[0].new_im_size_[1]; + auto im_tensor = predictor_->GetInputTensor("image"); + im_tensor->Reshape({batch_size, 3, h, w}); + std::vector inputs_data(batch_size * 3 * h * w); + for (int i = 0; i < batch_size; ++i) { + std::copy(inputs_batch_[i].im_data_.begin(), + inputs_batch_[i].im_data_.end(), + inputs_data.begin() + i * 3 * h * w); + } + im_tensor->copy_from_cpu(inputs_data.data()); + if (name == "YOLOv3") { + auto im_size_tensor = predictor_->GetInputTensor("im_size"); + im_size_tensor->Reshape({batch_size, 2}); + std::vector inputs_data_size(batch_size * 2); + for (int i = 0; i < batch_size; ++i) { + std::copy(inputs_batch_[i].ori_im_size_.begin(), + inputs_batch_[i].ori_im_size_.end(), + inputs_data_size.begin() + 2 * i); + } + im_size_tensor->copy_from_cpu(inputs_data_size.data()); + } else if (name == "FasterRCNN" || name == "MaskRCNN") { + auto im_info_tensor = predictor_->GetInputTensor("im_info"); + auto im_shape_tensor = predictor_->GetInputTensor("im_shape"); + im_info_tensor->Reshape({batch_size, 3}); + im_shape_tensor->Reshape({batch_size, 3}); + + std::vector im_info(3 * batch_size); + std::vector im_shape(3 * batch_size); + for (int i = 0; i < batch_size; ++i) { + float ori_h = static_cast(inputs_batch_[i].ori_im_size_[0]); + float ori_w = static_cast(inputs_batch_[i].ori_im_size_[1]); + float new_h = static_cast(inputs_batch_[i].new_im_size_[0]); + float new_w = static_cast(inputs_batch_[i].new_im_size_[1]); + im_info[i * 3] = new_h; + im_info[i * 3 + 1] = new_w; + im_info[i * 3 + 2] = inputs_batch_[i].scale; + im_shape[i * 3] = ori_h; + im_shape[i * 3 + 1] = ori_w; + im_shape[i * 3 + 2] = 1.0; + } + im_info_tensor->copy_from_cpu(im_info.data()); + im_shape_tensor->copy_from_cpu(im_shape.data()); + } + // 使用加载的模型进行预测 + predictor_->ZeroCopyRun(); + + // 读取所有box + std::vector output_box; + auto output_names = predictor_->GetOutputNames(); + auto output_box_tensor = predictor_->GetOutputTensor(output_names[0]); + std::vector output_box_shape = output_box_tensor->shape(); + int size = 1; + for (const auto& i : output_box_shape) { + size *= i; + } + output_box.resize(size); + output_box_tensor->copy_to_cpu(output_box.data()); + if (size < 6) { + std::cerr << "[WARNING] There's no object detected." << std::endl; + return true; + } + auto lod_vector = output_box_tensor->lod(); + int num_boxes = size / 6; + // 解析预测框box + for (int i = 0; i < lod_vector[0].size() - 1; ++i) { + for (int j = lod_vector[0][i]; j < lod_vector[0][i + 1]; ++j) { + Box box; + box.category_id = static_cast(round(output_box[j * 6])); + box.category = labels[box.category_id]; + box.score = output_box[j * 6 + 1]; + float xmin = output_box[j * 6 + 2]; + float ymin = output_box[j * 6 + 3]; + float xmax = output_box[j * 6 + 4]; + float ymax = output_box[j * 6 + 5]; + float w = xmax - xmin + 1; + float h = ymax - ymin + 1; + box.coordinate = {xmin, ymin, w, h}; + (*result)[i].boxes.push_back(std::move(box)); + } + } + + // 实例分割需解析mask + if (name == "MaskRCNN") { + std::vector output_mask; + auto output_mask_tensor = predictor_->GetOutputTensor(output_names[1]); + std::vector output_mask_shape = output_mask_tensor->shape(); + int masks_size = 1; + for (const auto& i : output_mask_shape) { + masks_size *= i; + } + int mask_pixels = output_mask_shape[2] * output_mask_shape[3]; + int classes = output_mask_shape[1]; + output_mask.resize(masks_size); + output_mask_tensor->copy_to_cpu(output_mask.data()); + int mask_idx = 0; + for (int i = 0; i < lod_vector[0].size() - 1; ++i) { + (*result)[i].mask_resolution = output_mask_shape[2]; + for (int j = 0; j < (*result)[i].boxes.size(); ++j) { + Box* box = &(*result)[i].boxes[j]; + int category_id = box->category_id; + auto begin_mask = output_mask.begin() + + (mask_idx * classes + category_id) * mask_pixels; + auto end_mask = begin_mask + mask_pixels; + box->mask.data.assign(begin_mask, end_mask); + box->mask.shape = {static_cast(box->coordinate[2]), + static_cast(box->coordinate[3])}; + mask_idx++; + } + } + } + return true; } bool Model::predict(const cv::Mat& im, SegResult* result) { @@ -256,13 +521,11 @@ bool Model::predict(const cv::Mat& im, SegResult* result) { inputs_.clear(); if (type == "classifier") { std::cerr << "Loading model is a 'classifier', ClsResult should be passed " - "to function predict()!" - << std::endl; + "to function predict()!" << std::endl; return false; } else if (type == "detector") { std::cerr << "Loading model is a 'detector', DetResult should be passed to " - "function predict()!" - << std::endl; + "function predict()!" << std::endl; return false; } @@ -290,6 +553,7 @@ bool Model::predict(const cv::Mat& im, SegResult* result) { size *= i; result->label_map.shape.push_back(i); } + result->label_map.data.resize(size); output_label_tensor->copy_to_cpu(result->label_map.data.data()); @@ -301,6 +565,7 @@ bool Model::predict(const cv::Mat& im, SegResult* result) { size *= i; result->score_map.shape.push_back(i); } + result->score_map.data.resize(size); output_score_tensor->copy_to_cpu(result->score_map.data.data()); @@ -327,8 +592,8 @@ bool Model::predict(const cv::Mat& im, SegResult* result) { inputs_.im_size_before_resize_.pop_back(); auto padding_w = before_shape[0]; auto padding_h = before_shape[1]; - mask_label = mask_label(cv::Rect(0, 0, padding_w, padding_h)); - mask_score = mask_score(cv::Rect(0, 0, padding_w, padding_h)); + mask_label = mask_label(cv::Rect(0, 0, padding_h, padding_w)); + mask_score = mask_score(cv::Rect(0, 0, padding_h, padding_w)); } else if (*iter == "resize") { auto before_shape = inputs_.im_size_before_resize_[len_postprocess - idx]; inputs_.im_size_before_resize_.pop_back(); @@ -357,6 +622,156 @@ bool Model::predict(const cv::Mat& im, SegResult* result) { result->score_map.data.assign(mask_score.begin(), mask_score.end()); result->score_map.shape = {mask_score.rows, mask_score.cols}; + return true; +} + +bool Model::predict(const std::vector& im_batch, + std::vector* result, + int thread_num) { + for (auto& inputs : inputs_batch_) { + inputs.clear(); + } + if (type == "classifier") { + std::cerr << "Loading model is a 'classifier', ClsResult should be passed " + "to function predict()!" << std::endl; + return false; + } else if (type == "detector") { + std::cerr << "Loading model is a 'detector', DetResult should be passed to " + "function predict()!" << std::endl; + return false; + } + + // 处理输入图像 + inputs_batch_.assign(im_batch.size(), ImageBlob()); + if (!preprocess(im_batch, &inputs_batch_, thread_num)) { + std::cerr << "Preprocess failed!" << std::endl; + return false; + } + + int batch_size = im_batch.size(); + (*result).clear(); + (*result).resize(batch_size); + int h = inputs_batch_[0].new_im_size_[0]; + int w = inputs_batch_[0].new_im_size_[1]; + auto im_tensor = predictor_->GetInputTensor("image"); + im_tensor->Reshape({batch_size, 3, h, w}); + std::vector inputs_data(batch_size * 3 * h * w); + for (int i = 0; i < batch_size; ++i) { + std::copy(inputs_batch_[i].im_data_.begin(), + inputs_batch_[i].im_data_.end(), + inputs_data.begin() + i * 3 * h * w); + } + im_tensor->copy_from_cpu(inputs_data.data()); + // im_tensor->copy_from_cpu(inputs_.im_data_.data()); + + // 使用加载的模型进行预测 + predictor_->ZeroCopyRun(); + + // 获取预测置信度,经过argmax后的labelmap + auto output_names = predictor_->GetOutputNames(); + auto output_label_tensor = predictor_->GetOutputTensor(output_names[0]); + std::vector output_label_shape = output_label_tensor->shape(); + int size = 1; + for (const auto& i : output_label_shape) { + size *= i; + } + + std::vector output_labels(size, 0); + output_label_tensor->copy_to_cpu(output_labels.data()); + auto output_labels_iter = output_labels.begin(); + + int single_batch_size = size / batch_size; + for (int i = 0; i < batch_size; ++i) { + (*result)[i].label_map.data.resize(single_batch_size); + (*result)[i].label_map.shape.push_back(1); + for (int j = 1; j < output_label_shape.size(); ++j) { + (*result)[i].label_map.shape.push_back(output_label_shape[j]); + } + std::copy(output_labels_iter + i * single_batch_size, + output_labels_iter + (i + 1) * single_batch_size, + (*result)[i].label_map.data.data()); + } + + // 获取预测置信度scoremap + auto output_score_tensor = predictor_->GetOutputTensor(output_names[1]); + std::vector output_score_shape = output_score_tensor->shape(); + size = 1; + for (const auto& i : output_score_shape) { + size *= i; + } + + std::vector output_scores(size, 0); + output_score_tensor->copy_to_cpu(output_scores.data()); + auto output_scores_iter = output_scores.begin(); + + int single_batch_score_size = size / batch_size; + for (int i = 0; i < batch_size; ++i) { + (*result)[i].score_map.data.resize(single_batch_score_size); + (*result)[i].score_map.shape.push_back(1); + for (int j = 1; j < output_score_shape.size(); ++j) { + (*result)[i].score_map.shape.push_back(output_score_shape[j]); + } + std::copy(output_scores_iter + i * single_batch_score_size, + output_scores_iter + (i + 1) * single_batch_score_size, + (*result)[i].score_map.data.data()); + } + + // 解析输出结果到原图大小 + for (int i = 0; i < batch_size; ++i) { + std::vector label_map((*result)[i].label_map.data.begin(), + (*result)[i].label_map.data.end()); + cv::Mat mask_label((*result)[i].label_map.shape[1], + (*result)[i].label_map.shape[2], + CV_8UC1, + label_map.data()); + + cv::Mat mask_score((*result)[i].score_map.shape[2], + (*result)[i].score_map.shape[3], + CV_32FC1, + (*result)[i].score_map.data.data()); + int idx = 1; + int len_postprocess = inputs_batch_[i].im_size_before_resize_.size(); + for (std::vector::reverse_iterator iter = + inputs_batch_[i].reshape_order_.rbegin(); + iter != inputs_batch_[i].reshape_order_.rend(); + ++iter) { + if (*iter == "padding") { + auto before_shape = + inputs_batch_[i].im_size_before_resize_[len_postprocess - idx]; + inputs_batch_[i].im_size_before_resize_.pop_back(); + auto padding_w = before_shape[0]; + auto padding_h = before_shape[1]; + mask_label = mask_label(cv::Rect(0, 0, padding_h, padding_w)); + mask_score = mask_score(cv::Rect(0, 0, padding_h, padding_w)); + } else if (*iter == "resize") { + auto before_shape = + inputs_batch_[i].im_size_before_resize_[len_postprocess - idx]; + inputs_batch_[i].im_size_before_resize_.pop_back(); + auto resize_w = before_shape[0]; + auto resize_h = before_shape[1]; + cv::resize(mask_label, + mask_label, + cv::Size(resize_h, resize_w), + 0, + 0, + cv::INTER_NEAREST); + cv::resize(mask_score, + mask_score, + cv::Size(resize_h, resize_w), + 0, + 0, + cv::INTER_LINEAR); + } + ++idx; + } + (*result)[i].label_map.data.assign(mask_label.begin(), + mask_label.end()); + (*result)[i].label_map.shape = {mask_label.rows, mask_label.cols}; + (*result)[i].score_map.data.assign(mask_score.begin(), + mask_score.end()); + (*result)[i].score_map.shape = {mask_score.rows, mask_score.cols}; + } + return true; } -} // namespce of PaddleX +} // namespace PaddleX diff --git a/deploy/cpp/src/transforms.cpp b/deploy/cpp/src/transforms.cpp index e09f504a1bd653dc3f527a02765af3e9dca02c58..639471eb403c55d3acfa576c0a0ab2de8a296b41 100644 --- a/deploy/cpp/src/transforms.cpp +++ b/deploy/cpp/src/transforms.cpp @@ -95,11 +95,13 @@ bool Padding::Run(cv::Mat* im, ImageBlob* data) { if (width_ > 1 & height_ > 1) { padding_w = width_ - im->cols; padding_h = height_ - im->rows; - } else if (coarsest_stride_ > 1) { + } else if (coarsest_stride_ >= 1) { + int h = im->rows; + int w = im->cols; padding_h = - ceil(im->rows * 1.0 / coarsest_stride_) * coarsest_stride_ - im->rows; + ceil(h * 1.0 / coarsest_stride_) * coarsest_stride_ - im->rows; padding_w = - ceil(im->cols * 1.0 / coarsest_stride_) * coarsest_stride_ - im->cols; + ceil(w * 1.0 / coarsest_stride_) * coarsest_stride_ - im->cols; } if (padding_h < 0 || padding_w < 0) { @@ -221,4 +223,5 @@ bool Transforms::Run(cv::Mat* im, ImageBlob* data) { } return true; } + } // namespace PaddleX diff --git a/deploy/cpp/src/visualize.cpp b/deploy/cpp/src/visualize.cpp index 6ec09fd1c2b7a342ea3d31e784a80033d80f1014..1511887f097e20826f13c8c1f098ceea4efc0b5b 100644 --- a/deploy/cpp/src/visualize.cpp +++ b/deploy/cpp/src/visualize.cpp @@ -145,4 +145,4 @@ std::string generate_save_path(const std::string& save_dir, std::string image_name(file_path.substr(pos + 1)); return save_dir + OS_PATH_SEP + image_name; } -} // namespace of PaddleX +} // namespace PaddleX diff --git a/docs/apis/models/semantic_segmentation.md b/docs/apis/models/semantic_segmentation.md index 26a695a9564f6929ff586eaa179242b99b5466de..3ff66337fe64b35f29a2a7985cea040fcb233d82 100755 --- a/docs/apis/models/semantic_segmentation.md +++ b/docs/apis/models/semantic_segmentation.md @@ -186,10 +186,10 @@ paddlex.seg.HRNet(num_classes=2, width=18, use_bce_loss=False, use_dice_loss=Fal > **参数** > > - **num_classes** (int): 类别数。 -> > - **width** (int): 高分辨率分支中特征层的通道数量。默认值为18。可选择取值为[18, 30, 32, 40, 44, 48, 60, 64]。 +> > - **width** (int|str): 高分辨率分支中特征层的通道数量。默认值为18。可选择取值为[18, 30, 32, 40, 44, 48, 60, 64, '18_small_v1']。'18_small_v1'是18的轻量级版本。 > > - **use_bce_loss** (bool): 是否使用bce loss作为网络的损失函数,只能用于两类分割。可与dice loss同时使用。默认False。 > > - **use_dice_loss** (bool): 是否使用dice loss作为网络的损失函数,只能用于两类分割,可与bce loss同时使用。当use_bce_loss和use_dice_loss都为False时,使用交叉熵损失函数。默认False。 -> > - **class_weight** (list/str): 交叉熵损失函数各类损失的权重。当`class_weight`为list的时候,长度应为`num_classes`。当`class_weight`为str时, weight.lower()应为'dynamic',这时会根据每一轮各类像素的比重自行计算相应的权重,每一类的权重为:每类的比例 * num_classes。class_weight取默认值None是,各类的权重1,即平时使用的交叉熵损失函数。 +> > - **class_weight** (list|str): 交叉熵损失函数各类损失的权重。当`class_weight`为list的时候,长度应为`num_classes`。当`class_weight`为str时, weight.lower()应为'dynamic',这时会根据每一轮各类像素的比重自行计算相应的权重,每一类的权重为:每类的比例 * num_classes。class_weight取默认值None是,各类的权重1,即平时使用的交叉熵损失函数。 > > - **ignore_index** (int): label上忽略的值,label为`ignore_index`的像素不参与损失函数的计算。默认255。 ### train 训练接口 diff --git a/docs/apis/visualize.md b/docs/apis/visualize.md index 8fe45d4abb82c01c859f0be60bf6f52706eb4e52..2cdc96844758128545ffe3a1ebf815476cae1090 100755 --- a/docs/apis/visualize.md +++ b/docs/apis/visualize.md @@ -167,3 +167,20 @@ NormLIME是利用一定数量的样本来出一个全局的解释。由于NormLI ### 使用示例 > 对预测可解释性结果可视化的过程可参见[代码](https://github.com/PaddlePaddle/PaddleX/blob/develop/tutorials/interpret/normlime.py)。 + +## 数据预处理/增强过程可视化 +``` +paddlex.transforms.visualize(dataset, + img_count=3, + save_dir='vdl_output') +``` +对数据预处理/增强中间结果进行可视化。 +可使用VisualDL查看中间结果: +1. VisualDL启动方式: visualdl --logdir vdl_output --port 8001 +2. 浏览器打开 https://0.0.0.0:8001即可, + 其中0.0.0.0为本机访问,如为远程服务, 改成相应机器IP + +### 参数 +>* **dataset** (paddlex.datasets): 数据集读取器。 +>* **img_count** (int): 需要进行数据预处理/增强的图像数目。默认为3。 +>* **save_dir** (str): 日志保存的路径。默认为'vdl_output'。 \ No newline at end of file diff --git a/docs/paddlex_gui/download.md b/docs/paddlex_gui/download.md index 77bb9962b37498ec3279a51cdc1faa34da1f498b..bf5d2ceaeadfc14612d2d83498796108469ae166 100644 --- a/docs/paddlex_gui/download.md +++ b/docs/paddlex_gui/download.md @@ -25,4 +25,3 @@ * **硬盘空间**:建议SSD剩余空间1T以上(非必须) ***注:PaddleX在Windows及Mac OS系统只支持单卡模型。Windows系统暂不支持NCCL。*** - diff --git a/docs/paddlex_gui/how_to_use.md b/docs/paddlex_gui/how_to_use.md index 32740c114242ccc2c6b7ecacc3088ba163fe7a3c..db5e9b1f58b3012e1104a7dfe8ff63394ecf3eee 100644 --- a/docs/paddlex_gui/how_to_use.md +++ b/docs/paddlex_gui/how_to_use.md @@ -42,7 +42,7 @@ PaddleX GUI是一个应用PaddleX实现的一个图形化开发客户端产品 在开始模型训练前,您需要根据不同的任务类型,将数据标注为相应的格式。目前PaddleX支持【图像分类】、【目标检测】、【语义分割】、【实例分割】四种任务类型。不同类型任务的数据处理方式可查看[数据标注方式](https://paddlex.readthedocs.io/zh_CN/latest/appendix/datasets.html)。 - + **第二步:导入我的数据集** @@ -116,26 +116,26 @@ PaddleX GUI是一个应用PaddleX实现的一个图形化开发客户端产品 PaddleX完全采用您本地的硬件进行计算,深度学习任务确实对算力要求较高,为了使您能快速体验应用PaddleX进行开发,我们适配了CPU硬件,但强烈建议您使用GPU以提升训练速度和开发体验。 - + 2. **我可以在服务器或云平台上部署PaddleX么?** PaddleX GUI是一个适配本地单机安装的客户端,无法在服务器上直接进行部署,您可以直接使用PaddleX API,或采用飞桨核心框架进行服务器上的部署。如果您希望使用公有算力,强烈建议您尝试飞桨产品系列中的 [EasyDL](https://ai.baidu.com/easydl/) 或 [AI Studio](https://aistudio.baidu.com/aistudio/index)进行开发。 - + 3. **PaddleX支持EasyData标注的数据吗?** 支持,PaddleX可顺畅读取EasyData标注的数据。但当前版本的PaddleX GUI暂时无法支持直接导入EasyData数据格式,您可以参照文档,将[数据集进行转换](https://paddlex.readthedocs.io/zh_CN/latest/appendix/how_to_convert_dataset.html)再导入PaddleX GUI进行后续开发。 同时,我们也在紧密开发PaddleX GUI可直接导入EasyData数据格式的功能。 - - + + 4. **为什么模型裁剪分析耗时这么长?** 模型裁剪分析过程是对模型各卷积层的敏感度信息进行分析,根据各参数对模型效果的影响进行不同比例的裁剪。此过程需要重复多次直至FLOPS满足要求,最后再进行精调训练获得最终裁剪后的模型,因此耗时较长。有关模型裁剪的原理,可参见文档[剪裁原理介绍](https://paddlepaddle.github.io/PaddleSlim/algo/algo.html#2-%E5%8D%B7%E7%A7%AF%E6%A0%B8%E5%89%AA%E8%A3%81%E5%8E%9F%E7%90%86) - + 5. **如何调用后端代码?** diff --git a/docs/tutorials/datasets.md b/docs/tutorials/datasets.md index b197b43b6c1ce2dd8c91bae3c484573365493ba0..8264d06a91ba1125036d4ab44f1fc06fe11d3049 100755 --- a/docs/tutorials/datasets.md +++ b/docs/tutorials/datasets.md @@ -224,7 +224,7 @@ labelB └--labels.txt # 标签列表文件 ``` -其中,图像文件名应与json文件名一一对应。 +其中,图像文件名应与json文件名一一对应。 每个json文件存储于`labels`相关的信息。如下所示: ``` @@ -269,17 +269,17 @@ labelB └--labels.txt # 标签列表文件 ``` -其中,图像文件名应与json文件名一一对应。 +其中,图像文件名应与json文件名一一对应。 每个json文件存储于`labels`相关的信息。如下所示: ``` -"labels": [{"y1": 18, "x2": 883, "x1": 371, "y2": 404, "name": "labelA", - "mask": "kVfc0`0Zg0 **注意:由于PaddleX代码的持续更新,版本低于1.0.0的模型(模型版本可查看model.yml文件中的version字段)暂时无法直接用于预测部署,参考[模型版本升级](../../upgrade_version.md)对模型版本进行升级。** - -编译成功后,预测demo的可执行程序分别为`build/demo/detector`,`build/demo/classifer`,`build/demo/segmenter`,用户可根据自己的模型类型选择,其主要命令参数说明如下: +编译成功后,预测demo的可执行程序分别为`build/demo/detector`,`build/demo/classifier`,`build/demo/segmenter`,用户可根据自己的模型类型选择,其主要命令参数说明如下: | 参数 | 说明 | | ---- | ---- | @@ -127,34 +124,37 @@ yaml-cpp.zip文件下载后无需解压,在cmake/yaml.cmake中将`URL https:// | image | 要预测的图片文件路径 | | image_list | 按行存储图片路径的.txt文件 | | use_gpu | 是否使用 GPU 预测, 支持值为0或1(默认值为0) | -| use_trt | 是否使用 TensorTr 预测, 支持值为0或1(默认值为0) | +| use_trt | 是否使用 TensorRT 预测, 支持值为0或1(默认值为0) | | gpu_id | GPU 设备ID, 默认值为0 | | save_dir | 保存可视化结果的路径, 默认值为"output",**classfier无该参数** | +| key | 加密过程中产生的密钥信息,默认值为""表示加载的是未加密的模型 | +| batch_size | 预测的批量大小,默认为1 | +| thread_num | 预测的线程数,默认为cpu处理器个数 | ## 样例 -可使用[小度熊识别模型](../deploy_python.html#inference)中导出的`inference_model`和测试图片进行预测。 +可使用[小度熊识别模型](../deploy_python.html#inference)中导出的`inference_model`和测试图片进行预测,导出到/root/projects,模型路径为/root/projects/inference_model。 `样例一`: -不使用`GPU`测试图片 `/path/to/xiaoduxiong.jpeg` +不使用`GPU`测试图片 `/root/projects/images/xiaoduxiong.jpeg` ```shell -./build/demo/detector --model_dir=/path/to/inference_model --image=/path/to/xiaoduxiong.jpeg --save_dir=output +./build/demo/detector --model_dir=/root/projects/inference_model --image=/root/projects/images/xiaoduxiong.jpeg --save_dir=output ``` 图片文件`可视化预测结果`会保存在`save_dir`参数设置的目录下。 `样例二`: -使用`GPU`预测多个图片`/path/to/image_list.txt`,image_list.txt内容的格式如下: +使用`GPU`预测多个图片`/root/projects/image_list.txt`,image_list.txt内容的格式如下: ``` -/path/to/images/xiaoduxiong1.jpeg -/path/to/images/xiaoduxiong2.jpeg +/root/projects/images/xiaoduxiong1.jpeg +/root/projects/images/xiaoduxiong2.jpeg ... -/path/to/images/xiaoduxiongn.jpeg +/root/projects/images/xiaoduxiongn.jpeg ``` ```shell -./build/demo/detector --model_dir=/path/to/models/inference_model --image_list=/root/projects/images_list.txt --use_gpu=1 --save_dir=output +./build/demo/detector --model_dir=/root/projects/inference_model --image_list=/root/projects/images_list.txt --use_gpu=1 --save_dir=output --batch_size=2 --thread_num=2 ``` 图片文件`可视化预测结果`会保存在`save_dir`参数设置的目录下。 diff --git a/docs/tutorials/deploy/deploy_server/deploy_cpp/deploy_cpp_win_vs2019.md b/docs/tutorials/deploy/deploy_server/deploy_cpp/deploy_cpp_win_vs2019.md index a8e9d015865d1108c70ca876c98e83b79c531269..48d936fd8a9e75e668b44db08352eebe1c20b338 100755 --- a/docs/tutorials/deploy/deploy_server/deploy_cpp/deploy_cpp_win_vs2019.md +++ b/docs/tutorials/deploy/deploy_server/deploy_cpp/deploy_cpp_win_vs2019.md @@ -10,11 +10,10 @@ Windows 平台下,我们使用`Visual Studio 2019 Community` 进行了测试 请确保系统已经安装好上述基本软件,我们使用的是`VS2019`的社区版。 -**下面所有示例以工作目录为 `D:\projects`演示**。 +**下面所有示例以工作目录为 `D:\projects`演示。** -### Step1: 下载代码 +### Step1: 下载PaddleX预测代码 -下载源代码 ```shell d: mkdir projects @@ -22,25 +21,24 @@ cd projects git clone https://github.com/PaddlePaddle/PaddleX.git ``` -**说明**:其中`C++`预测代码在`PaddleX/deploy/cpp` 目录,该目录不依赖任何`PaddleX`下其他目录。 +**说明**:其中`C++`预测代码在`PaddleX\deploy\cpp` 目录,该目录不依赖任何`PaddleX`下其他目录。 ### Step2: 下载PaddlePaddle C++ 预测库 fluid_inference -PaddlePaddle C++ 预测库针对不同的`CPU`,`CUDA`,以及是否支持TensorRT,提供了不同的预编译版本,目前PaddleX依赖于Paddle1.7版本,以下提供了多个不同版本的Paddle预测库: +PaddlePaddle C++ 预测库针对是否使用GPU、是否支持TensorRT、以及不同的CUDA版本提供了已经编译好的预测库,目前PaddleX依赖于Paddle 1.8,基于Paddle 1.8的Paddle预测库下载链接如下所示: -| 版本说明 | 预测库(1.7.2版本) | 编译器 | 构建工具| cuDNN | CUDA +| 版本说明 | 预测库(1.8.2版本) | 编译器 | 构建工具| cuDNN | CUDA | | ---- | ---- | ---- | ---- | ---- | ---- | -| cpu_avx_mkl | [fluid_inference.zip](https://paddle-wheel.bj.bcebos.com/1.7.2/win-infer/mkl/cpu/fluid_inference_install_dir.zip) | MSVC 2015 update 3 | CMake v3.16.0 | -| cpu_avx_openblas | [fluid_inference.zip](https://paddle-wheel.bj.bcebos.com/1.7.2/win-infer/open/cpu/fluid_inference_install_dir.zip) | MSVC 2015 update 3 | CMake v3.16.0 | -| cuda9.0_cudnn7_avx_mkl | [fluid_inference.zip](https://paddle-wheel.bj.bcebos.com/1.7.2/win-infer/mkl/post97/fluid_inference_install_dir.zip) | MSVC 2015 update 3 | CMake v3.16.0 | 7.4.1 | 9.0 | -| cuda9.0_cudnn7_avx_openblas | [fluid_inference.zip](https://paddle-wheel.bj.bcebos.com/1.7.2/win-infer/open/post97/fluid_inference_install_dir.zip) | MSVC 2015 update 3 | CMake v3.16.0 | 7.4.1 | 9.0 | -| cuda10.0_cudnn7_avx_mkl | [fluid_inference.zip](https://paddle-wheel.bj.bcebos.com/1.7.2/win-infer/mkl/post107/fluid_inference_install_dir.zip) | MSVC 2015 update 3 | CMake v3.16.0 | 7.5.0 | 10.0 | +| cpu_avx_mkl | [fluid_inference.zip](https://paddle-wheel.bj.bcebos.com/1.8.2/win-infer/mkl/cpu/fluid_inference_install_dir.zip) | MSVC 2015 update 3 | CMake v3.16.0 | +| cpu_avx_openblas | [fluid_inference.zip](https://paddle-wheel.bj.bcebos.com/1.8.2/win-infer/open/cpu/fluid_inference_install_dir.zip) | MSVC 2015 update 3 | CMake v3.16.0 | +| cuda9.0_cudnn7_avx_mkl | [fluid_inference.zip](https://paddle-wheel.bj.bcebos.com/1.8.2/win-infer/mkl/post97/fluid_inference_install_dir.zip) | MSVC 2015 update 3 | CMake v3.16.0 | 7.4.1 | 9.0 | +| cuda9.0_cudnn7_avx_openblas | [fluid_inference.zip](https://paddle-wheel.bj.bcebos.com/1.8.2/win-infer/open/post97/fluid_inference_install_dir.zip) | MSVC 2015 update 3 | CMake v3.16.0 | 7.4.1 | 9.0 | +| cuda10.0_cudnn7_avx_mkl | [fluid_inference.zip](https://paddle-wheel.bj.bcebos.com/1.8.2/win-infer/mkl/post107/fluid_inference_install_dir.zip) | MSVC 2015 update 3 | CMake v3.16.0 | 7.5.0 | 9.0 | +请根据实际情况选择下载,如若以上版本不满足您的需求,请至[C++预测库下载列表](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/advanced_guide/inference_deployment/inference/windows_cpp_inference.html)选择符合的版本。 -更多和更新的版本,请根据实际情况下载: [C++预测库下载列表](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/advanced_guide/inference_deployment/inference/build_and_install_lib_cn.html#id1) - -解压后`D:\projects\fluid_inference*\`目录下主要包含的内容为: +将预测库解压后,其所在目录(例如`D:\projects\fluid_inference\`)下主要包含的内容有: ``` ├── \paddle\ # paddle核心库和头文件 | @@ -51,8 +49,8 @@ PaddlePaddle C++ 预测库针对不同的`CPU`,`CUDA`,以及是否支持Tens ### Step3: 安装配置OpenCV -1. 在OpenCV官网下载适用于Windows平台的3.4.6版本, [下载地址](https://sourceforge.net/projects/opencvlibrary/files/3.4.6/opencv-3.4.6-vc14_vc15.exe/download) -2. 运行下载的可执行文件,将OpenCV解压至指定目录,如`D:\projects\opencv` +1. 在OpenCV官网下载适用于Windows平台的3.4.6版本, [下载地址](https://bj.bcebos.com/paddleseg/deploy/opencv-3.4.6-vc14_vc15.exe) +2. 运行下载的可执行文件,将OpenCV解压至指定目录,例如`D:\projects\opencv` 3. 配置环境变量,如下流程所示 - 我的电脑->属性->高级系统设置->环境变量 - 在系统变量中找到Path(如没有,自行创建),并双击编辑 @@ -63,22 +61,21 @@ PaddlePaddle C++ 预测库针对不同的`CPU`,`CUDA`,以及是否支持Tens 1. 打开Visual Studio 2019 Community,点击`继续但无需代码` ![step2](../../images/vs2019_step1.png) - 2. 点击: `文件`->`打开`->`CMake` ![step2.1](../../images/vs2019_step2.png) -选择项目代码所在路径,并打开`CMakeList.txt`: +选择C++预测代码所在路径(例如`D:\projects\PaddleX\deploy\cpp`),并打开`CMakeList.txt`: ![step2.2](../../images/vs2019_step3.png) - -3. 点击:`项目`->`PADDLEX_INFERENCE的CMake设置` +3. 点击:`项目`->`CMake设置` ![step3](../../images/vs2019_step4.png) - 4. 点击`浏览`,分别设置编译选项指定`CUDA`、`OpenCV`、`Paddle预测库`的路径 -依赖库路径的含义说明如下(带*表示仅在使用**GPU版本**预测库时指定, 其中CUDA库版本尽量对齐,**使用9.0、10.0版本,不使用9.2、10.1等版本CUDA库**): +![step3](../../images/vs2019_step5.png) + +依赖库路径的含义说明如下(带*表示仅在使用**GPU版本**预测库时指定, 其中CUDA库版本尽量与Paddle预测库的对齐,例如Paddle预测库是**使用9.0、10.0版本**编译的,则编译PaddleX预测代码时**不使用9.2、10.1等版本**CUDA库): | 参数名 | 含义 | | ---- | ---- | @@ -87,38 +84,33 @@ PaddlePaddle C++ 预测库针对不同的`CPU`,`CUDA`,以及是否支持Tens | PADDLE_DIR | Paddle c++预测库的路径 | **注意:** -1. 使用`CPU`版预测库,请把`WITH_GPU`的`值`去掉勾 - +1. 如果使用`CPU`版预测库,请把`WITH_GPU`的`值`去掉勾 2. 如果使用的是`openblas`版本,请把`WITH_MKL`的`值`去掉勾 - 3. Windows环境下编译会自动下载YAML,如果编译环境无法访问外网,可手动下载: [yaml-cpp.zip](https://bj.bcebos.com/paddlex/deploy/deps/yaml-cpp.zip) - yaml-cpp.zip文件下载后无需解压,在cmake/yaml.cmake中将`URL https://bj.bcebos.com/paddlex/deploy/deps/yaml-cpp.zip` 中的网址,改为下载文件的路径。 +4. 如果需要使用模型加密功能,需要手动下载[Windows预测模型加密工具](https://bj.bcebos.com/paddlex/tools/win/paddlex-encryption.zip)。例如解压到D:/projects,解压后目录为D:/projects/paddlex-encryption。编译时需勾选WITH_EBNCRYPTION并且在ENCRTYPTION_DIR填入D:/projects/paddlex-encryption。 -![step4](../../images/vs2019_step5.png) +![step_encryption](../../images/vs2019_step_encryption.png) -**设置完成后**, 点击上图中`保存并生成CMake缓存以加载变量`。 +![step4](../../images/vs2019_step6.png) +**设置完成后**, 点击上图中`保存并生成CMake缓存以加载变量`。 5. 点击`生成`->`全部生成` -![step6](../../images/vs2019_step6.png) - +![step6](../../images/vs2019_step7.png) ### Step5: 预测及可视化 - **在加载模型前,请检查你的模型目录中文件应该包括`model.yml`、`__model__`和`__params__`三个文件。如若不满足这个条件,请参考[模型导出为Inference文档](../deploy_python.html#inference)将模型导出为部署格式。** -**注意:由于PaddleX代码的持续更新,版本低于1.0.0的模型(模型版本可查看model.yml文件中的version字段)暂时无法直接用于预测部署,参考[模型版本升级](../../upgrade_version.md)对模型版本进行升级。** - 上述`Visual Studio 2019`编译产出的可执行文件在`out\build\x64-Release`目录下,打开`cmd`,并切换到该目录: ``` -d: +D: cd D:\projects\PaddleX\deploy\cpp\out\build\x64-Release ``` -编译成功后,预测demo的入口程序为`paddlex_inference\detector.exe`,`paddlex_inference\classifer.exe`,`paddlex_inference\segmenter.exe`,用户可根据自己的模型类型选择,其主要命令参数说明如下: +编译成功后,预测demo的入口程序为`paddlex_inference\detector.exe`,`paddlex_inference\classifier.exe`,`paddlex_inference\segmenter.exe`,用户可根据自己的模型类型选择,其主要命令参数说明如下: | 参数 | 说明 | | ---- | ---- | @@ -128,33 +120,45 @@ cd D:\projects\PaddleX\deploy\cpp\out\build\x64-Release | use_gpu | 是否使用 GPU 预测, 支持值为0或1(默认值为0) | | gpu_id | GPU 设备ID, 默认值为0 | | save_dir | 保存可视化结果的路径, 默认值为"output",classfier无该参数 | - +| key | 加密过程中产生的密钥信息,默认值为""表示加载的是未加密的模型 | +| batch_size | 预测的批量大小,默认为1 | +| thread_num | 预测的线程数,默认为cpu处理器个数 | ## 样例 -可使用[小度熊识别模型](../deploy_python.md)中导出的`inference_model`和测试图片进行预测。 +可使用[小度熊识别模型](../deploy_python.md)中导出的`inference_model`和测试图片进行预测, 例如导出到D:\projects,模型路径为D:\projects\inference_model。 -`样例一`: +### 样例一:(使用未加密的模型对单张图像做预测) -不使用`GPU`测试图片 `\\path\\to\\xiaoduxiong.jpeg` +不使用`GPU`测试图片 `D:\images\xiaoduxiong.jpeg` -```shell -.\\paddlex_inference\\detector.exe --model_dir=\\path\\to\\inference_model --image=D:\\images\\xiaoduxiong.jpeg --save_dir=output +``` +.\paddlex_inference\detector.exe --model_dir=D:\projects\inference_model --image=D:\images\xiaoduxiong.jpeg --save_dir=output ``` 图片文件`可视化预测结果`会保存在`save_dir`参数设置的目录下。 -`样例二`: +### 样例二:(使用未加密的模型对图像列表做预测) -使用`GPU`预测多个图片`\\path\\to\\image_list.txt`,image_list.txt内容的格式如下: +使用`GPU`预测多个图片`D:\images\image_list.txt`,image_list.txt内容的格式如下: ``` -\\path\\to\\images\\xiaoduxiong1.jpeg -\\path\\to\\images\\xiaoduxiong2.jpeg +D:\images\xiaoduxiong1.jpeg +D:\images\xiaoduxiong2.jpeg ... -\\path\\to\\images\\xiaoduxiongn.jpeg +D:\images\xiaoduxiongn.jpeg ``` -```shell -.\\paddlex_inference\\detector.exe --model_dir=\\path\\to\\inference_model --image_list=\\path\\to\\images_list.txt --use_gpu=1 --save_dir=output +``` +.\paddlex_inference\detector.exe --model_dir=D:\projects\inference_model --image_list=D:\images\image_list.txt --use_gpu=1 --save_dir=output --batch_size=2 --thread_num=2 ``` 图片文件`可视化预测结果`会保存在`save_dir`参数设置的目录下。 + +### 样例三:(使用加密后的模型对单张图片进行预测) + +如果未对模型进行加密,请参考[加密PaddleX模型](../encryption.html#paddlex)对模型进行加密。例如加密后的模型所在目录为`D:\projects\encrypted_inference_model`。 + +``` +.\paddlex_inference\detector.exe --model_dir=D:\projects\encrypted_inference_model --image=D:\images\xiaoduxiong.jpeg --save_dir=output --key=kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c= +``` + +`--key`传入加密工具输出的密钥,例如`kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c=`, 图片文件可视化预测结果会保存在`save_dir`参数设置的目录下。 diff --git a/docs/tutorials/deploy/deploy_server/encryption.md b/docs/tutorials/deploy/deploy_server/encryption.md index 7090421823bb3bbe2017818a3fc2f7e96608dda9..89eee6b8f1089964834bc0d88d1306f8ac3961ba 100644 --- a/docs/tutorials/deploy/deploy_server/encryption.md +++ b/docs/tutorials/deploy/deploy_server/encryption.md @@ -2,7 +2,7 @@ PaddleX提供一个轻量级的模型加密部署方案,通过PaddleX内置的模型加密工具对推理模型进行加密,预测部署SDK支持直接加载密文模型并完成推理,提升AI模型部署的安全性。 -**注意:目前加密方案仅支持Linux系统** +**目前加密方案已支持Windows,Linux系统** ## 1. 方案简介 @@ -20,7 +20,7 @@ PaddleX提供一个轻量级的模型加密部署方案,通过PaddleX内置的 ![](../images/encryption_process.png) -下面是对提供的C/C++加解密库内部实现的中文描述,参考以下步骤可以实现 一套加解密库 来适应自己的场景并通过内存数据load到paddlepaddle中(c/c++预测服务) +下面是对提供的C/C++加解密库内部实现的中文描述,参考以下步骤可以实现一套加解密库来适应自己的场景并通过内存数据加载到Paddle Inference预测库中 > 1)考虑到加密的模型文件解密后需要从内存加载数据,使用conbine的模式生成模型文件和参数文件。 > @@ -34,15 +34,17 @@ PaddleX提供一个轻量级的模型加密部署方案,通过PaddleX内置的 > > 6)在模型解密环节根据加密后的文件读取相关的加密数据到内存中,对内存数据使用AES算法进行解密,注意解密时需要采用与加密时一致的加密算法和加密的模式,以及密钥的数据和长度,否则会导致解密后数据错误。 > -> 7)集成模型预测的C/C++库,在具体使用paddlepaddle预测时一般涉及paddle::AnalysisConfig和paddle:Predictor,为了能够从内存数据中直接load解密后的模型明文数据(避免模型解密后创建临时文件),这里需要将AnalysisConfig的模型加载函数从SetModel替换为SetModelBuffer来实现从内存中加载模型数据。 +> 7)集成模型预测的C/C++库,在具体使用预测时一般涉及paddle::AnalysisConfig和paddle:Predictor,为了能够从内存数据中直接load解密后的模型明文数据(避免模型解密后创建临时文件),这里需要将AnalysisConfig的模型加载函数从SetModel替换为SetModelBuffer来实现从内存中加载模型数据。 需要注意的是,在本方案中,密钥集成在上层预测服务的代码中。故模型的安全强度等同于代码抵御逆向调试的强度。为了保护密钥和模型的安全,开发者还需对自己的应用进行加固保护。常见的应用加固手段有:代码混淆,二进制文件加壳 等等,亦或将加密机制更改为AES白盒加密技术来保护密钥。这类技术领域内有大量商业和开源产品可供选择,此处不一一赘述。 ### 1.2 加密工具 -[PaddleX模型加密工具](https://bj.bcebos.com/paddlex/tools/paddlex-encryption.zip)。在编译部署代码时,编译脚本会自动下载加密工具,您也可以选择手动下载。 +[Linux版本 PaddleX模型加密工具](https://bj.bcebos.com/paddlex/tools/paddlex-encryption.zip),编译脚本会自动下载该版本加密工具,您也可以选择手动下载。 -加密工具包含内容为: +[Windows版本 PaddleX模型加密工具](https://bj.bcebos.com/paddlex/tools/win/paddlex-encryption.zip),该版本加密工具需手动下载,如果您在使用Visual Studio 2019编译C++预测代码的过程中已经下载过该工具,此处可不必重复下载。 + +Linux加密工具包含内容为: ``` paddlex-encryption ├── include # 头文件:paddle_model_decrypt.h(解密)和paddle_model_encrypt.h(加密) @@ -52,22 +54,40 @@ paddlex-encryption └── tool # paddlex_encrypt_tool ``` +Windows加密工具包含内容为: +``` +paddlex-encryption +├── include # 头文件:paddle_model_decrypt.h(解密)和paddle_model_encrypt.h(加密) +| +├── lib # pmodel-encrypt.dll和pmodel-decrypt.dll动态库 pmodel-encrypt.lib和pmodel-encrypt.lib静态库 +| +└── tool # paddlex_encrypt_tool.exe 模型加密工具 +``` ### 1.3 加密PaddleX模型 对模型完成加密后,加密工具会产生随机密钥信息(用于AES加解密使用),需要在后续加密部署时传入该密钥来用于解密。 > 密钥由32字节key + 16字节iv组成, 注意这里产生的key是经过base64编码后的,这样可以扩充key的选取范围 +Linux平台: ``` -./paddlex-encryption/tool/paddlex_encrypt_tool -model_dir /path/to/paddlex_inference_model -save_dir /path/to/paddlex_encrypted_model +# 假设模型在/root/projects下 +./paddlex-encryption/tool/paddlex_encrypt_tool -model_dir /root/projects/paddlex_inference_model -save_dir /root/projects/paddlex_encrypted_model ``` -`-model_dir`用于指定inference模型路径(参考[导出inference模型](deploy_python.html#inference)将模型导出为inference格式模型),可使用[导出小度熊识别模型](deploy_python.html#inference)中导出的`inference_model`(**注意**:由于PaddleX代码的持续更新,版本低于1.0.0的模型暂时无法直接用于预测部署,参考[模型版本升级](../upgrade_version.md)对模型版本进行升级。)。加密完成后,加密过的模型会保存至指定的`-save_dir`下,包含`__model__.encrypted`、`__params__.encrypted`和`model.yml`三个文件,同时生成密钥信息,命令输出如下图所示,密钥为`kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c=` +Windows平台: +``` +# 假设模型在D:/projects下 +.\paddlex-encryption\tool\paddlex_encrypt_tool.exe -model_dir D:\projects\paddlex_inference_model -save_dir D:\projects\paddlex_encrypted_model +``` + +`-model_dir`用于指定inference模型路径(参考[导出inference模型](deploy_python.html#inference)将模型导出为inference格式模型),可使用[导出小度熊识别模型](deploy_python.html#inference)中导出的`inference_model`。加密完成后,加密过的模型会保存至指定的`-save_dir`下,包含`__model__.encrypted`、`__params__.encrypted`和`model.yml`三个文件,同时生成密钥信息,命令输出如下图所示,密钥为`kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c=` ![](../images/encrypt.png) ## 2. PaddleX C++加密部署 -参考[Linux平台编译指南](deploy_cpp/deploy_cpp_linux.html#linux)编译C++部署代码。编译成功后,预测demo的可执行程序分别为`build/demo/detector`,`build/demo/classifer`,`build/demo/segmenter`,用户可根据自己的模型类型选择,其主要命令参数说明如下: +### 2.1 Linux平台使用 +参考[Linux平台编译指南](deploy_cpp/deploy_cpp_linux.md)编译C++部署代码。编译成功后,预测demo的可执行程序分别为`build/demo/detector`,`build/demo/classifier`,`build/demo/segmenter`,用户可根据自己的模型类型选择,其主要命令参数说明如下: | 参数 | 说明 | | ---- | ---- | @@ -75,36 +95,72 @@ paddlex-encryption | image | 要预测的图片文件路径 | | image_list | 按行存储图片路径的.txt文件 | | use_gpu | 是否使用 GPU 预测, 支持值为0或1(默认值为0) | -| use_trt | 是否使用 TensorTr 预测, 支持值为0或1(默认值为0) | +| use_trt | 是否使用 TensorRT 预测, 支持值为0或1(默认值为0) | | gpu_id | GPU 设备ID, 默认值为0 | | save_dir | 保存可视化结果的路径, 默认值为"output",classifier无该参数 | | key | 加密过程中产生的密钥信息,默认值为""表示加载的是未加密的模型 | +| batch_size | 预测的批量大小,默认为1 | +| thread_num | 预测的线程数,默认为cpu处理器个数 | -## 样例 +### 样例 -可使用[导出小度熊识别模型](deploy_python.html#inference)中的测试图片进行预测。 +可使用[导出小度熊识别模型](deploy_python.md#inference)中的测试图片进行预测。 -`样例一`: +#### 样例一: -不使用`GPU`测试图片 `/path/to/xiaoduxiong.jpeg` +不使用`GPU`测试图片 `/root/projects/images/xiaoduxiong.jpeg` ```shell -./build/demo/detector --model_dir=/path/to/inference_model --image=/path/to/xiaoduxiong.jpeg --save_dir=output --key=kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c= +./build/demo/detector --model_dir=/root/projects/paddlex_encrypted_model --image=/root/projects/xiaoduxiong.jpeg --save_dir=output --key=kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c= ``` `--key`传入加密工具输出的密钥,例如`kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c=`, 图片文件`可视化预测结果`会保存在`save_dir`参数设置的目录下。 -`样例二`: +#### 样例二: -使用`GPU`预测多个图片`/path/to/image_list.txt`,image_list.txt内容的格式如下: +使用`GPU`预测多个图片`/root/projects/image_list.txt`,image_list.txt内容的格式如下: ``` -/path/to/images/xiaoduxiong1.jpeg -/path/to/images/xiaoduxiong2.jpeg +/root/projects/images/xiaoduxiong1.jpeg +/root/projects/xiaoduxiong2.jpeg ... -/path/to/images/xiaoduxiongn.jpeg +/root/projects/xiaoduxiongn.jpeg +``` +```shell +./build/demo/detector --model_dir=/root/projects/models/paddlex_encrypted_model --image_list=/root/projects/images_list.txt --use_gpu=1 --save_dir=output --key=kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c= ``` +`--key`传入加密工具输出的密钥,例如`kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c=`, 图片文件`可视化预测结果`会保存在`save_dir`参数设置的目录下。 + +### 2.2 Windows平台使用 +参考[Windows平台编译指南](deploy_cpp/deploy_cpp_win_vs2019.md)。需自行下载Windows版PaddleX加密工具压缩包,解压,在编译指南的编译流程基础上,在CMake设置中勾选WITH_ENCRYPTION,ENCRYPTION_DIR填写为加密工具包解压后的目录,再进行编译。参数与Linux版本预测部署一致。预测demo的入口程序为paddlex_inference\detector.exe,paddlex_inference\classifier.exe,paddlex_inference\segmenter.exe。 + +### 样例 + +可使用[导出小度熊识别模型](deploy_python.md#inference)中的测试图片进行预测。 + +#### 样例一: + +不使用`GPU`测试单张图片,例如图片为`D:\images\xiaoduxiong.jpeg`,加密后的模型目录为`D:\projects\paddlex_encrypted_model` + ```shell -./build/demo/detector --model_dir=/path/to/models/inference_model --image_list=/root/projects/images_list.txt --use_gpu=1 --save_dir=output --key=kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c= +.\paddlex_inference\detector.exe --model_dir=D:\projects\paddlex_encrypted_model --image=D:\images\xiaoduxiong.jpeg --save_dir=output --key=kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c= +``` +`--key`传入加密工具输出的密钥,例如`kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c=`, 图片文件`可视化预测结果`会保存在`save_dir`参数设置的目录下。 + + +#### 样例二: + +使用`GPU`预测图片列表,例如图片列表为`D:\projects\image_list.txt`,`image_list.txt`的内容如下: +``` +D:\projects\images\xiaoduxiong1.jpeg +D:\projects\images\xiaoduxiong2.jpeg +... +D:\projects\images\xiaoduxiongn.jpeg +``` + +加密后的模型目录例如为`D:\projects\paddlex_encrypted_model` + +``` +.\paddlex_inference\detector.exe --model_dir=D:\projects\paddlex_encrypted_model --image_list=D:\projects\images_list.txt --use_gpu=1 --save_dir=output --key=kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c= ``` `--key`传入加密工具输出的密钥,例如`kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c=`, 图片文件`可视化预测结果`会保存在`save_dir`参数设置的目录下。 diff --git a/docs/tutorials/deploy/images/vs2019_step4.png b/docs/tutorials/deploy/images/vs2019_step4.png index 74fe7b224dc9b3533066111ab2a9cd877a5bbc68..9df86c9824f5fde45bc6df440ddc813742ce460f 100644 Binary files a/docs/tutorials/deploy/images/vs2019_step4.png and b/docs/tutorials/deploy/images/vs2019_step4.png differ diff --git a/docs/tutorials/deploy/images/vs2019_step5.png b/docs/tutorials/deploy/images/vs2019_step5.png old mode 100755 new mode 100644 index 0986e823812e2316c4fd0f2e6cb260a1204fda40..e5349cf08b209561b5cd11e3f5b68e4aa6c6e295 Binary files a/docs/tutorials/deploy/images/vs2019_step5.png and b/docs/tutorials/deploy/images/vs2019_step5.png differ diff --git a/docs/tutorials/deploy/images/vs2019_step6.png b/docs/tutorials/deploy/images/vs2019_step6.png index 86a8039cbd2a9f8fb499ed72d386b5c02b30c86c..0986e823812e2316c4fd0f2e6cb260a1204fda40 100755 Binary files a/docs/tutorials/deploy/images/vs2019_step6.png and b/docs/tutorials/deploy/images/vs2019_step6.png differ diff --git a/docs/tutorials/deploy/images/vs2019_step7.png b/docs/tutorials/deploy/images/vs2019_step7.png new file mode 100755 index 0000000000000000000000000000000000000000..86a8039cbd2a9f8fb499ed72d386b5c02b30c86c Binary files /dev/null and b/docs/tutorials/deploy/images/vs2019_step7.png differ diff --git a/docs/tutorials/deploy/images/vs2019_step_encryption.png b/docs/tutorials/deploy/images/vs2019_step_encryption.png new file mode 100644 index 0000000000000000000000000000000000000000..27a606799363b8b0f383ebd06f86a9a20e133ce9 Binary files /dev/null and b/docs/tutorials/deploy/images/vs2019_step_encryption.png differ diff --git a/docs/tutorials/deploy/upgrade_version.md b/docs/tutorials/deploy/upgrade_version.md index aac33928448d75bf6965fbadbd4ff114e6156196..3fbe92026593b0f0deb39f0e5b6cd2baa4b953b2 100644 --- a/docs/tutorials/deploy/upgrade_version.md +++ b/docs/tutorials/deploy/upgrade_version.md @@ -11,4 +11,4 @@ ``` paddlex --export_inference --model_dir=/path/to/low_version_model --save_dir=SSpath/to/high_version_model ``` -`--model_dir`为版本号小于1.0.0的模型路径,可以是PaddleX训练过程保存的模型,也可以是导出为inference格式的模型。`--save_dir`为转换为高版本的模型,后续可用于多端部署。 \ No newline at end of file +`--model_dir`为版本号小于1.0.0的模型路径,可以是PaddleX训练过程保存的模型,也可以是导出为inference格式的模型。`--save_dir`为转换为高版本的模型,后续可用于多端部署。 diff --git a/examples/human_segmentation/README.md b/examples/human_segmentation/README.md new file mode 100644 index 0000000000000000000000000000000000000000..18d1f22f3b48979602028e13d1045b63991794b8 --- /dev/null +++ b/examples/human_segmentation/README.md @@ -0,0 +1,181 @@ +# HumanSeg人像分割模型 + +本教程基于PaddleX核心分割网络,提供针对人像分割场景从预训练模型、Fine-tune、视频分割预测部署的全流程应用指南。 + +## 安装 + +**前置依赖** +* paddlepaddle >= 1.8.0 +* python >= 3.5 + +``` +pip install paddlex -i https://mirror.baidu.com/pypi/simple +``` +安装的相关问题参考[PaddleX安装](https://paddlex.readthedocs.io/zh_CN/latest/install.html) + +## 预训练模型 +HumanSeg开放了在大规模人像数据上训练的两个预训练模型,满足多种使用场景的需求 + +| 模型类型 | Checkpoint Parameter | Inference Model | Quant Inference Model | 备注 | +| --- | --- | --- | ---| --- | +| HumanSeg-server | [humanseg_server_params](https://paddlex.bj.bcebos.com/humanseg/models/humanseg_server.pdparams) | [humanseg_server_inference](https://paddlex.bj.bcebos.com/humanseg/models/humanseg_server_inference.zip) | -- | 高精度模型,适用于服务端GPU且背景复杂的人像场景, 模型结构为Deeplabv3+/Xcetion65, 输入大小(512, 512) | +| HumanSeg-mobile | [humanseg_mobile_params](https://paddlex.bj.bcebos.com/humanseg/models/humanseg_mobile.pdparams) | [humanseg_mobile_inference](https://paddlex.bj.bcebos.com/humanseg/models/humanseg_mobile_inference.zip) | [humanseg_mobile_quant](https://paddlex.bj.bcebos.com/humanseg/models/humanseg_mobile_quant.zip) | 轻量级模型, 适用于移动端或服务端CPU的前置摄像头场景,模型结构为HRNet_w18_samll_v1,输入大小(192, 192) | + + +模型性能 + +| 模型 | 模型大小 | 计算耗时 | +| --- | --- | --- | +|humanseg_server_inference| 158M | - | +|humanseg_mobile_inference | 5.8 M | 42.35ms | +|humanseg_mobile_quant | 1.6M | 24.93ms | + +计算耗时运行环境: 小米,cpu:骁龙855, 内存:6GB, 图片大小:192*192 + + +**NOTE:** +其中Checkpoint Parameter为模型权重,用于Fine-tuning场景。 + +* Inference Model和Quant Inference Model为预测部署模型,包含`__model__`计算图结构、`__params__`模型参数和`model.yaml`基础的模型配置信息。 + +* 其中Inference Model适用于服务端的CPU和GPU预测部署,Qunat Inference Model为量化版本,适用于通过Paddle Lite进行移动端等端侧设备部署。 + +执行以下脚本进行HumanSeg预训练模型的下载 +```bash +python pretrain_weights/download_pretrain_weights.py +``` + +## 下载测试数据 +我们提供了[supervise.ly](https://supervise.ly/)发布人像分割数据集**Supervisely Persons**, 从中随机抽取一小部分并转化成PaddleX可直接加载数据格式。通过运行以下代码进行快速下载,其中包含手机前置摄像头的人像测试视频`video_test.mp4`. + +```bash +python data/download_data.py +``` + +## 快速体验视频流人像分割 +结合DIS(Dense Inverse Search-basedmethod)光流算法预测结果与分割结果,改善视频流人像分割 +```bash +# 通过电脑摄像头进行实时分割处理 +python video_infer.py --model_dir pretrain_weights/humanseg_mobile_inference + +# 对人像视频进行分割处理 +python video_infer.py --model_dir pretrain_weights/humanseg_mobile_inference --video_path data/video_test.mp4 +``` + +视频分割结果如下: + + + +根据所选背景进行背景替换,背景可以是一张图片,也可以是一段视频。 +```bash +# 通过电脑摄像头进行实时背景替换处理, 也可通过'--background_video_path'传入背景视频 +python bg_replace.py --model_dir pretrain_weights/humanseg_mobile_inference --background_image_path data/background.jpg + +# 对人像视频进行背景替换处理, 也可通过'--background_video_path'传入背景视频 +python bg_replace.py --model_dir pretrain_weights/humanseg_mobile_inference --video_path data/video_test.mp4 --background_image_path data/background.jpg + +# 对单张图像进行背景替换 +python bg_replace.py --model_dir pretrain_weights/humanseg_mobile_inference --image_path data/human_image.jpg --background_image_path data/background.jpg + +``` + +背景替换结果如下: + + + + +**NOTE**: + +视频分割处理时间需要几分钟,请耐心等待。 + +提供的模型适用于手机摄像头竖屏拍摄场景,宽屏效果会略差一些。 + +## 训练 +使用下述命令基于与训练模型进行Fine-tuning,请确保选用的模型结构`model_type`与模型参数`pretrain_weights`匹配。 +```bash +# 指定GPU卡号(以0号卡为例) +export CUDA_VISIBLE_DEVICES=0 +# 若不使用GPU,则将CUDA_VISIBLE_DEVICES指定为空 +# export CUDA_VISIBLE_DEVICES= +python train.py --model_type HumanSegMobile \ +--save_dir output/ \ +--data_dir data/mini_supervisely \ +--train_list data/mini_supervisely/train.txt \ +--val_list data/mini_supervisely/val.txt \ +--pretrain_weights pretrain_weights/humanseg_mobile_params \ +--batch_size 8 \ +--learning_rate 0.001 \ +--num_epochs 10 \ +--image_shape 192 192 +``` +其中参数含义如下: +* `--model_type`: 模型类型,可选项为:HumanSegServer和HumanSegMobile +* `--save_dir`: 模型保存路径 +* `--data_dir`: 数据集路径 +* `--train_list`: 训练集列表路径 +* `--val_list`: 验证集列表路径 +* `--pretrain_weights`: 预训练模型路径 +* `--batch_size`: 批大小 +* `--learning_rate`: 初始学习率 +* `--num_epochs`: 训练轮数 +* `--image_shape`: 网络输入图像大小(w, h) + +更多命令行帮助可运行下述命令进行查看: +```bash +python train.py --help +``` +**NOTE** +可通过更换`--model_type`变量与对应的`--pretrain_weights`使用不同的模型快速尝试。 + +## 评估 +使用下述命令进行评估 +```bash +python eval.py --model_dir output/best_model \ +--data_dir data/mini_supervisely \ +--val_list data/mini_supervisely/val.txt \ +--image_shape 192 192 +``` +其中参数含义如下: +* `--model_dir`: 模型路径 +* `--data_dir`: 数据集路径 +* `--val_list`: 验证集列表路径 +* `--image_shape`: 网络输入图像大小(w, h) + +## 预测 +使用下述命令进行预测, 预测结果默认保存在`./output/result/`文件夹中。 +```bash +python infer.py --model_dir output/best_model \ +--data_dir data/mini_supervisely \ +--test_list data/mini_supervisely/test.txt \ +--save_dir output/result \ +--image_shape 192 192 +``` +其中参数含义如下: +* `--model_dir`: 模型路径 +* `--data_dir`: 数据集路径 +* `--test_list`: 测试集列表路径 +* `--image_shape`: 网络输入图像大小(w, h) + +## 模型导出 +```bash +paddlex --export_inference --model_dir output/best_model \ +--save_dir output/export +``` +其中参数含义如下: +* `--model_dir`: 模型路径 +* `--save_dir`: 导出模型保存路径 + +## 离线量化 +```bash +python quant_offline.py --model_dir output/best_model \ +--data_dir data/mini_supervisely \ +--quant_list data/mini_supervisely/val.txt \ +--save_dir output/quant_offline \ +--image_shape 192 192 +``` +其中参数含义如下: +* `--model_dir`: 待量化模型路径 +* `--data_dir`: 数据集路径 +* `--quant_list`: 量化数据集列表路径,一般直接选择训练集或验证集 +* `--save_dir`: 量化模型保存路径 +* `--image_shape`: 网络输入图像大小(w, h) diff --git a/examples/human_segmentation/bg_replace.py b/examples/human_segmentation/bg_replace.py new file mode 100644 index 0000000000000000000000000000000000000000..e0c1cc4261f0c946aaf07c11b5c4f6d1c21f6dca --- /dev/null +++ b/examples/human_segmentation/bg_replace.py @@ -0,0 +1,314 @@ +# coding: utf8 +# 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. + +import argparse +import os +import os.path as osp +import cv2 +import numpy as np + +from postprocess import postprocess, threshold_mask +import paddlex as pdx +import paddlex.utils.logging as logging +from paddlex.seg import transforms + + +def parse_args(): + parser = argparse.ArgumentParser( + description='HumanSeg inference for video') + parser.add_argument( + '--model_dir', + dest='model_dir', + help='Model path for inference', + type=str) + parser.add_argument( + '--image_path', + dest='image_path', + help='Image including human', + type=str, + default=None) + parser.add_argument( + '--background_image_path', + dest='background_image_path', + help='Background image for replacing', + type=str, + default=None) + parser.add_argument( + '--video_path', + dest='video_path', + help='Video path for inference', + type=str, + default=None) + parser.add_argument( + '--background_video_path', + dest='background_video_path', + help='Background video path for replacing', + type=str, + default=None) + parser.add_argument( + '--save_dir', + dest='save_dir', + help='The directory for saving the inference results', + type=str, + default='./output') + parser.add_argument( + "--image_shape", + dest="image_shape", + help="The image shape for net inputs.", + nargs=2, + default=[192, 192], + type=int) + + return parser.parse_args() + + +def bg_replace(label_map, img, bg): + h, w, _ = img.shape + bg = cv2.resize(bg, (w, h)) + label_map = np.repeat(label_map[:, :, np.newaxis], 3, axis=2) + comb = (label_map * img + (1 - label_map) * bg).astype(np.uint8) + return comb + + +def recover(img, im_info): + if im_info[0] == 'resize': + w, h = im_info[1][1], im_info[1][0] + img = cv2.resize(img, (w, h), cv2.INTER_LINEAR) + elif im_info[0] == 'padding': + w, h = im_info[1][0], im_info[1][0] + img = img[0:h, 0:w, :] + return img + + +def infer(args): + resize_h = args.image_shape[1] + resize_w = args.image_shape[0] + + test_transforms = transforms.Compose([transforms.Normalize()]) + model = pdx.load_model(args.model_dir) + + if not osp.exists(args.save_dir): + os.makedirs(args.save_dir) + + # 图像背景替换 + if args.image_path is not None: + if not osp.exists(args.image_path): + raise Exception('The --image_path is not existed: {}'.format( + args.image_path)) + if args.background_image_path is None: + raise Exception( + 'The --background_image_path is not set. Please set it') + else: + if not osp.exists(args.background_image_path): + raise Exception( + 'The --background_image_path is not existed: {}'.format( + args.background_image_path)) + + img = cv2.imread(args.image_path) + im_shape = img.shape + im_scale_x = float(resize_w) / float(im_shape[1]) + im_scale_y = float(resize_h) / float(im_shape[0]) + im = cv2.resize( + img, + None, + None, + fx=im_scale_x, + fy=im_scale_y, + interpolation=cv2.INTER_LINEAR) + image = im.astype('float32') + im_info = ('resize', im_shape[0:2]) + pred = model.predict(image, test_transforms) + label_map = pred['label_map'] + label_map = recover(label_map, im_info) + bg = cv2.imread(args.background_image_path) + save_name = osp.basename(args.image_path) + save_path = osp.join(args.save_dir, save_name) + result = bg_replace(label_map, img, bg) + cv2.imwrite(save_path, result) + + # 视频背景替换,如果提供背景视频则以背景视频作为背景,否则采用提供的背景图片 + else: + is_video_bg = False + if args.background_video_path is not None: + if not osp.exists(args.background_video_path): + raise Exception( + 'The --background_video_path is not existed: {}'.format( + args.background_video_path)) + is_video_bg = True + elif args.background_image_path is not None: + if not osp.exists(args.background_image_path): + raise Exception( + 'The --background_image_path is not existed: {}'.format( + args.background_image_path)) + else: + raise Exception( + 'Please offer backgound image or video. You should set --backbground_iamge_paht or --background_video_path' + ) + + disflow = cv2.DISOpticalFlow_create( + cv2.DISOPTICAL_FLOW_PRESET_ULTRAFAST) + prev_gray = np.zeros((resize_h, resize_w), np.uint8) + prev_cfd = np.zeros((resize_h, resize_w), np.float32) + is_init = True + if args.video_path is not None: + logging.info('Please wait. It is computing......') + if not osp.exists(args.video_path): + raise Exception('The --video_path is not existed: {}'.format( + args.video_path)) + + cap_video = cv2.VideoCapture(args.video_path) + fps = cap_video.get(cv2.CAP_PROP_FPS) + width = int(cap_video.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(cap_video.get(cv2.CAP_PROP_FRAME_HEIGHT)) + save_name = osp.basename(args.video_path) + save_name = save_name.split('.')[0] + save_path = osp.join(args.save_dir, save_name + '.avi') + + cap_out = cv2.VideoWriter( + save_path, + cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'), fps, + (width, height)) + + if is_video_bg: + cap_bg = cv2.VideoCapture(args.background_video_path) + frames_bg = cap_bg.get(cv2.CAP_PROP_FRAME_COUNT) + current_frame_bg = 1 + else: + img_bg = cv2.imread(args.background_image_path) + while cap_video.isOpened(): + ret, frame = cap_video.read() + if ret: + im_shape = frame.shape + im_scale_x = float(resize_w) / float(im_shape[1]) + im_scale_y = float(resize_h) / float(im_shape[0]) + im = cv2.resize( + frame, + None, + None, + fx=im_scale_x, + fy=im_scale_y, + interpolation=cv2.INTER_LINEAR) + image = im.astype('float32') + im_info = ('resize', im_shape[0:2]) + pred = model.predict(image, test_transforms) + score_map = pred['score_map'] + cur_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) + cur_gray = cv2.resize(cur_gray, (resize_w, resize_h)) + score_map = 255 * score_map[:, :, 1] + optflow_map = postprocess(cur_gray, score_map, prev_gray, prev_cfd, \ + disflow, is_init) + prev_gray = cur_gray.copy() + prev_cfd = optflow_map.copy() + is_init = False + optflow_map = cv2.GaussianBlur(optflow_map, (3, 3), 0) + optflow_map = threshold_mask( + optflow_map, thresh_bg=0.2, thresh_fg=0.8) + score_map = recover(optflow_map, im_info) + + #循环读取背景帧 + if is_video_bg: + ret_bg, frame_bg = cap_bg.read() + if ret_bg: + if current_frame_bg == frames_bg: + current_frame_bg = 1 + cap_bg.set(cv2.CAP_PROP_POS_FRAMES, 0) + else: + break + current_frame_bg += 1 + comb = bg_replace(score_map, frame, frame_bg) + else: + comb = bg_replace(score_map, frame, img_bg) + + cap_out.write(comb) + else: + break + + if is_video_bg: + cap_bg.release() + cap_video.release() + cap_out.release() + + # 当没有输入预测图像和视频的时候,则打开摄像头 + else: + cap_video = cv2.VideoCapture(0) + if not cap_video.isOpened(): + raise IOError("Error opening video stream or file, " + "--video_path whether existing: {}" + " or camera whether working".format( + args.video_path)) + return + + if is_video_bg: + cap_bg = cv2.VideoCapture(args.background_video_path) + frames_bg = cap_bg.get(cv2.CAP_PROP_FRAME_COUNT) + current_frame_bg = 1 + else: + img_bg = cv2.imread(args.background_image_path) + while cap_video.isOpened(): + ret, frame = cap_video.read() + if ret: + im_shape = frame.shape + im_scale_x = float(resize_w) / float(im_shape[1]) + im_scale_y = float(resize_h) / float(im_shape[0]) + im = cv2.resize( + frame, + None, + None, + fx=im_scale_x, + fy=im_scale_y, + interpolation=cv2.INTER_LINEAR) + image = im.astype('float32') + im_info = ('resize', im_shape[0:2]) + pred = model.predict(image, test_transforms) + score_map = pred['score_map'] + cur_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) + cur_gray = cv2.resize(cur_gray, (resize_w, resize_h)) + score_map = 255 * score_map[:, :, 1] + optflow_map = postprocess(cur_gray, score_map, prev_gray, prev_cfd, \ + disflow, is_init) + prev_gray = cur_gray.copy() + prev_cfd = optflow_map.copy() + is_init = False + optflow_map = cv2.GaussianBlur(optflow_map, (3, 3), 0) + optflow_map = threshold_mask( + optflow_map, thresh_bg=0.2, thresh_fg=0.8) + score_map = recover(optflow_map, im_info) + + #循环读取背景帧 + if is_video_bg: + ret_bg, frame_bg = cap_bg.read() + if ret_bg: + if current_frame_bg == frames_bg: + current_frame_bg = 1 + cap_bg.set(cv2.CAP_PROP_POS_FRAMES, 0) + else: + break + current_frame_bg += 1 + comb = bg_replace(score_map, frame, frame_bg) + else: + comb = bg_replace(score_map, frame, img_bg) + cv2.imshow('HumanSegmentation', comb) + if cv2.waitKey(1) & 0xFF == ord('q'): + break + else: + break + if is_video_bg: + cap_bg.release() + cap_video.release() + + +if __name__ == "__main__": + args = parse_args() + infer(args) diff --git a/examples/human_segmentation/data/download_data.py b/examples/human_segmentation/data/download_data.py new file mode 100644 index 0000000000000000000000000000000000000000..941b4cc81ef05335c867c6c1eea20c07c44c7360 --- /dev/null +++ b/examples/human_segmentation/data/download_data.py @@ -0,0 +1,33 @@ +# 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. + +import sys +import os + +LOCAL_PATH = os.path.dirname(os.path.abspath(__file__)) + +import paddlex as pdx + + +def download_data(savepath): + url = "https://paddleseg.bj.bcebos.com/humanseg/data/mini_supervisely.zip" + pdx.utils.download_and_decompress(url=url, path=savepath) + + url = "https://paddleseg.bj.bcebos.com/humanseg/data/video_test.zip" + pdx.utils.download_and_decompress(url=url, path=savepath) + + +if __name__ == "__main__": + download_data(LOCAL_PATH) + print("Data download finish!") diff --git a/examples/human_segmentation/eval.py b/examples/human_segmentation/eval.py new file mode 100644 index 0000000000000000000000000000000000000000..a6e05ea0b2c463b948a1a021fa74f01512985675 --- /dev/null +++ b/examples/human_segmentation/eval.py @@ -0,0 +1,85 @@ +# coding: utf8 +# 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. + +import argparse +import paddlex as pdx +import paddlex.utils.logging as logging +from paddlex.seg import transforms + + +def parse_args(): + parser = argparse.ArgumentParser(description='HumanSeg training') + parser.add_argument( + '--model_dir', + dest='model_dir', + help='Model path for evaluating', + type=str, + default='output/best_model') + parser.add_argument( + '--data_dir', + dest='data_dir', + help='The root directory of dataset', + type=str) + parser.add_argument( + '--val_list', + dest='val_list', + help='Val list file of dataset', + type=str, + default=None) + parser.add_argument( + '--batch_size', + dest='batch_size', + help='Mini batch size', + type=int, + default=128) + parser.add_argument( + "--image_shape", + dest="image_shape", + help="The image shape for net inputs.", + nargs=2, + default=[192, 192], + type=int) + return parser.parse_args() + + +def dict2str(dict_input): + out = '' + for k, v in dict_input.items(): + try: + v = round(float(v), 6) + except: + pass + out = out + '{}={}, '.format(k, v) + return out.strip(', ') + + +def evaluate(args): + eval_transforms = transforms.Compose( + [transforms.Resize(args.image_shape), transforms.Normalize()]) + + eval_dataset = pdx.datasets.SegDataset( + data_dir=args.data_dir, + file_list=args.val_list, + transforms=eval_transforms) + + model = pdx.load_model(args.model_dir) + metrics = model.evaluate(eval_dataset, args.batch_size) + logging.info('[EVAL] Finished, {} .'.format(dict2str(metrics))) + + +if __name__ == '__main__': + args = parse_args() + + evaluate(args) diff --git a/examples/human_segmentation/infer.py b/examples/human_segmentation/infer.py new file mode 100644 index 0000000000000000000000000000000000000000..c78df7ae51609299a44d1c706197c56e2a20618e --- /dev/null +++ b/examples/human_segmentation/infer.py @@ -0,0 +1,109 @@ +# coding: utf8 +# 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. + +import argparse +import os +import os.path as osp +import cv2 +import numpy as np +import tqdm + +import paddlex as pdx +from paddlex.seg import transforms + + +def parse_args(): + parser = argparse.ArgumentParser( + description='HumanSeg prediction and visualization') + parser.add_argument( + '--model_dir', + dest='model_dir', + help='Model path for prediction', + type=str) + parser.add_argument( + '--data_dir', + dest='data_dir', + help='The root directory of dataset', + type=str) + parser.add_argument( + '--test_list', + dest='test_list', + help='Test list file of dataset', + type=str) + parser.add_argument( + '--save_dir', + dest='save_dir', + help='The directory for saving the inference results', + type=str, + default='./output/result') + parser.add_argument( + "--image_shape", + dest="image_shape", + help="The image shape for net inputs.", + nargs=2, + default=[192, 192], + type=int) + return parser.parse_args() + + +def infer(args): + def makedir(path): + sub_dir = osp.dirname(path) + if not osp.exists(sub_dir): + os.makedirs(sub_dir) + + test_transforms = transforms.Compose( + [transforms.Resize(args.image_shape), transforms.Normalize()]) + model = pdx.load_model(args.model_dir) + added_saved_path = osp.join(args.save_dir, 'added') + mat_saved_path = osp.join(args.save_dir, 'mat') + scoremap_saved_path = osp.join(args.save_dir, 'scoremap') + + with open(args.test_list, 'r') as f: + files = f.readlines() + + for file in tqdm.tqdm(files): + file = file.strip() + im_file = osp.join(args.data_dir, file) + im = cv2.imread(im_file) + result = model.predict(im_file, transforms=test_transforms) + + # save added image + added_image = pdx.seg.visualize( + im_file, result, weight=0.6, save_dir=None) + added_image_file = osp.join(added_saved_path, file) + makedir(added_image_file) + cv2.imwrite(added_image_file, added_image) + + # save score map + score_map = result['score_map'][:, :, 1] + score_map = (score_map * 255).astype(np.uint8) + score_map_file = osp.join(scoremap_saved_path, file) + makedir(score_map_file) + cv2.imwrite(score_map_file, score_map) + + # save mat image + score_map = np.expand_dims(score_map, axis=-1) + mat_image = np.concatenate([im, score_map], axis=2) + mat_file = osp.join(mat_saved_path, file) + ext = osp.splitext(mat_file)[-1] + mat_file = mat_file.replace(ext, '.png') + makedir(mat_file) + cv2.imwrite(mat_file, mat_image) + + +if __name__ == '__main__': + args = parse_args() + infer(args) diff --git a/examples/human_segmentation/postprocess.py b/examples/human_segmentation/postprocess.py new file mode 100644 index 0000000000000000000000000000000000000000..88e5dcc80f3d49d7d5625e74fe4de313b59fa844 --- /dev/null +++ b/examples/human_segmentation/postprocess.py @@ -0,0 +1,125 @@ +# coding: utf8 +# 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. + +import numpy as np + + +def cal_optical_flow_tracking(pre_gray, cur_gray, prev_cfd, dl_weights, + disflow): + """计算光流跟踪匹配点和光流图 + 输入参数: + pre_gray: 上一帧灰度图 + cur_gray: 当前帧灰度图 + prev_cfd: 上一帧光流图 + dl_weights: 融合权重图 + disflow: 光流数据结构 + 返回值: + is_track: 光流点跟踪二值图,即是否具有光流点匹配 + track_cfd: 光流跟踪图 + """ + check_thres = 8 + h, w = pre_gray.shape[:2] + track_cfd = np.zeros_like(prev_cfd) + is_track = np.zeros_like(pre_gray) + flow_fw = disflow.calc(pre_gray, cur_gray, None) + flow_bw = disflow.calc(cur_gray, pre_gray, None) + flow_fw = np.round(flow_fw).astype(np.int) + flow_bw = np.round(flow_bw).astype(np.int) + y_list = np.array(range(h)) + x_list = np.array(range(w)) + yv, xv = np.meshgrid(y_list, x_list) + yv, xv = yv.T, xv.T + cur_x = xv + flow_fw[:, :, 0] + cur_y = yv + flow_fw[:, :, 1] + + # 超出边界不跟踪 + not_track = (cur_x < 0) + (cur_x >= w) + (cur_y < 0) + (cur_y >= h) + flow_bw[~not_track] = flow_bw[cur_y[~not_track], cur_x[~not_track]] + not_track += (np.square(flow_fw[:, :, 0] + flow_bw[:, :, 0]) + + np.square(flow_fw[:, :, 1] + flow_bw[:, :, 1]) + ) >= check_thres + track_cfd[cur_y[~not_track], cur_x[~not_track]] = prev_cfd[~not_track] + + is_track[cur_y[~not_track], cur_x[~not_track]] = 1 + + not_flow = np.all(np.abs(flow_fw) == 0, + axis=-1) * np.all(np.abs(flow_bw) == 0, axis=-1) + dl_weights[cur_y[not_flow], cur_x[not_flow]] = 0.05 + return track_cfd, is_track, dl_weights + + +def fuse_optical_flow_tracking(track_cfd, dl_cfd, dl_weights, is_track): + """光流追踪图和人像分割结构融合 + 输入参数: + track_cfd: 光流追踪图 + dl_cfd: 当前帧分割结果 + dl_weights: 融合权重图 + is_track: 光流点匹配二值图 + 返回 + cur_cfd: 光流跟踪图和人像分割结果融合图 + """ + fusion_cfd = dl_cfd.copy() + is_track = is_track.astype(np.bool) + fusion_cfd[is_track] = dl_weights[is_track] * dl_cfd[is_track] + ( + 1 - dl_weights[is_track]) * track_cfd[is_track] + # 确定区域 + index_certain = ((dl_cfd > 0.9) + (dl_cfd < 0.1)) * is_track + index_less01 = (dl_weights < 0.1) * index_certain + fusion_cfd[index_less01] = 0.3 * dl_cfd[index_less01] + 0.7 * track_cfd[ + index_less01] + index_larger09 = (dl_weights >= 0.1) * index_certain + fusion_cfd[index_larger09] = 0.4 * dl_cfd[ + index_larger09] + 0.6 * track_cfd[index_larger09] + return fusion_cfd + + +def threshold_mask(img, thresh_bg, thresh_fg): + dst = (img / 255.0 - thresh_bg) / (thresh_fg - thresh_bg) + dst[np.where(dst > 1)] = 1 + dst[np.where(dst < 0)] = 0 + return dst.astype(np.float32) + + +def postprocess(cur_gray, scoremap, prev_gray, pre_cfd, disflow, is_init): + """光流优化 + Args: + cur_gray : 当前帧灰度图 + pre_gray : 前一帧灰度图 + pre_cfd :前一帧融合结果 + scoremap : 当前帧分割结果 + difflow : 光流 + is_init : 是否第一帧 + Returns: + fusion_cfd : 光流追踪图和预测结果融合图 + """ + h, w = scoremap.shape + cur_cfd = scoremap.copy() + + if is_init: + if h <= 64 or w <= 64: + disflow.setFinestScale(1) + elif h <= 160 or w <= 160: + disflow.setFinestScale(2) + else: + disflow.setFinestScale(3) + fusion_cfd = cur_cfd + else: + weights = np.ones((h, w), np.float32) * 0.3 + track_cfd, is_track, weights = cal_optical_flow_tracking( + prev_gray, cur_gray, pre_cfd, weights, disflow) + fusion_cfd = fuse_optical_flow_tracking(track_cfd, cur_cfd, weights, + is_track) + + return fusion_cfd diff --git a/examples/human_segmentation/pretrain_weights/download_pretrain_weights.py b/examples/human_segmentation/pretrain_weights/download_pretrain_weights.py new file mode 100644 index 0000000000000000000000000000000000000000..be961ab6ebca2f8fef2e5573a817ccfd29fee41a --- /dev/null +++ b/examples/human_segmentation/pretrain_weights/download_pretrain_weights.py @@ -0,0 +1,40 @@ +# coding: utf8 +# 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. + +import sys +import os + +LOCAL_PATH = os.path.dirname(os.path.abspath(__file__)) + +import paddlex as pdx +import paddlehub as hub + +model_urls = { + "PaddleX_HumanSeg_Server_Params": + "https://bj.bcebos.com/paddlex/models/humanseg/humanseg_server_params.tar", + "PaddleX_HumanSeg_Server_Inference": + "https://bj.bcebos.com/paddlex/models/humanseg/humanseg_server_inference.tar", + "PaddleX_HumanSeg_Mobile_Params": + "https://bj.bcebos.com/paddlex/models/humanseg/humanseg_mobile_params.tar", + "PaddleX_HumanSeg_Mobile_Inference": + "https://bj.bcebos.com/paddlex/models/humanseg/humanseg_mobile_inference.tar", + "PaddleX_HumanSeg_Mobile_Quant": + "https://bj.bcebos.com/paddlex/models/humanseg/humanseg_mobile_quant.tar" +} + +if __name__ == "__main__": + for model_name, url in model_urls.items(): + pdx.utils.download_and_decompress(url=url, path=LOCAL_PATH) + print("Pretrained Model download success!") diff --git a/examples/human_segmentation/quant_offline.py b/examples/human_segmentation/quant_offline.py new file mode 100644 index 0000000000000000000000000000000000000000..a801f8d02263f8dab98f3250478a289337492ae4 --- /dev/null +++ b/examples/human_segmentation/quant_offline.py @@ -0,0 +1,85 @@ +# coding: utf8 +# 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. + +import argparse +import paddlex as pdx +from paddlex.seg import transforms + + +def parse_args(): + parser = argparse.ArgumentParser(description='HumanSeg training') + parser.add_argument( + '--model_dir', + dest='model_dir', + help='Model path for quant', + type=str, + default='output/best_model') + parser.add_argument( + '--batch_size', + dest='batch_size', + help='Mini batch size', + type=int, + default=1) + parser.add_argument( + '--batch_nums', + dest='batch_nums', + help='Batch number for quant', + type=int, + default=10) + parser.add_argument( + '--data_dir', + dest='data_dir', + help='the root directory of dataset', + type=str) + parser.add_argument( + '--quant_list', + dest='quant_list', + help='Image file list for model quantization, it can be vat.txt or train.txt', + type=str, + default=None) + parser.add_argument( + '--save_dir', + dest='save_dir', + help='The directory for saving the quant model', + type=str, + default='./output/quant_offline') + parser.add_argument( + "--image_shape", + dest="image_shape", + help="The image shape for net inputs.", + nargs=2, + default=[192, 192], + type=int) + return parser.parse_args() + + +def evaluate(args): + eval_transforms = transforms.Compose( + [transforms.Resize(args.image_shape), transforms.Normalize()]) + + eval_dataset = pdx.datasets.SegDataset( + data_dir=args.data_dir, + file_list=args.quant_list, + transforms=eval_transforms) + + model = pdx.load_model(args.model_dir) + pdx.slim.export_quant_model(model, eval_dataset, args.batch_size, + args.batch_nums, args.save_dir) + + +if __name__ == '__main__': + args = parse_args() + + evaluate(args) diff --git a/examples/human_segmentation/train.py b/examples/human_segmentation/train.py new file mode 100644 index 0000000000000000000000000000000000000000..a7df98f360a78c2624814fc75bb0c382e19b7e95 --- /dev/null +++ b/examples/human_segmentation/train.py @@ -0,0 +1,156 @@ +# coding: utf8 +# 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. + +import argparse + +import paddlex as pdx +from paddlex.seg import transforms + +MODEL_TYPE = ['HumanSegMobile', 'HumanSegServer'] + + +def parse_args(): + parser = argparse.ArgumentParser(description='HumanSeg training') + parser.add_argument( + '--model_type', + dest='model_type', + help="Model type for traing, which is one of ('HumanSegMobile', 'HumanSegServer')", + type=str, + default='HumanSegMobile') + parser.add_argument( + '--data_dir', + dest='data_dir', + help='The root directory of dataset', + type=str) + parser.add_argument( + '--train_list', + dest='train_list', + help='Train list file of dataset', + type=str) + parser.add_argument( + '--val_list', + dest='val_list', + help='Val list file of dataset', + type=str, + default=None) + parser.add_argument( + '--save_dir', + dest='save_dir', + help='The directory for saving the model snapshot', + type=str, + default='./output') + parser.add_argument( + '--num_classes', + dest='num_classes', + help='Number of classes', + type=int, + default=2) + parser.add_argument( + "--image_shape", + dest="image_shape", + help="The image shape for net inputs.", + nargs=2, + default=[192, 192], + type=int) + parser.add_argument( + '--num_epochs', + dest='num_epochs', + help='Number epochs for training', + type=int, + default=100) + parser.add_argument( + '--batch_size', + dest='batch_size', + help='Mini batch size', + type=int, + default=128) + parser.add_argument( + '--learning_rate', + dest='learning_rate', + help='Learning rate', + type=float, + default=0.01) + parser.add_argument( + '--pretrain_weights', + dest='pretrain_weights', + help='The path of pretrianed weight', + type=str, + default=None) + parser.add_argument( + '--resume_checkpoint', + dest='resume_checkpoint', + help='The path of resume checkpoint', + type=str, + default=None) + parser.add_argument( + '--use_vdl', + dest='use_vdl', + help='Whether to use visualdl', + action='store_true') + parser.add_argument( + '--save_interval_epochs', + dest='save_interval_epochs', + help='The interval epochs for save a model snapshot', + type=int, + default=5) + + return parser.parse_args() + + +def train(args): + train_transforms = transforms.Compose([ + transforms.Resize(args.image_shape), transforms.RandomHorizontalFlip(), + transforms.Normalize() + ]) + + eval_transforms = transforms.Compose( + [transforms.Resize(args.image_shape), transforms.Normalize()]) + + train_dataset = pdx.datasets.SegDataset( + data_dir=args.data_dir, + file_list=args.train_list, + transforms=train_transforms, + shuffle=True) + eval_dataset = pdx.datasets.SegDataset( + data_dir=args.data_dir, + file_list=args.val_list, + transforms=eval_transforms) + + if args.model_type == 'HumanSegMobile': + model = pdx.seg.HRNet( + num_classes=args.num_classes, width='18_small_v1') + elif args.model_type == 'HumanSegServer': + model = pdx.seg.DeepLabv3p( + num_classes=args.num_classes, backbone='Xception65') + else: + raise ValueError( + "--model_type: {} is set wrong, it shold be one of ('HumanSegMobile', " + "'HumanSegLite', 'HumanSegServer')".format(args.model_type)) + model.train( + num_epochs=args.num_epochs, + train_dataset=train_dataset, + train_batch_size=args.batch_size, + eval_dataset=eval_dataset, + save_interval_epochs=args.save_interval_epochs, + learning_rate=args.learning_rate, + pretrain_weights=args.pretrain_weights, + resume_checkpoint=args.resume_checkpoint, + save_dir=args.save_dir, + use_vdl=args.use_vdl) + + +if __name__ == '__main__': + args = parse_args() + train(args) diff --git a/examples/human_segmentation/video_infer.py b/examples/human_segmentation/video_infer.py new file mode 100644 index 0000000000000000000000000000000000000000..c2a67fe0032eae19e937580ff35e53ba09d1118f --- /dev/null +++ b/examples/human_segmentation/video_infer.py @@ -0,0 +1,187 @@ +# coding: utf8 +# 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. + +import argparse +import os +import os.path as osp +import cv2 +import numpy as np + +from postprocess import postprocess, threshold_mask +import paddlex as pdx +import paddlex.utils.logging as logging +from paddlex.seg import transforms + + +def parse_args(): + parser = argparse.ArgumentParser( + description='HumanSeg inference for video') + parser.add_argument( + '--model_dir', + dest='model_dir', + help='Model path for inference', + type=str) + parser.add_argument( + '--video_path', + dest='video_path', + help='Video path for inference, camera will be used if the path not existing', + type=str, + default=None) + parser.add_argument( + '--save_dir', + dest='save_dir', + help='The directory for saving the inference results', + type=str, + default='./output') + parser.add_argument( + "--image_shape", + dest="image_shape", + help="The image shape for net inputs.", + nargs=2, + default=[192, 192], + type=int) + + return parser.parse_args() + + +def recover(img, im_info): + if im_info[0] == 'resize': + w, h = im_info[1][1], im_info[1][0] + img = cv2.resize(img, (w, h), cv2.INTER_LINEAR) + elif im_info[0] == 'padding': + w, h = im_info[1][0], im_info[1][0] + img = img[0:h, 0:w, :] + return img + + +def video_infer(args): + resize_h = args.image_shape[1] + resize_w = args.image_shape[0] + + model = pdx.load_model(args.model_dir) + test_transforms = transforms.Compose([transforms.Normalize()]) + if not args.video_path: + cap = cv2.VideoCapture(0) + else: + cap = cv2.VideoCapture(args.video_path) + if not cap.isOpened(): + raise IOError("Error opening video stream or file, " + "--video_path whether existing: {}" + " or camera whether working".format(args.video_path)) + return + + width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + + disflow = cv2.DISOpticalFlow_create(cv2.DISOPTICAL_FLOW_PRESET_ULTRAFAST) + prev_gray = np.zeros((resize_h, resize_w), np.uint8) + prev_cfd = np.zeros((resize_h, resize_w), np.float32) + is_init = True + + fps = cap.get(cv2.CAP_PROP_FPS) + if args.video_path: + logging.info("Please wait. It is computing......") + # 用于保存预测结果视频 + if not osp.exists(args.save_dir): + os.makedirs(args.save_dir) + out = cv2.VideoWriter( + osp.join(args.save_dir, 'result.avi'), + cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'), fps, (width, height)) + # 开始获取视频帧 + while cap.isOpened(): + ret, frame = cap.read() + if ret: + im_shape = frame.shape + im_scale_x = float(resize_w) / float(im_shape[1]) + im_scale_y = float(resize_h) / float(im_shape[0]) + im = cv2.resize( + frame, + None, + None, + fx=im_scale_x, + fy=im_scale_y, + interpolation=cv2.INTER_LINEAR) + image = im.astype('float32') + im_info = ('resize', im_shape[0:2]) + pred = model.predict(image, test_transforms) + score_map = pred['score_map'] + cur_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) + score_map = 255 * score_map[:, :, 1] + optflow_map = postprocess(cur_gray, score_map, prev_gray, prev_cfd, \ + disflow, is_init) + prev_gray = cur_gray.copy() + prev_cfd = optflow_map.copy() + is_init = False + optflow_map = cv2.GaussianBlur(optflow_map, (3, 3), 0) + optflow_map = threshold_mask( + optflow_map, thresh_bg=0.2, thresh_fg=0.8) + img_matting = np.repeat( + optflow_map[:, :, np.newaxis], 3, axis=2) + img_matting = recover(img_matting, im_info) + bg_im = np.ones_like(img_matting) * 255 + comb = (img_matting * frame + + (1 - img_matting) * bg_im).astype(np.uint8) + out.write(comb) + else: + break + cap.release() + out.release() + + else: + while cap.isOpened(): + ret, frame = cap.read() + if ret: + im_shape = frame.shape + im_scale_x = float(resize_w) / float(im_shape[1]) + im_scale_y = float(resize_h) / float(im_shape[0]) + im = cv2.resize( + frame, + None, + None, + fx=im_scale_x, + fy=im_scale_y, + interpolation=cv2.INTER_LINEAR) + image = im.astype('float32') + im_info = ('resize', im_shape[0:2]) + pred = model.predict(image, test_transforms) + score_map = pred['score_map'] + cur_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) + cur_gray = cv2.resize(cur_gray, (resize_w, resize_h)) + score_map = 255 * score_map[:, :, 1] + optflow_map = postprocess(cur_gray, score_map, prev_gray, prev_cfd, \ + disflow, is_init) + prev_gray = cur_gray.copy() + prev_cfd = optflow_map.copy() + is_init = False + optflow_map = cv2.GaussianBlur(optflow_map, (3, 3), 0) + optflow_map = threshold_mask( + optflow_map, thresh_bg=0.2, thresh_fg=0.8) + img_matting = np.repeat( + optflow_map[:, :, np.newaxis], 3, axis=2) + img_matting = recover(img_matting, im_info) + bg_im = np.ones_like(img_matting) * 255 + comb = (img_matting * frame + + (1 - img_matting) * bg_im).astype(np.uint8) + cv2.imshow('HumanSegmentation', comb) + if cv2.waitKey(1) & 0xFF == ord('q'): + break + else: + break + cap.release() + + +if __name__ == "__main__": + args = parse_args() + video_infer(args) diff --git a/new_tutorials/train/README.md b/new_tutorials/train/README.md deleted file mode 100644 index fc319d16d0c795f856600355d43c18ef413eae0e..0000000000000000000000000000000000000000 --- a/new_tutorials/train/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# 使用教程——训练模型 - -本目录下整理了使用PaddleX训练模型的示例代码,代码中均提供了示例数据的自动下载,并均使用单张GPU卡进行训练。 - -|代码 | 模型任务 | 数据 | -|------|--------|---------| -|classification/mobilenetv2.py | 图像分类MobileNetV2 | 蔬菜分类 | -|classification/resnet50.py | 图像分类ResNet50 | 蔬菜分类 | -|detection/faster_rcnn_r50_fpn.py | 目标检测FasterRCNN | 昆虫检测 | -|detection/mask_rcnn_f50_fpn.py | 实例分割MaskRCNN | 垃圾分拣 | -|segmentation/deeplabv3p.py | 语义分割DeepLabV3| 视盘分割 | -|segmentation/unet.py | 语义分割UNet | 视盘分割 | -|segmentation/hrnet.py | 语义分割HRNet | 视盘分割 | -|segmentation/fast_scnn.py | 语义分割FastSCNN | 视盘分割 | - - -## 开始训练 -在安装PaddleX后,使用如下命令开始训练 -``` -python classification/mobilenetv2.py -``` diff --git a/new_tutorials/train/classification/mobilenetv2.py b/new_tutorials/train/classification/mobilenetv2.py deleted file mode 100644 index 9a075526a3cbb7e560c133f08faef68ea5a07121..0000000000000000000000000000000000000000 --- a/new_tutorials/train/classification/mobilenetv2.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -# 选择使用0号卡 -os.environ['CUDA_VISIBLE_DEVICES'] = '0' - -from paddlex.cls import transforms -import paddlex as pdx - -# 下载和解压蔬菜分类数据集 -veg_dataset = 'https://bj.bcebos.com/paddlex/datasets/vegetables_cls.tar.gz' -pdx.utils.download_and_decompress(veg_dataset, path='./') - -# 定义训练和验证时的transforms -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/transforms/cls_transforms.html#composedclstransforms -train_transforms = transforms.ComposedClsTransforms(mode='train', crop_size=[224, 224]) -eval_transforms = transforms.ComposedClsTransforms(mode='eval', crop_size=[224, 224]) - -# 定义训练和验证所用的数据集 -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/datasets/classification.html#imagenet -train_dataset = pdx.datasets.ImageNet( - data_dir='vegetables_cls', - file_list='vegetables_cls/train_list.txt', - label_list='vegetables_cls/labels.txt', - transforms=train_transforms, - shuffle=True) -eval_dataset = pdx.datasets.ImageNet( - data_dir='vegetables_cls', - file_list='vegetables_cls/val_list.txt', - label_list='vegetables_cls/labels.txt', - transforms=eval_transforms) - -# 初始化模型,并进行训练 -# 可使用VisualDL查看训练指标 -# VisualDL启动方式: visualdl --logdir output/mobilenetv2/vdl_log --port 8001 -# 浏览器打开 https://0.0.0.0:8001即可 -# 其中0.0.0.0为本机访问,如为远程服务, 改成相应机器IP - -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/models/classification.html#resnet50 -model = pdx.cls.MobileNetV2(num_classes=len(train_dataset.labels)) -model.train( - num_epochs=10, - train_dataset=train_dataset, - train_batch_size=32, - eval_dataset=eval_dataset, - lr_decay_epochs=[4, 6, 8], - learning_rate=0.025, - save_dir='output/mobilenetv2', - use_vdl=True) diff --git a/new_tutorials/train/classification/resnet50.py b/new_tutorials/train/classification/resnet50.py deleted file mode 100644 index bf56a605f1c3376057c1ab9283fa1251491b2750..0000000000000000000000000000000000000000 --- a/new_tutorials/train/classification/resnet50.py +++ /dev/null @@ -1,56 +0,0 @@ -import os -# 选择使用0号卡 -os.environ['CUDA_VISIBLE_DEVICES'] = '0' - -import paddle.fluid as fluid -from paddlex.cls import transforms -import paddlex as pdx - -# 下载和解压蔬菜分类数据集 -veg_dataset = 'https://bj.bcebos.com/paddlex/datasets/vegetables_cls.tar.gz' -pdx.utils.download_and_decompress(veg_dataset, path='./') - -# 定义训练和验证时的transforms -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/transforms/cls_transforms.html#composedclstransforms -train_transforms = transforms.ComposedClsTransforms(mode='train', crop_size=[224, 224]) -eval_transforms = transforms.ComposedClsTransforms(mode='eval', crop_size=[224, 224]) - -# 定义训练和验证所用的数据集 -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/datasets/classification.html#imagenet -train_dataset = pdx.datasets.ImageNet( - data_dir='vegetables_cls', - file_list='vegetables_cls/train_list.txt', - label_list='vegetables_cls/labels.txt', - transforms=train_transforms, - shuffle=True) -eval_dataset = pdx.datasets.ImageNet( - data_dir='vegetables_cls', - file_list='vegetables_cls/val_list.txt', - label_list='vegetables_cls/labels.txt', - transforms=eval_transforms) - -# PaddleX支持自定义构建优化器 -step_each_epoch = train_dataset.num_samples // 32 -learning_rate = fluid.layers.cosine_decay( - learning_rate=0.025, step_each_epoch=step_each_epoch, epochs=10) -optimizer = fluid.optimizer.Momentum( - learning_rate=learning_rate, - momentum=0.9, - regularization=fluid.regularizer.L2Decay(4e-5)) - -# 初始化模型,并进行训练 -# 可使用VisualDL查看训练指标 -# VisualDL启动方式: visualdl --logdir output/resnet50/vdl_log --port 8001 -# 浏览器打开 https://0.0.0.0:8001即可 -# 其中0.0.0.0为本机访问,如为远程服务, 改成相应机器IP - -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/models/classification.html#resnet50 -model = pdx.cls.ResNet50(num_classes=len(train_dataset.labels)) -model.train( - num_epochs=10, - train_dataset=train_dataset, - train_batch_size=32, - eval_dataset=eval_dataset, - optimizer=optimizer, - save_dir='output/resnet50', - use_vdl=True) diff --git a/new_tutorials/train/detection/faster_rcnn_r50_fpn.py b/new_tutorials/train/detection/faster_rcnn_r50_fpn.py deleted file mode 100644 index a64b711c3af48cb85cfd8a82938785ca386a99ec..0000000000000000000000000000000000000000 --- a/new_tutorials/train/detection/faster_rcnn_r50_fpn.py +++ /dev/null @@ -1,49 +0,0 @@ -import os -# 选择使用0号卡 -os.environ['CUDA_VISIBLE_DEVICES'] = '0' - -from paddlex.det import transforms -import paddlex as pdx - -# 下载和解压昆虫检测数据集 -insect_dataset = 'https://bj.bcebos.com/paddlex/datasets/insect_det.tar.gz' -pdx.utils.download_and_decompress(insect_dataset, path='./') - -# 定义训练和验证时的transforms -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/transforms/det_transforms.html#composedrcnntransforms -train_transforms = transforms.ComposedRCNNTransforms(mode='train', min_max_size=[800, 1333]) -eval_transforms = transforms.ComposedRCNNTransforms(mode='eval', min_max_size=[800, 1333]) - -# 定义训练和验证所用的数据集 -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/datasets/detection.html#vocdetection -train_dataset = pdx.datasets.VOCDetection( - data_dir='insect_det', - file_list='insect_det/train_list.txt', - label_list='insect_det/labels.txt', - transforms=train_transforms, - shuffle=True) -eval_dataset = pdx.datasets.VOCDetection( - data_dir='insect_det', - file_list='insect_det/val_list.txt', - label_list='insect_det/labels.txt', - transforms=eval_transforms) - -# 初始化模型,并进行训练 -# 可使用VisualDL查看训练指标 -# VisualDL启动方式: visualdl --logdir output/faster_rcnn_r50_fpn/vdl_log --port 8001 -# 浏览器打开 https://0.0.0.0:8001即可 -# 其中0.0.0.0为本机访问,如为远程服务, 改成相应机器IP -# num_classes 需要设置为包含背景类的类别数,即: 目标类别数量 + 1 - -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/models/detection.html#fasterrcnn -num_classes = len(train_dataset.labels) + 1 -model = pdx.det.FasterRCNN(num_classes=num_classes) -model.train( - num_epochs=12, - train_dataset=train_dataset, - train_batch_size=2, - eval_dataset=eval_dataset, - learning_rate=0.0025, - lr_decay_epochs=[8, 11], - save_dir='output/faster_rcnn_r50_fpn', - use_vdl=True) diff --git a/new_tutorials/train/detection/mask_rcnn_r50_fpn.py b/new_tutorials/train/detection/mask_rcnn_r50_fpn.py deleted file mode 100644 index f2ebf6e20f18054bf16452eb6e60b9ea24f20748..0000000000000000000000000000000000000000 --- a/new_tutorials/train/detection/mask_rcnn_r50_fpn.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -# 选择使用0号卡 -os.environ['CUDA_VISIBLE_DEVICES'] = '0' - -from paddlex.det import transforms -import paddlex as pdx - -# 下载和解压小度熊分拣数据集 -xiaoduxiong_dataset = 'https://bj.bcebos.com/paddlex/datasets/xiaoduxiong_ins_det.tar.gz' -pdx.utils.download_and_decompress(xiaoduxiong_dataset, path='./') - -# 定义训练和验证时的transforms -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/transforms/det_transforms.html#composedrcnntransforms -train_transforms = transforms.ComposedRCNNTransforms(mode='train', min_max_size=[800, 1333]) -eval_transforms = transforms.ComposedRCNNTransforms(mode='eval', min_max_size=[800, 1333]) - -# 定义训练和验证所用的数据集 -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/datasets/detection.html#cocodetection -train_dataset = pdx.datasets.CocoDetection( - data_dir='xiaoduxiong_ins_det/JPEGImages', - ann_file='xiaoduxiong_ins_det/train.json', - transforms=train_transforms, - shuffle=True) -eval_dataset = pdx.datasets.CocoDetection( - data_dir='xiaoduxiong_ins_det/JPEGImages', - ann_file='xiaoduxiong_ins_det/val.json', - transforms=eval_transforms) - -# 初始化模型,并进行训练 -# 可使用VisualDL查看训练指标 -# VisualDL启动方式: visualdl --logdir output/mask_rcnn_r50_fpn/vdl_log --port 8001 -# 浏览器打开 https://0.0.0.0:8001即可 -# 其中0.0.0.0为本机访问,如为远程服务, 改成相应机器IP -# num_classes 需要设置为包含背景类的类别数,即: 目标类别数量 + 1 - -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/models/instance_segmentation.html#maskrcnn -num_classes = len(train_dataset.labels) + 1 -model = pdx.det.MaskRCNN(num_classes=num_classes) -model.train( - num_epochs=12, - train_dataset=train_dataset, - train_batch_size=1, - eval_dataset=eval_dataset, - learning_rate=0.00125, - warmup_steps=10, - lr_decay_epochs=[8, 11], - save_dir='output/mask_rcnn_r50_fpn', - use_vdl=True) diff --git a/new_tutorials/train/detection/yolov3_darknet53.py b/new_tutorials/train/detection/yolov3_darknet53.py deleted file mode 100644 index b862004a863cfb1577db9fb3a8cf866f1e685613..0000000000000000000000000000000000000000 --- a/new_tutorials/train/detection/yolov3_darknet53.py +++ /dev/null @@ -1,50 +0,0 @@ -import os -# 选择使用0号卡 -os.environ['CUDA_VISIBLE_DEVICES'] = '0' - -from paddlex.det import transforms -import paddlex as pdx - -# 下载和解压昆虫检测数据集 -insect_dataset = 'https://bj.bcebos.com/paddlex/datasets/insect_det.tar.gz' -pdx.utils.download_and_decompress(insect_dataset, path='./') - -# 定义训练和验证时的transforms -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/transforms/det_transforms.html#composedyolotransforms -train_transforms = transforms.ComposedYOLOv3Transforms( - mode='train', shape=[608, 608]) -eval_transforms = transforms.ComposedYOLOv3Transforms( - mode='eval', shape=[608, 608]) - -# 定义训练和验证所用的数据集 -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/datasets/detection.html#vocdetection -train_dataset = pdx.datasets.VOCDetection( - data_dir='insect_det', - file_list='insect_det/train_list.txt', - label_list='insect_det/labels.txt', - transforms=train_transforms, - shuffle=True) -eval_dataset = pdx.datasets.VOCDetection( - data_dir='insect_det', - file_list='insect_det/val_list.txt', - label_list='insect_det/labels.txt', - transforms=eval_transforms) - -# 初始化模型,并进行训练 -# 可使用VisualDL查看训练指标 -# VisualDL启动方式: visualdl --logdir output/yolov3_darknet/vdl_log --port 8001 -# 浏览器打开 https://0.0.0.0:8001即可 -# 其中0.0.0.0为本机访问,如为远程服务, 改成相应机器IP - -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/models/detection.html#yolov3 -num_classes = len(train_dataset.labels) -model = pdx.det.YOLOv3(num_classes=num_classes, backbone='DarkNet53') -model.train( - num_epochs=270, - train_dataset=train_dataset, - train_batch_size=8, - eval_dataset=eval_dataset, - learning_rate=0.000125, - lr_decay_epochs=[210, 240], - save_dir='output/yolov3_darknet53', - use_vdl=True) diff --git a/new_tutorials/train/segmentation/deeplabv3p.py b/new_tutorials/train/segmentation/deeplabv3p.py deleted file mode 100644 index cb18fcfad65331d02b04abe3c3a76fa0356fb5b8..0000000000000000000000000000000000000000 --- a/new_tutorials/train/segmentation/deeplabv3p.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -# 选择使用0号卡 -os.environ['CUDA_VISIBLE_DEVICES'] = '0' - -import paddlex as pdx -from paddlex.seg import transforms - -# 下载和解压视盘分割数据集 -optic_dataset = 'https://bj.bcebos.com/paddlex/datasets/optic_disc_seg.tar.gz' -pdx.utils.download_and_decompress(optic_dataset, path='./') - -# 定义训练和验证时的transforms -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/transforms/seg_transforms.html#composedsegtransforms -train_transforms = transforms.ComposedSegTransforms(mode='train', train_crop_size=[769, 769]) -eval_transforms = transforms.ComposedSegTransforms(mode='eval') - -train_transforms.add_augmenters([ - transforms.RandomRotate() -]) - -# 定义训练和验证所用的数据集 -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/datasets/semantic_segmentation.html#segdataset -train_dataset = pdx.datasets.SegDataset( - data_dir='optic_disc_seg', - file_list='optic_disc_seg/train_list.txt', - label_list='optic_disc_seg/labels.txt', - transforms=train_transforms, - shuffle=True) -eval_dataset = pdx.datasets.SegDataset( - data_dir='optic_disc_seg', - file_list='optic_disc_seg/val_list.txt', - label_list='optic_disc_seg/labels.txt', - transforms=eval_transforms) - -# 初始化模型,并进行训练 -# 可使用VisualDL查看训练指标 -# VisualDL启动方式: visualdl --logdir output/deeplab/vdl_log --port 8001 -# 浏览器打开 https://0.0.0.0:8001即可 -# 其中0.0.0.0为本机访问,如为远程服务, 改成相应机器IP - -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/models/semantic_segmentation.html#deeplabv3p -num_classes = len(train_dataset.labels) -model = pdx.seg.DeepLabv3p(num_classes=num_classes) -model.train( - num_epochs=40, - train_dataset=train_dataset, - train_batch_size=4, - eval_dataset=eval_dataset, - learning_rate=0.01, - save_dir='output/deeplab', - use_vdl=True) diff --git a/new_tutorials/train/segmentation/hrnet.py b/new_tutorials/train/segmentation/hrnet.py deleted file mode 100644 index 98fdd1b925bd4707001fdad56b3ffdc6bb2b58ae..0000000000000000000000000000000000000000 --- a/new_tutorials/train/segmentation/hrnet.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -# 选择使用0号卡 -os.environ['CUDA_VISIBLE_DEVICES'] = '0' - -import paddlex as pdx -from paddlex.seg import transforms - -# 下载和解压视盘分割数据集 -optic_dataset = 'https://bj.bcebos.com/paddlex/datasets/optic_disc_seg.tar.gz' -pdx.utils.download_and_decompress(optic_dataset, path='./') - -# 定义训练和验证时的transforms -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/transforms/seg_transforms.html#composedsegtransforms -train_transforms = transforms.ComposedSegTransforms(mode='train', train_crop_size=[769, 769]) -eval_transforms = transforms.ComposedSegTransforms(mode='eval') - -# 定义训练和验证所用的数据集 -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/datasets/semantic_segmentation.html#segdataset -train_dataset = pdx.datasets.SegDataset( - data_dir='optic_disc_seg', - file_list='optic_disc_seg/train_list.txt', - label_list='optic_disc_seg/labels.txt', - transforms=train_transforms, - shuffle=True) -eval_dataset = pdx.datasets.SegDataset( - data_dir='optic_disc_seg', - file_list='optic_disc_seg/val_list.txt', - label_list='optic_disc_seg/labels.txt', - transforms=eval_transforms) - -# 初始化模型,并进行训练 -# 可使用VisualDL查看训练指标 -# VisualDL启动方式: visualdl --logdir output/unet/vdl_log --port 8001 -# 浏览器打开 https://0.0.0.0:8001即可 -# 其中0.0.0.0为本机访问,如为远程服务, 改成相应机器IP - -# https://paddlex.readthedocs.io/zh_CN/latest/apis/models/semantic_segmentation.html#hrnet -num_classes = len(train_dataset.labels) -model = pdx.seg.HRNet(num_classes=num_classes) -model.train( - num_epochs=20, - train_dataset=train_dataset, - train_batch_size=4, - eval_dataset=eval_dataset, - learning_rate=0.01, - save_dir='output/hrnet', - use_vdl=True) diff --git a/new_tutorials/train/segmentation/unet.py b/new_tutorials/train/segmentation/unet.py deleted file mode 100644 index ddf4f7991a690b0d0d506967df0c140f60945e85..0000000000000000000000000000000000000000 --- a/new_tutorials/train/segmentation/unet.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -# 选择使用0号卡 -os.environ['CUDA_VISIBLE_DEVICES'] = '0' - -import paddlex as pdx -from paddlex.seg import transforms - -# 下载和解压视盘分割数据集 -optic_dataset = 'https://bj.bcebos.com/paddlex/datasets/optic_disc_seg.tar.gz' -pdx.utils.download_and_decompress(optic_dataset, path='./') - -# 定义训练和验证时的transforms -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/transforms/seg_transforms.html#composedsegtransforms -train_transforms = transforms.ComposedSegTransforms(mode='train', train_crop_size=[769, 769]) -eval_transforms = transforms.ComposedSegTransforms(mode='eval') - -# 定义训练和验证所用的数据集 -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/datasets/semantic_segmentation.html#segdataset -train_dataset = pdx.datasets.SegDataset( - data_dir='optic_disc_seg', - file_list='optic_disc_seg/train_list.txt', - label_list='optic_disc_seg/labels.txt', - transforms=train_transforms, - shuffle=True) -eval_dataset = pdx.datasets.SegDataset( - data_dir='optic_disc_seg', - file_list='optic_disc_seg/val_list.txt', - label_list='optic_disc_seg/labels.txt', - transforms=eval_transforms) - -# 初始化模型,并进行训练 -# 可使用VisualDL查看训练指标 -# VisualDL启动方式: visualdl --logdir output/unet/vdl_log --port 8001 -# 浏览器打开 https://0.0.0.0:8001即可 -# 其中0.0.0.0为本机访问,如为远程服务, 改成相应机器IP - -# API说明: https://paddlex.readthedocs.io/zh_CN/latest/apis/models/semantic_segmentation.html#unet -num_classes = len(train_dataset.labels) -model = pdx.seg.UNet(num_classes=num_classes) -model.train( - num_epochs=20, - train_dataset=train_dataset, - train_batch_size=4, - eval_dataset=eval_dataset, - learning_rate=0.01, - save_dir='output/unet', - use_vdl=True) diff --git a/paddlex/__init__.py b/paddlex/__init__.py index 6fc8aff1d3fdbc08a7474627bf38f2af17599fb3..7743882a6334e257c1a7a4b94566aff3a8a55667 100644 --- a/paddlex/__init__.py +++ b/paddlex/__init__.py @@ -48,6 +48,7 @@ if hub.version.hub_version < '1.6.2': env_info = get_environ_info() load_model = cv.models.load_model datasets = cv.datasets +transforms = cv.transforms log_level = 2 diff --git a/paddlex/cv/datasets/coco.py b/paddlex/cv/datasets/coco.py index 97e791be5ed3cac1656fba4429d90f1653bfe1be..264b2da1e6a6aa9e15bf8a2ae9b3fbdc3ee75f1b 100644 --- a/paddlex/cv/datasets/coco.py +++ b/paddlex/cv/datasets/coco.py @@ -100,7 +100,7 @@ class CocoDetection(VOCDetection): gt_score = np.ones((num_bbox, 1), dtype=np.float32) is_crowd = np.zeros((num_bbox, 1), dtype=np.int32) difficult = np.zeros((num_bbox, 1), dtype=np.int32) - gt_poly = None + gt_poly = [None] * num_bbox for i, box in enumerate(bboxes): catid = box['category_id'] @@ -108,8 +108,6 @@ class CocoDetection(VOCDetection): gt_bbox[i, :] = box['clean_bbox'] is_crowd[i][0] = box['iscrowd'] if 'segmentation' in box: - if gt_poly is None: - gt_poly = [None] * num_bbox gt_poly[i] = box['segmentation'] im_info = { @@ -121,10 +119,9 @@ class CocoDetection(VOCDetection): 'gt_class': gt_class, 'gt_bbox': gt_bbox, 'gt_score': gt_score, + 'gt_poly': gt_poly, 'difficult': difficult } - if gt_poly is not None: - label_info['gt_poly'] = gt_poly coco_rec = (im_info, label_info) self.file_list.append([im_fname, coco_rec]) diff --git a/paddlex/cv/datasets/seg_dataset.py b/paddlex/cv/datasets/seg_dataset.py index 61697e3d799ccb0ca765410a81e7257741acfb44..6e8bfae1ca623ed90a6d583042627cf4aecb2ea6 100644 --- a/paddlex/cv/datasets/seg_dataset.py +++ b/paddlex/cv/datasets/seg_dataset.py @@ -1,4 +1,4 @@ -# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# 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. @@ -28,7 +28,7 @@ class SegDataset(Dataset): Args: data_dir (str): 数据集所在的目录路径。 file_list (str): 描述数据集图片文件和对应标注文件的文件路径(文本内每行路径为相对data_dir的相对路)。 - label_list (str): 描述数据集包含的类别信息文件路径。 + label_list (str): 描述数据集包含的类别信息文件路径。默认值为None。 transforms (list): 数据集中每个样本的预处理/增强算子。 num_workers (int): 数据集中样本在预处理过程中的线程或进程数。默认为4。 buffer_size (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。 @@ -40,7 +40,7 @@ class SegDataset(Dataset): def __init__(self, data_dir, file_list, - label_list, + label_list=None, transforms=None, num_workers='auto', buffer_size=100, @@ -56,10 +56,11 @@ class SegDataset(Dataset): self.labels = list() self._epoch = 0 - with open(label_list, encoding=get_encoding(label_list)) as f: - for line in f: - item = line.strip() - self.labels.append(item) + if label_list is not None: + with open(label_list, encoding=get_encoding(label_list)) as f: + for line in f: + item = line.strip() + self.labels.append(item) with open(file_list, encoding=get_encoding(file_list)) as f: for line in f: @@ -69,8 +70,8 @@ class SegDataset(Dataset): full_path_im = osp.join(data_dir, items[0]) full_path_label = osp.join(data_dir, items[1]) if not osp.exists(full_path_im): - raise IOError( - 'The image file {} is not exist!'.format(full_path_im)) + raise IOError('The image file {} is not exist!'.format( + full_path_im)) if not osp.exists(full_path_label): raise IOError('The image file {} is not exist!'.format( full_path_label)) diff --git a/paddlex/cv/datasets/voc.py b/paddlex/cv/datasets/voc.py index f4d125837667fb87f7723acc32886297e633ed09..276891894b9636e5de8bed566fe234bf212bcad3 100644 --- a/paddlex/cv/datasets/voc.py +++ b/paddlex/cv/datasets/voc.py @@ -106,16 +106,23 @@ class VOCDetection(Dataset): ct = int(tree.find('id').text) im_id = np.array([int(tree.find('id').text)]) pattern = re.compile('', re.IGNORECASE) - obj_tag = pattern.findall(str(ET.tostringlist(tree.getroot())))[0][1:-1] + obj_match = pattern.findall( + str(ET.tostringlist(tree.getroot()))) + if len(obj_match) == 0: + continue + obj_tag = obj_match[0][1:-1] objs = tree.findall(obj_tag) pattern = re.compile('', re.IGNORECASE) - size_tag = pattern.findall(str(ET.tostringlist(tree.getroot())))[0][1:-1] + size_tag = pattern.findall( + str(ET.tostringlist(tree.getroot())))[0][1:-1] size_element = tree.find(size_tag) pattern = re.compile('', re.IGNORECASE) - width_tag = pattern.findall(str(ET.tostringlist(size_element)))[0][1:-1] + width_tag = pattern.findall( + str(ET.tostringlist(size_element)))[0][1:-1] im_w = float(size_element.find(width_tag).text) pattern = re.compile('', re.IGNORECASE) - height_tag = pattern.findall(str(ET.tostringlist(size_element)))[0][1:-1] + height_tag = pattern.findall( + str(ET.tostringlist(size_element)))[0][1:-1] im_h = float(size_element.find(height_tag).text) gt_bbox = np.zeros((len(objs), 4), dtype=np.float32) gt_class = np.zeros((len(objs), 1), dtype=np.int32) @@ -124,29 +131,36 @@ class VOCDetection(Dataset): difficult = np.zeros((len(objs), 1), dtype=np.int32) for i, obj in enumerate(objs): pattern = re.compile('', re.IGNORECASE) - name_tag = pattern.findall(str(ET.tostringlist(obj)))[0][1:-1] + name_tag = pattern.findall(str(ET.tostringlist(obj)))[0][ + 1:-1] cname = obj.find(name_tag).text.strip() gt_class[i][0] = cname2cid[cname] pattern = re.compile('', re.IGNORECASE) - diff_tag = pattern.findall(str(ET.tostringlist(obj)))[0][1:-1] + diff_tag = pattern.findall(str(ET.tostringlist(obj)))[0][ + 1:-1] try: _difficult = int(obj.find(diff_tag).text) except Exception: _difficult = 0 pattern = re.compile('', re.IGNORECASE) - box_tag = pattern.findall(str(ET.tostringlist(obj)))[0][1:-1] + box_tag = pattern.findall(str(ET.tostringlist(obj)))[0][1: + -1] box_element = obj.find(box_tag) pattern = re.compile('', re.IGNORECASE) - xmin_tag = pattern.findall(str(ET.tostringlist(box_element)))[0][1:-1] + xmin_tag = pattern.findall( + str(ET.tostringlist(box_element)))[0][1:-1] x1 = float(box_element.find(xmin_tag).text) pattern = re.compile('', re.IGNORECASE) - ymin_tag = pattern.findall(str(ET.tostringlist(box_element)))[0][1:-1] + ymin_tag = pattern.findall( + str(ET.tostringlist(box_element)))[0][1:-1] y1 = float(box_element.find(ymin_tag).text) pattern = re.compile('', re.IGNORECASE) - xmax_tag = pattern.findall(str(ET.tostringlist(box_element)))[0][1:-1] + xmax_tag = pattern.findall( + str(ET.tostringlist(box_element)))[0][1:-1] x2 = float(box_element.find(xmax_tag).text) pattern = re.compile('', re.IGNORECASE) - ymax_tag = pattern.findall(str(ET.tostringlist(box_element)))[0][1:-1] + ymax_tag = pattern.findall( + str(ET.tostringlist(box_element)))[0][1:-1] y2 = float(box_element.find(ymax_tag).text) x1 = max(0, x1) y1 = max(0, y1) @@ -176,6 +190,7 @@ class VOCDetection(Dataset): 'gt_class': gt_class, 'gt_bbox': gt_bbox, 'gt_score': gt_score, + 'gt_poly': [], 'difficult': difficult } voc_rec = (im_info, label_info) diff --git a/paddlex/cv/models/base.py b/paddlex/cv/models/base.py index adfa79f9b7003744f5fe5d401c4a31dc3a924a28..ff7b5cc5dee2d9a9b600d911823905a2a344f076 100644 --- a/paddlex/cv/models/base.py +++ b/paddlex/cv/models/base.py @@ -74,6 +74,7 @@ class BaseAPI: self.status = 'Normal' # 已完成迭代轮数,为恢复训练时的起始轮数 self.completed_epochs = 0 + self.scope = fluid.global_scope() def _get_single_card_bs(self, batch_size): if batch_size % len(self.places) == 0: @@ -85,6 +86,10 @@ class BaseAPI: 'place'])) def build_program(self): + if hasattr(paddlex, 'model_built') and paddlex.model_built: + logging.error( + "Function model.train() only can be called once in your code.") + paddlex.model_built = True # 构建训练网络 self.train_inputs, self.train_outputs = self.build_net(mode='train') self.train_prog = fluid.default_main_program() @@ -143,7 +148,7 @@ class BaseAPI: outputs=self.test_outputs, batch_size=batch_size, batch_nums=batch_num, - scope=None, + scope=self.scope, algo='KL', quantizable_op_type=["conv2d", "depthwise_conv2d", "mul"], is_full_quantize=False, @@ -341,23 +346,24 @@ class BaseAPI: var.name for var in list(self.test_inputs.values()) ] test_outputs = list(self.test_outputs.values()) - if self.__class__.__name__ == 'MaskRCNN': - from paddlex.utils.save import save_mask_inference_model - save_mask_inference_model( - dirname=save_dir, - executor=self.exe, - params_filename='__params__', - feeded_var_names=test_input_names, - target_vars=test_outputs, - main_program=self.test_prog) - else: - fluid.io.save_inference_model( - dirname=save_dir, - executor=self.exe, - params_filename='__params__', - feeded_var_names=test_input_names, - target_vars=test_outputs, - main_program=self.test_prog) + with fluid.scope_guard(self.scope): + if self.__class__.__name__ == 'MaskRCNN': + from paddlex.utils.save import save_mask_inference_model + save_mask_inference_model( + dirname=save_dir, + executor=self.exe, + params_filename='__params__', + feeded_var_names=test_input_names, + target_vars=test_outputs, + main_program=self.test_prog) + else: + fluid.io.save_inference_model( + dirname=save_dir, + executor=self.exe, + params_filename='__params__', + feeded_var_names=test_input_names, + target_vars=test_outputs, + main_program=self.test_prog) model_info = self.get_model_info() model_info['status'] = 'Infer' diff --git a/paddlex/cv/models/classifier.py b/paddlex/cv/models/classifier.py index 7821d41d587ce0499653bd051059a71ffe962c48..feace078efc060fb307de7cd6f98becf688dff88 100644 --- a/paddlex/cv/models/classifier.py +++ b/paddlex/cv/models/classifier.py @@ -232,9 +232,10 @@ class BaseClassifier(BaseAPI): true_labels = list() pred_scores = list() if not hasattr(self, 'parallel_test_prog'): - self.parallel_test_prog = fluid.CompiledProgram( - self.test_prog).with_data_parallel( - share_vars_from=self.parallel_train_prog) + with fluid.scope_guard(self.scope): + self.parallel_test_prog = fluid.CompiledProgram( + self.test_prog).with_data_parallel( + share_vars_from=self.parallel_train_prog) batch_size_each_gpu = self._get_single_card_bs(batch_size) logging.info( "Start to evaluating(total_samples={}, total_steps={})...".format( @@ -248,9 +249,11 @@ class BaseClassifier(BaseAPI): num_pad_samples = batch_size - num_samples pad_images = np.tile(images[0:1], (num_pad_samples, 1, 1, 1)) images = np.concatenate([images, pad_images]) - outputs = self.exe.run(self.parallel_test_prog, - feed={'image': images}, - fetch_list=list(self.test_outputs.values())) + with fluid.scope_guard(self.scope): + outputs = self.exe.run( + self.parallel_test_prog, + feed={'image': images}, + fetch_list=list(self.test_outputs.values())) outputs = [outputs[0][:num_samples]] true_labels.extend(labels) pred_scores.extend(outputs[0].tolist()) @@ -325,10 +328,11 @@ class BaseClassifier(BaseAPI): im = BaseClassifier._preprocess(images, transforms, self.model_type, self.__class__.__name__) - result = self.exe.run(self.test_prog, - feed={'image': im}, - fetch_list=list(self.test_outputs.values()), - use_program_cache=True) + with fluid.scope_guard(self.scope): + result = self.exe.run(self.test_prog, + feed={'image': im}, + fetch_list=list(self.test_outputs.values()), + use_program_cache=True) preds = BaseClassifier._postprocess(result, true_topk, self.labels) @@ -362,10 +366,11 @@ class BaseClassifier(BaseAPI): self.model_type, self.__class__.__name__, thread_num) - result = self.exe.run(self.test_prog, - feed={'image': im}, - fetch_list=list(self.test_outputs.values()), - use_program_cache=True) + with fluid.scope_guard(self.scope): + result = self.exe.run(self.test_prog, + feed={'image': im}, + fetch_list=list(self.test_outputs.values()), + use_program_cache=True) preds = BaseClassifier._postprocess(result, true_topk, self.labels) diff --git a/paddlex/cv/models/deeplabv3p.py b/paddlex/cv/models/deeplabv3p.py index 08531afe566300981b7f133f208a8d0ad45d542b..d4e12f7960c4d653e18fab198a1c8ed4367e062d 100644 --- a/paddlex/cv/models/deeplabv3p.py +++ b/paddlex/cv/models/deeplabv3p.py @@ -330,9 +330,10 @@ class DeepLabv3p(BaseAPI): data_generator = eval_dataset.generator( batch_size=batch_size, drop_last=False) if not hasattr(self, 'parallel_test_prog'): - self.parallel_test_prog = fluid.CompiledProgram( - self.test_prog).with_data_parallel( - share_vars_from=self.parallel_train_prog) + with fluid.scope_guard(self.scope): + self.parallel_test_prog = fluid.CompiledProgram( + self.test_prog).with_data_parallel( + share_vars_from=self.parallel_train_prog) logging.info( "Start to evaluating(total_samples={}, total_steps={})...".format( eval_dataset.num_samples, total_steps)) @@ -356,10 +357,12 @@ class DeepLabv3p(BaseAPI): pad_images = np.tile(images[0:1], (num_pad_samples, 1, 1, 1)) images = np.concatenate([images, pad_images]) feed_data = {'image': images} - outputs = self.exe.run(self.parallel_test_prog, - feed=feed_data, - fetch_list=list(self.test_outputs.values()), - return_numpy=True) + with fluid.scope_guard(self.scope): + outputs = self.exe.run( + self.parallel_test_prog, + feed=feed_data, + fetch_list=list(self.test_outputs.values()), + return_numpy=True) pred = outputs[0] if num_samples < batch_size: pred = pred[0:num_samples] @@ -453,10 +456,11 @@ class DeepLabv3p(BaseAPI): im, im_info = DeepLabv3p._preprocess( images, transforms, self.model_type, self.__class__.__name__) - result = self.exe.run(self.test_prog, - feed={'image': im}, - fetch_list=list(self.test_outputs.values()), - use_program_cache=True) + with fluid.scope_guard(self.scope): + result = self.exe.run(self.test_prog, + feed={'image': im}, + fetch_list=list(self.test_outputs.values()), + use_program_cache=True) preds = DeepLabv3p._postprocess(result, im_info) return preds[0] @@ -483,10 +487,11 @@ class DeepLabv3p(BaseAPI): img_file_list, transforms, self.model_type, self.__class__.__name__, thread_num) - result = self.exe.run(self.test_prog, - feed={'image': im}, - fetch_list=list(self.test_outputs.values()), - use_program_cache=True) + with fluid.scope_guard(self.scope): + result = self.exe.run(self.test_prog, + feed={'image': im}, + fetch_list=list(self.test_outputs.values()), + use_program_cache=True) preds = DeepLabv3p._postprocess(result, im_info) return preds diff --git a/paddlex/cv/models/faster_rcnn.py b/paddlex/cv/models/faster_rcnn.py index 1f02efe5ce7487a404c5aa1888afe1ab73684023..9d6e12c07ae1a11d3c19369752fe524beb7b001f 100644 --- a/paddlex/cv/models/faster_rcnn.py +++ b/paddlex/cv/models/faster_rcnn.py @@ -349,10 +349,12 @@ class FasterRCNN(BaseAPI): 'im_info': im_infos, 'im_shape': im_shapes, } - outputs = self.exe.run(self.test_prog, - feed=[feed_data], - fetch_list=list(self.test_outputs.values()), - return_numpy=False) + with fluid.scope_guard(self.scope): + outputs = self.exe.run( + self.test_prog, + feed=[feed_data], + fetch_list=list(self.test_outputs.values()), + return_numpy=False) res = { 'bbox': (np.array(outputs[0]), outputs[0].recursive_sequence_lengths()) @@ -450,15 +452,16 @@ class FasterRCNN(BaseAPI): im, im_resize_info, im_shape = FasterRCNN._preprocess( images, transforms, self.model_type, self.__class__.__name__) - result = self.exe.run(self.test_prog, - feed={ - 'image': im, - 'im_info': im_resize_info, - 'im_shape': im_shape - }, - fetch_list=list(self.test_outputs.values()), - return_numpy=False, - use_program_cache=True) + with fluid.scope_guard(self.scope): + result = self.exe.run(self.test_prog, + feed={ + 'image': im, + 'im_info': im_resize_info, + 'im_shape': im_shape + }, + fetch_list=list(self.test_outputs.values()), + return_numpy=False, + use_program_cache=True) preds = FasterRCNN._postprocess(result, list(self.test_outputs.keys()), @@ -493,15 +496,16 @@ class FasterRCNN(BaseAPI): img_file_list, transforms, self.model_type, self.__class__.__name__, thread_num) - result = self.exe.run(self.test_prog, - feed={ - 'image': im, - 'im_info': im_resize_info, - 'im_shape': im_shape - }, - fetch_list=list(self.test_outputs.values()), - return_numpy=False, - use_program_cache=True) + with fluid.scope_guard(self.scope): + result = self.exe.run(self.test_prog, + feed={ + 'image': im, + 'im_info': im_resize_info, + 'im_shape': im_shape + }, + fetch_list=list(self.test_outputs.values()), + return_numpy=False, + use_program_cache=True) preds = FasterRCNN._postprocess(result, list(self.test_outputs.keys()), diff --git a/paddlex/cv/models/hrnet.py b/paddlex/cv/models/hrnet.py index 3a000feee5fe6a2b6a93662e1dc65754d6e1cd68..d3af363ceac925d40552da22360759553c0090f7 100644 --- a/paddlex/cv/models/hrnet.py +++ b/paddlex/cv/models/hrnet.py @@ -1,11 +1,11 @@ # copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. -# +# # 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. @@ -24,11 +24,12 @@ class HRNet(DeepLabv3p): Args: num_classes (int): 类别数。 - width (int): 高分辨率分支中特征层的通道数量。默认值为18。可选择取值为[18, 30, 32, 40, 44, 48, 60, 64]。 + width (int|str): 高分辨率分支中特征层的通道数量。默认值为18。可选择取值为[18, 30, 32, 40, 44, 48, 60, 64, '18_small_v1']。 + '18_small_v1'是18的轻量级版本。 use_bce_loss (bool): 是否使用bce loss作为网络的损失函数,只能用于两类分割。可与dice loss同时使用。默认False。 use_dice_loss (bool): 是否使用dice loss作为网络的损失函数,只能用于两类分割,可与bce loss同时使用。 当use_bce_loss和use_dice_loss都为False时,使用交叉熵损失函数。默认False。 - class_weight (list/str): 交叉熵损失函数各类损失的权重。当class_weight为list的时候,长度应为 + class_weight (list|str): 交叉熵损失函数各类损失的权重。当class_weight为list的时候,长度应为 num_classes。当class_weight为str时, weight.lower()应为'dynamic',这时会根据每一轮各类像素的比重 自行计算相应的权重,每一类的权重为:每类的比例 * num_classes。class_weight取默认值None是,各类的权重1, 即平时使用的交叉熵损失函数。 @@ -168,6 +169,6 @@ class HRNet(DeepLabv3p): return super(HRNet, self).train( num_epochs, train_dataset, train_batch_size, eval_dataset, save_interval_epochs, log_interval_steps, save_dir, - pretrain_weights, optimizer, learning_rate, lr_decay_power, use_vdl, - sensitivities_file, eval_metric_loss, early_stop, + pretrain_weights, optimizer, learning_rate, lr_decay_power, + use_vdl, sensitivities_file, eval_metric_loss, early_stop, early_stop_patience, resume_checkpoint) diff --git a/paddlex/cv/models/load_model.py b/paddlex/cv/models/load_model.py index 55c81c2805c9a83805fb2cd0232d836f25179821..e6bbac0c238292a410096430f8ea77168e7b5341 100644 --- a/paddlex/cv/models/load_model.py +++ b/paddlex/cv/models/load_model.py @@ -25,6 +25,7 @@ from paddlex.cv.transforms import build_transforms, build_transforms_v1 def load_model(model_dir, fixed_input_shape=None): + model_scope = fluid.Scope() if not osp.exists(osp.join(model_dir, "model.yml")): raise Exception("There's not model.yml in {}".format(model_dir)) with open(osp.join(model_dir, "model.yml")) as f: @@ -52,38 +53,40 @@ def load_model(model_dir, fixed_input_shape=None): format(fixed_input_shape)) model.fixed_input_shape = fixed_input_shape - if status == "Normal" or \ - status == "Prune" or status == "fluid.save": - startup_prog = fluid.Program() - model.test_prog = fluid.Program() - with fluid.program_guard(model.test_prog, startup_prog): - with fluid.unique_name.guard(): - model.test_inputs, model.test_outputs = model.build_net( - mode='test') - model.test_prog = model.test_prog.clone(for_test=True) - model.exe.run(startup_prog) - if status == "Prune": - from .slim.prune import update_program - model.test_prog = update_program(model.test_prog, model_dir, - model.places[0]) - import pickle - with open(osp.join(model_dir, 'model.pdparams'), 'rb') as f: - load_dict = pickle.load(f) - fluid.io.set_program_state(model.test_prog, load_dict) + with fluid.scope_guard(model_scope): + if status == "Normal" or \ + status == "Prune" or status == "fluid.save": + startup_prog = fluid.Program() + model.test_prog = fluid.Program() + with fluid.program_guard(model.test_prog, startup_prog): + with fluid.unique_name.guard(): + model.test_inputs, model.test_outputs = model.build_net( + mode='test') + model.test_prog = model.test_prog.clone(for_test=True) + model.exe.run(startup_prog) + if status == "Prune": + from .slim.prune import update_program + model.test_prog = update_program(model.test_prog, model_dir, + model.places[0]) + import pickle + with open(osp.join(model_dir, 'model.pdparams'), 'rb') as f: + load_dict = pickle.load(f) + fluid.io.set_program_state(model.test_prog, load_dict) - elif status == "Infer" or \ - status == "Quant" or status == "fluid.save_inference_model": - [prog, input_names, outputs] = fluid.io.load_inference_model( - model_dir, model.exe, params_filename='__params__') - model.test_prog = prog - test_outputs_info = info['_ModelInputsOutputs']['test_outputs'] - model.test_inputs = OrderedDict() - model.test_outputs = OrderedDict() - for name in input_names: - model.test_inputs[name] = model.test_prog.global_block().var(name) - for i, out in enumerate(outputs): - var_desc = test_outputs_info[i] - model.test_outputs[var_desc[0]] = out + elif status == "Infer" or \ + status == "Quant" or status == "fluid.save_inference_model": + [prog, input_names, outputs] = fluid.io.load_inference_model( + model_dir, model.exe, params_filename='__params__') + model.test_prog = prog + test_outputs_info = info['_ModelInputsOutputs']['test_outputs'] + model.test_inputs = OrderedDict() + model.test_outputs = OrderedDict() + for name in input_names: + model.test_inputs[name] = model.test_prog.global_block().var( + name) + for i, out in enumerate(outputs): + var_desc = test_outputs_info[i] + model.test_outputs[var_desc[0]] = out if 'Transforms' in info: transforms_mode = info.get('TransformsMode', 'RGB') # 固定模型的输入shape @@ -108,6 +111,7 @@ def load_model(model_dir, fixed_input_shape=None): model.__dict__[k] = v logging.info("Model[{}] loaded.".format(info['Model'])) + model.scope = model_scope model.trainable = False model.status = status return model diff --git a/paddlex/cv/models/mask_rcnn.py b/paddlex/cv/models/mask_rcnn.py index a7cd0245f4b9aeb13b08a8f7c301dd74108ad807..731e6b3d5f3aa852c8a185e9df80351385286a39 100644 --- a/paddlex/cv/models/mask_rcnn.py +++ b/paddlex/cv/models/mask_rcnn.py @@ -289,10 +289,12 @@ class MaskRCNN(FasterRCNN): 'im_info': im_infos, 'im_shape': im_shapes, } - outputs = self.exe.run(self.test_prog, - feed=[feed_data], - fetch_list=list(self.test_outputs.values()), - return_numpy=False) + with fluid.scope_guard(self.scope): + outputs = self.exe.run( + self.test_prog, + feed=[feed_data], + fetch_list=list(self.test_outputs.values()), + return_numpy=False) res = { 'bbox': (np.array(outputs[0]), outputs[0].recursive_sequence_lengths()), @@ -385,15 +387,16 @@ class MaskRCNN(FasterRCNN): im, im_resize_info, im_shape = FasterRCNN._preprocess( images, transforms, self.model_type, self.__class__.__name__) - result = self.exe.run(self.test_prog, - feed={ - 'image': im, - 'im_info': im_resize_info, - 'im_shape': im_shape - }, - fetch_list=list(self.test_outputs.values()), - return_numpy=False, - use_program_cache=True) + with fluid.scope_guard(self.scope): + result = self.exe.run(self.test_prog, + feed={ + 'image': im, + 'im_info': im_resize_info, + 'im_shape': im_shape + }, + fetch_list=list(self.test_outputs.values()), + return_numpy=False, + use_program_cache=True) preds = MaskRCNN._postprocess(result, im_shape, list(self.test_outputs.keys()), @@ -428,15 +431,16 @@ class MaskRCNN(FasterRCNN): img_file_list, transforms, self.model_type, self.__class__.__name__, thread_num) - result = self.exe.run(self.test_prog, - feed={ - 'image': im, - 'im_info': im_resize_info, - 'im_shape': im_shape - }, - fetch_list=list(self.test_outputs.values()), - return_numpy=False, - use_program_cache=True) + with fluid.scope_guard(self.scope): + result = self.exe.run(self.test_prog, + feed={ + 'image': im, + 'im_info': im_resize_info, + 'im_shape': im_shape + }, + fetch_list=list(self.test_outputs.values()), + return_numpy=False, + use_program_cache=True) preds = MaskRCNN._postprocess(result, im_shape, list(self.test_outputs.keys()), diff --git a/paddlex/cv/models/slim/post_quantization.py b/paddlex/cv/models/slim/post_quantization.py index 29ded34d20e3af85d43759b518169df9545b66e8..c5570087821d8441174aa276d8e5ce22d5ff8e03 100644 --- a/paddlex/cv/models/slim/post_quantization.py +++ b/paddlex/cv/models/slim/post_quantization.py @@ -85,13 +85,13 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): self._support_quantize_op_type = \ list(set(QuantizationTransformPass._supported_quantizable_op_type + AddQuantDequantPass._supported_quantizable_op_type)) - + # Check inputs assert executor is not None, "The executor cannot be None." assert batch_size > 0, "The batch_size should be greater than 0." assert algo in self._support_algo_type, \ "The algo should be KL, abs_max or min_max." - + self._executor = executor self._dataset = dataset self._batch_size = batch_size @@ -154,20 +154,19 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): logging.info("Start to run batch!") for data in self._data_loader(): start = time.time() - self._executor.run( - program=self._program, - feed=data, - fetch_list=self._fetch_list, - return_numpy=False) + with fluid.scope_guard(self._scope): + self._executor.run(program=self._program, + feed=data, + fetch_list=self._fetch_list, + return_numpy=False) if self._algo == "KL": self._sample_data(batch_id) else: self._sample_threshold() end = time.time() - logging.debug('[Run batch data] Batch={}/{}, time_each_batch={} s.'.format( - str(batch_id + 1), - str(batch_ct), - str(end-start))) + logging.debug( + '[Run batch data] Batch={}/{}, time_each_batch={} s.'.format( + str(batch_id + 1), str(batch_ct), str(end - start))) batch_id += 1 if self._batch_nums and batch_id >= self._batch_nums: break @@ -194,15 +193,16 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): Returns: None ''' - feed_vars_names = [var.name for var in self._feed_list] - fluid.io.save_inference_model( - dirname=save_model_path, - feeded_var_names=feed_vars_names, - target_vars=self._fetch_list, - executor=self._executor, - params_filename='__params__', - main_program=self._program) - + with fluid.scope_guard(self._scope): + feed_vars_names = [var.name for var in self._feed_list] + fluid.io.save_inference_model( + dirname=save_model_path, + feeded_var_names=feed_vars_names, + target_vars=self._fetch_list, + executor=self._executor, + params_filename='__params__', + main_program=self._program) + def _load_model_data(self): ''' Set data loader. @@ -212,7 +212,8 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): self._data_loader = fluid.io.DataLoader.from_generator( feed_list=feed_vars, capacity=3 * self._batch_size, iterable=True) self._data_loader.set_sample_list_generator( - self._dataset.generator(self._batch_size, drop_last=True), + self._dataset.generator( + self._batch_size, drop_last=True), places=self._place) def _calculate_kl_threshold(self): @@ -235,10 +236,12 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): weight_threshold.append(abs_max_value) self._quantized_var_kl_threshold[var_name] = weight_threshold end = time.time() - logging.debug('[Calculate weight] Weight_id={}/{}, time_each_weight={} s.'.format( - str(ct), - str(len(self._quantized_weight_var_name)), - str(end-start))) + logging.debug( + '[Calculate weight] Weight_id={}/{}, time_each_weight={} s.'. + format( + str(ct), + str(len(self._quantized_weight_var_name)), str(end - + start))) ct += 1 ct = 1 @@ -257,10 +260,12 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): self._quantized_var_kl_threshold[var_name] = \ self._get_kl_scaling_factor(np.abs(sampling_data)) end = time.time() - logging.debug('[Calculate activation] Activation_id={}/{}, time_each_activation={} s.'.format( - str(ct), - str(len(self._quantized_act_var_name)), - str(end-start))) + logging.debug( + '[Calculate activation] Activation_id={}/{}, time_each_activation={} s.'. + format( + str(ct), + str(len(self._quantized_act_var_name)), + str(end - start))) ct += 1 else: for var_name in self._quantized_act_var_name: @@ -270,10 +275,10 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): self._quantized_var_kl_threshold[var_name] = \ self._get_kl_scaling_factor(np.abs(self._sampling_data[var_name])) end = time.time() - logging.debug('[Calculate activation] Activation_id={}/{}, time_each_activation={} s.'.format( - str(ct), - str(len(self._quantized_act_var_name)), - str(end-start))) + logging.debug( + '[Calculate activation] Activation_id={}/{}, time_each_activation={} s.'. + format( + str(ct), + str(len(self._quantized_act_var_name)), + str(end - start))) ct += 1 - - \ No newline at end of file diff --git a/paddlex/cv/models/slim/prune_config.py b/paddlex/cv/models/slim/prune_config.py index 4ca4215cd31dcf47bed7d3ae25c9ccae3c9a3dc8..64d7c45c7d5072f5d3826cc041ac175baa76f4fa 100644 --- a/paddlex/cv/models/slim/prune_config.py +++ b/paddlex/cv/models/slim/prune_config.py @@ -67,8 +67,7 @@ sensitivities_data = { 'https://bj.bcebos.com/paddlex/slim_prune/yolov3_darknet53.sensitivities', 'YOLOv3_ResNet34': 'https://bj.bcebos.com/paddlex/slim_prune/yolov3_resnet34.sensitivities', - 'UNet': - 'https://bj.bcebos.com/paddlex/slim_prune/unet.sensitivities', + 'UNet': 'https://bj.bcebos.com/paddlex/slim_prune/unet.sensitivities', 'DeepLabv3p_MobileNetV2_x0.25': 'https://bj.bcebos.com/paddlex/slim_prune/deeplab_mobilenetv2_x0.25_no_aspp_decoder.sensitivities', 'DeepLabv3p_MobileNetV2_x0.5': @@ -103,8 +102,8 @@ def get_sensitivities(flag, model, save_dir): model_type = model_name + '_' + model.backbone if model_type.startswith('DeepLabv3p_Xception'): model_type = model_type + '_' + 'aspp' + '_' + 'decoder' - elif hasattr(model, 'encoder_with_aspp') or hasattr( - model, 'enable_decoder'): + elif hasattr(model, 'encoder_with_aspp') or hasattr(model, + 'enable_decoder'): model_type = model_type + '_' + 'aspp' + '_' + 'decoder' if osp.isfile(flag): return flag @@ -116,7 +115,6 @@ def get_sensitivities(flag, model, save_dir): paddlex.utils.download(url, path=save_dir) return osp.join(save_dir, fname) - # try: # hub.download(fname, save_path=save_dir) # except Exception as e: @@ -126,7 +124,7 @@ def get_sensitivities(flag, model, save_dir): # model_type, fname)) # elif isinstance(e, hub.ServerConnectionError): # raise Exception( -# "Cannot get reource for model {}(key='{}'), please check your internet connecgtion" +# "Cannot get reource for model {}(key='{}'), please check your internet connection" # .format(model_type, fname)) # else: # raise Exception( @@ -162,27 +160,29 @@ def get_prune_params(model): if model_type == 'AlexNet': prune_names.remove('conv5_weights') if model_type == 'ShuffleNetV2': - not_prune_names = ['stage_2_1_conv5_weights', - 'stage_2_1_conv3_weights', - 'stage_2_2_conv3_weights', - 'stage_2_3_conv3_weights', - 'stage_2_4_conv3_weights', - 'stage_3_1_conv5_weights', - 'stage_3_1_conv3_weights', - 'stage_3_2_conv3_weights', - 'stage_3_3_conv3_weights', - 'stage_3_4_conv3_weights', - 'stage_3_5_conv3_weights', - 'stage_3_6_conv3_weights', - 'stage_3_7_conv3_weights', - 'stage_3_8_conv3_weights', - 'stage_4_1_conv5_weights', - 'stage_4_1_conv3_weights', - 'stage_4_2_conv3_weights', - 'stage_4_3_conv3_weights', - 'stage_4_4_conv3_weights',] + not_prune_names = [ + 'stage_2_1_conv5_weights', + 'stage_2_1_conv3_weights', + 'stage_2_2_conv3_weights', + 'stage_2_3_conv3_weights', + 'stage_2_4_conv3_weights', + 'stage_3_1_conv5_weights', + 'stage_3_1_conv3_weights', + 'stage_3_2_conv3_weights', + 'stage_3_3_conv3_weights', + 'stage_3_4_conv3_weights', + 'stage_3_5_conv3_weights', + 'stage_3_6_conv3_weights', + 'stage_3_7_conv3_weights', + 'stage_3_8_conv3_weights', + 'stage_4_1_conv5_weights', + 'stage_4_1_conv3_weights', + 'stage_4_2_conv3_weights', + 'stage_4_3_conv3_weights', + 'stage_4_4_conv3_weights', + ] for name in not_prune_names: - prune_names.remove(name) + prune_names.remove(name) elif model_type == "MobileNetV1": prune_names.append("conv1_weights") for param in program.global_block().all_parameters(): diff --git a/paddlex/cv/models/utils/pretrain_weights.py b/paddlex/cv/models/utils/pretrain_weights.py index d13668e7b19bfceabf370e66db617db014636d08..b50cdf071a58de5718108b163af32426f0231a9c 100644 --- a/paddlex/cv/models/utils/pretrain_weights.py +++ b/paddlex/cv/models/utils/pretrain_weights.py @@ -83,7 +83,7 @@ coco_pretrain = { 'YOLOv3_MobileNetV1_COCO': 'https://paddlemodels.bj.bcebos.com/object_detection/yolov3_mobilenet_v1.tar', 'YOLOv3_MobileNetV3_large_COCO': - 'https://paddlemodels.bj.bcebos.com/object_detection/yolov3_mobilenet_v3.pdparams', + 'https://bj.bcebos.com/paddlex/models/yolov3_mobilenet_v3.tar', 'YOLOv3_ResNet34_COCO': 'https://paddlemodels.bj.bcebos.com/object_detection/yolov3_r34.tar', 'YOLOv3_ResNet50_vd_COCO': @@ -203,7 +203,7 @@ def get_pretrain_weights(flag, class_name, backbone, save_dir): backbone)) elif isinstance(e, hub.ServerConnectionError): raise Exception( - "Cannot get reource for backbone {}, please check your internet connecgtion" + "Cannot get reource for backbone {}, please check your internet connection" .format(backbone)) else: raise Exception( @@ -231,7 +231,7 @@ def get_pretrain_weights(flag, class_name, backbone, save_dir): backbone)) elif isinstance(hub.ServerConnectionError): raise Exception( - "Cannot get reource for backbone {}, please check your internet connecgtion" + "Cannot get reource for backbone {}, please check your internet connection" .format(backbone)) else: raise Exception( diff --git a/paddlex/cv/models/yolo_v3.py b/paddlex/cv/models/yolo_v3.py index 73e144ca7b2cd1680ab82e0882b63b89cfb11679..dce18afe10344a837840445957564d7f20e21c9f 100644 --- a/paddlex/cv/models/yolo_v3.py +++ b/paddlex/cv/models/yolo_v3.py @@ -318,10 +318,12 @@ class YOLOv3(BaseAPI): images = np.array([d[0] for d in data]) im_sizes = np.array([d[1] for d in data]) feed_data = {'image': images, 'im_size': im_sizes} - outputs = self.exe.run(self.test_prog, - feed=[feed_data], - fetch_list=list(self.test_outputs.values()), - return_numpy=False) + with fluid.scope_guard(self.scope): + outputs = self.exe.run( + self.test_prog, + feed=[feed_data], + fetch_list=list(self.test_outputs.values()), + return_numpy=False) res = { 'bbox': (np.array(outputs[0]), outputs[0].recursive_sequence_lengths()) @@ -368,14 +370,7 @@ class YOLOv3(BaseAPI): return im, im_size @staticmethod - def _postprocess(results, test_outputs_keys, batch_size, num_classes, - labels): - res = { - k: (np.array(v), v.recursive_sequence_lengths()) - for k, v in zip(list(test_outputs_keys), results) - } - res['im_id'] = (np.array( - [[i] for i in range(batch_size)]).astype('int32'), [[]]) + def _postprocess(res, batch_size, num_classes, labels): clsid2catid = dict({i: i for i in range(num_classes)}) xywh_results = bbox2out([res], clsid2catid) preds = [[] for i in range(batch_size)] @@ -411,15 +406,21 @@ class YOLOv3(BaseAPI): im, im_size = YOLOv3._preprocess(images, transforms, self.model_type, self.__class__.__name__) - result = self.exe.run(self.test_prog, - feed={'image': im, - 'im_size': im_size}, - fetch_list=list(self.test_outputs.values()), - return_numpy=False, - use_program_cache=True) + with fluid.scope_guard(self.scope): + result = self.exe.run(self.test_prog, + feed={'image': im, + 'im_size': im_size}, + fetch_list=list(self.test_outputs.values()), + return_numpy=False, + use_program_cache=True) - preds = YOLOv3._postprocess(result, - list(self.test_outputs.keys()), + res = { + k: (np.array(v), v.recursive_sequence_lengths()) + for k, v in zip(list(self.test_outputs.keys()), result) + } + res['im_id'] = (np.array( + [[i] for i in range(len(images))]).astype('int32'), [[]]) + preds = YOLOv3._postprocess(res, len(images), self.num_classes, self.labels) return preds[0] @@ -448,15 +449,21 @@ class YOLOv3(BaseAPI): self.model_type, self.__class__.__name__, thread_num) - result = self.exe.run(self.test_prog, - feed={'image': im, - 'im_size': im_size}, - fetch_list=list(self.test_outputs.values()), - return_numpy=False, - use_program_cache=True) + with fluid.scope_guard(self.scope): + result = self.exe.run(self.test_prog, + feed={'image': im, + 'im_size': im_size}, + fetch_list=list(self.test_outputs.values()), + return_numpy=False, + use_program_cache=True) - preds = YOLOv3._postprocess(result, - list(self.test_outputs.keys()), + res = { + k: (np.array(v), v.recursive_sequence_lengths()) + for k, v in zip(list(self.test_outputs.keys()), result) + } + res['im_id'] = (np.array( + [[i] for i in range(len(img_file_list))]).astype('int32'), [[]]) + preds = YOLOv3._postprocess(res, len(img_file_list), self.num_classes, self.labels) return preds diff --git a/paddlex/cv/nets/hrnet.py b/paddlex/cv/nets/hrnet.py index a7934d385d4a53fd936410e37d3896fe21cb17ee..561c7594da2904632386c0d88e9d841c047fb2d2 100644 --- a/paddlex/cv/nets/hrnet.py +++ b/paddlex/cv/nets/hrnet.py @@ -51,15 +51,38 @@ class HRNet(object): self.width = width self.has_se = has_se + self.num_modules = { + '18_small_v1': [1, 1, 1, 1], + '18': [1, 1, 4, 3], + '30': [1, 1, 4, 3], + '32': [1, 1, 4, 3], + '40': [1, 1, 4, 3], + '44': [1, 1, 4, 3], + '48': [1, 1, 4, 3], + '60': [1, 1, 4, 3], + '64': [1, 1, 4, 3] + } + self.num_blocks = { + '18_small_v1': [[1], [2, 2], [2, 2, 2], [2, 2, 2, 2]], + '18': [[4], [4, 4], [4, 4, 4], [4, 4, 4, 4]], + '30': [[4], [4, 4], [4, 4, 4], [4, 4, 4, 4]], + '32': [[4], [4, 4], [4, 4, 4], [4, 4, 4, 4]], + '40': [[4], [4, 4], [4, 4, 4], [4, 4, 4, 4]], + '44': [[4], [4, 4], [4, 4, 4], [4, 4, 4, 4]], + '48': [[4], [4, 4], [4, 4, 4], [4, 4, 4, 4]], + '60': [[4], [4, 4], [4, 4, 4], [4, 4, 4, 4]], + '64': [[4], [4, 4], [4, 4, 4], [4, 4, 4, 4]] + } self.channels = { - 18: [[18, 36], [18, 36, 72], [18, 36, 72, 144]], - 30: [[30, 60], [30, 60, 120], [30, 60, 120, 240]], - 32: [[32, 64], [32, 64, 128], [32, 64, 128, 256]], - 40: [[40, 80], [40, 80, 160], [40, 80, 160, 320]], - 44: [[44, 88], [44, 88, 176], [44, 88, 176, 352]], - 48: [[48, 96], [48, 96, 192], [48, 96, 192, 384]], - 60: [[60, 120], [60, 120, 240], [60, 120, 240, 480]], - 64: [[64, 128], [64, 128, 256], [64, 128, 256, 512]], + '18_small_v1': [[32], [16, 32], [16, 32, 64], [16, 32, 64, 128]], + '18': [[64], [18, 36], [18, 36, 72], [18, 36, 72, 144]], + '30': [[64], [30, 60], [30, 60, 120], [30, 60, 120, 240]], + '32': [[64], [32, 64], [32, 64, 128], [32, 64, 128, 256]], + '40': [[64], [40, 80], [40, 80, 160], [40, 80, 160, 320]], + '44': [[64], [44, 88], [44, 88, 176], [44, 88, 176, 352]], + '48': [[64], [48, 96], [48, 96, 192], [48, 96, 192, 384]], + '60': [[64], [60, 120], [60, 120, 240], [60, 120, 240, 480]], + '64': [[64], [64, 128], [64, 128, 256], [64, 128, 256, 512]], } self.freeze_at = freeze_at @@ -73,31 +96,38 @@ class HRNet(object): def net(self, input): width = self.width - channels_2, channels_3, channels_4 = self.channels[width] - num_modules_2, num_modules_3, num_modules_4 = 1, 4, 3 + channels_1, channels_2, channels_3, channels_4 = self.channels[str( + width)] + num_modules_1, num_modules_2, num_modules_3, num_modules_4 = self.num_modules[ + str(width)] + num_blocks_1, num_blocks_2, num_blocks_3, num_blocks_4 = self.num_blocks[ + str(width)] x = self.conv_bn_layer( input=input, filter_size=3, - num_filters=64, + num_filters=channels_1[0], stride=2, if_act=True, name='layer1_1') x = self.conv_bn_layer( input=x, filter_size=3, - num_filters=64, + num_filters=channels_1[0], stride=2, if_act=True, name='layer1_2') - la1 = self.layer1(x, name='layer2') + la1 = self.layer1(x, num_blocks_1, channels_1, name='layer2') tr1 = self.transition_layer([la1], [256], channels_2, name='tr1') - st2 = self.stage(tr1, num_modules_2, channels_2, name='st2') + st2 = self.stage( + tr1, num_modules_2, num_blocks_2, channels_2, name='st2') tr2 = self.transition_layer(st2, channels_2, channels_3, name='tr2') - st3 = self.stage(tr2, num_modules_3, channels_3, name='st3') + st3 = self.stage( + tr2, num_modules_3, num_blocks_3, channels_3, name='st3') tr3 = self.transition_layer(st3, channels_3, channels_4, name='tr3') - st4 = self.stage(tr3, num_modules_4, channels_4, name='st4') + st4 = self.stage( + tr3, num_modules_4, num_blocks_4, channels_4, name='st4') # classification if self.num_classes: @@ -139,12 +169,12 @@ class HRNet(object): self.end_points = st4 return st4[-1] - def layer1(self, input, name=None): + def layer1(self, input, num_blocks, channels, name=None): conv = input - for i in range(4): + for i in range(num_blocks[0]): conv = self.bottleneck_block( conv, - num_filters=64, + num_filters=channels[0], downsample=True if i == 0 else False, name=name + '_' + str(i + 1)) return conv @@ -178,7 +208,7 @@ class HRNet(object): out = [] for i in range(len(channels)): residual = x[i] - for j in range(block_num): + for j in range(block_num[i]): residual = self.basic_block( residual, channels[i], @@ -240,10 +270,11 @@ class HRNet(object): def high_resolution_module(self, x, + num_blocks, channels, multi_scale_output=True, name=None): - residual = self.branches(x, 4, channels, name=name) + residual = self.branches(x, num_blocks, channels, name=name) out = self.fuse_layers( residual, channels, @@ -254,6 +285,7 @@ class HRNet(object): def stage(self, x, num_modules, + num_blocks, channels, multi_scale_output=True, name=None): @@ -262,12 +294,13 @@ class HRNet(object): if i == num_modules - 1 and multi_scale_output == False: out = self.high_resolution_module( out, + num_blocks, channels, multi_scale_output=False, name=name + '_' + str(i + 1)) else: out = self.high_resolution_module( - out, channels, name=name + '_' + str(i + 1)) + out, num_blocks, channels, name=name + '_' + str(i + 1)) return out diff --git a/paddlex/cv/nets/segmentation/hrnet.py b/paddlex/cv/nets/segmentation/hrnet.py index 6c7d8d93692e40047fa4ceb2f4153c18cee06ccd..209da9b507ba8e59a073fab616418c378a1e7cd5 100644 --- a/paddlex/cv/nets/segmentation/hrnet.py +++ b/paddlex/cv/nets/segmentation/hrnet.py @@ -82,7 +82,8 @@ class HRNet(object): st4[3] = fluid.layers.resize_bilinear(st4[3], out_shape=shape) out = fluid.layers.concat(st4, axis=1) - last_channels = sum(self.backbone.channels[self.backbone.width][-1]) + last_channels = sum(self.backbone.channels[str(self.backbone.width)][ + -1]) out = self._conv_bn_layer( input=out, diff --git a/paddlex/cv/transforms/__init__.py b/paddlex/cv/transforms/__init__.py index 1056964185433b8edfc601b6acf03ab87addbdc9..fc8494c7fd279fce03e70993a64349be38d11cfb 100644 --- a/paddlex/cv/transforms/__init__.py +++ b/paddlex/cv/transforms/__init__.py @@ -16,6 +16,9 @@ from . import cls_transforms from . import det_transforms from . import seg_transforms +from . import visualize +visualize = visualize.visualize + def build_transforms(model_type, transforms_info, to_rgb=True): if model_type == "classifier": diff --git a/paddlex/cv/transforms/cls_transforms.py b/paddlex/cv/transforms/cls_transforms.py index 6b11a0839d2b2bc891f8eb29dfe666a69d0c8f5d..606bb5b8d6eb4605510f734d9b737811ec22c477 100644 --- a/paddlex/cv/transforms/cls_transforms.py +++ b/paddlex/cv/transforms/cls_transforms.py @@ -32,10 +32,8 @@ class ClsTransform: class Compose(ClsTransform): """根据数据预处理/增强算子对输入数据进行操作。 所有操作的输入图像流形状均是[H, W, C],其中H为图像高,W为图像宽,C为图像通道数。 - Args: transforms (list): 数据预处理/增强算子。 - Raises: TypeError: 形参数据类型不满足需求。 ValueError: 数据长度不匹配。 @@ -434,6 +432,7 @@ class RandomDistort(ClsTransform): params['im'] = im if np.random.uniform(0, 1) < prob: im = ops[id](**params) + im = im.astype('float32') if label is None: return (im, ) else: diff --git a/paddlex/cv/transforms/det_transforms.py b/paddlex/cv/transforms/det_transforms.py index 655effeaff74c8569a13527fe62436e352229b19..9338b3062b1e459ad9453bcc7bd76a5ec9a09697 100644 --- a/paddlex/cv/transforms/det_transforms.py +++ b/paddlex/cv/transforms/det_transforms.py @@ -41,10 +41,8 @@ class DetTransform: class Compose(DetTransform): """根据数据预处理/增强列表对输入数据进行操作。 所有操作的输入图像流形状均是[H, W, C],其中H为图像高,W为图像宽,C为图像通道数。 - Args: transforms (list): 数据预处理/增强列表。 - Raises: TypeError: 形参数据类型不满足需求。 ValueError: 数据长度不匹配。 @@ -623,6 +621,7 @@ class RandomDistort(DetTransform): if np.random.uniform(0, 1) < prob: im = ops[id](**params) + im = im.astype('float32') if label_info is None: return (im, im_info) else: @@ -829,7 +828,7 @@ class RandomExpand(DetTransform): 'gt_class' not in label_info: raise TypeError('Cannot do RandomExpand! ' + \ 'Becasuse gt_bbox/gt_class is not in label_info!') - if np.random.uniform(0., 1.) < self.prob: + if np.random.uniform(0., 1.) > self.prob: return (im, im_info, label_info) if 'gt_class' in label_info and 0 in label_info['gt_class']: diff --git a/paddlex/cv/transforms/seg_transforms.py b/paddlex/cv/transforms/seg_transforms.py index 4932a7002983ff62c04ec0ed992efac323ee546b..f2bfb32ebeed11f84c27ab7f1d8e8920f21699a7 100644 --- a/paddlex/cv/transforms/seg_transforms.py +++ b/paddlex/cv/transforms/seg_transforms.py @@ -35,14 +35,11 @@ class SegTransform: class Compose(SegTransform): """根据数据预处理/增强算子对输入数据进行操作。 所有操作的输入图像流形状均是[H, W, C],其中H为图像高,W为图像宽,C为图像通道数。 - Args: transforms (list): 数据预处理/增强算子。 - Raises: TypeError: transforms不是list对象 ValueError: transforms元素个数小于1。 - """ def __init__(self, transforms): @@ -71,7 +68,6 @@ class Compose(SegTransform): 图像在过resize前shape为(200, 300), 过padding前shape为 (400, 600) label (str/np.ndarray): 标注图像路径/标注图像np.ndarray数据。 - Returns: tuple: 根据网络所需字段所组成的tuple;字段由transforms中的最后一个数据预处理操作决定。 """ @@ -1054,6 +1050,7 @@ class RandomDistort(SegTransform): params['im'] = im if np.random.uniform(0, 1) < prob: im = ops[id](**params) + im = im.astype('float32') if label is None: return (im, im_info) else: diff --git a/paddlex/cv/transforms/visualize.py b/paddlex/cv/transforms/visualize.py new file mode 100644 index 0000000000000000000000000000000000000000..2efb0fb8f26f1f5d1ec3f2e6f3239b38f3336c12 --- /dev/null +++ b/paddlex/cv/transforms/visualize.py @@ -0,0 +1,306 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# 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. + +import os +import os.path as osp +import cv2 +from PIL import Image +import numpy as np +import math +from .imgaug_support import execute_imgaug +from .cls_transforms import ClsTransform +from .det_transforms import DetTransform +from .seg_transforms import SegTransform +import paddlex as pdx +from paddlex.cv.models.utils.visualize import get_color_map_list + + +def _draw_rectangle_and_cname(img, xmin, ymin, xmax, ymax, cname, color): + """ 根据提供的标注信息,给图片描绘框体和类别显示 + + Args: + img: 图片路径 + xmin: 检测框最小的x坐标 + ymin: 检测框最小的y坐标 + xmax: 检测框最大的x坐标 + ymax: 检测框最大的y坐标 + cname: 类别信息 + color: 类别与颜色的对应信息 + """ + # 描绘检测框 + line_width = math.ceil(2 * max(img.shape[0:2]) / 600) + cv2.rectangle( + img, + pt1=(xmin, ymin), + pt2=(xmax, ymax), + color=color, + thickness=line_width) + return img + +def cls_compose(im, label=None, transforms=None, vdl_writer=None, step=0): + """ + Args: + im (str/np.ndarray): 图像路径/图像np.ndarray数据。 + label (int): 每张图像所对应的类别序号。 + vdl_writer (visualdl.LogWriter): VisualDL存储器,日志信息将保存在其中。 + 当为None时,不对日志进行保存。默认为None。 + step (int): 数据预处理的轮数,当vdl_writer不为None时有效。默认为0。 + + Returns: + tuple: 根据网络所需字段所组成的tuple; + 字段由transforms中的最后一个数据预处理操作决定。 + """ + if isinstance(im, np.ndarray): + if len(im.shape) != 3: + raise Exception( + "im should be 3-dimension, but now is {}-dimensions". + format(len(im.shape))) + else: + try: + im = cv2.imread(im).astype('float32') + except: + raise TypeError('Can\'t read The image file {}!'.format(im)) + im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB) + if vdl_writer is not None: + vdl_writer.add_image(tag='0. OriginalImage/' + str(step), + img=im, + step=0) + op_id = 1 + for op in transforms: + if isinstance(op, ClsTransform): + if vdl_writer is not None and hasattr(op, 'prob'): + op.prob = 1.0 + outputs = op(im, label) + im = outputs[0] + if len(outputs) == 2: + label = outputs[1] + if isinstance(op, pdx.cv.transforms.cls_transforms.Normalize): + continue + else: + import imgaug.augmenters as iaa + if isinstance(op, iaa.Augmenter): + im = execute_imgaug(op, im) + outputs = (im, ) + if label is not None: + outputs = (im, label) + if vdl_writer is not None: + tag = str(op_id) + '. ' + op.__class__.__name__ + '/' + str(step) + vdl_writer.add_image(tag=tag, + img=im, + step=0) + op_id += 1 + +def det_compose(im, im_info=None, label_info=None, transforms=None, vdl_writer=None, step=0, + labels=[], catid2color=None): + def decode_image(im_file, im_info, label_info): + if im_info is None: + im_info = dict() + if isinstance(im_file, np.ndarray): + if len(im_file.shape) != 3: + raise Exception( + "im should be 3-dimensions, but now is {}-dimensions". + format(len(im_file.shape))) + im = im_file + else: + try: + im = cv2.imread(im_file).astype('float32') + except: + raise TypeError('Can\'t read The image file {}!'.format( + im_file)) + im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB) + # make default im_info with [h, w, 1] + im_info['im_resize_info'] = np.array( + [im.shape[0], im.shape[1], 1.], dtype=np.float32) + im_info['image_shape'] = np.array([im.shape[0], + im.shape[1]]).astype('int32') + use_mixup = False + for t in transforms: + if type(t).__name__ == 'MixupImage': + use_mixup = True + if not use_mixup: + if 'mixup' in im_info: + del im_info['mixup'] + # decode mixup image + if 'mixup' in im_info: + im_info['mixup'] = \ + decode_image(im_info['mixup'][0], + im_info['mixup'][1], + im_info['mixup'][2]) + if label_info is None: + return (im, im_info) + else: + return (im, im_info, label_info) + + outputs = decode_image(im, im_info, label_info) + im = outputs[0] + im_info = outputs[1] + if len(outputs) == 3: + label_info = outputs[2] + if vdl_writer is not None: + vdl_writer.add_image(tag='0. OriginalImage/' + str(step), + img=im, + step=0) + op_id = 1 + bboxes = label_info['gt_bbox'] + transforms = [None] + transforms + for op in transforms: + if im is None: + return None + if isinstance(op, DetTransform) or op is None: + if vdl_writer is not None and hasattr(op, 'prob'): + op.prob = 1.0 + if op is not None: + outputs = op(im, im_info, label_info) + else: + outputs = (im, im_info, label_info) + im = outputs[0] + vdl_im = im + if vdl_writer is not None: + if isinstance(op, pdx.cv.transforms.det_transforms.ResizeByShort): + scale = outputs[1]['im_resize_info'][2] + bboxes = bboxes * scale + elif isinstance(op, pdx.cv.transforms.det_transforms.Resize): + h = outputs[1]['image_shape'][0] + w = outputs[1]['image_shape'][1] + target_size = op.target_size + if isinstance(target_size, int): + h_scale = float(target_size) / h + w_scale = float(target_size) / w + else: + h_scale = float(target_size[0]) / h + w_scale = float(target_size[1]) / w + bboxes[:,0] = bboxes[:,0] * w_scale + bboxes[:,1] = bboxes[:,1] * h_scale + bboxes[:,2] = bboxes[:,2] * w_scale + bboxes[:,3] = bboxes[:,3] * h_scale + else: + bboxes = outputs[2]['gt_bbox'] + if not isinstance(op, pdx.cv.transforms.det_transforms.RandomHorizontalFlip): + for i in range(bboxes.shape[0]): + bbox = bboxes[i] + cname = labels[outputs[2]['gt_class'][i][0]-1] + vdl_im = _draw_rectangle_and_cname(vdl_im, + int(bbox[0]), + int(bbox[1]), + int(bbox[2]), + int(bbox[3]), + cname, + catid2color[outputs[2]['gt_class'][i][0]-1]) + if isinstance(op, pdx.cv.transforms.det_transforms.Normalize): + continue + else: + im = execute_imgaug(op, im) + if label_info is not None: + outputs = (im, im_info, label_info) + else: + outputs = (im, im_info) + vdl_im = im + if vdl_writer is not None: + tag = str(op_id) + '. ' + op.__class__.__name__ + '/' + str(step) + if op is None: + tag = str(op_id) + '. OriginalImageWithGTBox/' + str(step) + vdl_writer.add_image(tag=tag, + img=vdl_im, + step=0) + op_id += 1 + +def seg_compose(im, im_info=None, label=None, transforms=None, vdl_writer=None, step=0): + if im_info is None: + im_info = list() + if isinstance(im, np.ndarray): + if len(im.shape) != 3: + raise Exception( + "im should be 3-dimensions, but now is {}-dimensions". + format(len(im.shape))) + else: + try: + im = cv2.imread(im).astype('float32') + except: + raise ValueError('Can\'t read The image file {}!'.format(im)) + im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB) + if label is not None: + if not isinstance(label, np.ndarray): + label = np.asarray(Image.open(label)) + if vdl_writer is not None: + vdl_writer.add_image(tag='0. OriginalImage' + '/' + str(step), + img=im, + step=0) + op_id = 1 + for op in transforms: + if isinstance(op, SegTransform): + outputs = op(im, im_info, label) + im = outputs[0] + if len(outputs) >= 2: + im_info = outputs[1] + if len(outputs) == 3: + label = outputs[2] + if isinstance(op, pdx.cv.transforms.seg_transforms.Normalize): + continue + else: + im = execute_imgaug(op, im) + if label is not None: + outputs = (im, im_info, label) + else: + outputs = (im, im_info) + if vdl_writer is not None: + tag = str(op_id) + '. ' + op.__class__.__name__ + '/' + str(step) + vdl_writer.add_image(tag=tag, + img=im, + step=0) + op_id += 1 + +def visualize(dataset, img_count=3, save_dir='vdl_output'): + '''对数据预处理/增强中间结果进行可视化。 + 可使用VisualDL查看中间结果: + 1. VisualDL启动方式: visualdl --logdir vdl_output --port 8001 + 2. 浏览器打开 https://0.0.0.0:8001即可, + 其中0.0.0.0为本机访问,如为远程服务, 改成相应机器IP + + Args: + dataset (paddlex.datasets): 数据集读取器。 + img_count (int): 需要进行数据预处理/增强的图像数目。默认为3。 + save_dir (str): 日志保存的路径。默认为'vdl_output'。 + ''' + if dataset.num_samples < img_count: + img_count = dataset.num_samples + transforms = dataset.transforms + if not osp.isdir(save_dir): + if osp.exists(save_dir): + os.remove(save_dir) + os.makedirs(save_dir) + from visualdl import LogWriter + vdl_save_dir = osp.join(save_dir, 'image_transforms') + vdl_writer = LogWriter(vdl_save_dir) + for i, data in enumerate(dataset.iterator()): + if i == img_count: + break + data.append(transforms.transforms) + data.append(vdl_writer) + data.append(i) + if isinstance(transforms, ClsTransform): + cls_compose(*data) + elif isinstance(transforms, DetTransform): + labels = dataset.labels + color_map = get_color_map_list(len(labels) + 1) + catid2color = {} + for catid in range(len(labels)): + catid2color[catid] = color_map[catid + 1] + data.append(labels) + data.append(catid2color) + det_compose(*data) + elif isinstance(transforms, SegTransform): + seg_compose(*data) + else: + raise Exception('The transform must the subclass of \ + ClsTransform or DetTransform or SegTransform!') diff --git a/paddlex/deploy.py b/paddlex/deploy.py index f3f0b3e65018bdcae34d48d813e7588ecd993351..7682365fd385132bf719f1864c1eeea1575ddcc3 100644 --- a/paddlex/deploy.py +++ b/paddlex/deploy.py @@ -205,7 +205,7 @@ class Predictor: """ preprocessed_input = self.preprocess([image]) model_pred = self.raw_predict(preprocessed_input) - im_shape = None if 'im_shape' in preprocessed_input else preprocessed_input[ + im_shape = None if 'im_shape' not in preprocessed_input else preprocessed_input[ 'im_shape'] results = self.postprocess( model_pred, topk=topk, batch_size=1, im_shape=im_shape) diff --git a/paddlex/tools/x2coco.py b/paddlex/tools/x2coco.py index 4c893dcc9319ffc4353d4e376a802301d047120a..48a8b3d8ba4cc6a4261ad809d9e9c957390da40f 100644 --- a/paddlex/tools/x2coco.py +++ b/paddlex/tools/x2coco.py @@ -100,7 +100,7 @@ class LabelMe2COCO(X2COCO): image["height"] = json_info["imageHeight"] image["width"] = json_info["imageWidth"] image["id"] = image_id + 1 - image["file_name"] = json_info["imagePath"].split("/")[-1] + image["file_name"] = osp.split(json_info["imagePath"])[-1] return image def generate_polygon_anns_field(self, height, width, diff --git a/tools/codestyle/clang_format.hook b/tools/codestyle/clang_format.hook index 1d928216867c0ba3897d71542fea44debf8d72a0..14300746ac343fa56c690bc43fc02659d690f73c 100755 --- a/tools/codestyle/clang_format.hook +++ b/tools/codestyle/clang_format.hook @@ -1,15 +1,15 @@ #!/bin/bash -set -e - -readonly VERSION="3.8" - -version=$(clang-format -version) - -if ! [[ $version == *"$VERSION"* ]]; then - echo "clang-format version check failed." - echo "a version contains '$VERSION' is needed, but get '$version'" - echo "you can install the right version, and make an soft-link to '\$PATH' env" - exit -1 -fi - -clang-format $@ +# set -e +# +# readonly VERSION="3.8" +# +# version=$(clang-format -version) +# +# if ! [[ $version == *"$VERSION"* ]]; then +# echo "clang-format version check failed." +# echo "a version contains '$VERSION' is needed, but get '$version'" +# echo "you can install the right version, and make an soft-link to '\$PATH' env" +# exit -1 +# fi +# +# clang-format $@ diff --git a/new_tutorials/train/segmentation/fast_scnn.py b/tutorials/train/segmentation/fast_scnn.py similarity index 100% rename from new_tutorials/train/segmentation/fast_scnn.py rename to tutorials/train/segmentation/fast_scnn.py