From bf899a2acac37567fc3ba33717c7a033959f3c18 Mon Sep 17 00:00:00 2001 From: AdamEiffel <2930043128@qq.com> Date: Tue, 29 Nov 2022 10:44:47 +0800 Subject: [PATCH 01/58] init uploads --- HelmetIdentification/CMakeLists.txt | 19 + HelmetIdentification/model/Helmet_yolov5.cfg | 12 + HelmetIdentification/model/imgclass.names | 3 + HelmetIdentification/readme.md | 1 + HelmetIdentification/src/CMakeLists.txt | 47 +++ HelmetIdentification/src/DataType.h | 94 +++++ HelmetIdentification/src/Hungarian.cpp | 165 +++++++++ HelmetIdentification/src/Hungarian.h | 46 +++ HelmetIdentification/src/KalmanTracker.cpp | 143 ++++++++ HelmetIdentification/src/KalmanTracker.h | 39 +++ HelmetIdentification/src/MOTConnection.cpp | 262 ++++++++++++++ HelmetIdentification/src/MOTConnection.h | 78 +++++ HelmetIdentification/src/cropResizePaste.hpp | 117 +++++++ HelmetIdentification/src/main-image.cpp | 170 +++++++++ HelmetIdentification/src/main.cpp | 283 +++++++++++++++ HelmetIdentification/src/utils.h | 351 +++++++++++++++++++ 16 files changed, 1830 insertions(+) create mode 100644 HelmetIdentification/CMakeLists.txt create mode 100644 HelmetIdentification/model/Helmet_yolov5.cfg create mode 100644 HelmetIdentification/model/imgclass.names create mode 100644 HelmetIdentification/readme.md create mode 100644 HelmetIdentification/src/CMakeLists.txt create mode 100644 HelmetIdentification/src/DataType.h create mode 100644 HelmetIdentification/src/Hungarian.cpp create mode 100644 HelmetIdentification/src/Hungarian.h create mode 100644 HelmetIdentification/src/KalmanTracker.cpp create mode 100644 HelmetIdentification/src/KalmanTracker.h create mode 100644 HelmetIdentification/src/MOTConnection.cpp create mode 100644 HelmetIdentification/src/MOTConnection.h create mode 100644 HelmetIdentification/src/cropResizePaste.hpp create mode 100644 HelmetIdentification/src/main-image.cpp create mode 100644 HelmetIdentification/src/main.cpp create mode 100644 HelmetIdentification/src/utils.h diff --git a/HelmetIdentification/CMakeLists.txt b/HelmetIdentification/CMakeLists.txt new file mode 100644 index 0000000..e0e21a9 --- /dev/null +++ b/HelmetIdentification/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (c) Huawei Technologies Co., Ltd. 2021. All rights reserved. + +# 最低CMake版本 +cmake_minimum_required(VERSION 3.5.1) + +# 项目名 +project(HelmetIdentification) + +# 配置环境变量MX_SDK_HOME,如:/home/xxxxxxx/MindX_SDK/mxVision,可在远程环境中用指令env查看 +set(MX_SDK_HOME $ENV{MX_SDK_HOME}) + +if (NOT DEFINED ENV{MX_SDK_HOME}) + set(MX_SDK_HOME "/usr/local/Ascend/mindx_sdk") + message(STATUS "set default MX_SDK_HOME: ${MX_SDK_HOME}") +else () + message(STATUS "env MX_SDK_HOME: ${MX_SDK_HOME}") +endif() + +add_subdirectory("./src") \ No newline at end of file diff --git a/HelmetIdentification/model/Helmet_yolov5.cfg b/HelmetIdentification/model/Helmet_yolov5.cfg new file mode 100644 index 0000000..bd40a19 --- /dev/null +++ b/HelmetIdentification/model/Helmet_yolov5.cfg @@ -0,0 +1,12 @@ +CLASS_NUM=3 +BIASES_NUM=18 +BIASES=10,13,16,30,33,23,30,61,62,45,59,119,116,90,156,198,373,326 +SCORE_THRESH=0.4 +OBJECTNESS_THRESH=0.3 +IOU_THRESH=0.5 +YOLO_TYPE=3 +ANCHOR_DIM=3 + +YOLO_VERSION=5 +FRAMEWORK=PyTorch +MODEL_TYPE=2 diff --git a/HelmetIdentification/model/imgclass.names b/HelmetIdentification/model/imgclass.names new file mode 100644 index 0000000..46ddf0e --- /dev/null +++ b/HelmetIdentification/model/imgclass.names @@ -0,0 +1,3 @@ +person +head +helmet \ No newline at end of file diff --git a/HelmetIdentification/readme.md b/HelmetIdentification/readme.md new file mode 100644 index 0000000..c899ba4 --- /dev/null +++ b/HelmetIdentification/readme.md @@ -0,0 +1 @@ +main.cpp是读取视频进行推理的代码,main-image.cpp是读取单张图片进行推理的代码。 \ No newline at end of file diff --git a/HelmetIdentification/src/CMakeLists.txt b/HelmetIdentification/src/CMakeLists.txt new file mode 100644 index 0000000..6fcd298 --- /dev/null +++ b/HelmetIdentification/src/CMakeLists.txt @@ -0,0 +1,47 @@ +# CMake lowest version requirement +cmake_minimum_required(VERSION 3.5.1) +# project information +project(Individual) + +# Compile options +add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0 -Dgoogle=mindxsdk_private) +add_compile_options(-std=c++11 -fPIC -fstack-protector-all -Wall -D_FORTIFY_SOURCE=2 -O2) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../../") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now,-z,noexecstack -s -pie -pthread") +set(CMAKE_SKIP_RPATH TRUE) + +SET(CMAKE_BUILD_TYPE "Debug") +SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb") +SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall") + +# Header path +include_directories( + ${MX_SDK_HOME}/include/ + ${MX_SDK_HOME}/opensource/include/ + ${MX_SDK_HOME}/opensource/include/opencv4/ + /home/HwHiAiUser/Ascend/ascend-toolkit/latest/include/ + ./ +) + +# add host lib path +link_directories( + ${MX_SDK_HOME}/lib/ + ${MX_SDK_HOME}/lib/modelpostprocessors + ${MX_SDK_HOME}/opensource/lib/ + ${MX_SDK_HOME}/opensource/lib64/ + /usr/lib/aarch64-linux-gnu/ + /home/HwHiAiUser/Ascend/ascend-toolkit/latest/lib64/ + /usr/local/Ascend/driver/lib64/ + ./ +) + + +aux_source_directory(. sourceList) + +add_executable(main ${sourceList}) + +target_link_libraries(main mxbase opencv_world boost_filesystem glog avformat avcodec avutil cpprest yolov3postprocess ascendcl acl_dvpp_mpi) + +install(TARGETS main DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/HelmetIdentification/src/DataType.h b/HelmetIdentification/src/DataType.h new file mode 100644 index 0000000..dc34a0a --- /dev/null +++ b/HelmetIdentification/src/DataType.h @@ -0,0 +1,94 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_DATATYPE_H +#define MXBASE_HELMETIDENTIFICATION_DATATYPE_H + +#include +#include +#include +#include +#include +#include +#include "opencv2/highgui.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" + +namespace ascendVehicleTracking { +#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) + + const int MODULE_QUEUE_SIZE = 1000; + + enum FrameMode { + FRAME_MODE_SEARCH = 0, + FRAME_MODE_REG + }; + + struct DataBuffer { + std::shared_ptr deviceData; + std::shared_ptr hostData; + uint32_t dataSize; // buffer size + DataBuffer() : deviceData(nullptr), hostData(nullptr), dataSize(0) {} + }; + + struct DetectInfo { + int32_t classId; + float confidence; + float minx; // x value of left-top point + float miny; // y value of left-top point + float height; + float width; + }; + + enum TraceFlag { + NEW_VEHICLE = 0, + TRACkED_VEHICLE, + LOST_VEHICLE + }; + + struct TraceInfo { + int32_t id; + TraceFlag flag; + int32_t survivalTime; // How long is it been since the first time, unit: detection period + int32_t detectedTime; // How long is the vehicle detected, unit: detection period + std::chrono::time_point createTime; + }; + + struct TrackLet { + TraceInfo info; + // reserved: kalman status parameter + int32_t lostTime; // undetected time for tracked vehicle + std::vector shortFeature; // nearest 10 frame + }; + + struct VehicleQuality { + float score; + }; + + struct Coordinate2D { + uint32_t x; + uint32_t y; + }; +} +// namespace ascendVehicleTracking + +struct AttrT { + AttrT(std::string name, std::string value) : name(std::move(name)), value(std::move(value)) {} + std::string name = {}; + std::string value = {}; +}; + +#endif diff --git a/HelmetIdentification/src/Hungarian.cpp b/HelmetIdentification/src/Hungarian.cpp new file mode 100644 index 0000000..3f6fcd2 --- /dev/null +++ b/HelmetIdentification/src/Hungarian.cpp @@ -0,0 +1,165 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Hungarian.h" +#include +#include +#include "MxBase/Log/Log.h" + +namespace { + const int INF = 0x3f3f3f3f; + const int VISITED = 1; + const int HUNGARIAN_CONTENT = 7; + const int X_MATCH_OFFSET = 0; + const int Y_MATCH_OFFSET = 1; + const int X_VALUE_OFFSET = 2; + const int Y_VALUE_OFFSET = 3; + const int SLACK_OFFSET = 4; + const int X_VISIT_OFFSET = 5; + const int Y_VISIT_OFFSET = 6; +} + +APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols) +{ + handle.max = (row > cols) ? row : cols; + auto adjMat = std::shared_ptr(); + adjMat.reset(new int[handle.max * handle.max], std::default_delete()); + if (adjMat == nullptr) { + LogFatal << "HungarianHandleInit new failed"; + return APP_ERR_ACL_FAILURE; + } + + handle.adjMat = adjMat; + + void* ptr[HUNGARIAN_CONTENT] = {nullptr}; + for (int i = 0; i < HUNGARIAN_CONTENT; ++i) { + ptr[i] = malloc(handle.max * sizeof(int)); + if (ptr[i] == nullptr) { + LogFatal << "HungarianHandleInit Malloc failed"; + return APP_ERR_ACL_FAILURE; + } + } + + handle.xMatch.reset((int *)ptr[X_MATCH_OFFSET], free); + handle.yMatch.reset((int *)ptr[Y_MATCH_OFFSET], free); + handle.xValue.reset((int *)ptr[X_VALUE_OFFSET], free); + handle.yValue.reset((int *)ptr[Y_VALUE_OFFSET], free); + handle.slack.reset((int *)ptr[SLACK_OFFSET], free); + handle.xVisit.reset((int *)ptr[X_VISIT_OFFSET], free); + handle.yVisit.reset((int *)ptr[Y_VISIT_OFFSET], free); + return APP_ERR_OK; +} + +static void HungarianInit(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) +{ + int i, j, value; + if (rows > cols) { + handle.transpose = true; + handle.cols = rows; + handle.rows = cols; + handle.resX = handle.yMatch.get(); + handle.resY = handle.xMatch.get(); + } else { + handle.transpose = false; + handle.rows = rows; + handle.cols = cols; + handle.resX = handle.xMatch.get(); + handle.resY = handle.yMatch.get(); + } + + for (i = 0; i < handle.rows; ++i) { + handle.xValue.get()[i] = 0; + handle.xMatch.get()[i] = -1; + for (j = 0; j < handle.cols; ++j) { + if (handle.transpose) { + value = cost[j][i]; + } else { + value = cost[i][j]; + } + handle.adjMat.get()[i * handle.cols + j] = value; + if (handle.xValue.get()[i] < value) { + handle.xValue.get()[i] = value; + } + } + } + + for (i = 0; i < handle.cols; ++i) { + handle.yValue.get()[i] = 0; + handle.yMatch.get()[i] = -1; + } +} + +static bool Match(HungarianHandle &handle, int id) +{ + int j, delta; + handle.xVisit.get()[id] = VISITED; + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] == VISITED) { + continue; + } + delta = handle.xValue.get()[id] + handle.yValue.get()[j] - handle.adjMat.get()[id * handle.cols + j]; + if (delta == 0) { + handle.yVisit.get()[j] = VISITED; + if (handle.yMatch.get()[j] == -1 || Match(handle, handle.yMatch.get()[j])) { + handle.yMatch.get()[j] = id; + handle.xMatch.get()[id] = j; + return true; + } + } else if (delta < handle.slack.get()[j]) { + handle.slack.get()[j] = delta; + } + } + return false; +} + +int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) +{ + HungarianInit(handle, cost, rows, cols); + int i, j, delta; + for (i = 0; i < handle.rows; ++i) { + while (true) { + std::fill(handle.xVisit.get(), handle.xVisit.get() + handle.rows, 0); + std::fill(handle.yVisit.get(), handle.yVisit.get() + handle.cols, 0); + for (j = 0; j < handle.cols; ++j) { + handle.slack.get()[j] = INF; + } + if (Match(handle, i)) { + break; + } + delta = INF; + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] != VISITED && delta > handle.slack.get()[j]) { + delta = handle.slack.get()[j]; + } + } + if (delta == INF) { + LogDebug << "Hungarian solve is invalid!"; + return -1; + } + for (j = 0; j < handle.rows; ++j) { + if (handle.xVisit.get()[j] == VISITED) { + handle.xValue.get()[j] -= delta; + } + } + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] == VISITED) { + handle.yValue.get()[j] += delta; + } + } + } + } + return handle.rows; +} diff --git a/HelmetIdentification/src/Hungarian.h b/HelmetIdentification/src/Hungarian.h new file mode 100644 index 0000000..2ae6a33 --- /dev/null +++ b/HelmetIdentification/src/Hungarian.h @@ -0,0 +1,46 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H +#define MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H + +#include +#include +#include "DataType.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" +#include "MxBase/ErrorCode/ErrorCodes.h" + +struct HungarianHandle { + int rows; + int cols; + int max; + int *resX; + int *resY; + bool transpose; + std::shared_ptr adjMat; + std::shared_ptr xMatch; + std::shared_ptr yMatch; + std::shared_ptr xValue; + std::shared_ptr yValue; + std::shared_ptr slack; + std::shared_ptr xVisit; + std::shared_ptr yVisit; +}; + +APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols); +int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols); + +#endif \ No newline at end of file diff --git a/HelmetIdentification/src/KalmanTracker.cpp b/HelmetIdentification/src/KalmanTracker.cpp new file mode 100644 index 0000000..69362f9 --- /dev/null +++ b/HelmetIdentification/src/KalmanTracker.cpp @@ -0,0 +1,143 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "KalmanTracker.h" + +namespace ascendVehicleTracking +{ + namespace + { + const int OFFSET = 2; + const int MULTIPLE = 2; + } + + /* + * The SORT algorithm uses a linear constant velocity model,which assumes 7 + * states, including + * x coordinate of bounding box center + * y coordinate of bounding box center + * area of bounding box + * aspect ratio of w to h + * velocity of x + * velocity of y + * variation rate of area + * + * The aspect ratio is considered to be unchanged, so there is no additive item + * for aspect ratio in the transitionMatrix + * + * + * Kalman filter equation step by step + * (1) X(k|k-1)=AX(k-1|k-1)+BU(k) + * X(k|k-1) is the predicted state(statePre),X(k-1|k-1) is the k-1 statePost,A + * is transitionMatrix, B is controlMatrix, U(k) is control state, in SORT U(k) is 0. + * + * (2) P(k|k-1)=AP(k-1|k-1)A'+Q + * P(k|k-1) is the predicted errorCovPre, P(k-1|k-1) is the k-1 errorCovPost, + * Q is processNoiseCov + * + * (3) Kg(k)=P(k|k-1)H'/(HP(k|k-1))H'+R + * Kg(k) is the kalman gain, the ratio of estimate variance in total variance, + * H is the measurementMatrix,R is the measurementNoiseCov + * + * (4) X(k|k)=X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) + * X(k|k) is the k statePost, Z(k) is the measurement of K, in SORT Z(k) is + * the detection result of k + * + * (5) P(k|k)=(1-Kg(k)H)P(k|k-1) + * P(k|k) is the errorCovPost + */ + void KalmanTracker::CvKalmanInit(MxBase::ObjectInfo initRect) + { + const int stateDim = 7; + const int measureDim = 4; + cvkalmanfilter_ = cv::KalmanFilter(stateDim, measureDim, 0); // zero control + measurement_ = cv::Mat::zeros(measureDim, 1, CV_32F); // 4 measurements, Z(k), according to detection results + // A, will not be updated + cvkalmanfilter_.transitionMatrix = (cv::Mat_(stateDim, stateDim) << 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1); + cvkalmanfilter_.measurementMatrix = (cv::Mat_(measureDim, stateDim) << 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0); + cv::setIdentity(cvkalmanfilter_.measurementMatrix); // H, will not be updated + cv::setIdentity(cvkalmanfilter_.processNoiseCov, cv::Scalar::all(1e-2)); // Q, will not be updated + cv::setIdentity(cvkalmanfilter_.measurementNoiseCov, cv::Scalar::all(1e-1)); // R, will bot be updated + cv::setIdentity(cvkalmanfilter_.errorCovPost, cv::Scalar::all(1)); // P(k-1|k-1), will be updated + + // initialize state vector with bounding box in + // [center_x,center_y,area,ratio] + // style, the velocity is 0 + // X(k-1|k-1) + cvkalmanfilter_.statePost.at(0, 0) = initRect.x0 + (initRect.x1 - initRect.x0) / MULTIPLE; + cvkalmanfilter_.statePost.at(1, 0) = initRect.y0 + (initRect.y1 - initRect.y0) / MULTIPLE; + cvkalmanfilter_.statePost.at(OFFSET, 0) = (initRect.x1 - initRect.x0) * (initRect.y1 - initRect.y0); + cvkalmanfilter_.statePost.at(OFFSET + 1, 0) = (initRect.x1 - initRect.x0) / (initRect.y1 - initRect.y0); + } + + // Predict the bounding box. + MxBase::ObjectInfo KalmanTracker::Predict() + { + // predict + // return X(k|k-1)=AX(k-1|k-1), and update + // P(k|k-1) <- AP(k-1|k-1)A'+Q + MxBase::ObjectInfo detectInfo = {}; + cv::Mat predictState = cvkalmanfilter_.predict(); + float *pData = (float *)(predictState.data); + float w = sqrt((*(pData + OFFSET)) * (*(pData + OFFSET + 1))); + if (w < DBL_EPSILON) + { + detectInfo.x0 = 0; + detectInfo.y0 = 0; + detectInfo.x1 = 0; + detectInfo.y1 = 0; + detectInfo.classId = 0; + return detectInfo; + } + if (w == 0) + { + MxBase::ObjectInfo w0DetectInfo = {}; + return w0DetectInfo; + } + float h = (*(pData + OFFSET)) / w; + float x = (*pData) - w / MULTIPLE; + float y = (*(pData + 1)) - h / MULTIPLE; + if (x < 0 && (*pData) > 0) + { + x = 0; + } + if (y < 0 && (*(pData + 1)) > 0) + { + y = 0; + } + detectInfo.x0 = x; + detectInfo.y0 = y; + detectInfo.x1 = x + w; + detectInfo.y1 = y + h; + return detectInfo; + } + + // Update the state using observed bounding box + void KalmanTracker::Update(MxBase::ObjectInfo stateMat) + { + // measurement_, update Z(k) + float *pData = (float *)(measurement_.data); + *pData = stateMat.x0 + (stateMat.x1 - stateMat.x0) / MULTIPLE; + *(pData + 1) = stateMat.y0 + (stateMat.y1 - stateMat.y0) / MULTIPLE; + *(pData + OFFSET) = (stateMat.x1 - stateMat.x0) * (stateMat.y1 - stateMat.y0); + *(pData + OFFSET + 1) = (stateMat.x1 - stateMat.x0) / (stateMat.y1 - stateMat.y0); + // update, do the following steps: + // Kg(k): P(k|k-1)H'/(HP(k|k-1))H'+R + // X(k|k): X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) + // P(k|k): (1-Kg(k)H)P(k|k-1) + cvkalmanfilter_.correct(measurement_); + } +} // namespace diff --git a/HelmetIdentification/src/KalmanTracker.h b/HelmetIdentification/src/KalmanTracker.h new file mode 100644 index 0000000..f5f3465 --- /dev/null +++ b/HelmetIdentification/src/KalmanTracker.h @@ -0,0 +1,39 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H +#define MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H + +#include "opencv2/highgui/highgui.hpp" +#include "opencv2/video/tracking.hpp" +#include "DataType.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" + +namespace ascendVehicleTracking { +class KalmanTracker { +public: + KalmanTracker() {} + ~KalmanTracker() {} + void CvKalmanInit(MxBase::ObjectInfo initRect); + MxBase::ObjectInfo Predict(); + void Update(MxBase::ObjectInfo stateMat); +private: + cv::KalmanFilter cvkalmanfilter_ = {}; + cv::Mat measurement_ = {}; +}; +} // namesapce ascendVehicleTracking + +#endif \ No newline at end of file diff --git a/HelmetIdentification/src/MOTConnection.cpp b/HelmetIdentification/src/MOTConnection.cpp new file mode 100644 index 0000000..14e4341 --- /dev/null +++ b/HelmetIdentification/src/MOTConnection.cpp @@ -0,0 +1,262 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MOTConnection.h" +#include +#include +#include +#include +#include "opencv2/highgui.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include "MxBase/Log/Log.h" +#include "MxBase/ErrorCode/ErrorCodes.h" +#include "Hungarian.h" + +namespace ascendVehicleTracking +{ + namespace + { + // convert double to int + const int FLOAT_TO_INT = 1000; + const int MULTIPLE = 0; + const double SIMILARITY_THRESHOLD = 0.66; + const int MULTIPLE_IOU = 6; + const float NORM_EPS = 1e-10; + const double TIME_COUNTS = 1000.0; + const double COST_TIME_MS_THRESHOLD = 10.; + const float WIDTH_RATE_THRESH = 1.f; + const float HEIGHT_RATE_THRESH = 1.f; + const float X_DIST_RATE_THRESH = 1.3f; + const float Y_DIST_RATE_THRESH = 1.f; + } // namespace + + // 计算bounding box的交并比 + float CalIOU(MxBase::ObjectInfo detect1, MxBase::ObjectInfo detect2) + { + cv::Rect_ bbox1(detect1.x0, detect1.y0, detect1.x1 - detect1.x0, detect1.y1 - detect1.y0); + cv::Rect_ bbox2(detect2.x0, detect2.y0, detect2.x1 - detect2.x0, detect2.y1 - detect2.y0); + float intersectionArea = (bbox1 & bbox2).area(); + float unionArea = bbox1.area() + bbox2.area() - intersectionArea; + if (unionArea < DBL_EPSILON) + { + return 0.f; + } + return (intersectionArea / unionArea); + } + + // 计算前后两帧的两个bounding box的相似度 + float CalSimilarity(const TraceLet &traceLet, const MxBase::ObjectInfo &objectInfo, const int &method, const double &kIOU) + { + return CalIOU(traceLet.detectInfo, objectInfo); + } + + // 过滤掉交并比小于阈值的匹配 + void MOTConnection::FilterLowThreshold(const HungarianHandle &hungarianHandleObj, + const std::vector> &disMatrix, std::vector &matchedTracedDetected, + std::vector &detectVehicleFlagVec) + { + for (unsigned int i = 0; i < traceList_.size(); ++i) + { + if ((hungarianHandleObj.resX[i] != -1) && + (disMatrix[i][hungarianHandleObj.resX[i]] >= (trackThreshold_ * FLOAT_TO_INT))) + { + matchedTracedDetected.push_back(cv::Point(i, hungarianHandleObj.resX[i])); + detectVehicleFlagVec[hungarianHandleObj.resX[i]] = true; + } + else + { + traceList_[i].info.flag = LOST_VEHICLE; + } + } + } + + // 更新没有匹配上的跟踪器 + void MOTConnection::UpdateUnmatchedTraceLet(const std::vector> &objInfos) + { + for (auto itr = traceList_.begin(); itr != traceList_.end();) + { + if ((*itr).info.flag != LOST_VEHICLE) + { + ++itr; + continue; + } + + (*itr).lostAge++; + (*itr).kalman.Update((*itr).detectInfo); + + if ((*itr).lostAge < lostThreshold_) + { + continue; + } + + itr = traceList_.erase(itr); + } + } + + // 更新匹配上的跟踪器 + void MOTConnection::UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, + std::vector> &objInfos) + { + for (unsigned int i = 0; i < matchedTracedDetected.size(); ++i) + { + int traceIndex = matchedTracedDetected[i].x; + int detectIndex = matchedTracedDetected[i].y; + if (traceList_[traceIndex].info.survivalTime > MULTIPLE) + { + traceList_[traceIndex].info.flag = TRACkED_VEHICLE; + } + traceList_[traceIndex].info.survivalTime++; + traceList_[traceIndex].info.detectedTime++; + traceList_[traceIndex].lostAge = 0; + traceList_[traceIndex].detectInfo = objInfos[0][detectIndex]; + traceList_[traceIndex].kalman.Update(objInfos[0][detectIndex]); + } + } + // 将没有匹配上的检测更新为新的检测器 + void MOTConnection::AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue) + { + using Time = std::chrono::high_resolution_clock; + for (auto &vehicleObject : unmatchedVehicleObjectQueue) + { + // add new detected into traceList + TraceLet traceLet; + generatedId_++; + traceLet.info.id = generatedId_; + traceLet.info.survivalTime = 1; + traceLet.info.detectedTime = 1; + traceLet.lostAge = 0; + traceLet.info.flag = NEW_VEHICLE; + traceLet.detectInfo = vehicleObject; + traceLet.info.createTime = Time::now(); + + traceLet.kalman.CvKalmanInit(vehicleObject); + traceList_.push_back(traceLet); + } + } + + void MOTConnection::UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, + std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue) + { + UpdateMatchedTraceLet(matchedTracedDetected, objInfos); // 更新匹配上的跟踪器 + AddNewDetectedVehicle(unmatchedVehicleObjectQueue); // 将没有匹配上的检测更新为新的检测器 + UpdateUnmatchedTraceLet(objInfos); // 更新没有匹配上的跟踪器 + } + + void MOTConnection::TrackObjectPredict() + { + // every traceLet should do kalman predict + for (auto &traceLet : traceList_) + { + traceLet.detectInfo = traceLet.kalman.Predict(); // 卡尔曼滤波预测的框 + } + } + + void MOTConnection::TrackObjectUpdate(const std::vector> &objInfos, + std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue) + { + if (objInfos[0].size() > 0) + { + LogDebug << "[frame id = " << 1 << "], trace size =" << traceList_.size() << "detect size = " << objInfos[0].size() << ""; + // init vehicle matched flag + std::vector detectVehicleFlagVec; + for (unsigned int i = 0; i < objInfos[0].size(); ++i) + { + detectVehicleFlagVec.push_back(false); + } + // calculate the associated matrix + std::vector> disMatrix; + disMatrix.clear(); + disMatrix.resize(traceList_.size(), std::vector(objInfos[0].size(), 0)); + for (unsigned int j = 0; j < objInfos[0].size(); ++j) + { + for (unsigned int i = 0; i < traceList_.size(); ++i) + { + // 计算交并比 + float sim = CalSimilarity(traceList_[i], objInfos[0][j], method_, kIOU_); // method_=1, kIOU_=1.0 + disMatrix[i][j] = (int)(sim * FLOAT_TO_INT); + } + } + + // solve the assignment problem using hungarian 匈牙利算法进行匹配 + HungarianHandle hungarianHandleObj = {}; + HungarianHandleInit(hungarianHandleObj, traceList_.size(), objInfos[0].size()); + HungarianSolve(hungarianHandleObj, disMatrix, traceList_.size(), objInfos[0].size()); + // filter out matched but with low distance 过滤掉匹配上但是交并比较小的 + FilterLowThreshold(hungarianHandleObj, disMatrix, matchedTracedDetected, detectVehicleFlagVec); + LogDebug << "matchedTracedDetected = " << matchedTracedDetected.size() << ""; + // fill unmatchedVehicleObjectQueue + for (unsigned int i = 0; i < detectVehicleFlagVec.size(); ++i) + { + if (detectVehicleFlagVec[i] == false) + { + unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); + } + } + } + } + + APP_ERROR MOTConnection::ProcessSort(std::vector> &objInfos, size_t frameId) + { + std::vector unmatchedVehicleObjectQueue; + std::vector matchedTracedDetected; + if (objInfos[0].size() == 0) + { + return APP_ERR_COMM_FAILURE; + } + + if (traceList_.size() > 0) + { + // every traceLet should do kalman predict + TrackObjectPredict(); // 卡尔曼滤波预测 + TrackObjectUpdate(objInfos, matchedTracedDetected, unmatchedVehicleObjectQueue); // 选出matched track、unmatched detection + } + else + { + // traceList is empty, all the vehicle detected in the new frame are unmatched. + if (objInfos[0].size() > 0) + { + for (unsigned int i = 0; i < objInfos[0].size(); ++i) + { + unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); + } + } + } + + // update all the tracelet in the tracelist per frame + UpdateTraceLetAndFrame(matchedTracedDetected, objInfos, unmatchedVehicleObjectQueue); // 用matched track、unmatched detection更新跟踪器 + return APP_ERR_OK; + } + + // 获取跟踪后的检测框 + APP_ERROR MOTConnection::GettrackResult(std::vector &objInfos_) + { + if (traceList_.size() > 0) + { + for (auto &traceLet : traceList_) + { + traceLet.detectInfo.classId = traceLet.info.id; + objInfos_.push_back(traceLet.detectInfo); + } + } + else + { + return APP_ERR_COMM_FAILURE; + } + return APP_ERR_OK; + } +} +// namespace ascendVehicleTracking \ No newline at end of file diff --git a/HelmetIdentification/src/MOTConnection.h b/HelmetIdentification/src/MOTConnection.h new file mode 100644 index 0000000..be54f17 --- /dev/null +++ b/HelmetIdentification/src/MOTConnection.h @@ -0,0 +1,78 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H +#define MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H + +#include +#include +#include +#include "KalmanTracker.h" +#include "Hungarian.h" +#include "DataType.h" +#include "MxBase/ErrorCode/ErrorCodes.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/Tensor/TensorBase/TensorBase.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +namespace ascendVehicleTracking { +struct TraceLet { + TraceInfo info = {}; + int32_t lostAge = 0; + KalmanTracker kalman; + std::list> shortFeatureQueue; + MxBase::ObjectInfo detectInfo = {}; +}; + +class MOTConnection { +public: + APP_ERROR ProcessSort(std::vector> &objInfos, size_t frameId); + APP_ERROR GettrackResult(std::vector &objInfos_); + +private: + double trackThreshold_ = 0.3; + double kIOU_ = 1.0; + int32_t method_ = 1; + int32_t lostThreshold_ = 3; + uint32_t maxNumberFeature_ = 0; + int32_t generatedId_ = 0; + std::vector traceList_ = {}; + +private: + + void FilterLowThreshold(const HungarianHandle &hungarianHandleObj, const std::vector> &disMatrix, + std::vector &matchedTracedDetected, std::vector &detectVehicleFlagVec); + + void UpdateUnmatchedTraceLet(const std::vector> &objInfos); + + void UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, + std::vector> &objInfos); + + void AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue); + + void UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, + std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue); + + void TrackObjectPredict(); + void TrackObjectUpdate(const std::vector> &objInfos, + std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue); +}; +} // namespace ascendVehicleTracking + +#endif \ No newline at end of file diff --git a/HelmetIdentification/src/cropResizePaste.hpp b/HelmetIdentification/src/cropResizePaste.hpp new file mode 100644 index 0000000..09d290f --- /dev/null +++ b/HelmetIdentification/src/cropResizePaste.hpp @@ -0,0 +1,117 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef CRP_H +#define CRP_H + +#include "MxBase/E2eInfer/Image/Image.h" +#include "MxBase/E2eInfer/Rect/Rect.h" +#include "MxBase/E2eInfer/Size/Size.h" + +#include "acl/dvpp/hi_dvpp.h" +#include "acl/acl.h" +#include "acl/acl_rt.h" + +#define CONVER_TO_PODD(NUM) (((NUM) % 2 != 0) ? (NUM) : ((NUM)-1)) +#define CONVER_TO_EVEN(NUM) (((NUM) % 2 == 0) ? (NUM) : ((NUM)-1)) +#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) + +//using namespace MxBase; + +MxBase::Rect GetPasteRect(uint32_t inputWidth, uint32_t inputHeight, uint32_t outputWidth, uint32_t outputHeight) +{ + bool widthRatioLarger = true; + float resizeRatio = static_cast(inputWidth) / outputWidth; + if (resizeRatio < (static_cast(inputHeight) / outputHeight)) + { + resizeRatio = static_cast(inputHeight) / outputHeight; + widthRatioLarger = false; + } + + // (x0, y0)是左上角坐标,(x1, y1)是右下角坐标,采用图片坐标系 + uint32_t x0; + uint32_t y0; + uint32_t x1; + uint32_t y1; + if (widthRatioLarger) + { + // 原图width大于height + x0 = 0; + y0 = 0; + x1 = outputWidth-1; + y1 = inputHeight / resizeRatio - 1; + } + else + { + // 原图height大于width + x0 = 0; + y0 = 0; + x1 = outputWidth / resizeRatio - 1; + y1 = outputHeight - 1; + } + + x0 = DVPP_ALIGN_UP(CONVER_TO_EVEN(x0), 16); // 16对齐 + x1 = DVPP_ALIGN_UP((x1 - x0 + 1), 16) + x0 - 1; + y0 = CONVER_TO_EVEN(y0); + y1 = CONVER_TO_PODD(y1); + MxBase::Rect res(x0, y0, x1, y1); + return res; +} + +MxBase::Image ConstructImage(uint32_t resizeWidth, uint32_t resizeHeight) +{ + void *addr; + uint32_t dataSize = resizeWidth * resizeHeight * 3 / 2; + auto ret = hi_mpi_dvpp_malloc(0, &addr, dataSize); + if (ret != APP_ERR_OK) + { + LogError << "hi_mpi_dvpp_malloc fail :" << ret; + } + // 第三个参数从128改成了0 + ret = aclrtMemset(addr, dataSize, 0, dataSize); + if (ret != APP_ERR_OK) + { + LogError << "aclrtMemset fail :" << ret; + } + std::shared_ptr data((uint8_t *)addr, hi_mpi_dvpp_free); + MxBase::Size imageSize(resizeWidth, resizeHeight); + MxBase::Image pastedImg(data, dataSize, 0, imageSize); + return pastedImg; +} + +std::pair GenerateRect(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight) +{ + uint32_t x1 = CONVER_TO_PODD(originalWidth - 1); + uint32_t y1 = CONVER_TO_PODD(originalHeight - 1); + MxBase::Rect cropRect(0, 0, x1, y1); + MxBase::Rect pasteRect = GetPasteRect(originalWidth, originalHeight, resizeWidth, resizeHeight); + std::pair cropPasteRect(cropRect, pasteRect); + return cropPasteRect; +} + +MxBase::Image resizeKeepAspectRatioFit(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight, MxBase::Image &decodeImage, MxBase::ImageProcessor& imageProcessor) +{ + std::pair cropPasteRect = GenerateRect(originalWidth, originalHeight, resizeWidth, resizeHeight); + MxBase::Image resizeImage = ConstructImage(resizeWidth, resizeHeight); + auto ret = imageProcessor.CropAndPaste(decodeImage, cropPasteRect, resizeImage); + if (ret != APP_ERR_OK) + { + LogError << "CropAndPaste fail :" << ret; + } + return resizeImage; +} + +#endif // CRP_H \ No newline at end of file diff --git a/HelmetIdentification/src/main-image.cpp b/HelmetIdentification/src/main-image.cpp new file mode 100644 index 0000000..9ec0e66 --- /dev/null +++ b/HelmetIdentification/src/main-image.cpp @@ -0,0 +1,170 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils.h" + +#include +#include +#include +using namespace std; + +// 如果在200DK上运行就改为 USE_200DK +#define USE_DVPP + +APP_ERROR readImage(std::string imgPath, MxBase::Image& image, ImageProcessor& imageProcessor) +{ + APP_ERROR ret; +#ifdef USE_DVPP + // if USE DVPP + ret = imageProcessor.Decode(imgPath, image); +#endif +#ifdef USE_200DK + std::shared_ptr dataPtr; + uint32_t dataSize; + // Get image data to memory, this method can be substituted or designed by yourself! + std::ifstream file; + file.open(imgPath.c_str(), std::ios::binary); + if (!file) + { + LogInfo << "Invalid file."; + return APP_ERR_COMM_INVALID_PARAM; + } + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); + + char *p = (char *)malloc(content.size()); + memcpy(p, content.data(), content.size()); + auto deleter = [](void *p) -> void + { + free(p); + p = nullptr; + }; + + dataPtr.reset(static_cast((void *)(p)), deleter); + dataSize = content.size(); + file.close(); + if (ret != APP_ERR_OK) + { + LogError << "Getimage failed, ret=" << ret; + return ret; + } + ret = imageProcessor.Decode(dataPtr, dataSize, image, ImageFormat::YUV_SP_420); + // endif +#endif + if (ret != APP_ERR_OK) + { + LogError << "Decode failed, ret=" << ret; + return ret; + } +} + +void poptProcess(std::vector modelOutputs, std::shared_ptr postProcessorDptr, MxBase::Size originalSize, MxBase::Size resizeSize) +{ + MxBase::ResizedImageInfo imgInfo; + auto shape = modelOutputs[0].GetShape(); + imgInfo.widthOriginal = originalSize.width; + imgInfo.heightOriginal = originalSize.height; + imgInfo.widthResize = resizeSize.width; + imgInfo.heightResize = resizeSize.height; + imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; + float resizeRate = originalSize.width > originalSize.height ? (originalSize.width / 640.0) : (originalSize.height / 640.0); + imgInfo.keepAspectRatioScaling = 1 / resizeRate; + std::vector imageInfoVec = {}; + imageInfoVec.push_back(imgInfo); + // make postProcess inputs + std::vector tensors; + for (size_t i = 0; i < modelOutputs.size(); i++) + { + MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); + MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); + tensors.push_back(tensorBase); + } + std::vector> objectInfos; + postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); + std::cout << "===---> Size of objectInfos is " << objectInfos.size() << std::endl; + for (size_t i = 0; i < objectInfos.size(); i++) + { + std::cout << "objectInfo-" << i << " , Size:" << objectInfos[i].size() << std::endl; + for (size_t j = 0; j < objectInfos[i].size(); j++) + { + std::cout << std::endl + << "*****objectInfo-" << i << ":" << j << std::endl; + std::cout << "x0 is " << objectInfos[i][j].x0 << std::endl; + std::cout << "y0 is " << objectInfos[i][j].y0 << std::endl; + std::cout << "x1 is " << objectInfos[i][j].x1 << std::endl; + std::cout << "y1 is " << objectInfos[i][j].y1 << std::endl; + std::cout << "confidence is " << objectInfos[i][j].confidence << std::endl; + std::cout << "classId is " << objectInfos[i][j].classId << std::endl; + std::cout << "className is " << objectInfos[i][j].className << std::endl; + } + } +} + +APP_ERROR main(int argc, char *argv[]) +{ + APP_ERROR ret; + + // global init + ret = MxBase::MxInit(); + if (ret != APP_ERR_OK) + { + LogError << "MxInit failed, ret=" << ret << "."; + } + // 检测是否输入了文件路径 + if (argc <= 1) + { + LogWarn << "Please input image path, such as 'test.jpg'."; + return APP_ERR_OK; + } + + // imageProcess init + MxBase::ImageProcessor imageProcessor(deviceId); + // model init + MxBase::Model yoloModel(modelPath, deviceId); + // postprocessor init + std::map postConfig; + postConfig.insert(std::pair("postProcessConfigPath", configPath)); + postConfig.insert(std::pair("labelPath", labelPath)); + std::shared_ptr postProcessorDptr = std::make_shared(); + postProcessorDptr->Init(postConfig); + + std::string imgPath = argv[1]; + // 读取图片 + MxBase::Image image; + readImage(imgPath, image, imageProcessor); + + // 缩放图片 + MxBase::Size originalSize = image.GetOriginalSize(); + MxBase::Size resizeSize = MxBase::Size(YOLOV5_RESIZE, YOLOV5_RESIZE); + MxBase::Image resizedImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); + + // 模型推理 + MxBase::Tensor tensorImg = resizedImage.ConvertToTensor(); + tensorImg.ToDevice(deviceId); + std::vector inputs; + inputs.push_back(tensorImg); + std::vector modelOutputs = yoloModel.Infer(inputs); + for (auto output : modelOutputs) + { + output.ToHost(); + } + + // 后处理 + poptProcess(modelOutputs, postProcessorDptr, originalSize, resizeSize); + + return APP_ERR_OK; +} \ No newline at end of file diff --git a/HelmetIdentification/src/main.cpp b/HelmetIdentification/src/main.cpp new file mode 100644 index 0000000..8fd8093 --- /dev/null +++ b/HelmetIdentification/src/main.cpp @@ -0,0 +1,283 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils.h" + +extern "C" +{ + #include "libavformat/avformat.h" +} + +#include +#include +#include +#include "unistd.h" +#include +#include +#include +#include "boost/filesystem.hpp" + +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "opencv2/opencv.hpp" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" +#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" +#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" +#include "MxBase/E2eInfer/DataType.h" + +#include +using namespace std; +using namespace videoInfo; +namespace frameConfig +{ + size_t channelId0 = 0; + size_t channelId1 = 1; + size_t frameCountChannel0 = 300; + size_t frameCountChannel1 = 300; + size_t skipIntervalChannel0 = 2; + size_t skipIntervalChannel1 = 2; + + // channel0对应文件的指针 + AVFormatContext *pFormatCtx0 = nullptr; + // channel1对应文件的指针 + AVFormatContext *pFormatCtx1 = nullptr; +} + +// ffmpeg拉流 +AVFormatContext *CreateFormatContext(std::string filePath) +{ + LogInfo << "start to CreatFormatContext!"; + // creat message for stream pull + AVFormatContext *formatContext = nullptr; + AVDictionary *options = nullptr; + + LogInfo << "start to avformat_open_input!"; + int ret = avformat_open_input(&formatContext, filePath.c_str(), nullptr, &options); + if (options != nullptr) + { + av_dict_free(&options); + } + if (ret != 0) + { + LogError << "Couldn't open input stream " << filePath.c_str() << " ret=" << ret; + return nullptr; + } + ret = avformat_find_stream_info(formatContext, nullptr); + if (ret != 0) + { + LogError << "Couldn't open input stream information"; + return nullptr; + } + return formatContext; +} + +// 真正的拉流函数 +void PullStream0(std::string filePath) +{ + av_register_all(); + avformat_network_init(); + frameConfig::pFormatCtx0 = avformat_alloc_context(); + frameConfig::pFormatCtx0 = CreateFormatContext(filePath); + av_dump_format(frameConfig::pFormatCtx0, 0, filePath.c_str(), 0); +} +void PullStream1(std::string filePath) +{ + av_register_all(); + avformat_network_init(); + frameConfig::pFormatCtx1 = avformat_alloc_context(); + frameConfig::pFormatCtx1 = CreateFormatContext(filePath); + av_dump_format(frameConfig::pFormatCtx1, 0, filePath.c_str(), 0); +} + +// 视频解码回调(样例代码,测试可以跑通,但是不能直接复用) +APP_ERROR CallBackVdec(Image &decodedImage, uint32_t channelId, uint32_t frameId, void *userData) +{ + FrameImage frameImage; + frameImage.image = decodedImage; + frameImage.channelId = channelId; + frameImage.frameId = frameId; + + videoInfo::g_threadsMutex_frameImageQueue.lock(); + videoInfo::frameImageQueue.push(frameImage); + videoInfo::g_threadsMutex_frameImageQueue.unlock(); + + return APP_ERR_OK; +} + +// 获取H264中的帧 +void GetFrame(AVPacket &pkt, FrameImage &frameImage, AVFormatContext *pFormatCtx) +{ + av_init_packet(&pkt); + int ret = av_read_frame(pFormatCtx, &pkt); + if (ret != 0) + { + LogInfo << "[StreamPuller] channel Read frame failed, continue!"; + if (ret == AVERROR_EOF) + { + LogInfo << "[StreamPuller] channel StreamPuller is EOF, over!"; + return; + } + return; + } + else + { + if (pkt.size <= 0) + { + LogError << "Invalid pkt.size: " << pkt.size; + return; + } + + // send to the device + auto hostDeleter = [](void *dataPtr) -> void {}; + MemoryData data(pkt.size, MemoryData::MEMORY_HOST); + MemoryData src((void *)(pkt.data), pkt.size, MemoryData::MEMORY_HOST); + APP_ERROR ret = MemoryHelper::MxbsMallocAndCopy(data, src); + if (ret != APP_ERR_OK) + { + LogError << "MxbsMallocAndCopy failed!"; + } + std::shared_ptr imageData((uint8_t *)data.ptrData, hostDeleter); + + Image subImage(imageData, pkt.size); + frameImage.image = subImage; + + LogDebug << "'channelId = " << frameImage.channelId << ", frameId = " << frameImage.frameId << " , dataSize = " << frameImage.image.GetDataSize(); + + av_packet_unref(&pkt); + } + return; +} + +// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 +void VdecThread0(size_t frameCount, size_t skipInterval, int32_t channelId) +{ + AVPacket pkt; + uint32_t frameId = 0; + // 解码器参数 + VideoDecodeConfig config; + VideoDecodeCallBack cPtr = CallBackVdec; + config.width = videoInfo::SRC_WIDTH; + config.height = videoInfo::SRC_HEIGHT; + config.callbackFunc = cPtr; + config.skipInterval = skipInterval; // 跳帧控制 + + std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); + for (size_t i = 0; i < frameCount; i++) + { + Image subImage; + FrameImage frame; + frame.channelId = 0; + frame.frameId = frameId; + frame.image = subImage; + GetFrame(pkt, frame, frameConfig::pFormatCtx0); + APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); + if (ret != APP_ERR_OK) + { + LogError << "videoDecoder Decode failed. ret is: " << ret; + } + frameId += 1; + } +} + +// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 +void VdecThread1(size_t frameCount, size_t skipInterval, int32_t channelId) +{ + AVPacket pkt; + uint32_t frameId = 0; + // 解码器参数 + VideoDecodeConfig config; + VideoDecodeCallBack cPtr = CallBackVdec; + config.width = videoInfo::SRC_WIDTH; + config.height = videoInfo::SRC_HEIGHT; + config.callbackFunc = cPtr; + // 跳帧控制 + config.skipInterval = skipInterval; + + std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); + for (size_t i = 0; i < frameCount; i++) + { + Image subImage; + FrameImage frame; + frame.channelId = 0; + frame.frameId = frameId; + frame.image = subImage; + GetFrame(pkt, frame, frameConfig::pFormatCtx1); + APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); + if (ret != APP_ERR_OK) + { + LogError << "videoDecoder Decode failed. ret is: " << ret; + } + frameId += 1; + } +} + + +APP_ERROR main(int argc, char *argv[]) +{ + // 检测是否输入了文件路径 + if (argc <= 1) + { + LogWarn << "Please input video path, such as './video_sample test.264'."; + return APP_ERR_OK; + } + + // 初始化 + APP_ERROR ret = MxBase::MxInit(); + if (ret != APP_ERR_OK) + { + LogError << "MxInit failed, ret=" << ret << "."; + } + + std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); + + // 视频流解码线程 + std::string videoPath0 = argv[1]; + PullStream0(videoPath0); + std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0); + std::string videoPath1 = argv[2]; + PullStream1(videoPath1); + std::thread threadVdec1(VdecThread1, frameConfig::frameCountChannel1, frameConfig::skipIntervalChannel1, frameConfig::channelId1); + threadVdec0.join(); + threadVdec1.join(); + + size_t target_count = videoInfo::frameImageQueue.size(); + LogInfo << "frameImageQueue.size: " << videoInfo::frameImageQueue.size(); + + // resize线程 + std::thread resizeThread(resizeMethod, start_time, target_count); + resizeThread.join(); + + // 推理线程 + std::thread inferThread(inferMethod, start_time, target_count); + inferThread.join(); + + // 后处理线程 + std::thread postprocessThread(postprocessMethod, start_time, target_count); + postprocessThread.join(); + + // 跟踪去重线程 + std::thread trackThread(trackMethod, start_time, target_count); + trackThread.join(); + + std::chrono::high_resolution_clock::time_point end_time = std::chrono::high_resolution_clock::now(); + double_t cost_time = std::chrono::duration_cast(end_time - start_time).count(); + LogInfo << "端到端时间为" << cost_time/videoInfo::MS_PPE_SECOND; + + return APP_ERR_OK; +} \ No newline at end of file diff --git a/HelmetIdentification/src/utils.h b/HelmetIdentification/src/utils.h new file mode 100644 index 0000000..9ea7b31 --- /dev/null +++ b/HelmetIdentification/src/utils.h @@ -0,0 +1,351 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_UTILS_H +#define MXBASE_HELMETIDENTIFICATION_UTILS_H + +#include +#include +#include +#include "unistd.h" +#include +#include +#include +#include "boost/filesystem.hpp" + +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "opencv2/opencv.hpp" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" +#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" +#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" +#include "MxBase/E2eInfer/DataType.h" + +#include "MxBase/MxBase.h" +#include "MOTConnection.h" +#include "cropResizePaste.hpp" +#include "chrono" +#include "time.h" + +struct FrameImage +{ + MxBase::Image image; + uint32_t frameId = 0; + uint32_t channelId = 0; +}; + +namespace videoInfo +{ + const uint32_t SRC_WIDTH = 1920; + const uint32_t SRC_HEIGHT = 1080; + + const uint32_t YUV_BYTE_NU = 3; + const uint32_t YUV_BYTE_DE = 2; + const uint32_t YOLOV5_RESIZE = 640; + + // 要检测的目标类别的标签 + const std::string TARGET_CLASS_NAME = "head"; + // 使用chrono计数结果为毫秒,需要除以1000转换为秒 + double MS_PPE_SECOND = 1000.0; + + const uint32_t DEVICE_ID = 0; + std::string labelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/imgclass.names"; + std::string configPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/Helmet_yolov5.cfg"; + std::string modelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/YOLOv5_s.om"; + + // 读入视频帧Image的队列 + std::queue frameImageQueue; + // 读入视频帧Image队列的线程锁 + std::mutex g_threadsMutex_frameImageQueue; + + // resize前即原始图像的vector + std::vector realImageVector; + // resize后Image的vector + std::vector resizedImageVector; + // resize后Image队列的线程锁 + std::mutex g_threadsMutex_resizedImageVector; + + // 推理后tensor的vector + std::vector> inferOutputVector; + // 推理后tensor队列的线程锁 + std::mutex g_threadsMutex_inferOutputVector; + + // 后处理后objectInfos的队列 map> + std::vector>> postprocessOutputVector; + // 后处理后objectInfos队列的线程锁 + std::mutex g_threadsMutex_postprocessOutputVector; +} +namespace fs = boost::filesystem; + +// resize线程 +void resizeMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point resize_start_time = std::chrono::high_resolution_clock::now(); + + MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); + + size_t resize_count = 0; + while (resize_count < target_count) + { + if (videoInfo::frameImageQueue.empty()) + { + continue; + } + // 取图像并resize + FrameImage frame = videoInfo::frameImageQueue.front(); + Image image = frame.image; + MxBase::Size originalSize = image.GetOriginalSize(); + MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); + MxBase::Image outputImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); + // 先将缩放后的图像放入resizeImage的队列 + FrameImage resizedFrame; + resizedFrame.channelId = frame.channelId; + resizedFrame.frameId = frame.frameId; + resizedFrame.image = outputImage; + videoInfo::g_threadsMutex_resizedImageVector.lock(); + frame.image.ToHost(); + videoInfo::realImageVector.push_back(frame); + videoInfo::resizedImageVector.push_back(resizedFrame); + videoInfo::g_threadsMutex_resizedImageVector.unlock(); + // 然后再将原图pop出去 + videoInfo::g_threadsMutex_frameImageQueue.lock(); + videoInfo::frameImageQueue.pop(); + videoInfo::g_threadsMutex_frameImageQueue.unlock(); + // 计数 + resize_count++; + } + std::chrono::high_resolution_clock::time_point resize_end_time = std::chrono::high_resolution_clock::now(); + double_t resize_cost_time = std::chrono::duration_cast(resize_end_time - resize_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t resize_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到缩放全部完成一共花费: " << resize_finish_time << ", 缩放本身花费: " << resize_cost_time; +} + +// 推理线程 +void inferMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point infer_start_time = std::chrono::high_resolution_clock::now(); + + std::shared_ptr modelDptr = std::make_shared(videoInfo::modelPath, videoInfo::DEVICE_ID); + + size_t infer_count = 0; + while (infer_count < target_count) + { + if (infer_count >= videoInfo::resizedImageVector.size()) + { + continue; + } + // 从resize后的队列中取图片 + FrameImage resizedFrame = videoInfo::resizedImageVector[infer_count]; + std::vector modelOutputs; + + MxBase::Tensor tensorImg = resizedFrame.image.ConvertToTensor(); + tensorImg.ToDevice(videoInfo::DEVICE_ID); + std::vector inputs; + inputs.push_back(tensorImg); + modelOutputs = modelDptr->Infer(inputs); + for (auto output : modelOutputs) + { + output.ToHost(); + } + + // 将推理结果存入队列 + videoInfo::g_threadsMutex_inferOutputVector.lock(); + videoInfo::inferOutputVector.push_back(modelOutputs); + videoInfo::g_threadsMutex_inferOutputVector.unlock(); + // 计数 + infer_count++; + } + std::chrono::high_resolution_clock::time_point infer_end_time = std::chrono::high_resolution_clock::now(); + double_t infer_cost_time = std::chrono::duration_cast(infer_end_time - infer_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t infer_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到推理全部完成一共花费: " << infer_finish_time << ", 推理本身花费: " << infer_cost_time; +} + +// 后处理线程 +void postprocessMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point postprocess_start_time = std::chrono::high_resolution_clock::now(); + + std::map postConfig; + postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); + postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); + std::shared_ptr postProcessorDptr = std::make_shared(); + if (postProcessorDptr == nullptr) + { + LogError << "init postProcessor failed, nullptr"; + } + postProcessorDptr->Init(postConfig); + + size_t postprocess_count = 0; + while (postprocess_count < target_count) + { + if (postprocess_count >= videoInfo::inferOutputVector.size()) + { + continue; + } + // 取原图信息用于计算 + MxBase::Size originalSize = videoInfo::realImageVector[postprocess_count].image.GetOriginalSize(); + // 从推理结果的队列里面取出推理结果 + std::vector modelOutputs = videoInfo::inferOutputVector[postprocess_count]; + FrameImage resizedFrame = videoInfo::resizedImageVector[postprocess_count]; + // 新的后处理过程 + MxBase::ResizedImageInfo imgInfo; + auto shape = modelOutputs[0].GetShape(); + imgInfo.widthOriginal = originalSize.width; + imgInfo.heightOriginal = originalSize.height; + imgInfo.widthResize = videoInfo::YOLOV5_RESIZE; + imgInfo.heightResize = videoInfo::YOLOV5_RESIZE; + imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; + // 因为yolov5要求输入图像为640*640,所以直接比较原图的height和width就好(如果不理解就去看cropResizePaste.hpp里的GetPasteRect函数) + float resizeRate = originalSize.width > originalSize.height ? (originalSize.width / videoInfo::YOLOV5_RESIZE) : (originalSize.height / videoInfo::YOLOV5_RESIZE); + imgInfo.keepAspectRatioScaling = 1 / resizeRate; + std::vector imageInfoVec = {}; + imageInfoVec.push_back(imgInfo); + // make postProcess inputs + std::vector tensors; + for (size_t i = 0; i < modelOutputs.size(); i++) + { + MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); + MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); + tensors.push_back(tensorBase); + } + // 后处理 + std::vector> objectInfos; + postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); + + // 将后处理结果存入队列 + videoInfo::g_threadsMutex_postprocessOutputVector.lock(); + videoInfo::postprocessOutputVector.push_back(objectInfos); + videoInfo::g_threadsMutex_postprocessOutputVector.unlock(); + // 计数 + postprocess_count++; + } + std::chrono::high_resolution_clock::time_point postprocess_end_time = std::chrono::high_resolution_clock::now(); + double_t postprocess_cost_time = std::chrono::duration_cast(postprocess_end_time - postprocess_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t postprocess_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到后处理全部完成一共花费: " << postprocess_finish_time << ", 后处理本身花费: " << postprocess_cost_time; +} + +// 跟踪去重线程 +void trackMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point track_start_time = std::chrono::high_resolution_clock::now(); + + std::shared_ptr tracker0 = std::make_shared(); + if (tracker0 == nullptr) + { + LogError << "init tracker0 failed, nullptr"; + } + std::shared_ptr tracker1 = std::make_shared(); + if (tracker1 == nullptr) + { + LogError << "init tracker1 failed, nullptr"; + } + // 用于计算帧率 + size_t old_count = 0; + std::chrono::high_resolution_clock::time_point count_time; + std::chrono::high_resolution_clock::time_point old_count_time = std::chrono::high_resolution_clock::now(); + size_t one_step = 2; + size_t track_count = 0; + while (track_count < target_count) + { + // 计算帧率 + // 如果count_time-old_count_time的值大于one_step,就计算一下这个step里面的帧数,然后除以step的值 + count_time = std::chrono::high_resolution_clock::now(); + if (std::chrono::duration_cast(count_time - old_count_time).count() / videoInfo::MS_PPE_SECOND > one_step) + { + old_count_time = count_time; + LogInfo << "rate: " << (track_count - old_count) / one_step * 1.0; + old_count = track_count; + } + // 下面是业务循环 + if (track_count >= videoInfo::postprocessOutputVector.size()) + { + continue; + } + // 从后处理结果的队列中取结果用于跟踪去重 + std::vector> objectInfos = videoInfo::postprocessOutputVector[track_count]; + FrameImage frame = videoInfo::realImageVector[track_count]; + MxBase::Size originalSize = frame.image.GetOriginalSize(); + + // 根据channelId的不同使用不同的tracker + std::vector objInfos_ = {}; + if (frame.channelId == 0) + { + tracker0->ProcessSort(objectInfos, frame.frameId); + APP_ERROR ret = tracker0->GettrackResult(objInfos_); + if (ret != APP_ERR_OK) + { + LogError << "No tracker0"; + } + } + else + { + tracker1->ProcessSort(objectInfos, frame.frameId); + APP_ERROR ret = tracker1->GettrackResult(objInfos_); + if (ret != APP_ERR_OK) + { + LogError << "No tracker1"; + } + } + + uint32_t video_height = originalSize.height; + uint32_t video_width = originalSize.width; + // 初始化OpenCV图像信息矩阵 + cv::Mat imgYuv = cv::Mat(video_height * videoInfo::YUV_BYTE_NU / videoInfo::YUV_BYTE_DE, video_width, CV_8UC1, frame.image.GetData().get()); + cv::Mat imgBgr = cv::Mat(video_height, video_width, CV_8UC3); + // 颜色空间转换 + cv::cvtColor(imgYuv, imgBgr, cv::COLOR_YUV420sp2BGR); + std::vector info; + bool headFlag = false; + for (uint32_t i = 0; i < objInfos_.size(); i++) + { + if (objInfos_[i].className == videoInfo::TARGET_CLASS_NAME) + { + headFlag = true; + LogWarn << "Warning:Not wearing a helmet, channelId:" << frame.channelId << ", frameId:" << frame.frameId; + // (blue, green, red) + const cv::Scalar color = cv::Scalar(0, 0, 255); + // width for rectangle + const uint32_t thickness = 2; + // draw the rectangle + cv::rectangle(imgBgr, + cv::Rect(objInfos_[i].x0, objInfos_[i].y0, objInfos_[i].x1 - objInfos_[i].x0, objInfos_[i].y1 - objInfos_[i].y0), + color, thickness); + } + } + // 如果检测结果中有head标签,就保存为图片 + if (headFlag) + { + // 把Mat类型的图像矩阵保存为图像到指定位置。 + std::string outPath = frame.channelId == 0 ? "one" : "two"; + std::string fileName = "./result/" + outPath + "/result" + std::to_string(frame.frameId) + ".jpg"; + cv::imwrite(fileName, imgBgr); + } + + // 计数 + track_count++; + } + std::chrono::high_resolution_clock::time_point track_end_time = std::chrono::high_resolution_clock::now(); + double_t track_cost_time = std::chrono::duration_cast(track_end_time - track_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t track_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到跟踪去重全部完成一共花费: " << track_finish_time << ", 跟踪去重本身花费: " << track_cost_time; +} + +#endif \ No newline at end of file From 76868c3c3df4ab3131ae933f04ec63fa15ecab7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 02:51:11 +0000 Subject: [PATCH 02/58] update HelmetIdentification/src/cropResizePaste.hpp. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification/src/cropResizePaste.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/HelmetIdentification/src/cropResizePaste.hpp b/HelmetIdentification/src/cropResizePaste.hpp index 09d290f..37c6e8f 100644 --- a/HelmetIdentification/src/cropResizePaste.hpp +++ b/HelmetIdentification/src/cropResizePaste.hpp @@ -29,8 +29,6 @@ #define CONVER_TO_EVEN(NUM) (((NUM) % 2 == 0) ? (NUM) : ((NUM)-1)) #define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) -//using namespace MxBase; - MxBase::Rect GetPasteRect(uint32_t inputWidth, uint32_t inputHeight, uint32_t outputWidth, uint32_t outputHeight) { bool widthRatioLarger = true; From 1f1a976bb5dd4e2b556dd9ca38d986a9ae486061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:37:00 +0000 Subject: [PATCH 03/58] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20images?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification/images/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 HelmetIdentification/images/.keep diff --git a/HelmetIdentification/images/.keep b/HelmetIdentification/images/.keep new file mode 100644 index 0000000..e69de29 From 4f0f015c208be1eb48aa80a0991ad968354ce8df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:37:27 +0000 Subject: [PATCH 04/58] =?UTF-8?q?readme=E4=B8=AD=E7=9A=84=E5=9B=BE?= =?UTF-8?q?=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification/images/flow.jpg | Bin 0 -> 78684 bytes HelmetIdentification/images/make.png | Bin 0 -> 13330 bytes HelmetIdentification/images/objInfo.png | Bin 0 -> 9455 bytes HelmetIdentification/images/rate.jpg | Bin 0 -> 103269 bytes HelmetIdentification/images/result0.jpg | Bin 0 -> 694240 bytes HelmetIdentification/images/warn.png | Bin 0 -> 29591 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 HelmetIdentification/images/flow.jpg create mode 100644 HelmetIdentification/images/make.png create mode 100644 HelmetIdentification/images/objInfo.png create mode 100644 HelmetIdentification/images/rate.jpg create mode 100644 HelmetIdentification/images/result0.jpg create mode 100644 HelmetIdentification/images/warn.png diff --git a/HelmetIdentification/images/flow.jpg b/HelmetIdentification/images/flow.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d943e936d03fef5c34c0c0352e87bd04b6d1f22e GIT binary patch literal 78684 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

W11h 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 G=sy5e3GJ8w literal 0 HcmV?d00001 diff --git a/HelmetIdentification/images/make.png b/HelmetIdentification/images/make.png new file mode 100644 index 0000000000000000000000000000000000000000..138f48a0df88f0d40df7f82f7c8359ce6fbf9875 GIT binary patch literal 13330 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 literal 0 HcmV?d00001 diff --git a/HelmetIdentification/images/objInfo.png b/HelmetIdentification/images/objInfo.png new file mode 100644 index 0000000000000000000000000000000000000000..e642d774a1931ec2a0eb423cb2b7d200461f6c76 GIT binary patch literal 9455 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 Lb%5(?cjW(MNSAT1(qq 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 ATmS$7 literal 0 HcmV?d00001 diff --git a/HelmetIdentification/images/result0.jpg b/HelmetIdentification/images/result0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d348d127557135c34e7614bb5288f0ca1ab8d8e2 GIT binary patch literal 694240 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ζ<(8|GUS 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

U^V87^ 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

{mPK>`FGd-9UPNFXMs&&C|dm|rz+l~yvpT8C? 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�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 literal 0 HcmV?d00001 diff --git a/HelmetIdentification/images/warn.png b/HelmetIdentification/images/warn.png new file mode 100644 index 0000000000000000000000000000000000000000..32f7425bdcc3edb357ebbd14772c5305042e28a4 GIT binary patch literal 29591 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 KME0?BLH`3Ikp!9m literal 0 HcmV?d00001 From 1305712ba6a535627cdeeb2e06ec3bb020165e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:37:34 +0000 Subject: [PATCH 05/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification/images/.keep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification/images/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 HelmetIdentification/images/.keep diff --git a/HelmetIdentification/images/.keep b/HelmetIdentification/images/.keep deleted file mode 100644 index e69de29..0000000 From e7c237f07597ac5f1d52c38a45ee9ae7d63fff5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:38:00 +0000 Subject: [PATCH 06/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification/readme.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 HelmetIdentification/readme.md diff --git a/HelmetIdentification/readme.md b/HelmetIdentification/readme.md deleted file mode 100644 index c899ba4..0000000 --- a/HelmetIdentification/readme.md +++ /dev/null @@ -1 +0,0 @@ -main.cpp是读取视频进行推理的代码,main-image.cpp是读取单张图片进行推理的代码。 \ No newline at end of file From 52e197c93132fec74f4494258c1a342421511c0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:38:14 +0000 Subject: [PATCH 07/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification/模板—README【模板】.md | 295 ++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 HelmetIdentification/模板—README【模板】.md diff --git a/HelmetIdentification/模板—README【模板】.md b/HelmetIdentification/模板—README【模板】.md new file mode 100644 index 0000000..14446ef --- /dev/null +++ b/HelmetIdentification/模板—README【模板】.md @@ -0,0 +1,295 @@ +# 标题(项目标题) + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + + + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── YOLOv5_s.om // 模型文件(需自行下载或转换) + └── imgclass.names // label文件 +├── src + ├── main.cpp // 安全帽检测的main函数入口 + ├── main-image.cpp // 读取图片测试精度 + ├── utils.h // 实现多线程的工具类 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── Hungar ian.h // 匈牙利算法的头文件 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── DataType.h // 自定义的数据结构 + ├── CMakeLists.txt // CMake文件 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + ├── two // 文件夹,保存第二路输入的结果(需要新建) +├── CMakeLists.txt // CMake文件 +``` + + + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + + + +### 1.6 特性及适用场景 TODO + +(项目特性和使用场景约束等) + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +2. 进入src文件夹,修改其中的utils.h文件 + + * 配置文件路径,根据服务器路径修改: + + **以下路径需要设置为绝对路径,设置为相对路径编译能够通过但是执行时会报错。** + + 第68行**labelPath**、第69行**configPath**、第70行**modelPath**。 + + * 输入视频宽高,根据264文件的属性修改(默认是宽1920高1080) + + 第55的`SRC_WIDTH`变量表示输入视频的宽 + + 第56行的`SRC_HEIGHT`变量表示输入视频的高 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video1Path} +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +图片示例: + +![](./images/result0.jpg) + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imagePath} +``` + +imagePath是图片路径 + +正确执行会输出检测到的目标框的信息,如下所示: + +![](./images/objInfo.png) + From 2832af9df158480ff0786b150beb0a46bc59d592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:38:26 +0000 Subject: [PATCH 08/58] =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D=20HelmetIdent?= =?UTF-8?q?ification/=E6=A8=A1=E6=9D=BF=E2=80=94README=E3=80=90=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E3=80=91.md=20=E4=B8=BA=20HelmetIdentification/readme?= =?UTF-8?q?.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification/{模板—README【模板】.md => readme.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename HelmetIdentification/{模板—README【模板】.md => readme.md} (100%) diff --git a/HelmetIdentification/模板—README【模板】.md b/HelmetIdentification/readme.md similarity index 100% rename from HelmetIdentification/模板—README【模板】.md rename to HelmetIdentification/readme.md From eaa04e57b3eaadcb6593cde0e0c37e57fb654afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:43:18 +0000 Subject: [PATCH 09/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification/readme.md | 295 --------------------------------- 1 file changed, 295 deletions(-) delete mode 100644 HelmetIdentification/readme.md diff --git a/HelmetIdentification/readme.md b/HelmetIdentification/readme.md deleted file mode 100644 index 14446ef..0000000 --- a/HelmetIdentification/readme.md +++ /dev/null @@ -1,295 +0,0 @@ -# 标题(项目标题) - -## 1 介绍 -安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 - -本项目支持2路视频实时分析,其主要流程为: - -​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 - -### 1.1 支持的产品 - -本项目以昇腾Atlas 200DK为主要的硬件平台。 - -### 1.2 支持的版本 - -本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 - -MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 - -### 1.3 软件方案介绍 - -请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 - -表1.1 系统方案各子系统功能描述: - -| 序号 | 子系统 | 功能描述 | -| ---- | ---------- | ------------------------------------------------------------ | -| 1 | 视频解码 | 从264视频流中解码图像帧 | -| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | -| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | -| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | -| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | -| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | - - - -### 1.4 代码目录结构与说明 - -本工程名称为HelmetIdentification_V2,工程目录如下图所示: - -``` -. -├── model - ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 - ├── YOLOv5_s.om // 模型文件(需自行下载或转换) - └── imgclass.names // label文件 -├── src - ├── main.cpp // 安全帽检测的main函数入口 - ├── main-image.cpp // 读取图片测试精度 - ├── utils.h // 实现多线程的工具类 - ├── cropResizePaste.hpp // 自定义的等比例缩放 - ├── MOTConnection.h // 跟踪匹配方法的头文件 - ├── MOTConnection.cpp // 跟踪匹配方法的实现 - ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 - ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 - ├── Hungar ian.h // 匈牙利算法的头文件 - ├── Hungarian.cpp // 匈牙利算法的实现 - ├── DataType.h // 自定义的数据结构 - ├── CMakeLists.txt // CMake文件 -├── result - ├── one // 文件夹,保存第一路输入的结果(需要新建) - ├── two // 文件夹,保存第二路输入的结果(需要新建) -├── CMakeLists.txt // CMake文件 -``` - - - -### 1.5 技术实现流程图 - -![](./images/flow.jpg) - - - -### 1.6 特性及适用场景 TODO - -(项目特性和使用场景约束等) - - - -## 2 环境依赖 - -### 2.1 软件依赖 - -环境依赖软件和版本如下表: - -| 软件 | 版本 | 说明 | 获取方式 | -| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | -| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | -| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | -| Ubuntu | 18.04 | | | - -### 2.2 环境变量 - -在编译运行项目前,需要设置环境变量: - -MindX SDK环境变量: - -. ${SDK-path}/set_env.sh - -CANN环境变量: - -. ${ascend-toolkit-path}/set_env.sh - -环境变量介绍 - -SDK-path:mxVision SDK安装路径 - -ascend-toolkit-path:CANN安装路径 - - - -## 3 模型转换 - -模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html - -本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 - -具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 - -运行命令如下: - -``` -sh atc-env.sh -``` - -脚本中包含atc命令: - -``` ---model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" -``` - -其参数如下表所示: - -| 参数名 | 参数描述 | -| ---------------- | ------------------------------------------------------------ | -| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | -| --model | 原始模型文件路径与文件名 | -| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | -| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | -| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | -| --input_shape | 模型输入数据的 shape。 | -| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | - -其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 - -注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 - -## 4 编译与运行 - -项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 - -### 4.1 测试性能 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 - -2. 进入src文件夹,修改其中的utils.h文件 - - * 配置文件路径,根据服务器路径修改: - - **以下路径需要设置为绝对路径,设置为相对路径编译能够通过但是执行时会报错。** - - 第68行**labelPath**、第69行**configPath**、第70行**modelPath**。 - - * 输入视频宽高,根据264文件的属性修改(默认是宽1920高1080) - - 第55的`SRC_WIDTH`变量表示输入视频的宽 - - 第56行的`SRC_HEIGHT`变量表示输入视频的高 - -3. 进入src文件夹,修改其中的CMakeLists.txt - - * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - - * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - - -**步骤2** 选择对应的main文件 - -测试性能使用main.cpp -需要删除main-image.cpp -可以使用如下命令进行: - -```shell -cd src -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹 - -如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): - -``` -mkdir result -cd result -mkdir one -mkdir two -``` - -退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 - -``` -./main ${video0Path} ${video1Path} -``` - -video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 - -如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 - -![](./images/warn.png) - -图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 - -图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 - -图片示例: - -![](./images/result0.jpg) - -在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: - -![](./images/rate.jpg) - -### 4.2 测试精度 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 -2. 进入src文件夹,修改其中的CMakeLists.txt - 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试精度使用main-image.cpp -需要删除main.cpp,然后将main-image.cpp重命名为main.cpp -可以使用如下命令进行: - -```sh -cd src -rm ./main.cpp -cp ./main-image.cpp ./main.cpp -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: - -``` -./main ${imagePath} -``` - -imagePath是图片路径 - -正确执行会输出检测到的目标框的信息,如下所示: - -![](./images/objInfo.png) - From 855da854686852c9d26e930a592733c858bbf08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:43:28 +0000 Subject: [PATCH 10/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification/readme.md | 297 +++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 HelmetIdentification/readme.md diff --git a/HelmetIdentification/readme.md b/HelmetIdentification/readme.md new file mode 100644 index 0000000..5ae1c3a --- /dev/null +++ b/HelmetIdentification/readme.md @@ -0,0 +1,297 @@ +# 标题(项目标题) + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + + + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── YOLOv5_s.om // 模型文件(需自行下载或转换) + └── imgclass.names // label文件 +├── src + ├── main.cpp // 安全帽检测的main函数入口 + ├── main-image.cpp // 读取图片测试精度 + ├── utils.h // 实现多线程的工具类 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── Hungar ian.h // 匈牙利算法的头文件 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── DataType.h // 自定义的数据结构 + └── CMakeLists.txt // CMake文件 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + └── two // 文件夹,保存第二路输入的结果(需要新建) +└── CMakeLists.txt // CMake文件 +``` + + + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + + + +### 1.6 特性及适用场景 TODO + +(项目特性和使用场景约束等) + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +2. 进入src文件夹,修改其中的utils.h文件 + + * 配置文件路径,根据服务器路径修改: + + **以下路径需要设置为绝对路径,设置为相对路径编译能够通过但是执行时会报错。** + + 第68行**labelPath**、第69行**configPath**、第70行**modelPath**。 + + * 输入视频宽高,根据264文件的属性修改(默认是宽1920高1080) + + 第55的`SRC_WIDTH`变量表示输入视频的宽 + + 第56行的`SRC_HEIGHT`变量表示输入视频的高 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video1Path} +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +图片示例: + +![](./images/result0.jpg) + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imagePath} +``` + +imagePath是图片路径(例如 ./main 000023.jpg) + +正确执行会输出检测到的目标框的信息,如下所示: + +![](./images/objInfo.png) + From ad03dcd621876ce3ecbddc3debc6dcd13121618e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:45:03 +0000 Subject: [PATCH 11/58] update HelmetIdentification/src/main-image.cpp. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification/src/main-image.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HelmetIdentification/src/main-image.cpp b/HelmetIdentification/src/main-image.cpp index 9ec0e66..27dd5c5 100644 --- a/HelmetIdentification/src/main-image.cpp +++ b/HelmetIdentification/src/main-image.cpp @@ -22,7 +22,7 @@ using namespace std; // 如果在200DK上运行就改为 USE_200DK -#define USE_DVPP +#define USE_200DK APP_ERROR readImage(std::string imgPath, MxBase::Image& image, ImageProcessor& imageProcessor) { From e2d01f7666060c1a5aff53dfb76108d51452cddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:46:18 +0000 Subject: [PATCH 12/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification/src?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification/src/CMakeLists.txt | 47 --- HelmetIdentification/src/DataType.h | 94 ----- HelmetIdentification/src/Hungarian.cpp | 165 --------- HelmetIdentification/src/Hungarian.h | 46 --- HelmetIdentification/src/KalmanTracker.cpp | 143 -------- HelmetIdentification/src/KalmanTracker.h | 39 --- HelmetIdentification/src/MOTConnection.cpp | 262 -------------- HelmetIdentification/src/MOTConnection.h | 78 ----- HelmetIdentification/src/cropResizePaste.hpp | 115 ------ HelmetIdentification/src/main-image.cpp | 170 --------- HelmetIdentification/src/main.cpp | 283 --------------- HelmetIdentification/src/utils.h | 351 ------------------- 12 files changed, 1793 deletions(-) delete mode 100644 HelmetIdentification/src/CMakeLists.txt delete mode 100644 HelmetIdentification/src/DataType.h delete mode 100644 HelmetIdentification/src/Hungarian.cpp delete mode 100644 HelmetIdentification/src/Hungarian.h delete mode 100644 HelmetIdentification/src/KalmanTracker.cpp delete mode 100644 HelmetIdentification/src/KalmanTracker.h delete mode 100644 HelmetIdentification/src/MOTConnection.cpp delete mode 100644 HelmetIdentification/src/MOTConnection.h delete mode 100644 HelmetIdentification/src/cropResizePaste.hpp delete mode 100644 HelmetIdentification/src/main-image.cpp delete mode 100644 HelmetIdentification/src/main.cpp delete mode 100644 HelmetIdentification/src/utils.h diff --git a/HelmetIdentification/src/CMakeLists.txt b/HelmetIdentification/src/CMakeLists.txt deleted file mode 100644 index 6fcd298..0000000 --- a/HelmetIdentification/src/CMakeLists.txt +++ /dev/null @@ -1,47 +0,0 @@ -# CMake lowest version requirement -cmake_minimum_required(VERSION 3.5.1) -# project information -project(Individual) - -# Compile options -add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0 -Dgoogle=mindxsdk_private) -add_compile_options(-std=c++11 -fPIC -fstack-protector-all -Wall -D_FORTIFY_SOURCE=2 -O2) - -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../../") -set(CMAKE_CXX_FLAGS_DEBUG "-g") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now,-z,noexecstack -s -pie -pthread") -set(CMAKE_SKIP_RPATH TRUE) - -SET(CMAKE_BUILD_TYPE "Debug") -SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb") -SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall") - -# Header path -include_directories( - ${MX_SDK_HOME}/include/ - ${MX_SDK_HOME}/opensource/include/ - ${MX_SDK_HOME}/opensource/include/opencv4/ - /home/HwHiAiUser/Ascend/ascend-toolkit/latest/include/ - ./ -) - -# add host lib path -link_directories( - ${MX_SDK_HOME}/lib/ - ${MX_SDK_HOME}/lib/modelpostprocessors - ${MX_SDK_HOME}/opensource/lib/ - ${MX_SDK_HOME}/opensource/lib64/ - /usr/lib/aarch64-linux-gnu/ - /home/HwHiAiUser/Ascend/ascend-toolkit/latest/lib64/ - /usr/local/Ascend/driver/lib64/ - ./ -) - - -aux_source_directory(. sourceList) - -add_executable(main ${sourceList}) - -target_link_libraries(main mxbase opencv_world boost_filesystem glog avformat avcodec avutil cpprest yolov3postprocess ascendcl acl_dvpp_mpi) - -install(TARGETS main DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/HelmetIdentification/src/DataType.h b/HelmetIdentification/src/DataType.h deleted file mode 100644 index dc34a0a..0000000 --- a/HelmetIdentification/src/DataType.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_DATATYPE_H -#define MXBASE_HELMETIDENTIFICATION_DATATYPE_H - -#include -#include -#include -#include -#include -#include -#include "opencv2/highgui.hpp" -#include "opencv2/imgcodecs.hpp" -#include "opencv2/imgproc.hpp" - -namespace ascendVehicleTracking { -#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) - - const int MODULE_QUEUE_SIZE = 1000; - - enum FrameMode { - FRAME_MODE_SEARCH = 0, - FRAME_MODE_REG - }; - - struct DataBuffer { - std::shared_ptr deviceData; - std::shared_ptr hostData; - uint32_t dataSize; // buffer size - DataBuffer() : deviceData(nullptr), hostData(nullptr), dataSize(0) {} - }; - - struct DetectInfo { - int32_t classId; - float confidence; - float minx; // x value of left-top point - float miny; // y value of left-top point - float height; - float width; - }; - - enum TraceFlag { - NEW_VEHICLE = 0, - TRACkED_VEHICLE, - LOST_VEHICLE - }; - - struct TraceInfo { - int32_t id; - TraceFlag flag; - int32_t survivalTime; // How long is it been since the first time, unit: detection period - int32_t detectedTime; // How long is the vehicle detected, unit: detection period - std::chrono::time_point createTime; - }; - - struct TrackLet { - TraceInfo info; - // reserved: kalman status parameter - int32_t lostTime; // undetected time for tracked vehicle - std::vector shortFeature; // nearest 10 frame - }; - - struct VehicleQuality { - float score; - }; - - struct Coordinate2D { - uint32_t x; - uint32_t y; - }; -} -// namespace ascendVehicleTracking - -struct AttrT { - AttrT(std::string name, std::string value) : name(std::move(name)), value(std::move(value)) {} - std::string name = {}; - std::string value = {}; -}; - -#endif diff --git a/HelmetIdentification/src/Hungarian.cpp b/HelmetIdentification/src/Hungarian.cpp deleted file mode 100644 index 3f6fcd2..0000000 --- a/HelmetIdentification/src/Hungarian.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Hungarian.h" -#include -#include -#include "MxBase/Log/Log.h" - -namespace { - const int INF = 0x3f3f3f3f; - const int VISITED = 1; - const int HUNGARIAN_CONTENT = 7; - const int X_MATCH_OFFSET = 0; - const int Y_MATCH_OFFSET = 1; - const int X_VALUE_OFFSET = 2; - const int Y_VALUE_OFFSET = 3; - const int SLACK_OFFSET = 4; - const int X_VISIT_OFFSET = 5; - const int Y_VISIT_OFFSET = 6; -} - -APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols) -{ - handle.max = (row > cols) ? row : cols; - auto adjMat = std::shared_ptr(); - adjMat.reset(new int[handle.max * handle.max], std::default_delete()); - if (adjMat == nullptr) { - LogFatal << "HungarianHandleInit new failed"; - return APP_ERR_ACL_FAILURE; - } - - handle.adjMat = adjMat; - - void* ptr[HUNGARIAN_CONTENT] = {nullptr}; - for (int i = 0; i < HUNGARIAN_CONTENT; ++i) { - ptr[i] = malloc(handle.max * sizeof(int)); - if (ptr[i] == nullptr) { - LogFatal << "HungarianHandleInit Malloc failed"; - return APP_ERR_ACL_FAILURE; - } - } - - handle.xMatch.reset((int *)ptr[X_MATCH_OFFSET], free); - handle.yMatch.reset((int *)ptr[Y_MATCH_OFFSET], free); - handle.xValue.reset((int *)ptr[X_VALUE_OFFSET], free); - handle.yValue.reset((int *)ptr[Y_VALUE_OFFSET], free); - handle.slack.reset((int *)ptr[SLACK_OFFSET], free); - handle.xVisit.reset((int *)ptr[X_VISIT_OFFSET], free); - handle.yVisit.reset((int *)ptr[Y_VISIT_OFFSET], free); - return APP_ERR_OK; -} - -static void HungarianInit(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) -{ - int i, j, value; - if (rows > cols) { - handle.transpose = true; - handle.cols = rows; - handle.rows = cols; - handle.resX = handle.yMatch.get(); - handle.resY = handle.xMatch.get(); - } else { - handle.transpose = false; - handle.rows = rows; - handle.cols = cols; - handle.resX = handle.xMatch.get(); - handle.resY = handle.yMatch.get(); - } - - for (i = 0; i < handle.rows; ++i) { - handle.xValue.get()[i] = 0; - handle.xMatch.get()[i] = -1; - for (j = 0; j < handle.cols; ++j) { - if (handle.transpose) { - value = cost[j][i]; - } else { - value = cost[i][j]; - } - handle.adjMat.get()[i * handle.cols + j] = value; - if (handle.xValue.get()[i] < value) { - handle.xValue.get()[i] = value; - } - } - } - - for (i = 0; i < handle.cols; ++i) { - handle.yValue.get()[i] = 0; - handle.yMatch.get()[i] = -1; - } -} - -static bool Match(HungarianHandle &handle, int id) -{ - int j, delta; - handle.xVisit.get()[id] = VISITED; - for (j = 0; j < handle.cols; ++j) { - if (handle.yVisit.get()[j] == VISITED) { - continue; - } - delta = handle.xValue.get()[id] + handle.yValue.get()[j] - handle.adjMat.get()[id * handle.cols + j]; - if (delta == 0) { - handle.yVisit.get()[j] = VISITED; - if (handle.yMatch.get()[j] == -1 || Match(handle, handle.yMatch.get()[j])) { - handle.yMatch.get()[j] = id; - handle.xMatch.get()[id] = j; - return true; - } - } else if (delta < handle.slack.get()[j]) { - handle.slack.get()[j] = delta; - } - } - return false; -} - -int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) -{ - HungarianInit(handle, cost, rows, cols); - int i, j, delta; - for (i = 0; i < handle.rows; ++i) { - while (true) { - std::fill(handle.xVisit.get(), handle.xVisit.get() + handle.rows, 0); - std::fill(handle.yVisit.get(), handle.yVisit.get() + handle.cols, 0); - for (j = 0; j < handle.cols; ++j) { - handle.slack.get()[j] = INF; - } - if (Match(handle, i)) { - break; - } - delta = INF; - for (j = 0; j < handle.cols; ++j) { - if (handle.yVisit.get()[j] != VISITED && delta > handle.slack.get()[j]) { - delta = handle.slack.get()[j]; - } - } - if (delta == INF) { - LogDebug << "Hungarian solve is invalid!"; - return -1; - } - for (j = 0; j < handle.rows; ++j) { - if (handle.xVisit.get()[j] == VISITED) { - handle.xValue.get()[j] -= delta; - } - } - for (j = 0; j < handle.cols; ++j) { - if (handle.yVisit.get()[j] == VISITED) { - handle.yValue.get()[j] += delta; - } - } - } - } - return handle.rows; -} diff --git a/HelmetIdentification/src/Hungarian.h b/HelmetIdentification/src/Hungarian.h deleted file mode 100644 index 2ae6a33..0000000 --- a/HelmetIdentification/src/Hungarian.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H -#define MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H - -#include -#include -#include "DataType.h" -#include "MxBase/PostProcessBases/PostProcessDataType.h" -#include "MxBase/ErrorCode/ErrorCodes.h" - -struct HungarianHandle { - int rows; - int cols; - int max; - int *resX; - int *resY; - bool transpose; - std::shared_ptr adjMat; - std::shared_ptr xMatch; - std::shared_ptr yMatch; - std::shared_ptr xValue; - std::shared_ptr yValue; - std::shared_ptr slack; - std::shared_ptr xVisit; - std::shared_ptr yVisit; -}; - -APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols); -int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols); - -#endif \ No newline at end of file diff --git a/HelmetIdentification/src/KalmanTracker.cpp b/HelmetIdentification/src/KalmanTracker.cpp deleted file mode 100644 index 69362f9..0000000 --- a/HelmetIdentification/src/KalmanTracker.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "KalmanTracker.h" - -namespace ascendVehicleTracking -{ - namespace - { - const int OFFSET = 2; - const int MULTIPLE = 2; - } - - /* - * The SORT algorithm uses a linear constant velocity model,which assumes 7 - * states, including - * x coordinate of bounding box center - * y coordinate of bounding box center - * area of bounding box - * aspect ratio of w to h - * velocity of x - * velocity of y - * variation rate of area - * - * The aspect ratio is considered to be unchanged, so there is no additive item - * for aspect ratio in the transitionMatrix - * - * - * Kalman filter equation step by step - * (1) X(k|k-1)=AX(k-1|k-1)+BU(k) - * X(k|k-1) is the predicted state(statePre),X(k-1|k-1) is the k-1 statePost,A - * is transitionMatrix, B is controlMatrix, U(k) is control state, in SORT U(k) is 0. - * - * (2) P(k|k-1)=AP(k-1|k-1)A'+Q - * P(k|k-1) is the predicted errorCovPre, P(k-1|k-1) is the k-1 errorCovPost, - * Q is processNoiseCov - * - * (3) Kg(k)=P(k|k-1)H'/(HP(k|k-1))H'+R - * Kg(k) is the kalman gain, the ratio of estimate variance in total variance, - * H is the measurementMatrix,R is the measurementNoiseCov - * - * (4) X(k|k)=X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) - * X(k|k) is the k statePost, Z(k) is the measurement of K, in SORT Z(k) is - * the detection result of k - * - * (5) P(k|k)=(1-Kg(k)H)P(k|k-1) - * P(k|k) is the errorCovPost - */ - void KalmanTracker::CvKalmanInit(MxBase::ObjectInfo initRect) - { - const int stateDim = 7; - const int measureDim = 4; - cvkalmanfilter_ = cv::KalmanFilter(stateDim, measureDim, 0); // zero control - measurement_ = cv::Mat::zeros(measureDim, 1, CV_32F); // 4 measurements, Z(k), according to detection results - // A, will not be updated - cvkalmanfilter_.transitionMatrix = (cv::Mat_(stateDim, stateDim) << 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1); - cvkalmanfilter_.measurementMatrix = (cv::Mat_(measureDim, stateDim) << 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0); - cv::setIdentity(cvkalmanfilter_.measurementMatrix); // H, will not be updated - cv::setIdentity(cvkalmanfilter_.processNoiseCov, cv::Scalar::all(1e-2)); // Q, will not be updated - cv::setIdentity(cvkalmanfilter_.measurementNoiseCov, cv::Scalar::all(1e-1)); // R, will bot be updated - cv::setIdentity(cvkalmanfilter_.errorCovPost, cv::Scalar::all(1)); // P(k-1|k-1), will be updated - - // initialize state vector with bounding box in - // [center_x,center_y,area,ratio] - // style, the velocity is 0 - // X(k-1|k-1) - cvkalmanfilter_.statePost.at(0, 0) = initRect.x0 + (initRect.x1 - initRect.x0) / MULTIPLE; - cvkalmanfilter_.statePost.at(1, 0) = initRect.y0 + (initRect.y1 - initRect.y0) / MULTIPLE; - cvkalmanfilter_.statePost.at(OFFSET, 0) = (initRect.x1 - initRect.x0) * (initRect.y1 - initRect.y0); - cvkalmanfilter_.statePost.at(OFFSET + 1, 0) = (initRect.x1 - initRect.x0) / (initRect.y1 - initRect.y0); - } - - // Predict the bounding box. - MxBase::ObjectInfo KalmanTracker::Predict() - { - // predict - // return X(k|k-1)=AX(k-1|k-1), and update - // P(k|k-1) <- AP(k-1|k-1)A'+Q - MxBase::ObjectInfo detectInfo = {}; - cv::Mat predictState = cvkalmanfilter_.predict(); - float *pData = (float *)(predictState.data); - float w = sqrt((*(pData + OFFSET)) * (*(pData + OFFSET + 1))); - if (w < DBL_EPSILON) - { - detectInfo.x0 = 0; - detectInfo.y0 = 0; - detectInfo.x1 = 0; - detectInfo.y1 = 0; - detectInfo.classId = 0; - return detectInfo; - } - if (w == 0) - { - MxBase::ObjectInfo w0DetectInfo = {}; - return w0DetectInfo; - } - float h = (*(pData + OFFSET)) / w; - float x = (*pData) - w / MULTIPLE; - float y = (*(pData + 1)) - h / MULTIPLE; - if (x < 0 && (*pData) > 0) - { - x = 0; - } - if (y < 0 && (*(pData + 1)) > 0) - { - y = 0; - } - detectInfo.x0 = x; - detectInfo.y0 = y; - detectInfo.x1 = x + w; - detectInfo.y1 = y + h; - return detectInfo; - } - - // Update the state using observed bounding box - void KalmanTracker::Update(MxBase::ObjectInfo stateMat) - { - // measurement_, update Z(k) - float *pData = (float *)(measurement_.data); - *pData = stateMat.x0 + (stateMat.x1 - stateMat.x0) / MULTIPLE; - *(pData + 1) = stateMat.y0 + (stateMat.y1 - stateMat.y0) / MULTIPLE; - *(pData + OFFSET) = (stateMat.x1 - stateMat.x0) * (stateMat.y1 - stateMat.y0); - *(pData + OFFSET + 1) = (stateMat.x1 - stateMat.x0) / (stateMat.y1 - stateMat.y0); - // update, do the following steps: - // Kg(k): P(k|k-1)H'/(HP(k|k-1))H'+R - // X(k|k): X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) - // P(k|k): (1-Kg(k)H)P(k|k-1) - cvkalmanfilter_.correct(measurement_); - } -} // namespace diff --git a/HelmetIdentification/src/KalmanTracker.h b/HelmetIdentification/src/KalmanTracker.h deleted file mode 100644 index f5f3465..0000000 --- a/HelmetIdentification/src/KalmanTracker.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H -#define MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H - -#include "opencv2/highgui/highgui.hpp" -#include "opencv2/video/tracking.hpp" -#include "DataType.h" -#include "MxBase/PostProcessBases/PostProcessDataType.h" - -namespace ascendVehicleTracking { -class KalmanTracker { -public: - KalmanTracker() {} - ~KalmanTracker() {} - void CvKalmanInit(MxBase::ObjectInfo initRect); - MxBase::ObjectInfo Predict(); - void Update(MxBase::ObjectInfo stateMat); -private: - cv::KalmanFilter cvkalmanfilter_ = {}; - cv::Mat measurement_ = {}; -}; -} // namesapce ascendVehicleTracking - -#endif \ No newline at end of file diff --git a/HelmetIdentification/src/MOTConnection.cpp b/HelmetIdentification/src/MOTConnection.cpp deleted file mode 100644 index 14e4341..0000000 --- a/HelmetIdentification/src/MOTConnection.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "MOTConnection.h" -#include -#include -#include -#include -#include "opencv2/highgui.hpp" -#include "opencv2/imgcodecs.hpp" -#include "opencv2/imgproc.hpp" -#include "MxBase/Log/Log.h" -#include "MxBase/ErrorCode/ErrorCodes.h" -#include "Hungarian.h" - -namespace ascendVehicleTracking -{ - namespace - { - // convert double to int - const int FLOAT_TO_INT = 1000; - const int MULTIPLE = 0; - const double SIMILARITY_THRESHOLD = 0.66; - const int MULTIPLE_IOU = 6; - const float NORM_EPS = 1e-10; - const double TIME_COUNTS = 1000.0; - const double COST_TIME_MS_THRESHOLD = 10.; - const float WIDTH_RATE_THRESH = 1.f; - const float HEIGHT_RATE_THRESH = 1.f; - const float X_DIST_RATE_THRESH = 1.3f; - const float Y_DIST_RATE_THRESH = 1.f; - } // namespace - - // 计算bounding box的交并比 - float CalIOU(MxBase::ObjectInfo detect1, MxBase::ObjectInfo detect2) - { - cv::Rect_ bbox1(detect1.x0, detect1.y0, detect1.x1 - detect1.x0, detect1.y1 - detect1.y0); - cv::Rect_ bbox2(detect2.x0, detect2.y0, detect2.x1 - detect2.x0, detect2.y1 - detect2.y0); - float intersectionArea = (bbox1 & bbox2).area(); - float unionArea = bbox1.area() + bbox2.area() - intersectionArea; - if (unionArea < DBL_EPSILON) - { - return 0.f; - } - return (intersectionArea / unionArea); - } - - // 计算前后两帧的两个bounding box的相似度 - float CalSimilarity(const TraceLet &traceLet, const MxBase::ObjectInfo &objectInfo, const int &method, const double &kIOU) - { - return CalIOU(traceLet.detectInfo, objectInfo); - } - - // 过滤掉交并比小于阈值的匹配 - void MOTConnection::FilterLowThreshold(const HungarianHandle &hungarianHandleObj, - const std::vector> &disMatrix, std::vector &matchedTracedDetected, - std::vector &detectVehicleFlagVec) - { - for (unsigned int i = 0; i < traceList_.size(); ++i) - { - if ((hungarianHandleObj.resX[i] != -1) && - (disMatrix[i][hungarianHandleObj.resX[i]] >= (trackThreshold_ * FLOAT_TO_INT))) - { - matchedTracedDetected.push_back(cv::Point(i, hungarianHandleObj.resX[i])); - detectVehicleFlagVec[hungarianHandleObj.resX[i]] = true; - } - else - { - traceList_[i].info.flag = LOST_VEHICLE; - } - } - } - - // 更新没有匹配上的跟踪器 - void MOTConnection::UpdateUnmatchedTraceLet(const std::vector> &objInfos) - { - for (auto itr = traceList_.begin(); itr != traceList_.end();) - { - if ((*itr).info.flag != LOST_VEHICLE) - { - ++itr; - continue; - } - - (*itr).lostAge++; - (*itr).kalman.Update((*itr).detectInfo); - - if ((*itr).lostAge < lostThreshold_) - { - continue; - } - - itr = traceList_.erase(itr); - } - } - - // 更新匹配上的跟踪器 - void MOTConnection::UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, - std::vector> &objInfos) - { - for (unsigned int i = 0; i < matchedTracedDetected.size(); ++i) - { - int traceIndex = matchedTracedDetected[i].x; - int detectIndex = matchedTracedDetected[i].y; - if (traceList_[traceIndex].info.survivalTime > MULTIPLE) - { - traceList_[traceIndex].info.flag = TRACkED_VEHICLE; - } - traceList_[traceIndex].info.survivalTime++; - traceList_[traceIndex].info.detectedTime++; - traceList_[traceIndex].lostAge = 0; - traceList_[traceIndex].detectInfo = objInfos[0][detectIndex]; - traceList_[traceIndex].kalman.Update(objInfos[0][detectIndex]); - } - } - // 将没有匹配上的检测更新为新的检测器 - void MOTConnection::AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue) - { - using Time = std::chrono::high_resolution_clock; - for (auto &vehicleObject : unmatchedVehicleObjectQueue) - { - // add new detected into traceList - TraceLet traceLet; - generatedId_++; - traceLet.info.id = generatedId_; - traceLet.info.survivalTime = 1; - traceLet.info.detectedTime = 1; - traceLet.lostAge = 0; - traceLet.info.flag = NEW_VEHICLE; - traceLet.detectInfo = vehicleObject; - traceLet.info.createTime = Time::now(); - - traceLet.kalman.CvKalmanInit(vehicleObject); - traceList_.push_back(traceLet); - } - } - - void MOTConnection::UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, - std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue) - { - UpdateMatchedTraceLet(matchedTracedDetected, objInfos); // 更新匹配上的跟踪器 - AddNewDetectedVehicle(unmatchedVehicleObjectQueue); // 将没有匹配上的检测更新为新的检测器 - UpdateUnmatchedTraceLet(objInfos); // 更新没有匹配上的跟踪器 - } - - void MOTConnection::TrackObjectPredict() - { - // every traceLet should do kalman predict - for (auto &traceLet : traceList_) - { - traceLet.detectInfo = traceLet.kalman.Predict(); // 卡尔曼滤波预测的框 - } - } - - void MOTConnection::TrackObjectUpdate(const std::vector> &objInfos, - std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue) - { - if (objInfos[0].size() > 0) - { - LogDebug << "[frame id = " << 1 << "], trace size =" << traceList_.size() << "detect size = " << objInfos[0].size() << ""; - // init vehicle matched flag - std::vector detectVehicleFlagVec; - for (unsigned int i = 0; i < objInfos[0].size(); ++i) - { - detectVehicleFlagVec.push_back(false); - } - // calculate the associated matrix - std::vector> disMatrix; - disMatrix.clear(); - disMatrix.resize(traceList_.size(), std::vector(objInfos[0].size(), 0)); - for (unsigned int j = 0; j < objInfos[0].size(); ++j) - { - for (unsigned int i = 0; i < traceList_.size(); ++i) - { - // 计算交并比 - float sim = CalSimilarity(traceList_[i], objInfos[0][j], method_, kIOU_); // method_=1, kIOU_=1.0 - disMatrix[i][j] = (int)(sim * FLOAT_TO_INT); - } - } - - // solve the assignment problem using hungarian 匈牙利算法进行匹配 - HungarianHandle hungarianHandleObj = {}; - HungarianHandleInit(hungarianHandleObj, traceList_.size(), objInfos[0].size()); - HungarianSolve(hungarianHandleObj, disMatrix, traceList_.size(), objInfos[0].size()); - // filter out matched but with low distance 过滤掉匹配上但是交并比较小的 - FilterLowThreshold(hungarianHandleObj, disMatrix, matchedTracedDetected, detectVehicleFlagVec); - LogDebug << "matchedTracedDetected = " << matchedTracedDetected.size() << ""; - // fill unmatchedVehicleObjectQueue - for (unsigned int i = 0; i < detectVehicleFlagVec.size(); ++i) - { - if (detectVehicleFlagVec[i] == false) - { - unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); - } - } - } - } - - APP_ERROR MOTConnection::ProcessSort(std::vector> &objInfos, size_t frameId) - { - std::vector unmatchedVehicleObjectQueue; - std::vector matchedTracedDetected; - if (objInfos[0].size() == 0) - { - return APP_ERR_COMM_FAILURE; - } - - if (traceList_.size() > 0) - { - // every traceLet should do kalman predict - TrackObjectPredict(); // 卡尔曼滤波预测 - TrackObjectUpdate(objInfos, matchedTracedDetected, unmatchedVehicleObjectQueue); // 选出matched track、unmatched detection - } - else - { - // traceList is empty, all the vehicle detected in the new frame are unmatched. - if (objInfos[0].size() > 0) - { - for (unsigned int i = 0; i < objInfos[0].size(); ++i) - { - unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); - } - } - } - - // update all the tracelet in the tracelist per frame - UpdateTraceLetAndFrame(matchedTracedDetected, objInfos, unmatchedVehicleObjectQueue); // 用matched track、unmatched detection更新跟踪器 - return APP_ERR_OK; - } - - // 获取跟踪后的检测框 - APP_ERROR MOTConnection::GettrackResult(std::vector &objInfos_) - { - if (traceList_.size() > 0) - { - for (auto &traceLet : traceList_) - { - traceLet.detectInfo.classId = traceLet.info.id; - objInfos_.push_back(traceLet.detectInfo); - } - } - else - { - return APP_ERR_COMM_FAILURE; - } - return APP_ERR_OK; - } -} -// namespace ascendVehicleTracking \ No newline at end of file diff --git a/HelmetIdentification/src/MOTConnection.h b/HelmetIdentification/src/MOTConnection.h deleted file mode 100644 index be54f17..0000000 --- a/HelmetIdentification/src/MOTConnection.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H -#define MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H - -#include -#include -#include -#include "KalmanTracker.h" -#include "Hungarian.h" -#include "DataType.h" -#include "MxBase/ErrorCode/ErrorCodes.h" -#include "MxBase/DvppWrapper/DvppWrapper.h" -#include "MxBase/MemoryHelper/MemoryHelper.h" -#include "MxBase/DeviceManager/DeviceManager.h" -#include "MxBase/Tensor/TensorBase/TensorBase.h" -#include "MxBase/PostProcessBases/PostProcessDataType.h" -#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" - -namespace ascendVehicleTracking { -struct TraceLet { - TraceInfo info = {}; - int32_t lostAge = 0; - KalmanTracker kalman; - std::list> shortFeatureQueue; - MxBase::ObjectInfo detectInfo = {}; -}; - -class MOTConnection { -public: - APP_ERROR ProcessSort(std::vector> &objInfos, size_t frameId); - APP_ERROR GettrackResult(std::vector &objInfos_); - -private: - double trackThreshold_ = 0.3; - double kIOU_ = 1.0; - int32_t method_ = 1; - int32_t lostThreshold_ = 3; - uint32_t maxNumberFeature_ = 0; - int32_t generatedId_ = 0; - std::vector traceList_ = {}; - -private: - - void FilterLowThreshold(const HungarianHandle &hungarianHandleObj, const std::vector> &disMatrix, - std::vector &matchedTracedDetected, std::vector &detectVehicleFlagVec); - - void UpdateUnmatchedTraceLet(const std::vector> &objInfos); - - void UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, - std::vector> &objInfos); - - void AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue); - - void UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, - std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue); - - void TrackObjectPredict(); - void TrackObjectUpdate(const std::vector> &objInfos, - std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue); -}; -} // namespace ascendVehicleTracking - -#endif \ No newline at end of file diff --git a/HelmetIdentification/src/cropResizePaste.hpp b/HelmetIdentification/src/cropResizePaste.hpp deleted file mode 100644 index 37c6e8f..0000000 --- a/HelmetIdentification/src/cropResizePaste.hpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef CRP_H -#define CRP_H - -#include "MxBase/E2eInfer/Image/Image.h" -#include "MxBase/E2eInfer/Rect/Rect.h" -#include "MxBase/E2eInfer/Size/Size.h" - -#include "acl/dvpp/hi_dvpp.h" -#include "acl/acl.h" -#include "acl/acl_rt.h" - -#define CONVER_TO_PODD(NUM) (((NUM) % 2 != 0) ? (NUM) : ((NUM)-1)) -#define CONVER_TO_EVEN(NUM) (((NUM) % 2 == 0) ? (NUM) : ((NUM)-1)) -#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) - -MxBase::Rect GetPasteRect(uint32_t inputWidth, uint32_t inputHeight, uint32_t outputWidth, uint32_t outputHeight) -{ - bool widthRatioLarger = true; - float resizeRatio = static_cast(inputWidth) / outputWidth; - if (resizeRatio < (static_cast(inputHeight) / outputHeight)) - { - resizeRatio = static_cast(inputHeight) / outputHeight; - widthRatioLarger = false; - } - - // (x0, y0)是左上角坐标,(x1, y1)是右下角坐标,采用图片坐标系 - uint32_t x0; - uint32_t y0; - uint32_t x1; - uint32_t y1; - if (widthRatioLarger) - { - // 原图width大于height - x0 = 0; - y0 = 0; - x1 = outputWidth-1; - y1 = inputHeight / resizeRatio - 1; - } - else - { - // 原图height大于width - x0 = 0; - y0 = 0; - x1 = outputWidth / resizeRatio - 1; - y1 = outputHeight - 1; - } - - x0 = DVPP_ALIGN_UP(CONVER_TO_EVEN(x0), 16); // 16对齐 - x1 = DVPP_ALIGN_UP((x1 - x0 + 1), 16) + x0 - 1; - y0 = CONVER_TO_EVEN(y0); - y1 = CONVER_TO_PODD(y1); - MxBase::Rect res(x0, y0, x1, y1); - return res; -} - -MxBase::Image ConstructImage(uint32_t resizeWidth, uint32_t resizeHeight) -{ - void *addr; - uint32_t dataSize = resizeWidth * resizeHeight * 3 / 2; - auto ret = hi_mpi_dvpp_malloc(0, &addr, dataSize); - if (ret != APP_ERR_OK) - { - LogError << "hi_mpi_dvpp_malloc fail :" << ret; - } - // 第三个参数从128改成了0 - ret = aclrtMemset(addr, dataSize, 0, dataSize); - if (ret != APP_ERR_OK) - { - LogError << "aclrtMemset fail :" << ret; - } - std::shared_ptr data((uint8_t *)addr, hi_mpi_dvpp_free); - MxBase::Size imageSize(resizeWidth, resizeHeight); - MxBase::Image pastedImg(data, dataSize, 0, imageSize); - return pastedImg; -} - -std::pair GenerateRect(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight) -{ - uint32_t x1 = CONVER_TO_PODD(originalWidth - 1); - uint32_t y1 = CONVER_TO_PODD(originalHeight - 1); - MxBase::Rect cropRect(0, 0, x1, y1); - MxBase::Rect pasteRect = GetPasteRect(originalWidth, originalHeight, resizeWidth, resizeHeight); - std::pair cropPasteRect(cropRect, pasteRect); - return cropPasteRect; -} - -MxBase::Image resizeKeepAspectRatioFit(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight, MxBase::Image &decodeImage, MxBase::ImageProcessor& imageProcessor) -{ - std::pair cropPasteRect = GenerateRect(originalWidth, originalHeight, resizeWidth, resizeHeight); - MxBase::Image resizeImage = ConstructImage(resizeWidth, resizeHeight); - auto ret = imageProcessor.CropAndPaste(decodeImage, cropPasteRect, resizeImage); - if (ret != APP_ERR_OK) - { - LogError << "CropAndPaste fail :" << ret; - } - return resizeImage; -} - -#endif // CRP_H \ No newline at end of file diff --git a/HelmetIdentification/src/main-image.cpp b/HelmetIdentification/src/main-image.cpp deleted file mode 100644 index 27dd5c5..0000000 --- a/HelmetIdentification/src/main-image.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "utils.h" - -#include -#include -#include -using namespace std; - -// 如果在200DK上运行就改为 USE_200DK -#define USE_200DK - -APP_ERROR readImage(std::string imgPath, MxBase::Image& image, ImageProcessor& imageProcessor) -{ - APP_ERROR ret; -#ifdef USE_DVPP - // if USE DVPP - ret = imageProcessor.Decode(imgPath, image); -#endif -#ifdef USE_200DK - std::shared_ptr dataPtr; - uint32_t dataSize; - // Get image data to memory, this method can be substituted or designed by yourself! - std::ifstream file; - file.open(imgPath.c_str(), std::ios::binary); - if (!file) - { - LogInfo << "Invalid file."; - return APP_ERR_COMM_INVALID_PARAM; - } - std::stringstream buffer; - buffer << file.rdbuf(); - std::string content = buffer.str(); - - char *p = (char *)malloc(content.size()); - memcpy(p, content.data(), content.size()); - auto deleter = [](void *p) -> void - { - free(p); - p = nullptr; - }; - - dataPtr.reset(static_cast((void *)(p)), deleter); - dataSize = content.size(); - file.close(); - if (ret != APP_ERR_OK) - { - LogError << "Getimage failed, ret=" << ret; - return ret; - } - ret = imageProcessor.Decode(dataPtr, dataSize, image, ImageFormat::YUV_SP_420); - // endif -#endif - if (ret != APP_ERR_OK) - { - LogError << "Decode failed, ret=" << ret; - return ret; - } -} - -void poptProcess(std::vector modelOutputs, std::shared_ptr postProcessorDptr, MxBase::Size originalSize, MxBase::Size resizeSize) -{ - MxBase::ResizedImageInfo imgInfo; - auto shape = modelOutputs[0].GetShape(); - imgInfo.widthOriginal = originalSize.width; - imgInfo.heightOriginal = originalSize.height; - imgInfo.widthResize = resizeSize.width; - imgInfo.heightResize = resizeSize.height; - imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; - float resizeRate = originalSize.width > originalSize.height ? (originalSize.width / 640.0) : (originalSize.height / 640.0); - imgInfo.keepAspectRatioScaling = 1 / resizeRate; - std::vector imageInfoVec = {}; - imageInfoVec.push_back(imgInfo); - // make postProcess inputs - std::vector tensors; - for (size_t i = 0; i < modelOutputs.size(); i++) - { - MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); - MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); - tensors.push_back(tensorBase); - } - std::vector> objectInfos; - postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); - std::cout << "===---> Size of objectInfos is " << objectInfos.size() << std::endl; - for (size_t i = 0; i < objectInfos.size(); i++) - { - std::cout << "objectInfo-" << i << " , Size:" << objectInfos[i].size() << std::endl; - for (size_t j = 0; j < objectInfos[i].size(); j++) - { - std::cout << std::endl - << "*****objectInfo-" << i << ":" << j << std::endl; - std::cout << "x0 is " << objectInfos[i][j].x0 << std::endl; - std::cout << "y0 is " << objectInfos[i][j].y0 << std::endl; - std::cout << "x1 is " << objectInfos[i][j].x1 << std::endl; - std::cout << "y1 is " << objectInfos[i][j].y1 << std::endl; - std::cout << "confidence is " << objectInfos[i][j].confidence << std::endl; - std::cout << "classId is " << objectInfos[i][j].classId << std::endl; - std::cout << "className is " << objectInfos[i][j].className << std::endl; - } - } -} - -APP_ERROR main(int argc, char *argv[]) -{ - APP_ERROR ret; - - // global init - ret = MxBase::MxInit(); - if (ret != APP_ERR_OK) - { - LogError << "MxInit failed, ret=" << ret << "."; - } - // 检测是否输入了文件路径 - if (argc <= 1) - { - LogWarn << "Please input image path, such as 'test.jpg'."; - return APP_ERR_OK; - } - - // imageProcess init - MxBase::ImageProcessor imageProcessor(deviceId); - // model init - MxBase::Model yoloModel(modelPath, deviceId); - // postprocessor init - std::map postConfig; - postConfig.insert(std::pair("postProcessConfigPath", configPath)); - postConfig.insert(std::pair("labelPath", labelPath)); - std::shared_ptr postProcessorDptr = std::make_shared(); - postProcessorDptr->Init(postConfig); - - std::string imgPath = argv[1]; - // 读取图片 - MxBase::Image image; - readImage(imgPath, image, imageProcessor); - - // 缩放图片 - MxBase::Size originalSize = image.GetOriginalSize(); - MxBase::Size resizeSize = MxBase::Size(YOLOV5_RESIZE, YOLOV5_RESIZE); - MxBase::Image resizedImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); - - // 模型推理 - MxBase::Tensor tensorImg = resizedImage.ConvertToTensor(); - tensorImg.ToDevice(deviceId); - std::vector inputs; - inputs.push_back(tensorImg); - std::vector modelOutputs = yoloModel.Infer(inputs); - for (auto output : modelOutputs) - { - output.ToHost(); - } - - // 后处理 - poptProcess(modelOutputs, postProcessorDptr, originalSize, resizeSize); - - return APP_ERR_OK; -} \ No newline at end of file diff --git a/HelmetIdentification/src/main.cpp b/HelmetIdentification/src/main.cpp deleted file mode 100644 index 8fd8093..0000000 --- a/HelmetIdentification/src/main.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "utils.h" - -extern "C" -{ - #include "libavformat/avformat.h" -} - -#include -#include -#include -#include "unistd.h" -#include -#include -#include -#include "boost/filesystem.hpp" - -#include "MxBase/DeviceManager/DeviceManager.h" -#include "MxBase/DvppWrapper/DvppWrapper.h" -#include "MxBase/MemoryHelper/MemoryHelper.h" -#include "opencv2/opencv.hpp" -#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" - -#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" -#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" -#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" -#include "MxBase/E2eInfer/DataType.h" - -#include -using namespace std; -using namespace videoInfo; -namespace frameConfig -{ - size_t channelId0 = 0; - size_t channelId1 = 1; - size_t frameCountChannel0 = 300; - size_t frameCountChannel1 = 300; - size_t skipIntervalChannel0 = 2; - size_t skipIntervalChannel1 = 2; - - // channel0对应文件的指针 - AVFormatContext *pFormatCtx0 = nullptr; - // channel1对应文件的指针 - AVFormatContext *pFormatCtx1 = nullptr; -} - -// ffmpeg拉流 -AVFormatContext *CreateFormatContext(std::string filePath) -{ - LogInfo << "start to CreatFormatContext!"; - // creat message for stream pull - AVFormatContext *formatContext = nullptr; - AVDictionary *options = nullptr; - - LogInfo << "start to avformat_open_input!"; - int ret = avformat_open_input(&formatContext, filePath.c_str(), nullptr, &options); - if (options != nullptr) - { - av_dict_free(&options); - } - if (ret != 0) - { - LogError << "Couldn't open input stream " << filePath.c_str() << " ret=" << ret; - return nullptr; - } - ret = avformat_find_stream_info(formatContext, nullptr); - if (ret != 0) - { - LogError << "Couldn't open input stream information"; - return nullptr; - } - return formatContext; -} - -// 真正的拉流函数 -void PullStream0(std::string filePath) -{ - av_register_all(); - avformat_network_init(); - frameConfig::pFormatCtx0 = avformat_alloc_context(); - frameConfig::pFormatCtx0 = CreateFormatContext(filePath); - av_dump_format(frameConfig::pFormatCtx0, 0, filePath.c_str(), 0); -} -void PullStream1(std::string filePath) -{ - av_register_all(); - avformat_network_init(); - frameConfig::pFormatCtx1 = avformat_alloc_context(); - frameConfig::pFormatCtx1 = CreateFormatContext(filePath); - av_dump_format(frameConfig::pFormatCtx1, 0, filePath.c_str(), 0); -} - -// 视频解码回调(样例代码,测试可以跑通,但是不能直接复用) -APP_ERROR CallBackVdec(Image &decodedImage, uint32_t channelId, uint32_t frameId, void *userData) -{ - FrameImage frameImage; - frameImage.image = decodedImage; - frameImage.channelId = channelId; - frameImage.frameId = frameId; - - videoInfo::g_threadsMutex_frameImageQueue.lock(); - videoInfo::frameImageQueue.push(frameImage); - videoInfo::g_threadsMutex_frameImageQueue.unlock(); - - return APP_ERR_OK; -} - -// 获取H264中的帧 -void GetFrame(AVPacket &pkt, FrameImage &frameImage, AVFormatContext *pFormatCtx) -{ - av_init_packet(&pkt); - int ret = av_read_frame(pFormatCtx, &pkt); - if (ret != 0) - { - LogInfo << "[StreamPuller] channel Read frame failed, continue!"; - if (ret == AVERROR_EOF) - { - LogInfo << "[StreamPuller] channel StreamPuller is EOF, over!"; - return; - } - return; - } - else - { - if (pkt.size <= 0) - { - LogError << "Invalid pkt.size: " << pkt.size; - return; - } - - // send to the device - auto hostDeleter = [](void *dataPtr) -> void {}; - MemoryData data(pkt.size, MemoryData::MEMORY_HOST); - MemoryData src((void *)(pkt.data), pkt.size, MemoryData::MEMORY_HOST); - APP_ERROR ret = MemoryHelper::MxbsMallocAndCopy(data, src); - if (ret != APP_ERR_OK) - { - LogError << "MxbsMallocAndCopy failed!"; - } - std::shared_ptr imageData((uint8_t *)data.ptrData, hostDeleter); - - Image subImage(imageData, pkt.size); - frameImage.image = subImage; - - LogDebug << "'channelId = " << frameImage.channelId << ", frameId = " << frameImage.frameId << " , dataSize = " << frameImage.image.GetDataSize(); - - av_packet_unref(&pkt); - } - return; -} - -// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 -void VdecThread0(size_t frameCount, size_t skipInterval, int32_t channelId) -{ - AVPacket pkt; - uint32_t frameId = 0; - // 解码器参数 - VideoDecodeConfig config; - VideoDecodeCallBack cPtr = CallBackVdec; - config.width = videoInfo::SRC_WIDTH; - config.height = videoInfo::SRC_HEIGHT; - config.callbackFunc = cPtr; - config.skipInterval = skipInterval; // 跳帧控制 - - std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); - for (size_t i = 0; i < frameCount; i++) - { - Image subImage; - FrameImage frame; - frame.channelId = 0; - frame.frameId = frameId; - frame.image = subImage; - GetFrame(pkt, frame, frameConfig::pFormatCtx0); - APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); - if (ret != APP_ERR_OK) - { - LogError << "videoDecoder Decode failed. ret is: " << ret; - } - frameId += 1; - } -} - -// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 -void VdecThread1(size_t frameCount, size_t skipInterval, int32_t channelId) -{ - AVPacket pkt; - uint32_t frameId = 0; - // 解码器参数 - VideoDecodeConfig config; - VideoDecodeCallBack cPtr = CallBackVdec; - config.width = videoInfo::SRC_WIDTH; - config.height = videoInfo::SRC_HEIGHT; - config.callbackFunc = cPtr; - // 跳帧控制 - config.skipInterval = skipInterval; - - std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); - for (size_t i = 0; i < frameCount; i++) - { - Image subImage; - FrameImage frame; - frame.channelId = 0; - frame.frameId = frameId; - frame.image = subImage; - GetFrame(pkt, frame, frameConfig::pFormatCtx1); - APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); - if (ret != APP_ERR_OK) - { - LogError << "videoDecoder Decode failed. ret is: " << ret; - } - frameId += 1; - } -} - - -APP_ERROR main(int argc, char *argv[]) -{ - // 检测是否输入了文件路径 - if (argc <= 1) - { - LogWarn << "Please input video path, such as './video_sample test.264'."; - return APP_ERR_OK; - } - - // 初始化 - APP_ERROR ret = MxBase::MxInit(); - if (ret != APP_ERR_OK) - { - LogError << "MxInit failed, ret=" << ret << "."; - } - - std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); - - // 视频流解码线程 - std::string videoPath0 = argv[1]; - PullStream0(videoPath0); - std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0); - std::string videoPath1 = argv[2]; - PullStream1(videoPath1); - std::thread threadVdec1(VdecThread1, frameConfig::frameCountChannel1, frameConfig::skipIntervalChannel1, frameConfig::channelId1); - threadVdec0.join(); - threadVdec1.join(); - - size_t target_count = videoInfo::frameImageQueue.size(); - LogInfo << "frameImageQueue.size: " << videoInfo::frameImageQueue.size(); - - // resize线程 - std::thread resizeThread(resizeMethod, start_time, target_count); - resizeThread.join(); - - // 推理线程 - std::thread inferThread(inferMethod, start_time, target_count); - inferThread.join(); - - // 后处理线程 - std::thread postprocessThread(postprocessMethod, start_time, target_count); - postprocessThread.join(); - - // 跟踪去重线程 - std::thread trackThread(trackMethod, start_time, target_count); - trackThread.join(); - - std::chrono::high_resolution_clock::time_point end_time = std::chrono::high_resolution_clock::now(); - double_t cost_time = std::chrono::duration_cast(end_time - start_time).count(); - LogInfo << "端到端时间为" << cost_time/videoInfo::MS_PPE_SECOND; - - return APP_ERR_OK; -} \ No newline at end of file diff --git a/HelmetIdentification/src/utils.h b/HelmetIdentification/src/utils.h deleted file mode 100644 index 9ea7b31..0000000 --- a/HelmetIdentification/src/utils.h +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_UTILS_H -#define MXBASE_HELMETIDENTIFICATION_UTILS_H - -#include -#include -#include -#include "unistd.h" -#include -#include -#include -#include "boost/filesystem.hpp" - -#include "MxBase/DeviceManager/DeviceManager.h" -#include "MxBase/DvppWrapper/DvppWrapper.h" -#include "MxBase/MemoryHelper/MemoryHelper.h" -#include "opencv2/opencv.hpp" -#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" - -#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" -#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" -#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" -#include "MxBase/E2eInfer/DataType.h" - -#include "MxBase/MxBase.h" -#include "MOTConnection.h" -#include "cropResizePaste.hpp" -#include "chrono" -#include "time.h" - -struct FrameImage -{ - MxBase::Image image; - uint32_t frameId = 0; - uint32_t channelId = 0; -}; - -namespace videoInfo -{ - const uint32_t SRC_WIDTH = 1920; - const uint32_t SRC_HEIGHT = 1080; - - const uint32_t YUV_BYTE_NU = 3; - const uint32_t YUV_BYTE_DE = 2; - const uint32_t YOLOV5_RESIZE = 640; - - // 要检测的目标类别的标签 - const std::string TARGET_CLASS_NAME = "head"; - // 使用chrono计数结果为毫秒,需要除以1000转换为秒 - double MS_PPE_SECOND = 1000.0; - - const uint32_t DEVICE_ID = 0; - std::string labelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/imgclass.names"; - std::string configPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/Helmet_yolov5.cfg"; - std::string modelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/YOLOv5_s.om"; - - // 读入视频帧Image的队列 - std::queue frameImageQueue; - // 读入视频帧Image队列的线程锁 - std::mutex g_threadsMutex_frameImageQueue; - - // resize前即原始图像的vector - std::vector realImageVector; - // resize后Image的vector - std::vector resizedImageVector; - // resize后Image队列的线程锁 - std::mutex g_threadsMutex_resizedImageVector; - - // 推理后tensor的vector - std::vector> inferOutputVector; - // 推理后tensor队列的线程锁 - std::mutex g_threadsMutex_inferOutputVector; - - // 后处理后objectInfos的队列 map> - std::vector>> postprocessOutputVector; - // 后处理后objectInfos队列的线程锁 - std::mutex g_threadsMutex_postprocessOutputVector; -} -namespace fs = boost::filesystem; - -// resize线程 -void resizeMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point resize_start_time = std::chrono::high_resolution_clock::now(); - - MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); - - size_t resize_count = 0; - while (resize_count < target_count) - { - if (videoInfo::frameImageQueue.empty()) - { - continue; - } - // 取图像并resize - FrameImage frame = videoInfo::frameImageQueue.front(); - Image image = frame.image; - MxBase::Size originalSize = image.GetOriginalSize(); - MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); - MxBase::Image outputImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); - // 先将缩放后的图像放入resizeImage的队列 - FrameImage resizedFrame; - resizedFrame.channelId = frame.channelId; - resizedFrame.frameId = frame.frameId; - resizedFrame.image = outputImage; - videoInfo::g_threadsMutex_resizedImageVector.lock(); - frame.image.ToHost(); - videoInfo::realImageVector.push_back(frame); - videoInfo::resizedImageVector.push_back(resizedFrame); - videoInfo::g_threadsMutex_resizedImageVector.unlock(); - // 然后再将原图pop出去 - videoInfo::g_threadsMutex_frameImageQueue.lock(); - videoInfo::frameImageQueue.pop(); - videoInfo::g_threadsMutex_frameImageQueue.unlock(); - // 计数 - resize_count++; - } - std::chrono::high_resolution_clock::time_point resize_end_time = std::chrono::high_resolution_clock::now(); - double_t resize_cost_time = std::chrono::duration_cast(resize_end_time - resize_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t resize_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到缩放全部完成一共花费: " << resize_finish_time << ", 缩放本身花费: " << resize_cost_time; -} - -// 推理线程 -void inferMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point infer_start_time = std::chrono::high_resolution_clock::now(); - - std::shared_ptr modelDptr = std::make_shared(videoInfo::modelPath, videoInfo::DEVICE_ID); - - size_t infer_count = 0; - while (infer_count < target_count) - { - if (infer_count >= videoInfo::resizedImageVector.size()) - { - continue; - } - // 从resize后的队列中取图片 - FrameImage resizedFrame = videoInfo::resizedImageVector[infer_count]; - std::vector modelOutputs; - - MxBase::Tensor tensorImg = resizedFrame.image.ConvertToTensor(); - tensorImg.ToDevice(videoInfo::DEVICE_ID); - std::vector inputs; - inputs.push_back(tensorImg); - modelOutputs = modelDptr->Infer(inputs); - for (auto output : modelOutputs) - { - output.ToHost(); - } - - // 将推理结果存入队列 - videoInfo::g_threadsMutex_inferOutputVector.lock(); - videoInfo::inferOutputVector.push_back(modelOutputs); - videoInfo::g_threadsMutex_inferOutputVector.unlock(); - // 计数 - infer_count++; - } - std::chrono::high_resolution_clock::time_point infer_end_time = std::chrono::high_resolution_clock::now(); - double_t infer_cost_time = std::chrono::duration_cast(infer_end_time - infer_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t infer_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到推理全部完成一共花费: " << infer_finish_time << ", 推理本身花费: " << infer_cost_time; -} - -// 后处理线程 -void postprocessMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point postprocess_start_time = std::chrono::high_resolution_clock::now(); - - std::map postConfig; - postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); - postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); - std::shared_ptr postProcessorDptr = std::make_shared(); - if (postProcessorDptr == nullptr) - { - LogError << "init postProcessor failed, nullptr"; - } - postProcessorDptr->Init(postConfig); - - size_t postprocess_count = 0; - while (postprocess_count < target_count) - { - if (postprocess_count >= videoInfo::inferOutputVector.size()) - { - continue; - } - // 取原图信息用于计算 - MxBase::Size originalSize = videoInfo::realImageVector[postprocess_count].image.GetOriginalSize(); - // 从推理结果的队列里面取出推理结果 - std::vector modelOutputs = videoInfo::inferOutputVector[postprocess_count]; - FrameImage resizedFrame = videoInfo::resizedImageVector[postprocess_count]; - // 新的后处理过程 - MxBase::ResizedImageInfo imgInfo; - auto shape = modelOutputs[0].GetShape(); - imgInfo.widthOriginal = originalSize.width; - imgInfo.heightOriginal = originalSize.height; - imgInfo.widthResize = videoInfo::YOLOV5_RESIZE; - imgInfo.heightResize = videoInfo::YOLOV5_RESIZE; - imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; - // 因为yolov5要求输入图像为640*640,所以直接比较原图的height和width就好(如果不理解就去看cropResizePaste.hpp里的GetPasteRect函数) - float resizeRate = originalSize.width > originalSize.height ? (originalSize.width / videoInfo::YOLOV5_RESIZE) : (originalSize.height / videoInfo::YOLOV5_RESIZE); - imgInfo.keepAspectRatioScaling = 1 / resizeRate; - std::vector imageInfoVec = {}; - imageInfoVec.push_back(imgInfo); - // make postProcess inputs - std::vector tensors; - for (size_t i = 0; i < modelOutputs.size(); i++) - { - MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); - MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); - tensors.push_back(tensorBase); - } - // 后处理 - std::vector> objectInfos; - postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); - - // 将后处理结果存入队列 - videoInfo::g_threadsMutex_postprocessOutputVector.lock(); - videoInfo::postprocessOutputVector.push_back(objectInfos); - videoInfo::g_threadsMutex_postprocessOutputVector.unlock(); - // 计数 - postprocess_count++; - } - std::chrono::high_resolution_clock::time_point postprocess_end_time = std::chrono::high_resolution_clock::now(); - double_t postprocess_cost_time = std::chrono::duration_cast(postprocess_end_time - postprocess_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t postprocess_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到后处理全部完成一共花费: " << postprocess_finish_time << ", 后处理本身花费: " << postprocess_cost_time; -} - -// 跟踪去重线程 -void trackMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point track_start_time = std::chrono::high_resolution_clock::now(); - - std::shared_ptr tracker0 = std::make_shared(); - if (tracker0 == nullptr) - { - LogError << "init tracker0 failed, nullptr"; - } - std::shared_ptr tracker1 = std::make_shared(); - if (tracker1 == nullptr) - { - LogError << "init tracker1 failed, nullptr"; - } - // 用于计算帧率 - size_t old_count = 0; - std::chrono::high_resolution_clock::time_point count_time; - std::chrono::high_resolution_clock::time_point old_count_time = std::chrono::high_resolution_clock::now(); - size_t one_step = 2; - size_t track_count = 0; - while (track_count < target_count) - { - // 计算帧率 - // 如果count_time-old_count_time的值大于one_step,就计算一下这个step里面的帧数,然后除以step的值 - count_time = std::chrono::high_resolution_clock::now(); - if (std::chrono::duration_cast(count_time - old_count_time).count() / videoInfo::MS_PPE_SECOND > one_step) - { - old_count_time = count_time; - LogInfo << "rate: " << (track_count - old_count) / one_step * 1.0; - old_count = track_count; - } - // 下面是业务循环 - if (track_count >= videoInfo::postprocessOutputVector.size()) - { - continue; - } - // 从后处理结果的队列中取结果用于跟踪去重 - std::vector> objectInfos = videoInfo::postprocessOutputVector[track_count]; - FrameImage frame = videoInfo::realImageVector[track_count]; - MxBase::Size originalSize = frame.image.GetOriginalSize(); - - // 根据channelId的不同使用不同的tracker - std::vector objInfos_ = {}; - if (frame.channelId == 0) - { - tracker0->ProcessSort(objectInfos, frame.frameId); - APP_ERROR ret = tracker0->GettrackResult(objInfos_); - if (ret != APP_ERR_OK) - { - LogError << "No tracker0"; - } - } - else - { - tracker1->ProcessSort(objectInfos, frame.frameId); - APP_ERROR ret = tracker1->GettrackResult(objInfos_); - if (ret != APP_ERR_OK) - { - LogError << "No tracker1"; - } - } - - uint32_t video_height = originalSize.height; - uint32_t video_width = originalSize.width; - // 初始化OpenCV图像信息矩阵 - cv::Mat imgYuv = cv::Mat(video_height * videoInfo::YUV_BYTE_NU / videoInfo::YUV_BYTE_DE, video_width, CV_8UC1, frame.image.GetData().get()); - cv::Mat imgBgr = cv::Mat(video_height, video_width, CV_8UC3); - // 颜色空间转换 - cv::cvtColor(imgYuv, imgBgr, cv::COLOR_YUV420sp2BGR); - std::vector info; - bool headFlag = false; - for (uint32_t i = 0; i < objInfos_.size(); i++) - { - if (objInfos_[i].className == videoInfo::TARGET_CLASS_NAME) - { - headFlag = true; - LogWarn << "Warning:Not wearing a helmet, channelId:" << frame.channelId << ", frameId:" << frame.frameId; - // (blue, green, red) - const cv::Scalar color = cv::Scalar(0, 0, 255); - // width for rectangle - const uint32_t thickness = 2; - // draw the rectangle - cv::rectangle(imgBgr, - cv::Rect(objInfos_[i].x0, objInfos_[i].y0, objInfos_[i].x1 - objInfos_[i].x0, objInfos_[i].y1 - objInfos_[i].y0), - color, thickness); - } - } - // 如果检测结果中有head标签,就保存为图片 - if (headFlag) - { - // 把Mat类型的图像矩阵保存为图像到指定位置。 - std::string outPath = frame.channelId == 0 ? "one" : "two"; - std::string fileName = "./result/" + outPath + "/result" + std::to_string(frame.frameId) + ".jpg"; - cv::imwrite(fileName, imgBgr); - } - - // 计数 - track_count++; - } - std::chrono::high_resolution_clock::time_point track_end_time = std::chrono::high_resolution_clock::now(); - double_t track_cost_time = std::chrono::duration_cast(track_end_time - track_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t track_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到跟踪去重全部完成一共花费: " << track_finish_time << ", 跟踪去重本身花费: " << track_cost_time; -} - -#endif \ No newline at end of file From f639e6773dc0ed6f8e63c82995ffa8bc0da7bac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:46:30 +0000 Subject: [PATCH 13/58] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20src?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification/src/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 HelmetIdentification/src/.keep diff --git a/HelmetIdentification/src/.keep b/HelmetIdentification/src/.keep new file mode 100644 index 0000000..e69de29 From 26f08dd56009c1a28b4d4390b9610d0952167025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:47:17 +0000 Subject: [PATCH 14/58] =?UTF-8?q?=E4=BA=A4=E4=BB=98=E7=94=A8=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E5=8C=85=E6=8B=AC=E8=AF=BB=E5=8F=96=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=92=8C=E8=AF=BB=E5=8F=96=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification/src/CMakeLists.txt | 47 +++ HelmetIdentification/src/DataType.h | 94 +++++ HelmetIdentification/src/Hungarian.cpp | 165 +++++++++ HelmetIdentification/src/Hungarian.h | 46 +++ HelmetIdentification/src/KalmanTracker.cpp | 143 ++++++++ HelmetIdentification/src/KalmanTracker.h | 39 +++ HelmetIdentification/src/MOTConnection.cpp | 262 ++++++++++++++ HelmetIdentification/src/MOTConnection.h | 78 +++++ HelmetIdentification/src/cropResizePaste.hpp | 115 ++++++ HelmetIdentification/src/main-image.cpp | 170 +++++++++ HelmetIdentification/src/main.cpp | 283 +++++++++++++++ HelmetIdentification/src/utils.h | 351 +++++++++++++++++++ 12 files changed, 1793 insertions(+) create mode 100644 HelmetIdentification/src/CMakeLists.txt create mode 100644 HelmetIdentification/src/DataType.h create mode 100644 HelmetIdentification/src/Hungarian.cpp create mode 100644 HelmetIdentification/src/Hungarian.h create mode 100644 HelmetIdentification/src/KalmanTracker.cpp create mode 100644 HelmetIdentification/src/KalmanTracker.h create mode 100644 HelmetIdentification/src/MOTConnection.cpp create mode 100644 HelmetIdentification/src/MOTConnection.h create mode 100644 HelmetIdentification/src/cropResizePaste.hpp create mode 100644 HelmetIdentification/src/main-image.cpp create mode 100644 HelmetIdentification/src/main.cpp create mode 100644 HelmetIdentification/src/utils.h diff --git a/HelmetIdentification/src/CMakeLists.txt b/HelmetIdentification/src/CMakeLists.txt new file mode 100644 index 0000000..6fcd298 --- /dev/null +++ b/HelmetIdentification/src/CMakeLists.txt @@ -0,0 +1,47 @@ +# CMake lowest version requirement +cmake_minimum_required(VERSION 3.5.1) +# project information +project(Individual) + +# Compile options +add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0 -Dgoogle=mindxsdk_private) +add_compile_options(-std=c++11 -fPIC -fstack-protector-all -Wall -D_FORTIFY_SOURCE=2 -O2) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../../") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now,-z,noexecstack -s -pie -pthread") +set(CMAKE_SKIP_RPATH TRUE) + +SET(CMAKE_BUILD_TYPE "Debug") +SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb") +SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall") + +# Header path +include_directories( + ${MX_SDK_HOME}/include/ + ${MX_SDK_HOME}/opensource/include/ + ${MX_SDK_HOME}/opensource/include/opencv4/ + /home/HwHiAiUser/Ascend/ascend-toolkit/latest/include/ + ./ +) + +# add host lib path +link_directories( + ${MX_SDK_HOME}/lib/ + ${MX_SDK_HOME}/lib/modelpostprocessors + ${MX_SDK_HOME}/opensource/lib/ + ${MX_SDK_HOME}/opensource/lib64/ + /usr/lib/aarch64-linux-gnu/ + /home/HwHiAiUser/Ascend/ascend-toolkit/latest/lib64/ + /usr/local/Ascend/driver/lib64/ + ./ +) + + +aux_source_directory(. sourceList) + +add_executable(main ${sourceList}) + +target_link_libraries(main mxbase opencv_world boost_filesystem glog avformat avcodec avutil cpprest yolov3postprocess ascendcl acl_dvpp_mpi) + +install(TARGETS main DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/HelmetIdentification/src/DataType.h b/HelmetIdentification/src/DataType.h new file mode 100644 index 0000000..dc34a0a --- /dev/null +++ b/HelmetIdentification/src/DataType.h @@ -0,0 +1,94 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_DATATYPE_H +#define MXBASE_HELMETIDENTIFICATION_DATATYPE_H + +#include +#include +#include +#include +#include +#include +#include "opencv2/highgui.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" + +namespace ascendVehicleTracking { +#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) + + const int MODULE_QUEUE_SIZE = 1000; + + enum FrameMode { + FRAME_MODE_SEARCH = 0, + FRAME_MODE_REG + }; + + struct DataBuffer { + std::shared_ptr deviceData; + std::shared_ptr hostData; + uint32_t dataSize; // buffer size + DataBuffer() : deviceData(nullptr), hostData(nullptr), dataSize(0) {} + }; + + struct DetectInfo { + int32_t classId; + float confidence; + float minx; // x value of left-top point + float miny; // y value of left-top point + float height; + float width; + }; + + enum TraceFlag { + NEW_VEHICLE = 0, + TRACkED_VEHICLE, + LOST_VEHICLE + }; + + struct TraceInfo { + int32_t id; + TraceFlag flag; + int32_t survivalTime; // How long is it been since the first time, unit: detection period + int32_t detectedTime; // How long is the vehicle detected, unit: detection period + std::chrono::time_point createTime; + }; + + struct TrackLet { + TraceInfo info; + // reserved: kalman status parameter + int32_t lostTime; // undetected time for tracked vehicle + std::vector shortFeature; // nearest 10 frame + }; + + struct VehicleQuality { + float score; + }; + + struct Coordinate2D { + uint32_t x; + uint32_t y; + }; +} +// namespace ascendVehicleTracking + +struct AttrT { + AttrT(std::string name, std::string value) : name(std::move(name)), value(std::move(value)) {} + std::string name = {}; + std::string value = {}; +}; + +#endif diff --git a/HelmetIdentification/src/Hungarian.cpp b/HelmetIdentification/src/Hungarian.cpp new file mode 100644 index 0000000..3f6fcd2 --- /dev/null +++ b/HelmetIdentification/src/Hungarian.cpp @@ -0,0 +1,165 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Hungarian.h" +#include +#include +#include "MxBase/Log/Log.h" + +namespace { + const int INF = 0x3f3f3f3f; + const int VISITED = 1; + const int HUNGARIAN_CONTENT = 7; + const int X_MATCH_OFFSET = 0; + const int Y_MATCH_OFFSET = 1; + const int X_VALUE_OFFSET = 2; + const int Y_VALUE_OFFSET = 3; + const int SLACK_OFFSET = 4; + const int X_VISIT_OFFSET = 5; + const int Y_VISIT_OFFSET = 6; +} + +APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols) +{ + handle.max = (row > cols) ? row : cols; + auto adjMat = std::shared_ptr(); + adjMat.reset(new int[handle.max * handle.max], std::default_delete()); + if (adjMat == nullptr) { + LogFatal << "HungarianHandleInit new failed"; + return APP_ERR_ACL_FAILURE; + } + + handle.adjMat = adjMat; + + void* ptr[HUNGARIAN_CONTENT] = {nullptr}; + for (int i = 0; i < HUNGARIAN_CONTENT; ++i) { + ptr[i] = malloc(handle.max * sizeof(int)); + if (ptr[i] == nullptr) { + LogFatal << "HungarianHandleInit Malloc failed"; + return APP_ERR_ACL_FAILURE; + } + } + + handle.xMatch.reset((int *)ptr[X_MATCH_OFFSET], free); + handle.yMatch.reset((int *)ptr[Y_MATCH_OFFSET], free); + handle.xValue.reset((int *)ptr[X_VALUE_OFFSET], free); + handle.yValue.reset((int *)ptr[Y_VALUE_OFFSET], free); + handle.slack.reset((int *)ptr[SLACK_OFFSET], free); + handle.xVisit.reset((int *)ptr[X_VISIT_OFFSET], free); + handle.yVisit.reset((int *)ptr[Y_VISIT_OFFSET], free); + return APP_ERR_OK; +} + +static void HungarianInit(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) +{ + int i, j, value; + if (rows > cols) { + handle.transpose = true; + handle.cols = rows; + handle.rows = cols; + handle.resX = handle.yMatch.get(); + handle.resY = handle.xMatch.get(); + } else { + handle.transpose = false; + handle.rows = rows; + handle.cols = cols; + handle.resX = handle.xMatch.get(); + handle.resY = handle.yMatch.get(); + } + + for (i = 0; i < handle.rows; ++i) { + handle.xValue.get()[i] = 0; + handle.xMatch.get()[i] = -1; + for (j = 0; j < handle.cols; ++j) { + if (handle.transpose) { + value = cost[j][i]; + } else { + value = cost[i][j]; + } + handle.adjMat.get()[i * handle.cols + j] = value; + if (handle.xValue.get()[i] < value) { + handle.xValue.get()[i] = value; + } + } + } + + for (i = 0; i < handle.cols; ++i) { + handle.yValue.get()[i] = 0; + handle.yMatch.get()[i] = -1; + } +} + +static bool Match(HungarianHandle &handle, int id) +{ + int j, delta; + handle.xVisit.get()[id] = VISITED; + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] == VISITED) { + continue; + } + delta = handle.xValue.get()[id] + handle.yValue.get()[j] - handle.adjMat.get()[id * handle.cols + j]; + if (delta == 0) { + handle.yVisit.get()[j] = VISITED; + if (handle.yMatch.get()[j] == -1 || Match(handle, handle.yMatch.get()[j])) { + handle.yMatch.get()[j] = id; + handle.xMatch.get()[id] = j; + return true; + } + } else if (delta < handle.slack.get()[j]) { + handle.slack.get()[j] = delta; + } + } + return false; +} + +int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) +{ + HungarianInit(handle, cost, rows, cols); + int i, j, delta; + for (i = 0; i < handle.rows; ++i) { + while (true) { + std::fill(handle.xVisit.get(), handle.xVisit.get() + handle.rows, 0); + std::fill(handle.yVisit.get(), handle.yVisit.get() + handle.cols, 0); + for (j = 0; j < handle.cols; ++j) { + handle.slack.get()[j] = INF; + } + if (Match(handle, i)) { + break; + } + delta = INF; + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] != VISITED && delta > handle.slack.get()[j]) { + delta = handle.slack.get()[j]; + } + } + if (delta == INF) { + LogDebug << "Hungarian solve is invalid!"; + return -1; + } + for (j = 0; j < handle.rows; ++j) { + if (handle.xVisit.get()[j] == VISITED) { + handle.xValue.get()[j] -= delta; + } + } + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] == VISITED) { + handle.yValue.get()[j] += delta; + } + } + } + } + return handle.rows; +} diff --git a/HelmetIdentification/src/Hungarian.h b/HelmetIdentification/src/Hungarian.h new file mode 100644 index 0000000..2ae6a33 --- /dev/null +++ b/HelmetIdentification/src/Hungarian.h @@ -0,0 +1,46 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H +#define MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H + +#include +#include +#include "DataType.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" +#include "MxBase/ErrorCode/ErrorCodes.h" + +struct HungarianHandle { + int rows; + int cols; + int max; + int *resX; + int *resY; + bool transpose; + std::shared_ptr adjMat; + std::shared_ptr xMatch; + std::shared_ptr yMatch; + std::shared_ptr xValue; + std::shared_ptr yValue; + std::shared_ptr slack; + std::shared_ptr xVisit; + std::shared_ptr yVisit; +}; + +APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols); +int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols); + +#endif \ No newline at end of file diff --git a/HelmetIdentification/src/KalmanTracker.cpp b/HelmetIdentification/src/KalmanTracker.cpp new file mode 100644 index 0000000..69362f9 --- /dev/null +++ b/HelmetIdentification/src/KalmanTracker.cpp @@ -0,0 +1,143 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "KalmanTracker.h" + +namespace ascendVehicleTracking +{ + namespace + { + const int OFFSET = 2; + const int MULTIPLE = 2; + } + + /* + * The SORT algorithm uses a linear constant velocity model,which assumes 7 + * states, including + * x coordinate of bounding box center + * y coordinate of bounding box center + * area of bounding box + * aspect ratio of w to h + * velocity of x + * velocity of y + * variation rate of area + * + * The aspect ratio is considered to be unchanged, so there is no additive item + * for aspect ratio in the transitionMatrix + * + * + * Kalman filter equation step by step + * (1) X(k|k-1)=AX(k-1|k-1)+BU(k) + * X(k|k-1) is the predicted state(statePre),X(k-1|k-1) is the k-1 statePost,A + * is transitionMatrix, B is controlMatrix, U(k) is control state, in SORT U(k) is 0. + * + * (2) P(k|k-1)=AP(k-1|k-1)A'+Q + * P(k|k-1) is the predicted errorCovPre, P(k-1|k-1) is the k-1 errorCovPost, + * Q is processNoiseCov + * + * (3) Kg(k)=P(k|k-1)H'/(HP(k|k-1))H'+R + * Kg(k) is the kalman gain, the ratio of estimate variance in total variance, + * H is the measurementMatrix,R is the measurementNoiseCov + * + * (4) X(k|k)=X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) + * X(k|k) is the k statePost, Z(k) is the measurement of K, in SORT Z(k) is + * the detection result of k + * + * (5) P(k|k)=(1-Kg(k)H)P(k|k-1) + * P(k|k) is the errorCovPost + */ + void KalmanTracker::CvKalmanInit(MxBase::ObjectInfo initRect) + { + const int stateDim = 7; + const int measureDim = 4; + cvkalmanfilter_ = cv::KalmanFilter(stateDim, measureDim, 0); // zero control + measurement_ = cv::Mat::zeros(measureDim, 1, CV_32F); // 4 measurements, Z(k), according to detection results + // A, will not be updated + cvkalmanfilter_.transitionMatrix = (cv::Mat_(stateDim, stateDim) << 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1); + cvkalmanfilter_.measurementMatrix = (cv::Mat_(measureDim, stateDim) << 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0); + cv::setIdentity(cvkalmanfilter_.measurementMatrix); // H, will not be updated + cv::setIdentity(cvkalmanfilter_.processNoiseCov, cv::Scalar::all(1e-2)); // Q, will not be updated + cv::setIdentity(cvkalmanfilter_.measurementNoiseCov, cv::Scalar::all(1e-1)); // R, will bot be updated + cv::setIdentity(cvkalmanfilter_.errorCovPost, cv::Scalar::all(1)); // P(k-1|k-1), will be updated + + // initialize state vector with bounding box in + // [center_x,center_y,area,ratio] + // style, the velocity is 0 + // X(k-1|k-1) + cvkalmanfilter_.statePost.at(0, 0) = initRect.x0 + (initRect.x1 - initRect.x0) / MULTIPLE; + cvkalmanfilter_.statePost.at(1, 0) = initRect.y0 + (initRect.y1 - initRect.y0) / MULTIPLE; + cvkalmanfilter_.statePost.at(OFFSET, 0) = (initRect.x1 - initRect.x0) * (initRect.y1 - initRect.y0); + cvkalmanfilter_.statePost.at(OFFSET + 1, 0) = (initRect.x1 - initRect.x0) / (initRect.y1 - initRect.y0); + } + + // Predict the bounding box. + MxBase::ObjectInfo KalmanTracker::Predict() + { + // predict + // return X(k|k-1)=AX(k-1|k-1), and update + // P(k|k-1) <- AP(k-1|k-1)A'+Q + MxBase::ObjectInfo detectInfo = {}; + cv::Mat predictState = cvkalmanfilter_.predict(); + float *pData = (float *)(predictState.data); + float w = sqrt((*(pData + OFFSET)) * (*(pData + OFFSET + 1))); + if (w < DBL_EPSILON) + { + detectInfo.x0 = 0; + detectInfo.y0 = 0; + detectInfo.x1 = 0; + detectInfo.y1 = 0; + detectInfo.classId = 0; + return detectInfo; + } + if (w == 0) + { + MxBase::ObjectInfo w0DetectInfo = {}; + return w0DetectInfo; + } + float h = (*(pData + OFFSET)) / w; + float x = (*pData) - w / MULTIPLE; + float y = (*(pData + 1)) - h / MULTIPLE; + if (x < 0 && (*pData) > 0) + { + x = 0; + } + if (y < 0 && (*(pData + 1)) > 0) + { + y = 0; + } + detectInfo.x0 = x; + detectInfo.y0 = y; + detectInfo.x1 = x + w; + detectInfo.y1 = y + h; + return detectInfo; + } + + // Update the state using observed bounding box + void KalmanTracker::Update(MxBase::ObjectInfo stateMat) + { + // measurement_, update Z(k) + float *pData = (float *)(measurement_.data); + *pData = stateMat.x0 + (stateMat.x1 - stateMat.x0) / MULTIPLE; + *(pData + 1) = stateMat.y0 + (stateMat.y1 - stateMat.y0) / MULTIPLE; + *(pData + OFFSET) = (stateMat.x1 - stateMat.x0) * (stateMat.y1 - stateMat.y0); + *(pData + OFFSET + 1) = (stateMat.x1 - stateMat.x0) / (stateMat.y1 - stateMat.y0); + // update, do the following steps: + // Kg(k): P(k|k-1)H'/(HP(k|k-1))H'+R + // X(k|k): X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) + // P(k|k): (1-Kg(k)H)P(k|k-1) + cvkalmanfilter_.correct(measurement_); + } +} // namespace diff --git a/HelmetIdentification/src/KalmanTracker.h b/HelmetIdentification/src/KalmanTracker.h new file mode 100644 index 0000000..f5f3465 --- /dev/null +++ b/HelmetIdentification/src/KalmanTracker.h @@ -0,0 +1,39 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H +#define MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H + +#include "opencv2/highgui/highgui.hpp" +#include "opencv2/video/tracking.hpp" +#include "DataType.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" + +namespace ascendVehicleTracking { +class KalmanTracker { +public: + KalmanTracker() {} + ~KalmanTracker() {} + void CvKalmanInit(MxBase::ObjectInfo initRect); + MxBase::ObjectInfo Predict(); + void Update(MxBase::ObjectInfo stateMat); +private: + cv::KalmanFilter cvkalmanfilter_ = {}; + cv::Mat measurement_ = {}; +}; +} // namesapce ascendVehicleTracking + +#endif \ No newline at end of file diff --git a/HelmetIdentification/src/MOTConnection.cpp b/HelmetIdentification/src/MOTConnection.cpp new file mode 100644 index 0000000..14e4341 --- /dev/null +++ b/HelmetIdentification/src/MOTConnection.cpp @@ -0,0 +1,262 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MOTConnection.h" +#include +#include +#include +#include +#include "opencv2/highgui.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include "MxBase/Log/Log.h" +#include "MxBase/ErrorCode/ErrorCodes.h" +#include "Hungarian.h" + +namespace ascendVehicleTracking +{ + namespace + { + // convert double to int + const int FLOAT_TO_INT = 1000; + const int MULTIPLE = 0; + const double SIMILARITY_THRESHOLD = 0.66; + const int MULTIPLE_IOU = 6; + const float NORM_EPS = 1e-10; + const double TIME_COUNTS = 1000.0; + const double COST_TIME_MS_THRESHOLD = 10.; + const float WIDTH_RATE_THRESH = 1.f; + const float HEIGHT_RATE_THRESH = 1.f; + const float X_DIST_RATE_THRESH = 1.3f; + const float Y_DIST_RATE_THRESH = 1.f; + } // namespace + + // 计算bounding box的交并比 + float CalIOU(MxBase::ObjectInfo detect1, MxBase::ObjectInfo detect2) + { + cv::Rect_ bbox1(detect1.x0, detect1.y0, detect1.x1 - detect1.x0, detect1.y1 - detect1.y0); + cv::Rect_ bbox2(detect2.x0, detect2.y0, detect2.x1 - detect2.x0, detect2.y1 - detect2.y0); + float intersectionArea = (bbox1 & bbox2).area(); + float unionArea = bbox1.area() + bbox2.area() - intersectionArea; + if (unionArea < DBL_EPSILON) + { + return 0.f; + } + return (intersectionArea / unionArea); + } + + // 计算前后两帧的两个bounding box的相似度 + float CalSimilarity(const TraceLet &traceLet, const MxBase::ObjectInfo &objectInfo, const int &method, const double &kIOU) + { + return CalIOU(traceLet.detectInfo, objectInfo); + } + + // 过滤掉交并比小于阈值的匹配 + void MOTConnection::FilterLowThreshold(const HungarianHandle &hungarianHandleObj, + const std::vector> &disMatrix, std::vector &matchedTracedDetected, + std::vector &detectVehicleFlagVec) + { + for (unsigned int i = 0; i < traceList_.size(); ++i) + { + if ((hungarianHandleObj.resX[i] != -1) && + (disMatrix[i][hungarianHandleObj.resX[i]] >= (trackThreshold_ * FLOAT_TO_INT))) + { + matchedTracedDetected.push_back(cv::Point(i, hungarianHandleObj.resX[i])); + detectVehicleFlagVec[hungarianHandleObj.resX[i]] = true; + } + else + { + traceList_[i].info.flag = LOST_VEHICLE; + } + } + } + + // 更新没有匹配上的跟踪器 + void MOTConnection::UpdateUnmatchedTraceLet(const std::vector> &objInfos) + { + for (auto itr = traceList_.begin(); itr != traceList_.end();) + { + if ((*itr).info.flag != LOST_VEHICLE) + { + ++itr; + continue; + } + + (*itr).lostAge++; + (*itr).kalman.Update((*itr).detectInfo); + + if ((*itr).lostAge < lostThreshold_) + { + continue; + } + + itr = traceList_.erase(itr); + } + } + + // 更新匹配上的跟踪器 + void MOTConnection::UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, + std::vector> &objInfos) + { + for (unsigned int i = 0; i < matchedTracedDetected.size(); ++i) + { + int traceIndex = matchedTracedDetected[i].x; + int detectIndex = matchedTracedDetected[i].y; + if (traceList_[traceIndex].info.survivalTime > MULTIPLE) + { + traceList_[traceIndex].info.flag = TRACkED_VEHICLE; + } + traceList_[traceIndex].info.survivalTime++; + traceList_[traceIndex].info.detectedTime++; + traceList_[traceIndex].lostAge = 0; + traceList_[traceIndex].detectInfo = objInfos[0][detectIndex]; + traceList_[traceIndex].kalman.Update(objInfos[0][detectIndex]); + } + } + // 将没有匹配上的检测更新为新的检测器 + void MOTConnection::AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue) + { + using Time = std::chrono::high_resolution_clock; + for (auto &vehicleObject : unmatchedVehicleObjectQueue) + { + // add new detected into traceList + TraceLet traceLet; + generatedId_++; + traceLet.info.id = generatedId_; + traceLet.info.survivalTime = 1; + traceLet.info.detectedTime = 1; + traceLet.lostAge = 0; + traceLet.info.flag = NEW_VEHICLE; + traceLet.detectInfo = vehicleObject; + traceLet.info.createTime = Time::now(); + + traceLet.kalman.CvKalmanInit(vehicleObject); + traceList_.push_back(traceLet); + } + } + + void MOTConnection::UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, + std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue) + { + UpdateMatchedTraceLet(matchedTracedDetected, objInfos); // 更新匹配上的跟踪器 + AddNewDetectedVehicle(unmatchedVehicleObjectQueue); // 将没有匹配上的检测更新为新的检测器 + UpdateUnmatchedTraceLet(objInfos); // 更新没有匹配上的跟踪器 + } + + void MOTConnection::TrackObjectPredict() + { + // every traceLet should do kalman predict + for (auto &traceLet : traceList_) + { + traceLet.detectInfo = traceLet.kalman.Predict(); // 卡尔曼滤波预测的框 + } + } + + void MOTConnection::TrackObjectUpdate(const std::vector> &objInfos, + std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue) + { + if (objInfos[0].size() > 0) + { + LogDebug << "[frame id = " << 1 << "], trace size =" << traceList_.size() << "detect size = " << objInfos[0].size() << ""; + // init vehicle matched flag + std::vector detectVehicleFlagVec; + for (unsigned int i = 0; i < objInfos[0].size(); ++i) + { + detectVehicleFlagVec.push_back(false); + } + // calculate the associated matrix + std::vector> disMatrix; + disMatrix.clear(); + disMatrix.resize(traceList_.size(), std::vector(objInfos[0].size(), 0)); + for (unsigned int j = 0; j < objInfos[0].size(); ++j) + { + for (unsigned int i = 0; i < traceList_.size(); ++i) + { + // 计算交并比 + float sim = CalSimilarity(traceList_[i], objInfos[0][j], method_, kIOU_); // method_=1, kIOU_=1.0 + disMatrix[i][j] = (int)(sim * FLOAT_TO_INT); + } + } + + // solve the assignment problem using hungarian 匈牙利算法进行匹配 + HungarianHandle hungarianHandleObj = {}; + HungarianHandleInit(hungarianHandleObj, traceList_.size(), objInfos[0].size()); + HungarianSolve(hungarianHandleObj, disMatrix, traceList_.size(), objInfos[0].size()); + // filter out matched but with low distance 过滤掉匹配上但是交并比较小的 + FilterLowThreshold(hungarianHandleObj, disMatrix, matchedTracedDetected, detectVehicleFlagVec); + LogDebug << "matchedTracedDetected = " << matchedTracedDetected.size() << ""; + // fill unmatchedVehicleObjectQueue + for (unsigned int i = 0; i < detectVehicleFlagVec.size(); ++i) + { + if (detectVehicleFlagVec[i] == false) + { + unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); + } + } + } + } + + APP_ERROR MOTConnection::ProcessSort(std::vector> &objInfos, size_t frameId) + { + std::vector unmatchedVehicleObjectQueue; + std::vector matchedTracedDetected; + if (objInfos[0].size() == 0) + { + return APP_ERR_COMM_FAILURE; + } + + if (traceList_.size() > 0) + { + // every traceLet should do kalman predict + TrackObjectPredict(); // 卡尔曼滤波预测 + TrackObjectUpdate(objInfos, matchedTracedDetected, unmatchedVehicleObjectQueue); // 选出matched track、unmatched detection + } + else + { + // traceList is empty, all the vehicle detected in the new frame are unmatched. + if (objInfos[0].size() > 0) + { + for (unsigned int i = 0; i < objInfos[0].size(); ++i) + { + unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); + } + } + } + + // update all the tracelet in the tracelist per frame + UpdateTraceLetAndFrame(matchedTracedDetected, objInfos, unmatchedVehicleObjectQueue); // 用matched track、unmatched detection更新跟踪器 + return APP_ERR_OK; + } + + // 获取跟踪后的检测框 + APP_ERROR MOTConnection::GettrackResult(std::vector &objInfos_) + { + if (traceList_.size() > 0) + { + for (auto &traceLet : traceList_) + { + traceLet.detectInfo.classId = traceLet.info.id; + objInfos_.push_back(traceLet.detectInfo); + } + } + else + { + return APP_ERR_COMM_FAILURE; + } + return APP_ERR_OK; + } +} +// namespace ascendVehicleTracking \ No newline at end of file diff --git a/HelmetIdentification/src/MOTConnection.h b/HelmetIdentification/src/MOTConnection.h new file mode 100644 index 0000000..be54f17 --- /dev/null +++ b/HelmetIdentification/src/MOTConnection.h @@ -0,0 +1,78 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H +#define MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H + +#include +#include +#include +#include "KalmanTracker.h" +#include "Hungarian.h" +#include "DataType.h" +#include "MxBase/ErrorCode/ErrorCodes.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/Tensor/TensorBase/TensorBase.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +namespace ascendVehicleTracking { +struct TraceLet { + TraceInfo info = {}; + int32_t lostAge = 0; + KalmanTracker kalman; + std::list> shortFeatureQueue; + MxBase::ObjectInfo detectInfo = {}; +}; + +class MOTConnection { +public: + APP_ERROR ProcessSort(std::vector> &objInfos, size_t frameId); + APP_ERROR GettrackResult(std::vector &objInfos_); + +private: + double trackThreshold_ = 0.3; + double kIOU_ = 1.0; + int32_t method_ = 1; + int32_t lostThreshold_ = 3; + uint32_t maxNumberFeature_ = 0; + int32_t generatedId_ = 0; + std::vector traceList_ = {}; + +private: + + void FilterLowThreshold(const HungarianHandle &hungarianHandleObj, const std::vector> &disMatrix, + std::vector &matchedTracedDetected, std::vector &detectVehicleFlagVec); + + void UpdateUnmatchedTraceLet(const std::vector> &objInfos); + + void UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, + std::vector> &objInfos); + + void AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue); + + void UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, + std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue); + + void TrackObjectPredict(); + void TrackObjectUpdate(const std::vector> &objInfos, + std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue); +}; +} // namespace ascendVehicleTracking + +#endif \ No newline at end of file diff --git a/HelmetIdentification/src/cropResizePaste.hpp b/HelmetIdentification/src/cropResizePaste.hpp new file mode 100644 index 0000000..37c6e8f --- /dev/null +++ b/HelmetIdentification/src/cropResizePaste.hpp @@ -0,0 +1,115 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef CRP_H +#define CRP_H + +#include "MxBase/E2eInfer/Image/Image.h" +#include "MxBase/E2eInfer/Rect/Rect.h" +#include "MxBase/E2eInfer/Size/Size.h" + +#include "acl/dvpp/hi_dvpp.h" +#include "acl/acl.h" +#include "acl/acl_rt.h" + +#define CONVER_TO_PODD(NUM) (((NUM) % 2 != 0) ? (NUM) : ((NUM)-1)) +#define CONVER_TO_EVEN(NUM) (((NUM) % 2 == 0) ? (NUM) : ((NUM)-1)) +#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) + +MxBase::Rect GetPasteRect(uint32_t inputWidth, uint32_t inputHeight, uint32_t outputWidth, uint32_t outputHeight) +{ + bool widthRatioLarger = true; + float resizeRatio = static_cast(inputWidth) / outputWidth; + if (resizeRatio < (static_cast(inputHeight) / outputHeight)) + { + resizeRatio = static_cast(inputHeight) / outputHeight; + widthRatioLarger = false; + } + + // (x0, y0)是左上角坐标,(x1, y1)是右下角坐标,采用图片坐标系 + uint32_t x0; + uint32_t y0; + uint32_t x1; + uint32_t y1; + if (widthRatioLarger) + { + // 原图width大于height + x0 = 0; + y0 = 0; + x1 = outputWidth-1; + y1 = inputHeight / resizeRatio - 1; + } + else + { + // 原图height大于width + x0 = 0; + y0 = 0; + x1 = outputWidth / resizeRatio - 1; + y1 = outputHeight - 1; + } + + x0 = DVPP_ALIGN_UP(CONVER_TO_EVEN(x0), 16); // 16对齐 + x1 = DVPP_ALIGN_UP((x1 - x0 + 1), 16) + x0 - 1; + y0 = CONVER_TO_EVEN(y0); + y1 = CONVER_TO_PODD(y1); + MxBase::Rect res(x0, y0, x1, y1); + return res; +} + +MxBase::Image ConstructImage(uint32_t resizeWidth, uint32_t resizeHeight) +{ + void *addr; + uint32_t dataSize = resizeWidth * resizeHeight * 3 / 2; + auto ret = hi_mpi_dvpp_malloc(0, &addr, dataSize); + if (ret != APP_ERR_OK) + { + LogError << "hi_mpi_dvpp_malloc fail :" << ret; + } + // 第三个参数从128改成了0 + ret = aclrtMemset(addr, dataSize, 0, dataSize); + if (ret != APP_ERR_OK) + { + LogError << "aclrtMemset fail :" << ret; + } + std::shared_ptr data((uint8_t *)addr, hi_mpi_dvpp_free); + MxBase::Size imageSize(resizeWidth, resizeHeight); + MxBase::Image pastedImg(data, dataSize, 0, imageSize); + return pastedImg; +} + +std::pair GenerateRect(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight) +{ + uint32_t x1 = CONVER_TO_PODD(originalWidth - 1); + uint32_t y1 = CONVER_TO_PODD(originalHeight - 1); + MxBase::Rect cropRect(0, 0, x1, y1); + MxBase::Rect pasteRect = GetPasteRect(originalWidth, originalHeight, resizeWidth, resizeHeight); + std::pair cropPasteRect(cropRect, pasteRect); + return cropPasteRect; +} + +MxBase::Image resizeKeepAspectRatioFit(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight, MxBase::Image &decodeImage, MxBase::ImageProcessor& imageProcessor) +{ + std::pair cropPasteRect = GenerateRect(originalWidth, originalHeight, resizeWidth, resizeHeight); + MxBase::Image resizeImage = ConstructImage(resizeWidth, resizeHeight); + auto ret = imageProcessor.CropAndPaste(decodeImage, cropPasteRect, resizeImage); + if (ret != APP_ERR_OK) + { + LogError << "CropAndPaste fail :" << ret; + } + return resizeImage; +} + +#endif // CRP_H \ No newline at end of file diff --git a/HelmetIdentification/src/main-image.cpp b/HelmetIdentification/src/main-image.cpp new file mode 100644 index 0000000..ed14045 --- /dev/null +++ b/HelmetIdentification/src/main-image.cpp @@ -0,0 +1,170 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils.h" + +#include +#include +#include +using namespace std; + +// 如果在200DK上运行就改为 USE_200DK +#define USE_DVPP + +APP_ERROR readImage(std::string imgPath, MxBase::Image& image, MxBase::ImageProcessor& imageProcessor) +{ + APP_ERROR ret; +#ifdef USE_DVPP + // if USE DVPP + ret = imageProcessor.Decode(imgPath, image); +#endif +#ifdef USE_200DK + std::shared_ptr dataPtr; + uint32_t dataSize; + // Get image data to memory, this method can be substituted or designed by yourself! + std::ifstream file; + file.open(imgPath.c_str(), std::ios::binary); + if (!file) + { + LogInfo << "Invalid file."; + return APP_ERR_COMM_INVALID_PARAM; + } + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); + + char *p = (char *)malloc(content.size()); + memcpy(p, content.data(), content.size()); + auto deleter = [](void *p) -> void + { + free(p); + p = nullptr; + }; + + dataPtr.reset(static_cast((void *)(p)), deleter); + dataSize = content.size(); + file.close(); + if (ret != APP_ERR_OK) + { + LogError << "Getimage failed, ret=" << ret; + return ret; + } + ret = imageProcessor.Decode(dataPtr, dataSize, image, MxBase::ImageFormat::YUV_SP_420); + // endif +#endif + if (ret != APP_ERR_OK) + { + LogError << "Decode failed, ret=" << ret; + return ret; + } +} + +void poptProcess(std::vector modelOutputs, std::shared_ptr postProcessorDptr, MxBase::Size originalSize, MxBase::Size resizeSize) +{ + MxBase::ResizedImageInfo imgInfo; + auto shape = modelOutputs[0].GetShape(); + imgInfo.widthOriginal = originalSize.width; + imgInfo.heightOriginal = originalSize.height; + imgInfo.widthResize = resizeSize.width; + imgInfo.heightResize = resizeSize.height; + imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; + float resizeRate = originalSize.width > originalSize.height ? (originalSize.width / 640.0) : (originalSize.height / 640.0); + imgInfo.keepAspectRatioScaling = 1 / resizeRate; + std::vector imageInfoVec = {}; + imageInfoVec.push_back(imgInfo); + // make postProcess inputs + std::vector tensors; + for (size_t i = 0; i < modelOutputs.size(); i++) + { + MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); + MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); + tensors.push_back(tensorBase); + } + std::vector> objectInfos; + postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); + std::cout << "===---> Size of objectInfos is " << objectInfos.size() << std::endl; + for (size_t i = 0; i < objectInfos.size(); i++) + { + std::cout << "objectInfo-" << i << " , Size:" << objectInfos[i].size() << std::endl; + for (size_t j = 0; j < objectInfos[i].size(); j++) + { + std::cout << std::endl + << "*****objectInfo-" << i << ":" << j << std::endl; + std::cout << "x0 is " << objectInfos[i][j].x0 << std::endl; + std::cout << "y0 is " << objectInfos[i][j].y0 << std::endl; + std::cout << "x1 is " << objectInfos[i][j].x1 << std::endl; + std::cout << "y1 is " << objectInfos[i][j].y1 << std::endl; + std::cout << "confidence is " << objectInfos[i][j].confidence << std::endl; + std::cout << "classId is " << objectInfos[i][j].classId << std::endl; + std::cout << "className is " << objectInfos[i][j].className << std::endl; + } + } +} + +APP_ERROR main(int argc, char *argv[]) +{ + APP_ERROR ret; + + // global init + ret = MxBase::MxInit(); + if (ret != APP_ERR_OK) + { + LogError << "MxInit failed, ret=" << ret << "."; + } + // 检测是否输入了文件路径 + if (argc <= 1) + { + LogWarn << "Please input image path, such as 'test.jpg'."; + return APP_ERR_OK; + } + + // imageProcess init + MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); + // model init + MxBase::Model yoloModel(videoInfo::modelPath, videoInfo::DEVICE_ID); + // postprocessor init + std::map postConfig; + postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); + postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); + std::shared_ptr postProcessorDptr = std::make_shared(); + postProcessorDptr->Init(postConfig); + + std::string imgPath = argv[1]; + // 读取图片 + MxBase::Image image; + readImage(imgPath, image, imageProcessor); + + // 缩放图片 + MxBase::Size originalSize = image.GetOriginalSize(); + MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); + MxBase::Image resizedImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); + + // 模型推理 + MxBase::Tensor tensorImg = resizedImage.ConvertToTensor(); + tensorImg.ToDevice(videoInfo::DEVICE_ID); + std::vector inputs; + inputs.push_back(tensorImg); + std::vector modelOutputs = yoloModel.Infer(inputs); + for (auto output : modelOutputs) + { + output.ToHost(); + } + + // 后处理 + poptProcess(modelOutputs, postProcessorDptr, originalSize, resizeSize); + + return APP_ERR_OK; +} \ No newline at end of file diff --git a/HelmetIdentification/src/main.cpp b/HelmetIdentification/src/main.cpp new file mode 100644 index 0000000..8d3bf88 --- /dev/null +++ b/HelmetIdentification/src/main.cpp @@ -0,0 +1,283 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils.h" + +extern "C" +{ + #include "libavformat/avformat.h" +} + +#include +#include +#include +#include "unistd.h" +#include +#include +#include +#include "boost/filesystem.hpp" + +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "opencv2/opencv.hpp" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" +#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" +#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" +#include "MxBase/E2eInfer/DataType.h" + +#include +using namespace std; +using namespace videoInfo; +namespace frameConfig +{ + size_t channelId0 = 0; + size_t channelId1 = 1; + size_t frameCountChannel0 = 300; + size_t frameCountChannel1 = 300; + size_t skipIntervalChannel0 = 2; + size_t skipIntervalChannel1 = 2; + + // channel0对应文件的指针 + AVFormatContext *pFormatCtx0 = nullptr; + // channel1对应文件的指针 + AVFormatContext *pFormatCtx1 = nullptr; +} + +// ffmpeg拉流 +AVFormatContext *CreateFormatContext(std::string filePath) +{ + LogInfo << "start to CreatFormatContext!"; + // creat message for stream pull + AVFormatContext *formatContext = nullptr; + AVDictionary *options = nullptr; + + LogInfo << "start to avformat_open_input!"; + int ret = avformat_open_input(&formatContext, filePath.c_str(), nullptr, &options); + if (options != nullptr) + { + av_dict_free(&options); + } + if (ret != 0) + { + LogError << "Couldn't open input stream " << filePath.c_str() << " ret=" << ret; + return nullptr; + } + ret = avformat_find_stream_info(formatContext, nullptr); + if (ret != 0) + { + LogError << "Couldn't open input stream information"; + return nullptr; + } + return formatContext; +} + +// 真正的拉流函数 +void PullStream0(std::string filePath) +{ + av_register_all(); + avformat_network_init(); + frameConfig::pFormatCtx0 = avformat_alloc_context(); + frameConfig::pFormatCtx0 = CreateFormatContext(filePath); + av_dump_format(frameConfig::pFormatCtx0, 0, filePath.c_str(), 0); +} +void PullStream1(std::string filePath) +{ + av_register_all(); + avformat_network_init(); + frameConfig::pFormatCtx1 = avformat_alloc_context(); + frameConfig::pFormatCtx1 = CreateFormatContext(filePath); + av_dump_format(frameConfig::pFormatCtx1, 0, filePath.c_str(), 0); +} + +// 视频解码回调(样例代码,测试可以跑通,但是不能直接复用) +APP_ERROR CallBackVdec(Image &decodedImage, uint32_t channelId, uint32_t frameId, void *userData) +{ + FrameImage frameImage; + frameImage.image = decodedImage; + frameImage.channelId = channelId; + frameImage.frameId = frameId; + + videoInfo::g_threadsMutex_frameImageQueue.lock(); + videoInfo::frameImageQueue.push(frameImage); + videoInfo::g_threadsMutex_frameImageQueue.unlock(); + + return APP_ERR_OK; +} + +// 获取H264中的帧 +void GetFrame(AVPacket &pkt, FrameImage &frameImage, AVFormatContext *pFormatCtx) +{ + av_init_packet(&pkt); + int ret = av_read_frame(pFormatCtx, &pkt); + if (ret != 0) + { + LogInfo << "[StreamPuller] channel Read frame failed, continue!"; + if (ret == AVERROR_EOF) + { + LogInfo << "[StreamPuller] channel StreamPuller is EOF, over!"; + return; + } + return; + } + else + { + if (pkt.size <= 0) + { + LogError << "Invalid pkt.size: " << pkt.size; + return; + } + + // send to the device + auto hostDeleter = [](void *dataPtr) -> void {}; + MemoryData data(pkt.size, MemoryData::MEMORY_HOST); + MemoryData src((void *)(pkt.data), pkt.size, MemoryData::MEMORY_HOST); + APP_ERROR ret = MemoryHelper::MxbsMallocAndCopy(data, src); + if (ret != APP_ERR_OK) + { + LogError << "MxbsMallocAndCopy failed!"; + } + std::shared_ptr imageData((uint8_t *)data.ptrData, hostDeleter); + + Image subImage(imageData, pkt.size); + frameImage.image = subImage; + + LogDebug << "'channelId = " << frameImage.channelId << ", frameId = " << frameImage.frameId << " , dataSize = " << frameImage.image.GetDataSize(); + + av_packet_unref(&pkt); + } + return; +} + +// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 +void VdecThread0(size_t frameCount, size_t skipInterval, int32_t channelId) +{ + AVPacket pkt; + uint32_t frameId = 0; + // 解码器参数 + VideoDecodeConfig config; + VideoDecodeCallBack cPtr = CallBackVdec; + config.width = videoInfo::SRC_WIDTH; + config.height = videoInfo::SRC_HEIGHT; + config.callbackFunc = cPtr; + config.skipInterval = skipInterval; // 跳帧控制 + + std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); + for (size_t i = 0; i < frameCount; i++) + { + Image subImage; + FrameImage frame; + frame.channelId = 0; + frame.frameId = frameId; + frame.image = subImage; + GetFrame(pkt, frame, frameConfig::pFormatCtx0); + APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); + if (ret != APP_ERR_OK) + { + LogError << "videoDecoder Decode failed. ret is: " << ret; + } + frameId += 1; + } +} + +// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 +void VdecThread1(size_t frameCount, size_t skipInterval, int32_t channelId) +{ + AVPacket pkt; + uint32_t frameId = 0; + // 解码器参数 + VideoDecodeConfig config; + VideoDecodeCallBack cPtr = CallBackVdec; + config.width = videoInfo::SRC_WIDTH; + config.height = videoInfo::SRC_HEIGHT; + config.callbackFunc = cPtr; + // 跳帧控制 + config.skipInterval = skipInterval; + + std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); + for (size_t i = 0; i < frameCount; i++) + { + Image subImage; + FrameImage frame; + frame.channelId = 0; + frame.frameId = frameId; + frame.image = subImage; + GetFrame(pkt, frame, frameConfig::pFormatCtx1); + APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); + if (ret != APP_ERR_OK) + { + LogError << "videoDecoder Decode failed. ret is: " << ret; + } + frameId += 1; + } +} + + +APP_ERROR main(int argc, char *argv[]) +{ + // 检测是否输入了文件路径 + if (argc <= 1) + { + LogWarn << "Please input video path, such as './video_sample test.264'."; + return APP_ERR_OK; + } + + // 初始化 + APP_ERROR ret = MxBase::MxInit(); + if (ret != APP_ERR_OK) + { + LogError << "MxInit failed, ret=" << ret << "."; + } + + std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); + + // 视频流解码线程 + std::string videoPath0 = argv[1]; + PullStream0(videoPath0); + std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0); + std::string videoPath1 = argv[2]; + PullStream1(videoPath1); + std::thread threadVdec1(VdecThread1, frameConfig::frameCountChannel1, frameConfig::skipIntervalChannel1, frameConfig::channelId1); + threadVdec0.join(); + threadVdec1.join(); + + size_t target_count = videoInfo::frameImageQueue.size(); + LogInfo << "frameImageQueue.size: " << videoInfo::frameImageQueue.size(); + + // resize线程 + std::thread resizeThread(resizeMethod, start_time, target_count); + resizeThread.join(); + + // 推理线程 + std::thread inferThread(inferMethod, start_time, target_count); + inferThread.join(); + + // 后处理线程 + std::thread postprocessThread(postprocessMethod, start_time, target_count); + postprocessThread.join(); + + // 跟踪去重线程 + std::thread trackThread(trackMethod, start_time, target_count); + trackThread.join(); + + std::chrono::high_resolution_clock::time_point end_time = std::chrono::high_resolution_clock::now(); + double_t cost_time = std::chrono::duration_cast(end_time - start_time).count(); + LogInfo << "端到端时间为" << cost_time/videoInfo::MS_PPE_SECOND; + + return APP_ERR_OK; +} \ No newline at end of file diff --git a/HelmetIdentification/src/utils.h b/HelmetIdentification/src/utils.h new file mode 100644 index 0000000..f605346 --- /dev/null +++ b/HelmetIdentification/src/utils.h @@ -0,0 +1,351 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_UTILS_H +#define MXBASE_HELMETIDENTIFICATION_UTILS_H + +#include +#include +#include +#include "unistd.h" +#include +#include +#include +#include "boost/filesystem.hpp" + +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "opencv2/opencv.hpp" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" +#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" +#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" +#include "MxBase/E2eInfer/DataType.h" + +#include "MxBase/MxBase.h" +#include "MOTConnection.h" +#include "cropResizePaste.hpp" +#include "chrono" +#include "time.h" + +struct FrameImage +{ + MxBase::Image image; + uint32_t frameId = 0; + uint32_t channelId = 0; +}; + +namespace videoInfo +{ + const uint32_t SRC_WIDTH = 1920; + const uint32_t SRC_HEIGHT = 1080; + + const uint32_t YUV_BYTE_NU = 3; + const uint32_t YUV_BYTE_DE = 2; + const uint32_t YOLOV5_RESIZE = 640; + + // 要检测的目标类别的标签 + const std::string TARGET_CLASS_NAME = "head"; + // 使用chrono计数结果为毫秒,需要除以1000转换为秒 + double MS_PPE_SECOND = 1000.0; + + const uint32_t DEVICE_ID = 0; + std::string labelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/imgclass.names"; + std::string configPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/Helmet_yolov5.cfg"; + std::string modelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/YOLOv5_s.om"; + + // 读入视频帧Image的队列 + std::queue frameImageQueue; + // 读入视频帧Image队列的线程锁 + std::mutex g_threadsMutex_frameImageQueue; + + // resize前即原始图像的vector + std::vector realImageVector; + // resize后Image的vector + std::vector resizedImageVector; + // resize后Image队列的线程锁 + std::mutex g_threadsMutex_resizedImageVector; + + // 推理后tensor的vector + std::vector> inferOutputVector; + // 推理后tensor队列的线程锁 + std::mutex g_threadsMutex_inferOutputVector; + + // 后处理后objectInfos的队列 map> + std::vector>> postprocessOutputVector; + // 后处理后objectInfos队列的线程锁 + std::mutex g_threadsMutex_postprocessOutputVector; +} +namespace fs = boost::filesystem; + +// resize线程 +void resizeMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point resize_start_time = std::chrono::high_resolution_clock::now(); + + MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); + + size_t resize_count = 0; + while (resize_count < target_count) + { + if (videoInfo::frameImageQueue.empty()) + { + continue; + } + // 取图像并resize + FrameImage frame = videoInfo::frameImageQueue.front(); + Image image = frame.image; + MxBase::Size originalSize = image.GetOriginalSize(); + MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); + MxBase::Image outputImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); + // 先将缩放后的图像放入resizeImage的队列 + FrameImage resizedFrame; + resizedFrame.channelId = frame.channelId; + resizedFrame.frameId = frame.frameId; + resizedFrame.image = outputImage; + videoInfo::g_threadsMutex_resizedImageVector.lock(); + frame.image.ToHost(); + videoInfo::realImageVector.push_back(frame); + videoInfo::resizedImageVector.push_back(resizedFrame); + videoInfo::g_threadsMutex_resizedImageVector.unlock(); + // 然后再将原图pop出去 + videoInfo::g_threadsMutex_frameImageQueue.lock(); + videoInfo::frameImageQueue.pop(); + videoInfo::g_threadsMutex_frameImageQueue.unlock(); + // 计数 + resize_count++; + } + std::chrono::high_resolution_clock::time_point resize_end_time = std::chrono::high_resolution_clock::now(); + double_t resize_cost_time = std::chrono::duration_cast(resize_end_time - resize_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t resize_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到缩放全部完成一共花费: " << resize_finish_time << ", 缩放本身花费: " << resize_cost_time; +} + +// 推理线程 +void inferMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point infer_start_time = std::chrono::high_resolution_clock::now(); + + std::shared_ptr modelDptr = std::make_shared(videoInfo::modelPath, videoInfo::DEVICE_ID); + + size_t infer_count = 0; + while (infer_count < target_count) + { + if (infer_count >= videoInfo::resizedImageVector.size()) + { + continue; + } + // 从resize后的队列中取图片 + FrameImage resizedFrame = videoInfo::resizedImageVector[infer_count]; + std::vector modelOutputs; + + MxBase::Tensor tensorImg = resizedFrame.image.ConvertToTensor(); + tensorImg.ToDevice(videoInfo::DEVICE_ID); + std::vector inputs; + inputs.push_back(tensorImg); + modelOutputs = modelDptr->Infer(inputs); + for (auto output : modelOutputs) + { + output.ToHost(); + } + + // 将推理结果存入队列 + videoInfo::g_threadsMutex_inferOutputVector.lock(); + videoInfo::inferOutputVector.push_back(modelOutputs); + videoInfo::g_threadsMutex_inferOutputVector.unlock(); + // 计数 + infer_count++; + } + std::chrono::high_resolution_clock::time_point infer_end_time = std::chrono::high_resolution_clock::now(); + double_t infer_cost_time = std::chrono::duration_cast(infer_end_time - infer_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t infer_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到推理全部完成一共花费: " << infer_finish_time << ", 推理本身花费: " << infer_cost_time; +} + +// 后处理线程 +void postprocessMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point postprocess_start_time = std::chrono::high_resolution_clock::now(); + + std::map postConfig; + postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); + postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); + std::shared_ptr postProcessorDptr = std::make_shared(); + if (postProcessorDptr == nullptr) + { + LogError << "init postProcessor failed, nullptr"; + } + postProcessorDptr->Init(postConfig); + + size_t postprocess_count = 0; + while (postprocess_count < target_count) + { + if (postprocess_count >= videoInfo::inferOutputVector.size()) + { + continue; + } + // 取原图信息用于计算 + MxBase::Size originalSize = videoInfo::realImageVector[postprocess_count].image.GetOriginalSize(); + // 从推理结果的队列里面取出推理结果 + std::vector modelOutputs = videoInfo::inferOutputVector[postprocess_count]; + FrameImage resizedFrame = videoInfo::resizedImageVector[postprocess_count]; + // 新的后处理过程 + MxBase::ResizedImageInfo imgInfo; + auto shape = modelOutputs[0].GetShape(); + imgInfo.widthOriginal = originalSize.width; + imgInfo.heightOriginal = originalSize.height; + imgInfo.widthResize = videoInfo::YOLOV5_RESIZE; + imgInfo.heightResize = videoInfo::YOLOV5_RESIZE; + imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; + // 因为yolov5要求输入图像为640*640,所以直接比较原图的height和width就好(如果不理解就去看cropResizePaste.hpp里的GetPasteRect函数) + float resizeRate = originalSize.width > originalSize.height ? (originalSize.width / videoInfo::YOLOV5_RESIZE) : (originalSize.height / videoInfo::YOLOV5_RESIZE); + imgInfo.keepAspectRatioScaling = 1 / resizeRate; + std::vector imageInfoVec = {}; + imageInfoVec.push_back(imgInfo); + // make postProcess inputs + std::vector tensors; + for (size_t i = 0; i < modelOutputs.size(); i++) + { + MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); + MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); + tensors.push_back(tensorBase); + } + // 后处理 + std::vector> objectInfos; + postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); + + // 将后处理结果存入队列 + videoInfo::g_threadsMutex_postprocessOutputVector.lock(); + videoInfo::postprocessOutputVector.push_back(objectInfos); + videoInfo::g_threadsMutex_postprocessOutputVector.unlock(); + // 计数 + postprocess_count++; + } + std::chrono::high_resolution_clock::time_point postprocess_end_time = std::chrono::high_resolution_clock::now(); + double_t postprocess_cost_time = std::chrono::duration_cast(postprocess_end_time - postprocess_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t postprocess_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到后处理全部完成一共花费: " << postprocess_finish_time << ", 后处理本身花费: " << postprocess_cost_time; +} + +// 跟踪去重线程 +void trackMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point track_start_time = std::chrono::high_resolution_clock::now(); + + std::shared_ptr tracker0 = std::make_shared(); + if (tracker0 == nullptr) + { + LogError << "init tracker0 failed, nullptr"; + } + std::shared_ptr tracker1 = std::make_shared(); + if (tracker1 == nullptr) + { + LogError << "init tracker1 failed, nullptr"; + } + // 用于计算帧率 + size_t old_count = 0; + std::chrono::high_resolution_clock::time_point count_time; + std::chrono::high_resolution_clock::time_point old_count_time = std::chrono::high_resolution_clock::now(); + size_t one_step = 2; + size_t track_count = 0; + while (track_count < target_count) + { + // 计算帧率 + // 如果count_time-old_count_time的值大于one_step,就计算一下这个step里面的帧数,然后除以step的值 + count_time = std::chrono::high_resolution_clock::now(); + if (std::chrono::duration_cast(count_time - old_count_time).count() / videoInfo::MS_PPE_SECOND > one_step) + { + old_count_time = count_time; + LogInfo << "rate: " << (track_count - old_count) / one_step * 1.0; + old_count = track_count; + } + // 下面是业务循环 + if (track_count >= videoInfo::postprocessOutputVector.size()) + { + continue; + } + // 从后处理结果的队列中取结果用于跟踪去重 + std::vector> objectInfos = videoInfo::postprocessOutputVector[track_count]; + FrameImage frame = videoInfo::realImageVector[track_count]; + MxBase::Size originalSize = frame.image.GetOriginalSize(); + + // 根据channelId的不同使用不同的tracker + std::vector objInfos_ = {}; + if (frame.channelId == 0) + { + tracker0->ProcessSort(objectInfos, frame.frameId); + APP_ERROR ret = tracker0->GettrackResult(objInfos_); + if (ret != APP_ERR_OK) + { + LogError << "No tracker0"; + } + } + else + { + tracker1->ProcessSort(objectInfos, frame.frameId); + APP_ERROR ret = tracker1->GettrackResult(objInfos_); + if (ret != APP_ERR_OK) + { + LogError << "No tracker1"; + } + } + + uint32_t video_height = originalSize.height; + uint32_t video_width = originalSize.width; + // 初始化OpenCV图像信息矩阵 + cv::Mat imgYuv = cv::Mat(video_height * videoInfo::YUV_BYTE_NU / videoInfo::YUV_BYTE_DE, video_width, CV_8UC1, frame.image.GetData().get()); + cv::Mat imgBgr = cv::Mat(video_height, video_width, CV_8UC3); + // 颜色空间转换 + cv::cvtColor(imgYuv, imgBgr, cv::COLOR_YUV420sp2BGR); + std::vector info; + bool headFlag = false; + for (uint32_t i = 0; i < objInfos_.size(); i++) + { + if (objInfos_[i].className == videoInfo::TARGET_CLASS_NAME) + { + headFlag = true; + LogWarn << "Warning:Not wearing a helmet, channelId:" << frame.channelId << ", frameId:" << frame.frameId; + // (blue, green, red) + const cv::Scalar color = cv::Scalar(0, 0, 255); + // width for rectangle + const uint32_t thickness = 2; + // draw the rectangle + cv::rectangle(imgBgr, + cv::Rect(objInfos_[i].x0, objInfos_[i].y0, objInfos_[i].x1 - objInfos_[i].x0, objInfos_[i].y1 - objInfos_[i].y0), + color, thickness); + } + } + // 如果检测结果中有head标签,就保存为图片 + if (headFlag) + { + // 把Mat类型的图像矩阵保存为图像到指定位置。 + std::string outPath = frame.channelId == 0 ? "one" : "two"; + std::string fileName = "./result/" + outPath + "/result" + std::to_string(frame.frameId) + ".jpg"; + cv::imwrite(fileName, imgBgr); + } + + // 计数 + track_count++; + } + std::chrono::high_resolution_clock::time_point track_end_time = std::chrono::high_resolution_clock::now(); + double_t track_cost_time = std::chrono::duration_cast(track_end_time - track_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t track_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到跟踪去重全部完成一共花费: " << track_finish_time << ", 跟踪去重本身花费: " << track_cost_time; +} + +#endif \ No newline at end of file From 94667298237a477af7a3faa46de239d347b3bafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:47:27 +0000 Subject: [PATCH 15/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification/src/.keep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification/src/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 HelmetIdentification/src/.keep diff --git a/HelmetIdentification/src/.keep b/HelmetIdentification/src/.keep deleted file mode 100644 index e69de29..0000000 From 252ce022292d00d8d56c7d7ae56e394b4c515b89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 07:54:23 +0000 Subject: [PATCH 16/58] update HelmetIdentification/src/main.cpp. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification/src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HelmetIdentification/src/main.cpp b/HelmetIdentification/src/main.cpp index 8d3bf88..3a3a006 100644 --- a/HelmetIdentification/src/main.cpp +++ b/HelmetIdentification/src/main.cpp @@ -18,7 +18,7 @@ extern "C" { - #include "libavformat/avformat.h" +#include "libavformat/avformat.h" } #include From c209e1ec6c89da4a11e0d69e768308c3f94a07de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 09:24:19 +0000 Subject: [PATCH 17/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification/readme.md | 297 --------------------------------- 1 file changed, 297 deletions(-) delete mode 100644 HelmetIdentification/readme.md diff --git a/HelmetIdentification/readme.md b/HelmetIdentification/readme.md deleted file mode 100644 index 5ae1c3a..0000000 --- a/HelmetIdentification/readme.md +++ /dev/null @@ -1,297 +0,0 @@ -# 标题(项目标题) - -## 1 介绍 -安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 - -本项目支持2路视频实时分析,其主要流程为: - -​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 - -### 1.1 支持的产品 - -本项目以昇腾Atlas 200DK为主要的硬件平台。 - -### 1.2 支持的版本 - -本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 - -MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 - -### 1.3 软件方案介绍 - -请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 - -表1.1 系统方案各子系统功能描述: - -| 序号 | 子系统 | 功能描述 | -| ---- | ---------- | ------------------------------------------------------------ | -| 1 | 视频解码 | 从264视频流中解码图像帧 | -| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | -| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | -| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | -| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | -| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | - - - -### 1.4 代码目录结构与说明 - -本工程名称为HelmetIdentification_V2,工程目录如下图所示: - -``` -. -├── model - ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 - ├── YOLOv5_s.om // 模型文件(需自行下载或转换) - └── imgclass.names // label文件 -├── src - ├── main.cpp // 安全帽检测的main函数入口 - ├── main-image.cpp // 读取图片测试精度 - ├── utils.h // 实现多线程的工具类 - ├── cropResizePaste.hpp // 自定义的等比例缩放 - ├── MOTConnection.h // 跟踪匹配方法的头文件 - ├── MOTConnection.cpp // 跟踪匹配方法的实现 - ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 - ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 - ├── Hungar ian.h // 匈牙利算法的头文件 - ├── Hungarian.cpp // 匈牙利算法的实现 - ├── DataType.h // 自定义的数据结构 - └── CMakeLists.txt // CMake文件 -├── result - ├── one // 文件夹,保存第一路输入的结果(需要新建) - └── two // 文件夹,保存第二路输入的结果(需要新建) -└── CMakeLists.txt // CMake文件 -``` - - - -### 1.5 技术实现流程图 - -![](./images/flow.jpg) - - - -### 1.6 特性及适用场景 TODO - -(项目特性和使用场景约束等) - - - -## 2 环境依赖 - -### 2.1 软件依赖 - -环境依赖软件和版本如下表: - -| 软件 | 版本 | 说明 | 获取方式 | -| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | -| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | -| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | -| Ubuntu | 18.04 | | | - -### 2.2 环境变量 - -在编译运行项目前,需要设置环境变量: - -MindX SDK环境变量: - -. ${SDK-path}/set_env.sh - -CANN环境变量: - -. ${ascend-toolkit-path}/set_env.sh - -环境变量介绍 - -SDK-path:mxVision SDK安装路径 - -ascend-toolkit-path:CANN安装路径 - - - -## 3 模型转换 - -模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html - -本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 - -具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 - -运行命令如下: - -``` -sh atc-env.sh -``` - -脚本中包含atc命令: - -``` ---model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" -``` - -其参数如下表所示: - -| 参数名 | 参数描述 | -| ---------------- | ------------------------------------------------------------ | -| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | -| --model | 原始模型文件路径与文件名 | -| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | -| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | -| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | -| --input_shape | 模型输入数据的 shape。 | -| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | - -其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 - -注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 - -## 4 编译与运行 - -项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 - -### 4.1 测试性能 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 - -2. 进入src文件夹,修改其中的utils.h文件 - - * 配置文件路径,根据服务器路径修改: - - **以下路径需要设置为绝对路径,设置为相对路径编译能够通过但是执行时会报错。** - - 第68行**labelPath**、第69行**configPath**、第70行**modelPath**。 - - * 输入视频宽高,根据264文件的属性修改(默认是宽1920高1080) - - 第55的`SRC_WIDTH`变量表示输入视频的宽 - - 第56行的`SRC_HEIGHT`变量表示输入视频的高 - -3. 进入src文件夹,修改其中的CMakeLists.txt - - * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - - * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - - -**步骤2** 选择对应的main文件 - -测试性能使用main.cpp -需要删除main-image.cpp -可以使用如下命令进行: - -```shell -cd src -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹 - -如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): - -``` -mkdir result -cd result -mkdir one -mkdir two -``` - -退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 - -``` -./main ${video0Path} ${video1Path} -``` - -video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 - -如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 - -![](./images/warn.png) - -图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 - -图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 - -图片示例: - -![](./images/result0.jpg) - -在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: - -![](./images/rate.jpg) - -### 4.2 测试精度 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 -2. 进入src文件夹,修改其中的CMakeLists.txt - 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试精度使用main-image.cpp -需要删除main.cpp,然后将main-image.cpp重命名为main.cpp -可以使用如下命令进行: - -```sh -cd src -rm ./main.cpp -cp ./main-image.cpp ./main.cpp -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) - -从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: - -``` -./main ${imagePath} -``` - -imagePath是图片路径(例如 ./main 000023.jpg) - -正确执行会输出检测到的目标框的信息,如下所示: - -![](./images/objInfo.png) - From ccef70a50c21b2b18b4a44ff1c22768d128982a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 09:24:34 +0000 Subject: [PATCH 18/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification/readme.md | 297 +++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 HelmetIdentification/readme.md diff --git a/HelmetIdentification/readme.md b/HelmetIdentification/readme.md new file mode 100644 index 0000000..8530109 --- /dev/null +++ b/HelmetIdentification/readme.md @@ -0,0 +1,297 @@ +# 标题(项目标题) + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + + + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── YOLOv5_s.om // 模型文件(需自行下载或转换) + └── imgclass.names // label文件 +├── src + ├── main.cpp // 安全帽检测的main函数入口 + ├── main-image.cpp // 读取图片测试精度 + ├── utils.h // 实现多线程的工具类 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── Hungar ian.h // 匈牙利算法的头文件 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── DataType.h // 自定义的数据结构 + └── CMakeLists.txt // CMake文件 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + └── two // 文件夹,保存第二路输入的结果(需要新建) +└── CMakeLists.txt // CMake文件 +``` + + + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + + + +### 1.6 特性及适用场景 + +本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +2. 进入src文件夹,修改其中的utils.h文件 + + * 配置文件路径,根据服务器路径修改: + + **以下路径需要设置为绝对路径,设置为相对路径编译能够通过但是执行时会报错。** + + 第68行**labelPath**、第69行**configPath**、第70行**modelPath**。 + + * 输入视频宽高,根据264文件的属性修改(默认是宽1920高1080) + + 第55的`SRC_WIDTH`变量表示输入视频的宽 + + 第56行的`SRC_HEIGHT`变量表示输入视频的高 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video1Path} +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +图片示例: + +![](./images/result0.jpg) + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imagePath} +``` + +imagePath是图片路径(例如 ./main 000023.jpg) + +正确执行会输出检测到的目标框的信息,如下所示: + +![](./images/objInfo.png) + From 691ece11d62976ca08baaca30a2306f3ad21638c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 09:25:14 +0000 Subject: [PATCH 19/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification/readme.md | 297 --------------------------------- 1 file changed, 297 deletions(-) delete mode 100644 HelmetIdentification/readme.md diff --git a/HelmetIdentification/readme.md b/HelmetIdentification/readme.md deleted file mode 100644 index 8530109..0000000 --- a/HelmetIdentification/readme.md +++ /dev/null @@ -1,297 +0,0 @@ -# 标题(项目标题) - -## 1 介绍 -安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 - -本项目支持2路视频实时分析,其主要流程为: - -​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 - -### 1.1 支持的产品 - -本项目以昇腾Atlas 200DK为主要的硬件平台。 - -### 1.2 支持的版本 - -本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 - -MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 - -### 1.3 软件方案介绍 - -请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 - -表1.1 系统方案各子系统功能描述: - -| 序号 | 子系统 | 功能描述 | -| ---- | ---------- | ------------------------------------------------------------ | -| 1 | 视频解码 | 从264视频流中解码图像帧 | -| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | -| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | -| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | -| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | -| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | - - - -### 1.4 代码目录结构与说明 - -本工程名称为HelmetIdentification_V2,工程目录如下图所示: - -``` -. -├── model - ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 - ├── YOLOv5_s.om // 模型文件(需自行下载或转换) - └── imgclass.names // label文件 -├── src - ├── main.cpp // 安全帽检测的main函数入口 - ├── main-image.cpp // 读取图片测试精度 - ├── utils.h // 实现多线程的工具类 - ├── cropResizePaste.hpp // 自定义的等比例缩放 - ├── MOTConnection.h // 跟踪匹配方法的头文件 - ├── MOTConnection.cpp // 跟踪匹配方法的实现 - ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 - ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 - ├── Hungar ian.h // 匈牙利算法的头文件 - ├── Hungarian.cpp // 匈牙利算法的实现 - ├── DataType.h // 自定义的数据结构 - └── CMakeLists.txt // CMake文件 -├── result - ├── one // 文件夹,保存第一路输入的结果(需要新建) - └── two // 文件夹,保存第二路输入的结果(需要新建) -└── CMakeLists.txt // CMake文件 -``` - - - -### 1.5 技术实现流程图 - -![](./images/flow.jpg) - - - -### 1.6 特性及适用场景 - -本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 - - - -## 2 环境依赖 - -### 2.1 软件依赖 - -环境依赖软件和版本如下表: - -| 软件 | 版本 | 说明 | 获取方式 | -| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | -| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | -| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | -| Ubuntu | 18.04 | | | - -### 2.2 环境变量 - -在编译运行项目前,需要设置环境变量: - -MindX SDK环境变量: - -. ${SDK-path}/set_env.sh - -CANN环境变量: - -. ${ascend-toolkit-path}/set_env.sh - -环境变量介绍 - -SDK-path:mxVision SDK安装路径 - -ascend-toolkit-path:CANN安装路径 - - - -## 3 模型转换 - -模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html - -本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 - -具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 - -运行命令如下: - -``` -sh atc-env.sh -``` - -脚本中包含atc命令: - -``` ---model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" -``` - -其参数如下表所示: - -| 参数名 | 参数描述 | -| ---------------- | ------------------------------------------------------------ | -| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | -| --model | 原始模型文件路径与文件名 | -| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | -| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | -| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | -| --input_shape | 模型输入数据的 shape。 | -| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | - -其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 - -注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 - -## 4 编译与运行 - -项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 - -### 4.1 测试性能 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 - -2. 进入src文件夹,修改其中的utils.h文件 - - * 配置文件路径,根据服务器路径修改: - - **以下路径需要设置为绝对路径,设置为相对路径编译能够通过但是执行时会报错。** - - 第68行**labelPath**、第69行**configPath**、第70行**modelPath**。 - - * 输入视频宽高,根据264文件的属性修改(默认是宽1920高1080) - - 第55的`SRC_WIDTH`变量表示输入视频的宽 - - 第56行的`SRC_HEIGHT`变量表示输入视频的高 - -3. 进入src文件夹,修改其中的CMakeLists.txt - - * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - - * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - - -**步骤2** 选择对应的main文件 - -测试性能使用main.cpp -需要删除main-image.cpp -可以使用如下命令进行: - -```shell -cd src -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹 - -如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): - -``` -mkdir result -cd result -mkdir one -mkdir two -``` - -退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 - -``` -./main ${video0Path} ${video1Path} -``` - -video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 - -如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 - -![](./images/warn.png) - -图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 - -图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 - -图片示例: - -![](./images/result0.jpg) - -在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: - -![](./images/rate.jpg) - -### 4.2 测试精度 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 -2. 进入src文件夹,修改其中的CMakeLists.txt - 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试精度使用main-image.cpp -需要删除main.cpp,然后将main-image.cpp重命名为main.cpp -可以使用如下命令进行: - -```sh -cd src -rm ./main.cpp -cp ./main-image.cpp ./main.cpp -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) - -从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: - -``` -./main ${imagePath} -``` - -imagePath是图片路径(例如 ./main 000023.jpg) - -正确执行会输出检测到的目标框的信息,如下所示: - -![](./images/objInfo.png) - From ddda832fcb4cd09d14ccefde22a859cd4fc853f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 09:26:01 +0000 Subject: [PATCH 20/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification/readme.md | 291 +++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 HelmetIdentification/readme.md diff --git a/HelmetIdentification/readme.md b/HelmetIdentification/readme.md new file mode 100644 index 0000000..efa6b39 --- /dev/null +++ b/HelmetIdentification/readme.md @@ -0,0 +1,291 @@ +# 安全帽识别 + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── YOLOv5_s.om // 模型文件(需自行下载或转换) + └── imgclass.names // label文件 +├── src + ├── main.cpp // 安全帽检测的main函数入口 + ├── main-image.cpp // 读取图片测试精度 + ├── utils.h // 实现多线程的工具类 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── Hungar ian.h // 匈牙利算法的头文件 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── DataType.h // 自定义的数据结构 + └── CMakeLists.txt // CMake文件 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + └── two // 文件夹,保存第二路输入的结果(需要新建) +└── CMakeLists.txt // CMake文件 +``` + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + +### 1.6 特性及适用场景 + +本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +2. 进入src文件夹,修改其中的utils.h文件 + + * 配置文件路径,根据服务器路径修改: + + **以下路径需要设置为绝对路径,设置为相对路径编译能够通过但是执行时会报错。** + + 第68行**labelPath**、第69行**configPath**、第70行**modelPath**。 + + * 输入视频宽高,根据264文件的属性修改(默认是宽1920高1080) + + 第55的`SRC_WIDTH`变量表示输入视频的宽 + + 第56行的`SRC_HEIGHT`变量表示输入视频的高 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video1Path} +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +图片示例: + +![](./images/result0.jpg) + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imagePath} +``` + +imagePath是图片路径(例如 ./main 000023.jpg) + +正确执行会输出检测到的目标框的信息,如下所示: + +![](./images/objInfo.png) + From 28c2e435cffc5b243d3dcbef5a0370600aaef843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 29 Nov 2022 11:04:33 +0000 Subject: [PATCH 21/58] =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D=20HelmetIdent?= =?UTF-8?q?ification=20=E4=B8=BA=20HelmetIdentification=5FV2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CMakeLists.txt | 0 .../images/flow.jpg | Bin .../images/make.png | Bin .../images/objInfo.png | Bin .../images/rate.jpg | Bin .../images/result0.jpg | Bin .../images/warn.png | Bin .../model/Helmet_yolov5.cfg | 0 .../model/imgclass.names | 0 .../readme.md | 0 .../src/CMakeLists.txt | 0 .../src/DataType.h | 0 .../src/Hungarian.cpp | 0 .../src/Hungarian.h | 0 .../src/KalmanTracker.cpp | 0 .../src/KalmanTracker.h | 0 .../src/MOTConnection.cpp | 0 .../src/MOTConnection.h | 0 .../src/cropResizePaste.hpp | 0 .../src/main-image.cpp | 0 .../src/main.cpp | 0 .../src/utils.h | 0 22 files changed, 0 insertions(+), 0 deletions(-) rename {HelmetIdentification => HelmetIdentification_V2}/CMakeLists.txt (100%) rename {HelmetIdentification => HelmetIdentification_V2}/images/flow.jpg (100%) rename {HelmetIdentification => HelmetIdentification_V2}/images/make.png (100%) rename {HelmetIdentification => HelmetIdentification_V2}/images/objInfo.png (100%) rename {HelmetIdentification => HelmetIdentification_V2}/images/rate.jpg (100%) rename {HelmetIdentification => HelmetIdentification_V2}/images/result0.jpg (100%) rename {HelmetIdentification => HelmetIdentification_V2}/images/warn.png (100%) rename {HelmetIdentification => HelmetIdentification_V2}/model/Helmet_yolov5.cfg (100%) rename {HelmetIdentification => HelmetIdentification_V2}/model/imgclass.names (100%) rename {HelmetIdentification => HelmetIdentification_V2}/readme.md (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/CMakeLists.txt (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/DataType.h (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/Hungarian.cpp (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/Hungarian.h (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/KalmanTracker.cpp (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/KalmanTracker.h (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/MOTConnection.cpp (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/MOTConnection.h (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/cropResizePaste.hpp (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/main-image.cpp (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/main.cpp (100%) rename {HelmetIdentification => HelmetIdentification_V2}/src/utils.h (100%) diff --git a/HelmetIdentification/CMakeLists.txt b/HelmetIdentification_V2/CMakeLists.txt similarity index 100% rename from HelmetIdentification/CMakeLists.txt rename to HelmetIdentification_V2/CMakeLists.txt diff --git a/HelmetIdentification/images/flow.jpg b/HelmetIdentification_V2/images/flow.jpg similarity index 100% rename from HelmetIdentification/images/flow.jpg rename to HelmetIdentification_V2/images/flow.jpg diff --git a/HelmetIdentification/images/make.png b/HelmetIdentification_V2/images/make.png similarity index 100% rename from HelmetIdentification/images/make.png rename to HelmetIdentification_V2/images/make.png diff --git a/HelmetIdentification/images/objInfo.png b/HelmetIdentification_V2/images/objInfo.png similarity index 100% rename from HelmetIdentification/images/objInfo.png rename to HelmetIdentification_V2/images/objInfo.png diff --git a/HelmetIdentification/images/rate.jpg b/HelmetIdentification_V2/images/rate.jpg similarity index 100% rename from HelmetIdentification/images/rate.jpg rename to HelmetIdentification_V2/images/rate.jpg diff --git a/HelmetIdentification/images/result0.jpg b/HelmetIdentification_V2/images/result0.jpg similarity index 100% rename from HelmetIdentification/images/result0.jpg rename to HelmetIdentification_V2/images/result0.jpg diff --git a/HelmetIdentification/images/warn.png b/HelmetIdentification_V2/images/warn.png similarity index 100% rename from HelmetIdentification/images/warn.png rename to HelmetIdentification_V2/images/warn.png diff --git a/HelmetIdentification/model/Helmet_yolov5.cfg b/HelmetIdentification_V2/model/Helmet_yolov5.cfg similarity index 100% rename from HelmetIdentification/model/Helmet_yolov5.cfg rename to HelmetIdentification_V2/model/Helmet_yolov5.cfg diff --git a/HelmetIdentification/model/imgclass.names b/HelmetIdentification_V2/model/imgclass.names similarity index 100% rename from HelmetIdentification/model/imgclass.names rename to HelmetIdentification_V2/model/imgclass.names diff --git a/HelmetIdentification/readme.md b/HelmetIdentification_V2/readme.md similarity index 100% rename from HelmetIdentification/readme.md rename to HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification/src/CMakeLists.txt b/HelmetIdentification_V2/src/CMakeLists.txt similarity index 100% rename from HelmetIdentification/src/CMakeLists.txt rename to HelmetIdentification_V2/src/CMakeLists.txt diff --git a/HelmetIdentification/src/DataType.h b/HelmetIdentification_V2/src/DataType.h similarity index 100% rename from HelmetIdentification/src/DataType.h rename to HelmetIdentification_V2/src/DataType.h diff --git a/HelmetIdentification/src/Hungarian.cpp b/HelmetIdentification_V2/src/Hungarian.cpp similarity index 100% rename from HelmetIdentification/src/Hungarian.cpp rename to HelmetIdentification_V2/src/Hungarian.cpp diff --git a/HelmetIdentification/src/Hungarian.h b/HelmetIdentification_V2/src/Hungarian.h similarity index 100% rename from HelmetIdentification/src/Hungarian.h rename to HelmetIdentification_V2/src/Hungarian.h diff --git a/HelmetIdentification/src/KalmanTracker.cpp b/HelmetIdentification_V2/src/KalmanTracker.cpp similarity index 100% rename from HelmetIdentification/src/KalmanTracker.cpp rename to HelmetIdentification_V2/src/KalmanTracker.cpp diff --git a/HelmetIdentification/src/KalmanTracker.h b/HelmetIdentification_V2/src/KalmanTracker.h similarity index 100% rename from HelmetIdentification/src/KalmanTracker.h rename to HelmetIdentification_V2/src/KalmanTracker.h diff --git a/HelmetIdentification/src/MOTConnection.cpp b/HelmetIdentification_V2/src/MOTConnection.cpp similarity index 100% rename from HelmetIdentification/src/MOTConnection.cpp rename to HelmetIdentification_V2/src/MOTConnection.cpp diff --git a/HelmetIdentification/src/MOTConnection.h b/HelmetIdentification_V2/src/MOTConnection.h similarity index 100% rename from HelmetIdentification/src/MOTConnection.h rename to HelmetIdentification_V2/src/MOTConnection.h diff --git a/HelmetIdentification/src/cropResizePaste.hpp b/HelmetIdentification_V2/src/cropResizePaste.hpp similarity index 100% rename from HelmetIdentification/src/cropResizePaste.hpp rename to HelmetIdentification_V2/src/cropResizePaste.hpp diff --git a/HelmetIdentification/src/main-image.cpp b/HelmetIdentification_V2/src/main-image.cpp similarity index 100% rename from HelmetIdentification/src/main-image.cpp rename to HelmetIdentification_V2/src/main-image.cpp diff --git a/HelmetIdentification/src/main.cpp b/HelmetIdentification_V2/src/main.cpp similarity index 100% rename from HelmetIdentification/src/main.cpp rename to HelmetIdentification_V2/src/main.cpp diff --git a/HelmetIdentification/src/utils.h b/HelmetIdentification_V2/src/utils.h similarity index 100% rename from HelmetIdentification/src/utils.h rename to HelmetIdentification_V2/src/utils.h From 52c12f92784cc42777435c3fc2c5d9128e2ce86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Wed, 30 Nov 2022 12:43:15 +0000 Subject: [PATCH 22/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/readme.md | 291 ------------------------------ 1 file changed, 291 deletions(-) delete mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md deleted file mode 100644 index efa6b39..0000000 --- a/HelmetIdentification_V2/readme.md +++ /dev/null @@ -1,291 +0,0 @@ -# 安全帽识别 - -## 1 介绍 -安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 - -本项目支持2路视频实时分析,其主要流程为: - -​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 - -### 1.1 支持的产品 - -本项目以昇腾Atlas 200DK为主要的硬件平台。 - -### 1.2 支持的版本 - -本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 - -MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 - -### 1.3 软件方案介绍 - -请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 - -表1.1 系统方案各子系统功能描述: - -| 序号 | 子系统 | 功能描述 | -| ---- | ---------- | ------------------------------------------------------------ | -| 1 | 视频解码 | 从264视频流中解码图像帧 | -| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | -| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | -| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | -| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | -| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | - -### 1.4 代码目录结构与说明 - -本工程名称为HelmetIdentification_V2,工程目录如下图所示: - -``` -. -├── model - ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 - ├── YOLOv5_s.om // 模型文件(需自行下载或转换) - └── imgclass.names // label文件 -├── src - ├── main.cpp // 安全帽检测的main函数入口 - ├── main-image.cpp // 读取图片测试精度 - ├── utils.h // 实现多线程的工具类 - ├── cropResizePaste.hpp // 自定义的等比例缩放 - ├── MOTConnection.h // 跟踪匹配方法的头文件 - ├── MOTConnection.cpp // 跟踪匹配方法的实现 - ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 - ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 - ├── Hungar ian.h // 匈牙利算法的头文件 - ├── Hungarian.cpp // 匈牙利算法的实现 - ├── DataType.h // 自定义的数据结构 - └── CMakeLists.txt // CMake文件 -├── result - ├── one // 文件夹,保存第一路输入的结果(需要新建) - └── two // 文件夹,保存第二路输入的结果(需要新建) -└── CMakeLists.txt // CMake文件 -``` - -### 1.5 技术实现流程图 - -![](./images/flow.jpg) - -### 1.6 特性及适用场景 - -本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 - - - -## 2 环境依赖 - -### 2.1 软件依赖 - -环境依赖软件和版本如下表: - -| 软件 | 版本 | 说明 | 获取方式 | -| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | -| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | -| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | -| Ubuntu | 18.04 | | | - -### 2.2 环境变量 - -在编译运行项目前,需要设置环境变量: - -MindX SDK环境变量: - -. ${SDK-path}/set_env.sh - -CANN环境变量: - -. ${ascend-toolkit-path}/set_env.sh - -环境变量介绍 - -SDK-path:mxVision SDK安装路径 - -ascend-toolkit-path:CANN安装路径 - - - -## 3 模型转换 - -模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html - -本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 - -具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 - -运行命令如下: - -``` -sh atc-env.sh -``` - -脚本中包含atc命令: - -``` ---model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" -``` - -其参数如下表所示: - -| 参数名 | 参数描述 | -| ---------------- | ------------------------------------------------------------ | -| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | -| --model | 原始模型文件路径与文件名 | -| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | -| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | -| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | -| --input_shape | 模型输入数据的 shape。 | -| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | - -其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 - -注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 - -## 4 编译与运行 - -项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 - -### 4.1 测试性能 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 - -2. 进入src文件夹,修改其中的utils.h文件 - - * 配置文件路径,根据服务器路径修改: - - **以下路径需要设置为绝对路径,设置为相对路径编译能够通过但是执行时会报错。** - - 第68行**labelPath**、第69行**configPath**、第70行**modelPath**。 - - * 输入视频宽高,根据264文件的属性修改(默认是宽1920高1080) - - 第55的`SRC_WIDTH`变量表示输入视频的宽 - - 第56行的`SRC_HEIGHT`变量表示输入视频的高 - -3. 进入src文件夹,修改其中的CMakeLists.txt - - * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - - * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - - -**步骤2** 选择对应的main文件 - -测试性能使用main.cpp -需要删除main-image.cpp -可以使用如下命令进行: - -```shell -cd src -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹 - -如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): - -``` -mkdir result -cd result -mkdir one -mkdir two -``` - -退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 - -``` -./main ${video0Path} ${video1Path} -``` - -video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 - -如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 - -![](./images/warn.png) - -图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 - -图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 - -图片示例: - -![](./images/result0.jpg) - -在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: - -![](./images/rate.jpg) - -### 4.2 测试精度 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 -2. 进入src文件夹,修改其中的CMakeLists.txt - 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试精度使用main-image.cpp -需要删除main.cpp,然后将main-image.cpp重命名为main.cpp -可以使用如下命令进行: - -```sh -cd src -rm ./main.cpp -cp ./main-image.cpp ./main.cpp -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) - -从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: - -``` -./main ${imagePath} -``` - -imagePath是图片路径(例如 ./main 000023.jpg) - -正确执行会输出检测到的目标框的信息,如下所示: - -![](./images/objInfo.png) - From fcf5df528993c7ca5ceb8467cd852d12729266c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Wed, 30 Nov 2022 12:43:26 +0000 Subject: [PATCH 23/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/readme.md | 285 ++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md new file mode 100644 index 0000000..7136a72 --- /dev/null +++ b/HelmetIdentification_V2/readme.md @@ -0,0 +1,285 @@ +# 安全帽识别 + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── YOLOv5_s.om // 模型文件(需自行下载或转换) + └── imgclass.names // label文件 +├── src + ├── main.cpp // 安全帽检测的main函数入口 + ├── main-image.cpp // 读取图片测试精度 + ├── utils.h // 实现多线程的工具类 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── Hungar ian.h // 匈牙利算法的头文件 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── DataType.h // 自定义的数据结构 + └── CMakeLists.txt // CMake文件 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + └── two // 文件夹,保存第二路输入的结果(需要新建) +└── CMakeLists.txt // CMake文件 +``` + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + +### 1.6 特性及适用场景 + +本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +2. 进入src文件夹,修改其中的utils.h文件 + + * 配置文件路径,根据服务器路径修改: + + **以下路径需要设置为绝对路径,设置为相对路径编译能够通过但是执行时会报错。** + + 第68行**labelPath**、第69行**configPath**、第70行**modelPath**。 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video1Path} +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +图片示例: + +![](./images/result0.jpg) + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imagePath} +``` + +imagePath是图片路径(例如 ./main 000023.jpg) + +正确执行会输出检测到的目标框的信息,如下所示: + +![](./images/objInfo.png) + From e10b97fdcb3dc48b5b9a4ae13244752d3ff0500f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 05:50:17 +0000 Subject: [PATCH 24/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/src?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/src/CMakeLists.txt | 47 --- HelmetIdentification_V2/src/DataType.h | 94 ----- HelmetIdentification_V2/src/Hungarian.cpp | 165 -------- HelmetIdentification_V2/src/Hungarian.h | 46 --- HelmetIdentification_V2/src/KalmanTracker.cpp | 143 ------- HelmetIdentification_V2/src/KalmanTracker.h | 39 -- HelmetIdentification_V2/src/MOTConnection.cpp | 262 ------------- HelmetIdentification_V2/src/MOTConnection.h | 78 ---- .../src/cropResizePaste.hpp | 115 ------ HelmetIdentification_V2/src/main-image.cpp | 170 --------- HelmetIdentification_V2/src/main.cpp | 283 -------------- HelmetIdentification_V2/src/utils.h | 351 ------------------ 12 files changed, 1793 deletions(-) delete mode 100644 HelmetIdentification_V2/src/CMakeLists.txt delete mode 100644 HelmetIdentification_V2/src/DataType.h delete mode 100644 HelmetIdentification_V2/src/Hungarian.cpp delete mode 100644 HelmetIdentification_V2/src/Hungarian.h delete mode 100644 HelmetIdentification_V2/src/KalmanTracker.cpp delete mode 100644 HelmetIdentification_V2/src/KalmanTracker.h delete mode 100644 HelmetIdentification_V2/src/MOTConnection.cpp delete mode 100644 HelmetIdentification_V2/src/MOTConnection.h delete mode 100644 HelmetIdentification_V2/src/cropResizePaste.hpp delete mode 100644 HelmetIdentification_V2/src/main-image.cpp delete mode 100644 HelmetIdentification_V2/src/main.cpp delete mode 100644 HelmetIdentification_V2/src/utils.h diff --git a/HelmetIdentification_V2/src/CMakeLists.txt b/HelmetIdentification_V2/src/CMakeLists.txt deleted file mode 100644 index 6fcd298..0000000 --- a/HelmetIdentification_V2/src/CMakeLists.txt +++ /dev/null @@ -1,47 +0,0 @@ -# CMake lowest version requirement -cmake_minimum_required(VERSION 3.5.1) -# project information -project(Individual) - -# Compile options -add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0 -Dgoogle=mindxsdk_private) -add_compile_options(-std=c++11 -fPIC -fstack-protector-all -Wall -D_FORTIFY_SOURCE=2 -O2) - -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../../") -set(CMAKE_CXX_FLAGS_DEBUG "-g") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now,-z,noexecstack -s -pie -pthread") -set(CMAKE_SKIP_RPATH TRUE) - -SET(CMAKE_BUILD_TYPE "Debug") -SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb") -SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall") - -# Header path -include_directories( - ${MX_SDK_HOME}/include/ - ${MX_SDK_HOME}/opensource/include/ - ${MX_SDK_HOME}/opensource/include/opencv4/ - /home/HwHiAiUser/Ascend/ascend-toolkit/latest/include/ - ./ -) - -# add host lib path -link_directories( - ${MX_SDK_HOME}/lib/ - ${MX_SDK_HOME}/lib/modelpostprocessors - ${MX_SDK_HOME}/opensource/lib/ - ${MX_SDK_HOME}/opensource/lib64/ - /usr/lib/aarch64-linux-gnu/ - /home/HwHiAiUser/Ascend/ascend-toolkit/latest/lib64/ - /usr/local/Ascend/driver/lib64/ - ./ -) - - -aux_source_directory(. sourceList) - -add_executable(main ${sourceList}) - -target_link_libraries(main mxbase opencv_world boost_filesystem glog avformat avcodec avutil cpprest yolov3postprocess ascendcl acl_dvpp_mpi) - -install(TARGETS main DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/HelmetIdentification_V2/src/DataType.h b/HelmetIdentification_V2/src/DataType.h deleted file mode 100644 index dc34a0a..0000000 --- a/HelmetIdentification_V2/src/DataType.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_DATATYPE_H -#define MXBASE_HELMETIDENTIFICATION_DATATYPE_H - -#include -#include -#include -#include -#include -#include -#include "opencv2/highgui.hpp" -#include "opencv2/imgcodecs.hpp" -#include "opencv2/imgproc.hpp" - -namespace ascendVehicleTracking { -#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) - - const int MODULE_QUEUE_SIZE = 1000; - - enum FrameMode { - FRAME_MODE_SEARCH = 0, - FRAME_MODE_REG - }; - - struct DataBuffer { - std::shared_ptr deviceData; - std::shared_ptr hostData; - uint32_t dataSize; // buffer size - DataBuffer() : deviceData(nullptr), hostData(nullptr), dataSize(0) {} - }; - - struct DetectInfo { - int32_t classId; - float confidence; - float minx; // x value of left-top point - float miny; // y value of left-top point - float height; - float width; - }; - - enum TraceFlag { - NEW_VEHICLE = 0, - TRACkED_VEHICLE, - LOST_VEHICLE - }; - - struct TraceInfo { - int32_t id; - TraceFlag flag; - int32_t survivalTime; // How long is it been since the first time, unit: detection period - int32_t detectedTime; // How long is the vehicle detected, unit: detection period - std::chrono::time_point createTime; - }; - - struct TrackLet { - TraceInfo info; - // reserved: kalman status parameter - int32_t lostTime; // undetected time for tracked vehicle - std::vector shortFeature; // nearest 10 frame - }; - - struct VehicleQuality { - float score; - }; - - struct Coordinate2D { - uint32_t x; - uint32_t y; - }; -} -// namespace ascendVehicleTracking - -struct AttrT { - AttrT(std::string name, std::string value) : name(std::move(name)), value(std::move(value)) {} - std::string name = {}; - std::string value = {}; -}; - -#endif diff --git a/HelmetIdentification_V2/src/Hungarian.cpp b/HelmetIdentification_V2/src/Hungarian.cpp deleted file mode 100644 index 3f6fcd2..0000000 --- a/HelmetIdentification_V2/src/Hungarian.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Hungarian.h" -#include -#include -#include "MxBase/Log/Log.h" - -namespace { - const int INF = 0x3f3f3f3f; - const int VISITED = 1; - const int HUNGARIAN_CONTENT = 7; - const int X_MATCH_OFFSET = 0; - const int Y_MATCH_OFFSET = 1; - const int X_VALUE_OFFSET = 2; - const int Y_VALUE_OFFSET = 3; - const int SLACK_OFFSET = 4; - const int X_VISIT_OFFSET = 5; - const int Y_VISIT_OFFSET = 6; -} - -APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols) -{ - handle.max = (row > cols) ? row : cols; - auto adjMat = std::shared_ptr(); - adjMat.reset(new int[handle.max * handle.max], std::default_delete()); - if (adjMat == nullptr) { - LogFatal << "HungarianHandleInit new failed"; - return APP_ERR_ACL_FAILURE; - } - - handle.adjMat = adjMat; - - void* ptr[HUNGARIAN_CONTENT] = {nullptr}; - for (int i = 0; i < HUNGARIAN_CONTENT; ++i) { - ptr[i] = malloc(handle.max * sizeof(int)); - if (ptr[i] == nullptr) { - LogFatal << "HungarianHandleInit Malloc failed"; - return APP_ERR_ACL_FAILURE; - } - } - - handle.xMatch.reset((int *)ptr[X_MATCH_OFFSET], free); - handle.yMatch.reset((int *)ptr[Y_MATCH_OFFSET], free); - handle.xValue.reset((int *)ptr[X_VALUE_OFFSET], free); - handle.yValue.reset((int *)ptr[Y_VALUE_OFFSET], free); - handle.slack.reset((int *)ptr[SLACK_OFFSET], free); - handle.xVisit.reset((int *)ptr[X_VISIT_OFFSET], free); - handle.yVisit.reset((int *)ptr[Y_VISIT_OFFSET], free); - return APP_ERR_OK; -} - -static void HungarianInit(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) -{ - int i, j, value; - if (rows > cols) { - handle.transpose = true; - handle.cols = rows; - handle.rows = cols; - handle.resX = handle.yMatch.get(); - handle.resY = handle.xMatch.get(); - } else { - handle.transpose = false; - handle.rows = rows; - handle.cols = cols; - handle.resX = handle.xMatch.get(); - handle.resY = handle.yMatch.get(); - } - - for (i = 0; i < handle.rows; ++i) { - handle.xValue.get()[i] = 0; - handle.xMatch.get()[i] = -1; - for (j = 0; j < handle.cols; ++j) { - if (handle.transpose) { - value = cost[j][i]; - } else { - value = cost[i][j]; - } - handle.adjMat.get()[i * handle.cols + j] = value; - if (handle.xValue.get()[i] < value) { - handle.xValue.get()[i] = value; - } - } - } - - for (i = 0; i < handle.cols; ++i) { - handle.yValue.get()[i] = 0; - handle.yMatch.get()[i] = -1; - } -} - -static bool Match(HungarianHandle &handle, int id) -{ - int j, delta; - handle.xVisit.get()[id] = VISITED; - for (j = 0; j < handle.cols; ++j) { - if (handle.yVisit.get()[j] == VISITED) { - continue; - } - delta = handle.xValue.get()[id] + handle.yValue.get()[j] - handle.adjMat.get()[id * handle.cols + j]; - if (delta == 0) { - handle.yVisit.get()[j] = VISITED; - if (handle.yMatch.get()[j] == -1 || Match(handle, handle.yMatch.get()[j])) { - handle.yMatch.get()[j] = id; - handle.xMatch.get()[id] = j; - return true; - } - } else if (delta < handle.slack.get()[j]) { - handle.slack.get()[j] = delta; - } - } - return false; -} - -int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) -{ - HungarianInit(handle, cost, rows, cols); - int i, j, delta; - for (i = 0; i < handle.rows; ++i) { - while (true) { - std::fill(handle.xVisit.get(), handle.xVisit.get() + handle.rows, 0); - std::fill(handle.yVisit.get(), handle.yVisit.get() + handle.cols, 0); - for (j = 0; j < handle.cols; ++j) { - handle.slack.get()[j] = INF; - } - if (Match(handle, i)) { - break; - } - delta = INF; - for (j = 0; j < handle.cols; ++j) { - if (handle.yVisit.get()[j] != VISITED && delta > handle.slack.get()[j]) { - delta = handle.slack.get()[j]; - } - } - if (delta == INF) { - LogDebug << "Hungarian solve is invalid!"; - return -1; - } - for (j = 0; j < handle.rows; ++j) { - if (handle.xVisit.get()[j] == VISITED) { - handle.xValue.get()[j] -= delta; - } - } - for (j = 0; j < handle.cols; ++j) { - if (handle.yVisit.get()[j] == VISITED) { - handle.yValue.get()[j] += delta; - } - } - } - } - return handle.rows; -} diff --git a/HelmetIdentification_V2/src/Hungarian.h b/HelmetIdentification_V2/src/Hungarian.h deleted file mode 100644 index 2ae6a33..0000000 --- a/HelmetIdentification_V2/src/Hungarian.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H -#define MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H - -#include -#include -#include "DataType.h" -#include "MxBase/PostProcessBases/PostProcessDataType.h" -#include "MxBase/ErrorCode/ErrorCodes.h" - -struct HungarianHandle { - int rows; - int cols; - int max; - int *resX; - int *resY; - bool transpose; - std::shared_ptr adjMat; - std::shared_ptr xMatch; - std::shared_ptr yMatch; - std::shared_ptr xValue; - std::shared_ptr yValue; - std::shared_ptr slack; - std::shared_ptr xVisit; - std::shared_ptr yVisit; -}; - -APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols); -int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols); - -#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/KalmanTracker.cpp b/HelmetIdentification_V2/src/KalmanTracker.cpp deleted file mode 100644 index 69362f9..0000000 --- a/HelmetIdentification_V2/src/KalmanTracker.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "KalmanTracker.h" - -namespace ascendVehicleTracking -{ - namespace - { - const int OFFSET = 2; - const int MULTIPLE = 2; - } - - /* - * The SORT algorithm uses a linear constant velocity model,which assumes 7 - * states, including - * x coordinate of bounding box center - * y coordinate of bounding box center - * area of bounding box - * aspect ratio of w to h - * velocity of x - * velocity of y - * variation rate of area - * - * The aspect ratio is considered to be unchanged, so there is no additive item - * for aspect ratio in the transitionMatrix - * - * - * Kalman filter equation step by step - * (1) X(k|k-1)=AX(k-1|k-1)+BU(k) - * X(k|k-1) is the predicted state(statePre),X(k-1|k-1) is the k-1 statePost,A - * is transitionMatrix, B is controlMatrix, U(k) is control state, in SORT U(k) is 0. - * - * (2) P(k|k-1)=AP(k-1|k-1)A'+Q - * P(k|k-1) is the predicted errorCovPre, P(k-1|k-1) is the k-1 errorCovPost, - * Q is processNoiseCov - * - * (3) Kg(k)=P(k|k-1)H'/(HP(k|k-1))H'+R - * Kg(k) is the kalman gain, the ratio of estimate variance in total variance, - * H is the measurementMatrix,R is the measurementNoiseCov - * - * (4) X(k|k)=X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) - * X(k|k) is the k statePost, Z(k) is the measurement of K, in SORT Z(k) is - * the detection result of k - * - * (5) P(k|k)=(1-Kg(k)H)P(k|k-1) - * P(k|k) is the errorCovPost - */ - void KalmanTracker::CvKalmanInit(MxBase::ObjectInfo initRect) - { - const int stateDim = 7; - const int measureDim = 4; - cvkalmanfilter_ = cv::KalmanFilter(stateDim, measureDim, 0); // zero control - measurement_ = cv::Mat::zeros(measureDim, 1, CV_32F); // 4 measurements, Z(k), according to detection results - // A, will not be updated - cvkalmanfilter_.transitionMatrix = (cv::Mat_(stateDim, stateDim) << 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1); - cvkalmanfilter_.measurementMatrix = (cv::Mat_(measureDim, stateDim) << 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0); - cv::setIdentity(cvkalmanfilter_.measurementMatrix); // H, will not be updated - cv::setIdentity(cvkalmanfilter_.processNoiseCov, cv::Scalar::all(1e-2)); // Q, will not be updated - cv::setIdentity(cvkalmanfilter_.measurementNoiseCov, cv::Scalar::all(1e-1)); // R, will bot be updated - cv::setIdentity(cvkalmanfilter_.errorCovPost, cv::Scalar::all(1)); // P(k-1|k-1), will be updated - - // initialize state vector with bounding box in - // [center_x,center_y,area,ratio] - // style, the velocity is 0 - // X(k-1|k-1) - cvkalmanfilter_.statePost.at(0, 0) = initRect.x0 + (initRect.x1 - initRect.x0) / MULTIPLE; - cvkalmanfilter_.statePost.at(1, 0) = initRect.y0 + (initRect.y1 - initRect.y0) / MULTIPLE; - cvkalmanfilter_.statePost.at(OFFSET, 0) = (initRect.x1 - initRect.x0) * (initRect.y1 - initRect.y0); - cvkalmanfilter_.statePost.at(OFFSET + 1, 0) = (initRect.x1 - initRect.x0) / (initRect.y1 - initRect.y0); - } - - // Predict the bounding box. - MxBase::ObjectInfo KalmanTracker::Predict() - { - // predict - // return X(k|k-1)=AX(k-1|k-1), and update - // P(k|k-1) <- AP(k-1|k-1)A'+Q - MxBase::ObjectInfo detectInfo = {}; - cv::Mat predictState = cvkalmanfilter_.predict(); - float *pData = (float *)(predictState.data); - float w = sqrt((*(pData + OFFSET)) * (*(pData + OFFSET + 1))); - if (w < DBL_EPSILON) - { - detectInfo.x0 = 0; - detectInfo.y0 = 0; - detectInfo.x1 = 0; - detectInfo.y1 = 0; - detectInfo.classId = 0; - return detectInfo; - } - if (w == 0) - { - MxBase::ObjectInfo w0DetectInfo = {}; - return w0DetectInfo; - } - float h = (*(pData + OFFSET)) / w; - float x = (*pData) - w / MULTIPLE; - float y = (*(pData + 1)) - h / MULTIPLE; - if (x < 0 && (*pData) > 0) - { - x = 0; - } - if (y < 0 && (*(pData + 1)) > 0) - { - y = 0; - } - detectInfo.x0 = x; - detectInfo.y0 = y; - detectInfo.x1 = x + w; - detectInfo.y1 = y + h; - return detectInfo; - } - - // Update the state using observed bounding box - void KalmanTracker::Update(MxBase::ObjectInfo stateMat) - { - // measurement_, update Z(k) - float *pData = (float *)(measurement_.data); - *pData = stateMat.x0 + (stateMat.x1 - stateMat.x0) / MULTIPLE; - *(pData + 1) = stateMat.y0 + (stateMat.y1 - stateMat.y0) / MULTIPLE; - *(pData + OFFSET) = (stateMat.x1 - stateMat.x0) * (stateMat.y1 - stateMat.y0); - *(pData + OFFSET + 1) = (stateMat.x1 - stateMat.x0) / (stateMat.y1 - stateMat.y0); - // update, do the following steps: - // Kg(k): P(k|k-1)H'/(HP(k|k-1))H'+R - // X(k|k): X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) - // P(k|k): (1-Kg(k)H)P(k|k-1) - cvkalmanfilter_.correct(measurement_); - } -} // namespace diff --git a/HelmetIdentification_V2/src/KalmanTracker.h b/HelmetIdentification_V2/src/KalmanTracker.h deleted file mode 100644 index f5f3465..0000000 --- a/HelmetIdentification_V2/src/KalmanTracker.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H -#define MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H - -#include "opencv2/highgui/highgui.hpp" -#include "opencv2/video/tracking.hpp" -#include "DataType.h" -#include "MxBase/PostProcessBases/PostProcessDataType.h" - -namespace ascendVehicleTracking { -class KalmanTracker { -public: - KalmanTracker() {} - ~KalmanTracker() {} - void CvKalmanInit(MxBase::ObjectInfo initRect); - MxBase::ObjectInfo Predict(); - void Update(MxBase::ObjectInfo stateMat); -private: - cv::KalmanFilter cvkalmanfilter_ = {}; - cv::Mat measurement_ = {}; -}; -} // namesapce ascendVehicleTracking - -#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/MOTConnection.cpp b/HelmetIdentification_V2/src/MOTConnection.cpp deleted file mode 100644 index 14e4341..0000000 --- a/HelmetIdentification_V2/src/MOTConnection.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "MOTConnection.h" -#include -#include -#include -#include -#include "opencv2/highgui.hpp" -#include "opencv2/imgcodecs.hpp" -#include "opencv2/imgproc.hpp" -#include "MxBase/Log/Log.h" -#include "MxBase/ErrorCode/ErrorCodes.h" -#include "Hungarian.h" - -namespace ascendVehicleTracking -{ - namespace - { - // convert double to int - const int FLOAT_TO_INT = 1000; - const int MULTIPLE = 0; - const double SIMILARITY_THRESHOLD = 0.66; - const int MULTIPLE_IOU = 6; - const float NORM_EPS = 1e-10; - const double TIME_COUNTS = 1000.0; - const double COST_TIME_MS_THRESHOLD = 10.; - const float WIDTH_RATE_THRESH = 1.f; - const float HEIGHT_RATE_THRESH = 1.f; - const float X_DIST_RATE_THRESH = 1.3f; - const float Y_DIST_RATE_THRESH = 1.f; - } // namespace - - // 计算bounding box的交并比 - float CalIOU(MxBase::ObjectInfo detect1, MxBase::ObjectInfo detect2) - { - cv::Rect_ bbox1(detect1.x0, detect1.y0, detect1.x1 - detect1.x0, detect1.y1 - detect1.y0); - cv::Rect_ bbox2(detect2.x0, detect2.y0, detect2.x1 - detect2.x0, detect2.y1 - detect2.y0); - float intersectionArea = (bbox1 & bbox2).area(); - float unionArea = bbox1.area() + bbox2.area() - intersectionArea; - if (unionArea < DBL_EPSILON) - { - return 0.f; - } - return (intersectionArea / unionArea); - } - - // 计算前后两帧的两个bounding box的相似度 - float CalSimilarity(const TraceLet &traceLet, const MxBase::ObjectInfo &objectInfo, const int &method, const double &kIOU) - { - return CalIOU(traceLet.detectInfo, objectInfo); - } - - // 过滤掉交并比小于阈值的匹配 - void MOTConnection::FilterLowThreshold(const HungarianHandle &hungarianHandleObj, - const std::vector> &disMatrix, std::vector &matchedTracedDetected, - std::vector &detectVehicleFlagVec) - { - for (unsigned int i = 0; i < traceList_.size(); ++i) - { - if ((hungarianHandleObj.resX[i] != -1) && - (disMatrix[i][hungarianHandleObj.resX[i]] >= (trackThreshold_ * FLOAT_TO_INT))) - { - matchedTracedDetected.push_back(cv::Point(i, hungarianHandleObj.resX[i])); - detectVehicleFlagVec[hungarianHandleObj.resX[i]] = true; - } - else - { - traceList_[i].info.flag = LOST_VEHICLE; - } - } - } - - // 更新没有匹配上的跟踪器 - void MOTConnection::UpdateUnmatchedTraceLet(const std::vector> &objInfos) - { - for (auto itr = traceList_.begin(); itr != traceList_.end();) - { - if ((*itr).info.flag != LOST_VEHICLE) - { - ++itr; - continue; - } - - (*itr).lostAge++; - (*itr).kalman.Update((*itr).detectInfo); - - if ((*itr).lostAge < lostThreshold_) - { - continue; - } - - itr = traceList_.erase(itr); - } - } - - // 更新匹配上的跟踪器 - void MOTConnection::UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, - std::vector> &objInfos) - { - for (unsigned int i = 0; i < matchedTracedDetected.size(); ++i) - { - int traceIndex = matchedTracedDetected[i].x; - int detectIndex = matchedTracedDetected[i].y; - if (traceList_[traceIndex].info.survivalTime > MULTIPLE) - { - traceList_[traceIndex].info.flag = TRACkED_VEHICLE; - } - traceList_[traceIndex].info.survivalTime++; - traceList_[traceIndex].info.detectedTime++; - traceList_[traceIndex].lostAge = 0; - traceList_[traceIndex].detectInfo = objInfos[0][detectIndex]; - traceList_[traceIndex].kalman.Update(objInfos[0][detectIndex]); - } - } - // 将没有匹配上的检测更新为新的检测器 - void MOTConnection::AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue) - { - using Time = std::chrono::high_resolution_clock; - for (auto &vehicleObject : unmatchedVehicleObjectQueue) - { - // add new detected into traceList - TraceLet traceLet; - generatedId_++; - traceLet.info.id = generatedId_; - traceLet.info.survivalTime = 1; - traceLet.info.detectedTime = 1; - traceLet.lostAge = 0; - traceLet.info.flag = NEW_VEHICLE; - traceLet.detectInfo = vehicleObject; - traceLet.info.createTime = Time::now(); - - traceLet.kalman.CvKalmanInit(vehicleObject); - traceList_.push_back(traceLet); - } - } - - void MOTConnection::UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, - std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue) - { - UpdateMatchedTraceLet(matchedTracedDetected, objInfos); // 更新匹配上的跟踪器 - AddNewDetectedVehicle(unmatchedVehicleObjectQueue); // 将没有匹配上的检测更新为新的检测器 - UpdateUnmatchedTraceLet(objInfos); // 更新没有匹配上的跟踪器 - } - - void MOTConnection::TrackObjectPredict() - { - // every traceLet should do kalman predict - for (auto &traceLet : traceList_) - { - traceLet.detectInfo = traceLet.kalman.Predict(); // 卡尔曼滤波预测的框 - } - } - - void MOTConnection::TrackObjectUpdate(const std::vector> &objInfos, - std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue) - { - if (objInfos[0].size() > 0) - { - LogDebug << "[frame id = " << 1 << "], trace size =" << traceList_.size() << "detect size = " << objInfos[0].size() << ""; - // init vehicle matched flag - std::vector detectVehicleFlagVec; - for (unsigned int i = 0; i < objInfos[0].size(); ++i) - { - detectVehicleFlagVec.push_back(false); - } - // calculate the associated matrix - std::vector> disMatrix; - disMatrix.clear(); - disMatrix.resize(traceList_.size(), std::vector(objInfos[0].size(), 0)); - for (unsigned int j = 0; j < objInfos[0].size(); ++j) - { - for (unsigned int i = 0; i < traceList_.size(); ++i) - { - // 计算交并比 - float sim = CalSimilarity(traceList_[i], objInfos[0][j], method_, kIOU_); // method_=1, kIOU_=1.0 - disMatrix[i][j] = (int)(sim * FLOAT_TO_INT); - } - } - - // solve the assignment problem using hungarian 匈牙利算法进行匹配 - HungarianHandle hungarianHandleObj = {}; - HungarianHandleInit(hungarianHandleObj, traceList_.size(), objInfos[0].size()); - HungarianSolve(hungarianHandleObj, disMatrix, traceList_.size(), objInfos[0].size()); - // filter out matched but with low distance 过滤掉匹配上但是交并比较小的 - FilterLowThreshold(hungarianHandleObj, disMatrix, matchedTracedDetected, detectVehicleFlagVec); - LogDebug << "matchedTracedDetected = " << matchedTracedDetected.size() << ""; - // fill unmatchedVehicleObjectQueue - for (unsigned int i = 0; i < detectVehicleFlagVec.size(); ++i) - { - if (detectVehicleFlagVec[i] == false) - { - unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); - } - } - } - } - - APP_ERROR MOTConnection::ProcessSort(std::vector> &objInfos, size_t frameId) - { - std::vector unmatchedVehicleObjectQueue; - std::vector matchedTracedDetected; - if (objInfos[0].size() == 0) - { - return APP_ERR_COMM_FAILURE; - } - - if (traceList_.size() > 0) - { - // every traceLet should do kalman predict - TrackObjectPredict(); // 卡尔曼滤波预测 - TrackObjectUpdate(objInfos, matchedTracedDetected, unmatchedVehicleObjectQueue); // 选出matched track、unmatched detection - } - else - { - // traceList is empty, all the vehicle detected in the new frame are unmatched. - if (objInfos[0].size() > 0) - { - for (unsigned int i = 0; i < objInfos[0].size(); ++i) - { - unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); - } - } - } - - // update all the tracelet in the tracelist per frame - UpdateTraceLetAndFrame(matchedTracedDetected, objInfos, unmatchedVehicleObjectQueue); // 用matched track、unmatched detection更新跟踪器 - return APP_ERR_OK; - } - - // 获取跟踪后的检测框 - APP_ERROR MOTConnection::GettrackResult(std::vector &objInfos_) - { - if (traceList_.size() > 0) - { - for (auto &traceLet : traceList_) - { - traceLet.detectInfo.classId = traceLet.info.id; - objInfos_.push_back(traceLet.detectInfo); - } - } - else - { - return APP_ERR_COMM_FAILURE; - } - return APP_ERR_OK; - } -} -// namespace ascendVehicleTracking \ No newline at end of file diff --git a/HelmetIdentification_V2/src/MOTConnection.h b/HelmetIdentification_V2/src/MOTConnection.h deleted file mode 100644 index be54f17..0000000 --- a/HelmetIdentification_V2/src/MOTConnection.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H -#define MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H - -#include -#include -#include -#include "KalmanTracker.h" -#include "Hungarian.h" -#include "DataType.h" -#include "MxBase/ErrorCode/ErrorCodes.h" -#include "MxBase/DvppWrapper/DvppWrapper.h" -#include "MxBase/MemoryHelper/MemoryHelper.h" -#include "MxBase/DeviceManager/DeviceManager.h" -#include "MxBase/Tensor/TensorBase/TensorBase.h" -#include "MxBase/PostProcessBases/PostProcessDataType.h" -#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" - -namespace ascendVehicleTracking { -struct TraceLet { - TraceInfo info = {}; - int32_t lostAge = 0; - KalmanTracker kalman; - std::list> shortFeatureQueue; - MxBase::ObjectInfo detectInfo = {}; -}; - -class MOTConnection { -public: - APP_ERROR ProcessSort(std::vector> &objInfos, size_t frameId); - APP_ERROR GettrackResult(std::vector &objInfos_); - -private: - double trackThreshold_ = 0.3; - double kIOU_ = 1.0; - int32_t method_ = 1; - int32_t lostThreshold_ = 3; - uint32_t maxNumberFeature_ = 0; - int32_t generatedId_ = 0; - std::vector traceList_ = {}; - -private: - - void FilterLowThreshold(const HungarianHandle &hungarianHandleObj, const std::vector> &disMatrix, - std::vector &matchedTracedDetected, std::vector &detectVehicleFlagVec); - - void UpdateUnmatchedTraceLet(const std::vector> &objInfos); - - void UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, - std::vector> &objInfos); - - void AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue); - - void UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, - std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue); - - void TrackObjectPredict(); - void TrackObjectUpdate(const std::vector> &objInfos, - std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue); -}; -} // namespace ascendVehicleTracking - -#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/cropResizePaste.hpp b/HelmetIdentification_V2/src/cropResizePaste.hpp deleted file mode 100644 index 37c6e8f..0000000 --- a/HelmetIdentification_V2/src/cropResizePaste.hpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef CRP_H -#define CRP_H - -#include "MxBase/E2eInfer/Image/Image.h" -#include "MxBase/E2eInfer/Rect/Rect.h" -#include "MxBase/E2eInfer/Size/Size.h" - -#include "acl/dvpp/hi_dvpp.h" -#include "acl/acl.h" -#include "acl/acl_rt.h" - -#define CONVER_TO_PODD(NUM) (((NUM) % 2 != 0) ? (NUM) : ((NUM)-1)) -#define CONVER_TO_EVEN(NUM) (((NUM) % 2 == 0) ? (NUM) : ((NUM)-1)) -#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) - -MxBase::Rect GetPasteRect(uint32_t inputWidth, uint32_t inputHeight, uint32_t outputWidth, uint32_t outputHeight) -{ - bool widthRatioLarger = true; - float resizeRatio = static_cast(inputWidth) / outputWidth; - if (resizeRatio < (static_cast(inputHeight) / outputHeight)) - { - resizeRatio = static_cast(inputHeight) / outputHeight; - widthRatioLarger = false; - } - - // (x0, y0)是左上角坐标,(x1, y1)是右下角坐标,采用图片坐标系 - uint32_t x0; - uint32_t y0; - uint32_t x1; - uint32_t y1; - if (widthRatioLarger) - { - // 原图width大于height - x0 = 0; - y0 = 0; - x1 = outputWidth-1; - y1 = inputHeight / resizeRatio - 1; - } - else - { - // 原图height大于width - x0 = 0; - y0 = 0; - x1 = outputWidth / resizeRatio - 1; - y1 = outputHeight - 1; - } - - x0 = DVPP_ALIGN_UP(CONVER_TO_EVEN(x0), 16); // 16对齐 - x1 = DVPP_ALIGN_UP((x1 - x0 + 1), 16) + x0 - 1; - y0 = CONVER_TO_EVEN(y0); - y1 = CONVER_TO_PODD(y1); - MxBase::Rect res(x0, y0, x1, y1); - return res; -} - -MxBase::Image ConstructImage(uint32_t resizeWidth, uint32_t resizeHeight) -{ - void *addr; - uint32_t dataSize = resizeWidth * resizeHeight * 3 / 2; - auto ret = hi_mpi_dvpp_malloc(0, &addr, dataSize); - if (ret != APP_ERR_OK) - { - LogError << "hi_mpi_dvpp_malloc fail :" << ret; - } - // 第三个参数从128改成了0 - ret = aclrtMemset(addr, dataSize, 0, dataSize); - if (ret != APP_ERR_OK) - { - LogError << "aclrtMemset fail :" << ret; - } - std::shared_ptr data((uint8_t *)addr, hi_mpi_dvpp_free); - MxBase::Size imageSize(resizeWidth, resizeHeight); - MxBase::Image pastedImg(data, dataSize, 0, imageSize); - return pastedImg; -} - -std::pair GenerateRect(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight) -{ - uint32_t x1 = CONVER_TO_PODD(originalWidth - 1); - uint32_t y1 = CONVER_TO_PODD(originalHeight - 1); - MxBase::Rect cropRect(0, 0, x1, y1); - MxBase::Rect pasteRect = GetPasteRect(originalWidth, originalHeight, resizeWidth, resizeHeight); - std::pair cropPasteRect(cropRect, pasteRect); - return cropPasteRect; -} - -MxBase::Image resizeKeepAspectRatioFit(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight, MxBase::Image &decodeImage, MxBase::ImageProcessor& imageProcessor) -{ - std::pair cropPasteRect = GenerateRect(originalWidth, originalHeight, resizeWidth, resizeHeight); - MxBase::Image resizeImage = ConstructImage(resizeWidth, resizeHeight); - auto ret = imageProcessor.CropAndPaste(decodeImage, cropPasteRect, resizeImage); - if (ret != APP_ERR_OK) - { - LogError << "CropAndPaste fail :" << ret; - } - return resizeImage; -} - -#endif // CRP_H \ No newline at end of file diff --git a/HelmetIdentification_V2/src/main-image.cpp b/HelmetIdentification_V2/src/main-image.cpp deleted file mode 100644 index ed14045..0000000 --- a/HelmetIdentification_V2/src/main-image.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "utils.h" - -#include -#include -#include -using namespace std; - -// 如果在200DK上运行就改为 USE_200DK -#define USE_DVPP - -APP_ERROR readImage(std::string imgPath, MxBase::Image& image, MxBase::ImageProcessor& imageProcessor) -{ - APP_ERROR ret; -#ifdef USE_DVPP - // if USE DVPP - ret = imageProcessor.Decode(imgPath, image); -#endif -#ifdef USE_200DK - std::shared_ptr dataPtr; - uint32_t dataSize; - // Get image data to memory, this method can be substituted or designed by yourself! - std::ifstream file; - file.open(imgPath.c_str(), std::ios::binary); - if (!file) - { - LogInfo << "Invalid file."; - return APP_ERR_COMM_INVALID_PARAM; - } - std::stringstream buffer; - buffer << file.rdbuf(); - std::string content = buffer.str(); - - char *p = (char *)malloc(content.size()); - memcpy(p, content.data(), content.size()); - auto deleter = [](void *p) -> void - { - free(p); - p = nullptr; - }; - - dataPtr.reset(static_cast((void *)(p)), deleter); - dataSize = content.size(); - file.close(); - if (ret != APP_ERR_OK) - { - LogError << "Getimage failed, ret=" << ret; - return ret; - } - ret = imageProcessor.Decode(dataPtr, dataSize, image, MxBase::ImageFormat::YUV_SP_420); - // endif -#endif - if (ret != APP_ERR_OK) - { - LogError << "Decode failed, ret=" << ret; - return ret; - } -} - -void poptProcess(std::vector modelOutputs, std::shared_ptr postProcessorDptr, MxBase::Size originalSize, MxBase::Size resizeSize) -{ - MxBase::ResizedImageInfo imgInfo; - auto shape = modelOutputs[0].GetShape(); - imgInfo.widthOriginal = originalSize.width; - imgInfo.heightOriginal = originalSize.height; - imgInfo.widthResize = resizeSize.width; - imgInfo.heightResize = resizeSize.height; - imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; - float resizeRate = originalSize.width > originalSize.height ? (originalSize.width / 640.0) : (originalSize.height / 640.0); - imgInfo.keepAspectRatioScaling = 1 / resizeRate; - std::vector imageInfoVec = {}; - imageInfoVec.push_back(imgInfo); - // make postProcess inputs - std::vector tensors; - for (size_t i = 0; i < modelOutputs.size(); i++) - { - MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); - MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); - tensors.push_back(tensorBase); - } - std::vector> objectInfos; - postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); - std::cout << "===---> Size of objectInfos is " << objectInfos.size() << std::endl; - for (size_t i = 0; i < objectInfos.size(); i++) - { - std::cout << "objectInfo-" << i << " , Size:" << objectInfos[i].size() << std::endl; - for (size_t j = 0; j < objectInfos[i].size(); j++) - { - std::cout << std::endl - << "*****objectInfo-" << i << ":" << j << std::endl; - std::cout << "x0 is " << objectInfos[i][j].x0 << std::endl; - std::cout << "y0 is " << objectInfos[i][j].y0 << std::endl; - std::cout << "x1 is " << objectInfos[i][j].x1 << std::endl; - std::cout << "y1 is " << objectInfos[i][j].y1 << std::endl; - std::cout << "confidence is " << objectInfos[i][j].confidence << std::endl; - std::cout << "classId is " << objectInfos[i][j].classId << std::endl; - std::cout << "className is " << objectInfos[i][j].className << std::endl; - } - } -} - -APP_ERROR main(int argc, char *argv[]) -{ - APP_ERROR ret; - - // global init - ret = MxBase::MxInit(); - if (ret != APP_ERR_OK) - { - LogError << "MxInit failed, ret=" << ret << "."; - } - // 检测是否输入了文件路径 - if (argc <= 1) - { - LogWarn << "Please input image path, such as 'test.jpg'."; - return APP_ERR_OK; - } - - // imageProcess init - MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); - // model init - MxBase::Model yoloModel(videoInfo::modelPath, videoInfo::DEVICE_ID); - // postprocessor init - std::map postConfig; - postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); - postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); - std::shared_ptr postProcessorDptr = std::make_shared(); - postProcessorDptr->Init(postConfig); - - std::string imgPath = argv[1]; - // 读取图片 - MxBase::Image image; - readImage(imgPath, image, imageProcessor); - - // 缩放图片 - MxBase::Size originalSize = image.GetOriginalSize(); - MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); - MxBase::Image resizedImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); - - // 模型推理 - MxBase::Tensor tensorImg = resizedImage.ConvertToTensor(); - tensorImg.ToDevice(videoInfo::DEVICE_ID); - std::vector inputs; - inputs.push_back(tensorImg); - std::vector modelOutputs = yoloModel.Infer(inputs); - for (auto output : modelOutputs) - { - output.ToHost(); - } - - // 后处理 - poptProcess(modelOutputs, postProcessorDptr, originalSize, resizeSize); - - return APP_ERR_OK; -} \ No newline at end of file diff --git a/HelmetIdentification_V2/src/main.cpp b/HelmetIdentification_V2/src/main.cpp deleted file mode 100644 index 3a3a006..0000000 --- a/HelmetIdentification_V2/src/main.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "utils.h" - -extern "C" -{ -#include "libavformat/avformat.h" -} - -#include -#include -#include -#include "unistd.h" -#include -#include -#include -#include "boost/filesystem.hpp" - -#include "MxBase/DeviceManager/DeviceManager.h" -#include "MxBase/DvppWrapper/DvppWrapper.h" -#include "MxBase/MemoryHelper/MemoryHelper.h" -#include "opencv2/opencv.hpp" -#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" - -#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" -#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" -#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" -#include "MxBase/E2eInfer/DataType.h" - -#include -using namespace std; -using namespace videoInfo; -namespace frameConfig -{ - size_t channelId0 = 0; - size_t channelId1 = 1; - size_t frameCountChannel0 = 300; - size_t frameCountChannel1 = 300; - size_t skipIntervalChannel0 = 2; - size_t skipIntervalChannel1 = 2; - - // channel0对应文件的指针 - AVFormatContext *pFormatCtx0 = nullptr; - // channel1对应文件的指针 - AVFormatContext *pFormatCtx1 = nullptr; -} - -// ffmpeg拉流 -AVFormatContext *CreateFormatContext(std::string filePath) -{ - LogInfo << "start to CreatFormatContext!"; - // creat message for stream pull - AVFormatContext *formatContext = nullptr; - AVDictionary *options = nullptr; - - LogInfo << "start to avformat_open_input!"; - int ret = avformat_open_input(&formatContext, filePath.c_str(), nullptr, &options); - if (options != nullptr) - { - av_dict_free(&options); - } - if (ret != 0) - { - LogError << "Couldn't open input stream " << filePath.c_str() << " ret=" << ret; - return nullptr; - } - ret = avformat_find_stream_info(formatContext, nullptr); - if (ret != 0) - { - LogError << "Couldn't open input stream information"; - return nullptr; - } - return formatContext; -} - -// 真正的拉流函数 -void PullStream0(std::string filePath) -{ - av_register_all(); - avformat_network_init(); - frameConfig::pFormatCtx0 = avformat_alloc_context(); - frameConfig::pFormatCtx0 = CreateFormatContext(filePath); - av_dump_format(frameConfig::pFormatCtx0, 0, filePath.c_str(), 0); -} -void PullStream1(std::string filePath) -{ - av_register_all(); - avformat_network_init(); - frameConfig::pFormatCtx1 = avformat_alloc_context(); - frameConfig::pFormatCtx1 = CreateFormatContext(filePath); - av_dump_format(frameConfig::pFormatCtx1, 0, filePath.c_str(), 0); -} - -// 视频解码回调(样例代码,测试可以跑通,但是不能直接复用) -APP_ERROR CallBackVdec(Image &decodedImage, uint32_t channelId, uint32_t frameId, void *userData) -{ - FrameImage frameImage; - frameImage.image = decodedImage; - frameImage.channelId = channelId; - frameImage.frameId = frameId; - - videoInfo::g_threadsMutex_frameImageQueue.lock(); - videoInfo::frameImageQueue.push(frameImage); - videoInfo::g_threadsMutex_frameImageQueue.unlock(); - - return APP_ERR_OK; -} - -// 获取H264中的帧 -void GetFrame(AVPacket &pkt, FrameImage &frameImage, AVFormatContext *pFormatCtx) -{ - av_init_packet(&pkt); - int ret = av_read_frame(pFormatCtx, &pkt); - if (ret != 0) - { - LogInfo << "[StreamPuller] channel Read frame failed, continue!"; - if (ret == AVERROR_EOF) - { - LogInfo << "[StreamPuller] channel StreamPuller is EOF, over!"; - return; - } - return; - } - else - { - if (pkt.size <= 0) - { - LogError << "Invalid pkt.size: " << pkt.size; - return; - } - - // send to the device - auto hostDeleter = [](void *dataPtr) -> void {}; - MemoryData data(pkt.size, MemoryData::MEMORY_HOST); - MemoryData src((void *)(pkt.data), pkt.size, MemoryData::MEMORY_HOST); - APP_ERROR ret = MemoryHelper::MxbsMallocAndCopy(data, src); - if (ret != APP_ERR_OK) - { - LogError << "MxbsMallocAndCopy failed!"; - } - std::shared_ptr imageData((uint8_t *)data.ptrData, hostDeleter); - - Image subImage(imageData, pkt.size); - frameImage.image = subImage; - - LogDebug << "'channelId = " << frameImage.channelId << ", frameId = " << frameImage.frameId << " , dataSize = " << frameImage.image.GetDataSize(); - - av_packet_unref(&pkt); - } - return; -} - -// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 -void VdecThread0(size_t frameCount, size_t skipInterval, int32_t channelId) -{ - AVPacket pkt; - uint32_t frameId = 0; - // 解码器参数 - VideoDecodeConfig config; - VideoDecodeCallBack cPtr = CallBackVdec; - config.width = videoInfo::SRC_WIDTH; - config.height = videoInfo::SRC_HEIGHT; - config.callbackFunc = cPtr; - config.skipInterval = skipInterval; // 跳帧控制 - - std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); - for (size_t i = 0; i < frameCount; i++) - { - Image subImage; - FrameImage frame; - frame.channelId = 0; - frame.frameId = frameId; - frame.image = subImage; - GetFrame(pkt, frame, frameConfig::pFormatCtx0); - APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); - if (ret != APP_ERR_OK) - { - LogError << "videoDecoder Decode failed. ret is: " << ret; - } - frameId += 1; - } -} - -// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 -void VdecThread1(size_t frameCount, size_t skipInterval, int32_t channelId) -{ - AVPacket pkt; - uint32_t frameId = 0; - // 解码器参数 - VideoDecodeConfig config; - VideoDecodeCallBack cPtr = CallBackVdec; - config.width = videoInfo::SRC_WIDTH; - config.height = videoInfo::SRC_HEIGHT; - config.callbackFunc = cPtr; - // 跳帧控制 - config.skipInterval = skipInterval; - - std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); - for (size_t i = 0; i < frameCount; i++) - { - Image subImage; - FrameImage frame; - frame.channelId = 0; - frame.frameId = frameId; - frame.image = subImage; - GetFrame(pkt, frame, frameConfig::pFormatCtx1); - APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); - if (ret != APP_ERR_OK) - { - LogError << "videoDecoder Decode failed. ret is: " << ret; - } - frameId += 1; - } -} - - -APP_ERROR main(int argc, char *argv[]) -{ - // 检测是否输入了文件路径 - if (argc <= 1) - { - LogWarn << "Please input video path, such as './video_sample test.264'."; - return APP_ERR_OK; - } - - // 初始化 - APP_ERROR ret = MxBase::MxInit(); - if (ret != APP_ERR_OK) - { - LogError << "MxInit failed, ret=" << ret << "."; - } - - std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); - - // 视频流解码线程 - std::string videoPath0 = argv[1]; - PullStream0(videoPath0); - std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0); - std::string videoPath1 = argv[2]; - PullStream1(videoPath1); - std::thread threadVdec1(VdecThread1, frameConfig::frameCountChannel1, frameConfig::skipIntervalChannel1, frameConfig::channelId1); - threadVdec0.join(); - threadVdec1.join(); - - size_t target_count = videoInfo::frameImageQueue.size(); - LogInfo << "frameImageQueue.size: " << videoInfo::frameImageQueue.size(); - - // resize线程 - std::thread resizeThread(resizeMethod, start_time, target_count); - resizeThread.join(); - - // 推理线程 - std::thread inferThread(inferMethod, start_time, target_count); - inferThread.join(); - - // 后处理线程 - std::thread postprocessThread(postprocessMethod, start_time, target_count); - postprocessThread.join(); - - // 跟踪去重线程 - std::thread trackThread(trackMethod, start_time, target_count); - trackThread.join(); - - std::chrono::high_resolution_clock::time_point end_time = std::chrono::high_resolution_clock::now(); - double_t cost_time = std::chrono::duration_cast(end_time - start_time).count(); - LogInfo << "端到端时间为" << cost_time/videoInfo::MS_PPE_SECOND; - - return APP_ERR_OK; -} \ No newline at end of file diff --git a/HelmetIdentification_V2/src/utils.h b/HelmetIdentification_V2/src/utils.h deleted file mode 100644 index f605346..0000000 --- a/HelmetIdentification_V2/src/utils.h +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_UTILS_H -#define MXBASE_HELMETIDENTIFICATION_UTILS_H - -#include -#include -#include -#include "unistd.h" -#include -#include -#include -#include "boost/filesystem.hpp" - -#include "MxBase/DeviceManager/DeviceManager.h" -#include "MxBase/DvppWrapper/DvppWrapper.h" -#include "MxBase/MemoryHelper/MemoryHelper.h" -#include "opencv2/opencv.hpp" -#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" - -#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" -#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" -#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" -#include "MxBase/E2eInfer/DataType.h" - -#include "MxBase/MxBase.h" -#include "MOTConnection.h" -#include "cropResizePaste.hpp" -#include "chrono" -#include "time.h" - -struct FrameImage -{ - MxBase::Image image; - uint32_t frameId = 0; - uint32_t channelId = 0; -}; - -namespace videoInfo -{ - const uint32_t SRC_WIDTH = 1920; - const uint32_t SRC_HEIGHT = 1080; - - const uint32_t YUV_BYTE_NU = 3; - const uint32_t YUV_BYTE_DE = 2; - const uint32_t YOLOV5_RESIZE = 640; - - // 要检测的目标类别的标签 - const std::string TARGET_CLASS_NAME = "head"; - // 使用chrono计数结果为毫秒,需要除以1000转换为秒 - double MS_PPE_SECOND = 1000.0; - - const uint32_t DEVICE_ID = 0; - std::string labelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/imgclass.names"; - std::string configPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/Helmet_yolov5.cfg"; - std::string modelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/YOLOv5_s.om"; - - // 读入视频帧Image的队列 - std::queue frameImageQueue; - // 读入视频帧Image队列的线程锁 - std::mutex g_threadsMutex_frameImageQueue; - - // resize前即原始图像的vector - std::vector realImageVector; - // resize后Image的vector - std::vector resizedImageVector; - // resize后Image队列的线程锁 - std::mutex g_threadsMutex_resizedImageVector; - - // 推理后tensor的vector - std::vector> inferOutputVector; - // 推理后tensor队列的线程锁 - std::mutex g_threadsMutex_inferOutputVector; - - // 后处理后objectInfos的队列 map> - std::vector>> postprocessOutputVector; - // 后处理后objectInfos队列的线程锁 - std::mutex g_threadsMutex_postprocessOutputVector; -} -namespace fs = boost::filesystem; - -// resize线程 -void resizeMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point resize_start_time = std::chrono::high_resolution_clock::now(); - - MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); - - size_t resize_count = 0; - while (resize_count < target_count) - { - if (videoInfo::frameImageQueue.empty()) - { - continue; - } - // 取图像并resize - FrameImage frame = videoInfo::frameImageQueue.front(); - Image image = frame.image; - MxBase::Size originalSize = image.GetOriginalSize(); - MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); - MxBase::Image outputImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); - // 先将缩放后的图像放入resizeImage的队列 - FrameImage resizedFrame; - resizedFrame.channelId = frame.channelId; - resizedFrame.frameId = frame.frameId; - resizedFrame.image = outputImage; - videoInfo::g_threadsMutex_resizedImageVector.lock(); - frame.image.ToHost(); - videoInfo::realImageVector.push_back(frame); - videoInfo::resizedImageVector.push_back(resizedFrame); - videoInfo::g_threadsMutex_resizedImageVector.unlock(); - // 然后再将原图pop出去 - videoInfo::g_threadsMutex_frameImageQueue.lock(); - videoInfo::frameImageQueue.pop(); - videoInfo::g_threadsMutex_frameImageQueue.unlock(); - // 计数 - resize_count++; - } - std::chrono::high_resolution_clock::time_point resize_end_time = std::chrono::high_resolution_clock::now(); - double_t resize_cost_time = std::chrono::duration_cast(resize_end_time - resize_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t resize_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到缩放全部完成一共花费: " << resize_finish_time << ", 缩放本身花费: " << resize_cost_time; -} - -// 推理线程 -void inferMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point infer_start_time = std::chrono::high_resolution_clock::now(); - - std::shared_ptr modelDptr = std::make_shared(videoInfo::modelPath, videoInfo::DEVICE_ID); - - size_t infer_count = 0; - while (infer_count < target_count) - { - if (infer_count >= videoInfo::resizedImageVector.size()) - { - continue; - } - // 从resize后的队列中取图片 - FrameImage resizedFrame = videoInfo::resizedImageVector[infer_count]; - std::vector modelOutputs; - - MxBase::Tensor tensorImg = resizedFrame.image.ConvertToTensor(); - tensorImg.ToDevice(videoInfo::DEVICE_ID); - std::vector inputs; - inputs.push_back(tensorImg); - modelOutputs = modelDptr->Infer(inputs); - for (auto output : modelOutputs) - { - output.ToHost(); - } - - // 将推理结果存入队列 - videoInfo::g_threadsMutex_inferOutputVector.lock(); - videoInfo::inferOutputVector.push_back(modelOutputs); - videoInfo::g_threadsMutex_inferOutputVector.unlock(); - // 计数 - infer_count++; - } - std::chrono::high_resolution_clock::time_point infer_end_time = std::chrono::high_resolution_clock::now(); - double_t infer_cost_time = std::chrono::duration_cast(infer_end_time - infer_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t infer_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到推理全部完成一共花费: " << infer_finish_time << ", 推理本身花费: " << infer_cost_time; -} - -// 后处理线程 -void postprocessMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point postprocess_start_time = std::chrono::high_resolution_clock::now(); - - std::map postConfig; - postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); - postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); - std::shared_ptr postProcessorDptr = std::make_shared(); - if (postProcessorDptr == nullptr) - { - LogError << "init postProcessor failed, nullptr"; - } - postProcessorDptr->Init(postConfig); - - size_t postprocess_count = 0; - while (postprocess_count < target_count) - { - if (postprocess_count >= videoInfo::inferOutputVector.size()) - { - continue; - } - // 取原图信息用于计算 - MxBase::Size originalSize = videoInfo::realImageVector[postprocess_count].image.GetOriginalSize(); - // 从推理结果的队列里面取出推理结果 - std::vector modelOutputs = videoInfo::inferOutputVector[postprocess_count]; - FrameImage resizedFrame = videoInfo::resizedImageVector[postprocess_count]; - // 新的后处理过程 - MxBase::ResizedImageInfo imgInfo; - auto shape = modelOutputs[0].GetShape(); - imgInfo.widthOriginal = originalSize.width; - imgInfo.heightOriginal = originalSize.height; - imgInfo.widthResize = videoInfo::YOLOV5_RESIZE; - imgInfo.heightResize = videoInfo::YOLOV5_RESIZE; - imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; - // 因为yolov5要求输入图像为640*640,所以直接比较原图的height和width就好(如果不理解就去看cropResizePaste.hpp里的GetPasteRect函数) - float resizeRate = originalSize.width > originalSize.height ? (originalSize.width / videoInfo::YOLOV5_RESIZE) : (originalSize.height / videoInfo::YOLOV5_RESIZE); - imgInfo.keepAspectRatioScaling = 1 / resizeRate; - std::vector imageInfoVec = {}; - imageInfoVec.push_back(imgInfo); - // make postProcess inputs - std::vector tensors; - for (size_t i = 0; i < modelOutputs.size(); i++) - { - MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); - MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); - tensors.push_back(tensorBase); - } - // 后处理 - std::vector> objectInfos; - postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); - - // 将后处理结果存入队列 - videoInfo::g_threadsMutex_postprocessOutputVector.lock(); - videoInfo::postprocessOutputVector.push_back(objectInfos); - videoInfo::g_threadsMutex_postprocessOutputVector.unlock(); - // 计数 - postprocess_count++; - } - std::chrono::high_resolution_clock::time_point postprocess_end_time = std::chrono::high_resolution_clock::now(); - double_t postprocess_cost_time = std::chrono::duration_cast(postprocess_end_time - postprocess_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t postprocess_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到后处理全部完成一共花费: " << postprocess_finish_time << ", 后处理本身花费: " << postprocess_cost_time; -} - -// 跟踪去重线程 -void trackMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point track_start_time = std::chrono::high_resolution_clock::now(); - - std::shared_ptr tracker0 = std::make_shared(); - if (tracker0 == nullptr) - { - LogError << "init tracker0 failed, nullptr"; - } - std::shared_ptr tracker1 = std::make_shared(); - if (tracker1 == nullptr) - { - LogError << "init tracker1 failed, nullptr"; - } - // 用于计算帧率 - size_t old_count = 0; - std::chrono::high_resolution_clock::time_point count_time; - std::chrono::high_resolution_clock::time_point old_count_time = std::chrono::high_resolution_clock::now(); - size_t one_step = 2; - size_t track_count = 0; - while (track_count < target_count) - { - // 计算帧率 - // 如果count_time-old_count_time的值大于one_step,就计算一下这个step里面的帧数,然后除以step的值 - count_time = std::chrono::high_resolution_clock::now(); - if (std::chrono::duration_cast(count_time - old_count_time).count() / videoInfo::MS_PPE_SECOND > one_step) - { - old_count_time = count_time; - LogInfo << "rate: " << (track_count - old_count) / one_step * 1.0; - old_count = track_count; - } - // 下面是业务循环 - if (track_count >= videoInfo::postprocessOutputVector.size()) - { - continue; - } - // 从后处理结果的队列中取结果用于跟踪去重 - std::vector> objectInfos = videoInfo::postprocessOutputVector[track_count]; - FrameImage frame = videoInfo::realImageVector[track_count]; - MxBase::Size originalSize = frame.image.GetOriginalSize(); - - // 根据channelId的不同使用不同的tracker - std::vector objInfos_ = {}; - if (frame.channelId == 0) - { - tracker0->ProcessSort(objectInfos, frame.frameId); - APP_ERROR ret = tracker0->GettrackResult(objInfos_); - if (ret != APP_ERR_OK) - { - LogError << "No tracker0"; - } - } - else - { - tracker1->ProcessSort(objectInfos, frame.frameId); - APP_ERROR ret = tracker1->GettrackResult(objInfos_); - if (ret != APP_ERR_OK) - { - LogError << "No tracker1"; - } - } - - uint32_t video_height = originalSize.height; - uint32_t video_width = originalSize.width; - // 初始化OpenCV图像信息矩阵 - cv::Mat imgYuv = cv::Mat(video_height * videoInfo::YUV_BYTE_NU / videoInfo::YUV_BYTE_DE, video_width, CV_8UC1, frame.image.GetData().get()); - cv::Mat imgBgr = cv::Mat(video_height, video_width, CV_8UC3); - // 颜色空间转换 - cv::cvtColor(imgYuv, imgBgr, cv::COLOR_YUV420sp2BGR); - std::vector info; - bool headFlag = false; - for (uint32_t i = 0; i < objInfos_.size(); i++) - { - if (objInfos_[i].className == videoInfo::TARGET_CLASS_NAME) - { - headFlag = true; - LogWarn << "Warning:Not wearing a helmet, channelId:" << frame.channelId << ", frameId:" << frame.frameId; - // (blue, green, red) - const cv::Scalar color = cv::Scalar(0, 0, 255); - // width for rectangle - const uint32_t thickness = 2; - // draw the rectangle - cv::rectangle(imgBgr, - cv::Rect(objInfos_[i].x0, objInfos_[i].y0, objInfos_[i].x1 - objInfos_[i].x0, objInfos_[i].y1 - objInfos_[i].y0), - color, thickness); - } - } - // 如果检测结果中有head标签,就保存为图片 - if (headFlag) - { - // 把Mat类型的图像矩阵保存为图像到指定位置。 - std::string outPath = frame.channelId == 0 ? "one" : "two"; - std::string fileName = "./result/" + outPath + "/result" + std::to_string(frame.frameId) + ".jpg"; - cv::imwrite(fileName, imgBgr); - } - - // 计数 - track_count++; - } - std::chrono::high_resolution_clock::time_point track_end_time = std::chrono::high_resolution_clock::now(); - double_t track_cost_time = std::chrono::duration_cast(track_end_time - track_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t track_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到跟踪去重全部完成一共花费: " << track_finish_time << ", 跟踪去重本身花费: " << track_cost_time; -} - -#endif \ No newline at end of file From ef9540b1ae3b97dbd51129a8a08c10cd97e2e815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 05:50:33 +0000 Subject: [PATCH 25/58] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20SRC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/SRC/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 HelmetIdentification_V2/SRC/.keep diff --git a/HelmetIdentification_V2/SRC/.keep b/HelmetIdentification_V2/SRC/.keep new file mode 100644 index 0000000..e69de29 From 15ad01bba728a13b309645d68548a6c7db5dc701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 05:50:44 +0000 Subject: [PATCH 26/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/SRC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/SRC/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 HelmetIdentification_V2/SRC/.keep diff --git a/HelmetIdentification_V2/SRC/.keep b/HelmetIdentification_V2/SRC/.keep deleted file mode 100644 index e69de29..0000000 From 6f4b21d88d1f5b9ddb46498c09e1a339318b3827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 05:50:52 +0000 Subject: [PATCH 27/58] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20src?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/src/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 HelmetIdentification_V2/src/.keep diff --git a/HelmetIdentification_V2/src/.keep b/HelmetIdentification_V2/src/.keep new file mode 100644 index 0000000..e69de29 From 9eb7af061b1bd6137bde3766667c83470fa3b4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 05:51:24 +0000 Subject: [PATCH 28/58] =?UTF-8?q?=E4=BA=A4=E4=BB=98=E7=94=A8=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E5=8C=85=E6=8B=AC=E8=AF=BB=E5=8F=96=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=92=8C=E8=AF=BB=E5=8F=96=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/src/CMakeLists.txt | 47 +++ HelmetIdentification_V2/src/DataType.h | 94 +++++ HelmetIdentification_V2/src/Hungarian.cpp | 165 +++++++++ HelmetIdentification_V2/src/Hungarian.h | 46 +++ HelmetIdentification_V2/src/KalmanTracker.cpp | 143 +++++++ HelmetIdentification_V2/src/KalmanTracker.h | 39 ++ HelmetIdentification_V2/src/MOTConnection.cpp | 262 +++++++++++++ HelmetIdentification_V2/src/MOTConnection.h | 78 ++++ .../src/cropResizePaste.hpp | 114 ++++++ HelmetIdentification_V2/src/main-image.cpp | 170 +++++++++ HelmetIdentification_V2/src/main.cpp | 330 +++++++++++++++++ HelmetIdentification_V2/src/utils.h | 350 ++++++++++++++++++ 12 files changed, 1838 insertions(+) create mode 100644 HelmetIdentification_V2/src/CMakeLists.txt create mode 100644 HelmetIdentification_V2/src/DataType.h create mode 100644 HelmetIdentification_V2/src/Hungarian.cpp create mode 100644 HelmetIdentification_V2/src/Hungarian.h create mode 100644 HelmetIdentification_V2/src/KalmanTracker.cpp create mode 100644 HelmetIdentification_V2/src/KalmanTracker.h create mode 100644 HelmetIdentification_V2/src/MOTConnection.cpp create mode 100644 HelmetIdentification_V2/src/MOTConnection.h create mode 100644 HelmetIdentification_V2/src/cropResizePaste.hpp create mode 100644 HelmetIdentification_V2/src/main-image.cpp create mode 100644 HelmetIdentification_V2/src/main.cpp create mode 100644 HelmetIdentification_V2/src/utils.h diff --git a/HelmetIdentification_V2/src/CMakeLists.txt b/HelmetIdentification_V2/src/CMakeLists.txt new file mode 100644 index 0000000..6fcd298 --- /dev/null +++ b/HelmetIdentification_V2/src/CMakeLists.txt @@ -0,0 +1,47 @@ +# CMake lowest version requirement +cmake_minimum_required(VERSION 3.5.1) +# project information +project(Individual) + +# Compile options +add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0 -Dgoogle=mindxsdk_private) +add_compile_options(-std=c++11 -fPIC -fstack-protector-all -Wall -D_FORTIFY_SOURCE=2 -O2) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../../") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now,-z,noexecstack -s -pie -pthread") +set(CMAKE_SKIP_RPATH TRUE) + +SET(CMAKE_BUILD_TYPE "Debug") +SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb") +SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall") + +# Header path +include_directories( + ${MX_SDK_HOME}/include/ + ${MX_SDK_HOME}/opensource/include/ + ${MX_SDK_HOME}/opensource/include/opencv4/ + /home/HwHiAiUser/Ascend/ascend-toolkit/latest/include/ + ./ +) + +# add host lib path +link_directories( + ${MX_SDK_HOME}/lib/ + ${MX_SDK_HOME}/lib/modelpostprocessors + ${MX_SDK_HOME}/opensource/lib/ + ${MX_SDK_HOME}/opensource/lib64/ + /usr/lib/aarch64-linux-gnu/ + /home/HwHiAiUser/Ascend/ascend-toolkit/latest/lib64/ + /usr/local/Ascend/driver/lib64/ + ./ +) + + +aux_source_directory(. sourceList) + +add_executable(main ${sourceList}) + +target_link_libraries(main mxbase opencv_world boost_filesystem glog avformat avcodec avutil cpprest yolov3postprocess ascendcl acl_dvpp_mpi) + +install(TARGETS main DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/HelmetIdentification_V2/src/DataType.h b/HelmetIdentification_V2/src/DataType.h new file mode 100644 index 0000000..dc34a0a --- /dev/null +++ b/HelmetIdentification_V2/src/DataType.h @@ -0,0 +1,94 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_DATATYPE_H +#define MXBASE_HELMETIDENTIFICATION_DATATYPE_H + +#include +#include +#include +#include +#include +#include +#include "opencv2/highgui.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" + +namespace ascendVehicleTracking { +#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) + + const int MODULE_QUEUE_SIZE = 1000; + + enum FrameMode { + FRAME_MODE_SEARCH = 0, + FRAME_MODE_REG + }; + + struct DataBuffer { + std::shared_ptr deviceData; + std::shared_ptr hostData; + uint32_t dataSize; // buffer size + DataBuffer() : deviceData(nullptr), hostData(nullptr), dataSize(0) {} + }; + + struct DetectInfo { + int32_t classId; + float confidence; + float minx; // x value of left-top point + float miny; // y value of left-top point + float height; + float width; + }; + + enum TraceFlag { + NEW_VEHICLE = 0, + TRACkED_VEHICLE, + LOST_VEHICLE + }; + + struct TraceInfo { + int32_t id; + TraceFlag flag; + int32_t survivalTime; // How long is it been since the first time, unit: detection period + int32_t detectedTime; // How long is the vehicle detected, unit: detection period + std::chrono::time_point createTime; + }; + + struct TrackLet { + TraceInfo info; + // reserved: kalman status parameter + int32_t lostTime; // undetected time for tracked vehicle + std::vector shortFeature; // nearest 10 frame + }; + + struct VehicleQuality { + float score; + }; + + struct Coordinate2D { + uint32_t x; + uint32_t y; + }; +} +// namespace ascendVehicleTracking + +struct AttrT { + AttrT(std::string name, std::string value) : name(std::move(name)), value(std::move(value)) {} + std::string name = {}; + std::string value = {}; +}; + +#endif diff --git a/HelmetIdentification_V2/src/Hungarian.cpp b/HelmetIdentification_V2/src/Hungarian.cpp new file mode 100644 index 0000000..3f6fcd2 --- /dev/null +++ b/HelmetIdentification_V2/src/Hungarian.cpp @@ -0,0 +1,165 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Hungarian.h" +#include +#include +#include "MxBase/Log/Log.h" + +namespace { + const int INF = 0x3f3f3f3f; + const int VISITED = 1; + const int HUNGARIAN_CONTENT = 7; + const int X_MATCH_OFFSET = 0; + const int Y_MATCH_OFFSET = 1; + const int X_VALUE_OFFSET = 2; + const int Y_VALUE_OFFSET = 3; + const int SLACK_OFFSET = 4; + const int X_VISIT_OFFSET = 5; + const int Y_VISIT_OFFSET = 6; +} + +APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols) +{ + handle.max = (row > cols) ? row : cols; + auto adjMat = std::shared_ptr(); + adjMat.reset(new int[handle.max * handle.max], std::default_delete()); + if (adjMat == nullptr) { + LogFatal << "HungarianHandleInit new failed"; + return APP_ERR_ACL_FAILURE; + } + + handle.adjMat = adjMat; + + void* ptr[HUNGARIAN_CONTENT] = {nullptr}; + for (int i = 0; i < HUNGARIAN_CONTENT; ++i) { + ptr[i] = malloc(handle.max * sizeof(int)); + if (ptr[i] == nullptr) { + LogFatal << "HungarianHandleInit Malloc failed"; + return APP_ERR_ACL_FAILURE; + } + } + + handle.xMatch.reset((int *)ptr[X_MATCH_OFFSET], free); + handle.yMatch.reset((int *)ptr[Y_MATCH_OFFSET], free); + handle.xValue.reset((int *)ptr[X_VALUE_OFFSET], free); + handle.yValue.reset((int *)ptr[Y_VALUE_OFFSET], free); + handle.slack.reset((int *)ptr[SLACK_OFFSET], free); + handle.xVisit.reset((int *)ptr[X_VISIT_OFFSET], free); + handle.yVisit.reset((int *)ptr[Y_VISIT_OFFSET], free); + return APP_ERR_OK; +} + +static void HungarianInit(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) +{ + int i, j, value; + if (rows > cols) { + handle.transpose = true; + handle.cols = rows; + handle.rows = cols; + handle.resX = handle.yMatch.get(); + handle.resY = handle.xMatch.get(); + } else { + handle.transpose = false; + handle.rows = rows; + handle.cols = cols; + handle.resX = handle.xMatch.get(); + handle.resY = handle.yMatch.get(); + } + + for (i = 0; i < handle.rows; ++i) { + handle.xValue.get()[i] = 0; + handle.xMatch.get()[i] = -1; + for (j = 0; j < handle.cols; ++j) { + if (handle.transpose) { + value = cost[j][i]; + } else { + value = cost[i][j]; + } + handle.adjMat.get()[i * handle.cols + j] = value; + if (handle.xValue.get()[i] < value) { + handle.xValue.get()[i] = value; + } + } + } + + for (i = 0; i < handle.cols; ++i) { + handle.yValue.get()[i] = 0; + handle.yMatch.get()[i] = -1; + } +} + +static bool Match(HungarianHandle &handle, int id) +{ + int j, delta; + handle.xVisit.get()[id] = VISITED; + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] == VISITED) { + continue; + } + delta = handle.xValue.get()[id] + handle.yValue.get()[j] - handle.adjMat.get()[id * handle.cols + j]; + if (delta == 0) { + handle.yVisit.get()[j] = VISITED; + if (handle.yMatch.get()[j] == -1 || Match(handle, handle.yMatch.get()[j])) { + handle.yMatch.get()[j] = id; + handle.xMatch.get()[id] = j; + return true; + } + } else if (delta < handle.slack.get()[j]) { + handle.slack.get()[j] = delta; + } + } + return false; +} + +int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) +{ + HungarianInit(handle, cost, rows, cols); + int i, j, delta; + for (i = 0; i < handle.rows; ++i) { + while (true) { + std::fill(handle.xVisit.get(), handle.xVisit.get() + handle.rows, 0); + std::fill(handle.yVisit.get(), handle.yVisit.get() + handle.cols, 0); + for (j = 0; j < handle.cols; ++j) { + handle.slack.get()[j] = INF; + } + if (Match(handle, i)) { + break; + } + delta = INF; + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] != VISITED && delta > handle.slack.get()[j]) { + delta = handle.slack.get()[j]; + } + } + if (delta == INF) { + LogDebug << "Hungarian solve is invalid!"; + return -1; + } + for (j = 0; j < handle.rows; ++j) { + if (handle.xVisit.get()[j] == VISITED) { + handle.xValue.get()[j] -= delta; + } + } + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] == VISITED) { + handle.yValue.get()[j] += delta; + } + } + } + } + return handle.rows; +} diff --git a/HelmetIdentification_V2/src/Hungarian.h b/HelmetIdentification_V2/src/Hungarian.h new file mode 100644 index 0000000..2ae6a33 --- /dev/null +++ b/HelmetIdentification_V2/src/Hungarian.h @@ -0,0 +1,46 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H +#define MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H + +#include +#include +#include "DataType.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" +#include "MxBase/ErrorCode/ErrorCodes.h" + +struct HungarianHandle { + int rows; + int cols; + int max; + int *resX; + int *resY; + bool transpose; + std::shared_ptr adjMat; + std::shared_ptr xMatch; + std::shared_ptr yMatch; + std::shared_ptr xValue; + std::shared_ptr yValue; + std::shared_ptr slack; + std::shared_ptr xVisit; + std::shared_ptr yVisit; +}; + +APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols); +int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols); + +#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/KalmanTracker.cpp b/HelmetIdentification_V2/src/KalmanTracker.cpp new file mode 100644 index 0000000..69362f9 --- /dev/null +++ b/HelmetIdentification_V2/src/KalmanTracker.cpp @@ -0,0 +1,143 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "KalmanTracker.h" + +namespace ascendVehicleTracking +{ + namespace + { + const int OFFSET = 2; + const int MULTIPLE = 2; + } + + /* + * The SORT algorithm uses a linear constant velocity model,which assumes 7 + * states, including + * x coordinate of bounding box center + * y coordinate of bounding box center + * area of bounding box + * aspect ratio of w to h + * velocity of x + * velocity of y + * variation rate of area + * + * The aspect ratio is considered to be unchanged, so there is no additive item + * for aspect ratio in the transitionMatrix + * + * + * Kalman filter equation step by step + * (1) X(k|k-1)=AX(k-1|k-1)+BU(k) + * X(k|k-1) is the predicted state(statePre),X(k-1|k-1) is the k-1 statePost,A + * is transitionMatrix, B is controlMatrix, U(k) is control state, in SORT U(k) is 0. + * + * (2) P(k|k-1)=AP(k-1|k-1)A'+Q + * P(k|k-1) is the predicted errorCovPre, P(k-1|k-1) is the k-1 errorCovPost, + * Q is processNoiseCov + * + * (3) Kg(k)=P(k|k-1)H'/(HP(k|k-1))H'+R + * Kg(k) is the kalman gain, the ratio of estimate variance in total variance, + * H is the measurementMatrix,R is the measurementNoiseCov + * + * (4) X(k|k)=X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) + * X(k|k) is the k statePost, Z(k) is the measurement of K, in SORT Z(k) is + * the detection result of k + * + * (5) P(k|k)=(1-Kg(k)H)P(k|k-1) + * P(k|k) is the errorCovPost + */ + void KalmanTracker::CvKalmanInit(MxBase::ObjectInfo initRect) + { + const int stateDim = 7; + const int measureDim = 4; + cvkalmanfilter_ = cv::KalmanFilter(stateDim, measureDim, 0); // zero control + measurement_ = cv::Mat::zeros(measureDim, 1, CV_32F); // 4 measurements, Z(k), according to detection results + // A, will not be updated + cvkalmanfilter_.transitionMatrix = (cv::Mat_(stateDim, stateDim) << 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1); + cvkalmanfilter_.measurementMatrix = (cv::Mat_(measureDim, stateDim) << 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0); + cv::setIdentity(cvkalmanfilter_.measurementMatrix); // H, will not be updated + cv::setIdentity(cvkalmanfilter_.processNoiseCov, cv::Scalar::all(1e-2)); // Q, will not be updated + cv::setIdentity(cvkalmanfilter_.measurementNoiseCov, cv::Scalar::all(1e-1)); // R, will bot be updated + cv::setIdentity(cvkalmanfilter_.errorCovPost, cv::Scalar::all(1)); // P(k-1|k-1), will be updated + + // initialize state vector with bounding box in + // [center_x,center_y,area,ratio] + // style, the velocity is 0 + // X(k-1|k-1) + cvkalmanfilter_.statePost.at(0, 0) = initRect.x0 + (initRect.x1 - initRect.x0) / MULTIPLE; + cvkalmanfilter_.statePost.at(1, 0) = initRect.y0 + (initRect.y1 - initRect.y0) / MULTIPLE; + cvkalmanfilter_.statePost.at(OFFSET, 0) = (initRect.x1 - initRect.x0) * (initRect.y1 - initRect.y0); + cvkalmanfilter_.statePost.at(OFFSET + 1, 0) = (initRect.x1 - initRect.x0) / (initRect.y1 - initRect.y0); + } + + // Predict the bounding box. + MxBase::ObjectInfo KalmanTracker::Predict() + { + // predict + // return X(k|k-1)=AX(k-1|k-1), and update + // P(k|k-1) <- AP(k-1|k-1)A'+Q + MxBase::ObjectInfo detectInfo = {}; + cv::Mat predictState = cvkalmanfilter_.predict(); + float *pData = (float *)(predictState.data); + float w = sqrt((*(pData + OFFSET)) * (*(pData + OFFSET + 1))); + if (w < DBL_EPSILON) + { + detectInfo.x0 = 0; + detectInfo.y0 = 0; + detectInfo.x1 = 0; + detectInfo.y1 = 0; + detectInfo.classId = 0; + return detectInfo; + } + if (w == 0) + { + MxBase::ObjectInfo w0DetectInfo = {}; + return w0DetectInfo; + } + float h = (*(pData + OFFSET)) / w; + float x = (*pData) - w / MULTIPLE; + float y = (*(pData + 1)) - h / MULTIPLE; + if (x < 0 && (*pData) > 0) + { + x = 0; + } + if (y < 0 && (*(pData + 1)) > 0) + { + y = 0; + } + detectInfo.x0 = x; + detectInfo.y0 = y; + detectInfo.x1 = x + w; + detectInfo.y1 = y + h; + return detectInfo; + } + + // Update the state using observed bounding box + void KalmanTracker::Update(MxBase::ObjectInfo stateMat) + { + // measurement_, update Z(k) + float *pData = (float *)(measurement_.data); + *pData = stateMat.x0 + (stateMat.x1 - stateMat.x0) / MULTIPLE; + *(pData + 1) = stateMat.y0 + (stateMat.y1 - stateMat.y0) / MULTIPLE; + *(pData + OFFSET) = (stateMat.x1 - stateMat.x0) * (stateMat.y1 - stateMat.y0); + *(pData + OFFSET + 1) = (stateMat.x1 - stateMat.x0) / (stateMat.y1 - stateMat.y0); + // update, do the following steps: + // Kg(k): P(k|k-1)H'/(HP(k|k-1))H'+R + // X(k|k): X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) + // P(k|k): (1-Kg(k)H)P(k|k-1) + cvkalmanfilter_.correct(measurement_); + } +} // namespace diff --git a/HelmetIdentification_V2/src/KalmanTracker.h b/HelmetIdentification_V2/src/KalmanTracker.h new file mode 100644 index 0000000..f5f3465 --- /dev/null +++ b/HelmetIdentification_V2/src/KalmanTracker.h @@ -0,0 +1,39 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H +#define MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H + +#include "opencv2/highgui/highgui.hpp" +#include "opencv2/video/tracking.hpp" +#include "DataType.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" + +namespace ascendVehicleTracking { +class KalmanTracker { +public: + KalmanTracker() {} + ~KalmanTracker() {} + void CvKalmanInit(MxBase::ObjectInfo initRect); + MxBase::ObjectInfo Predict(); + void Update(MxBase::ObjectInfo stateMat); +private: + cv::KalmanFilter cvkalmanfilter_ = {}; + cv::Mat measurement_ = {}; +}; +} // namesapce ascendVehicleTracking + +#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/MOTConnection.cpp b/HelmetIdentification_V2/src/MOTConnection.cpp new file mode 100644 index 0000000..14e4341 --- /dev/null +++ b/HelmetIdentification_V2/src/MOTConnection.cpp @@ -0,0 +1,262 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MOTConnection.h" +#include +#include +#include +#include +#include "opencv2/highgui.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include "MxBase/Log/Log.h" +#include "MxBase/ErrorCode/ErrorCodes.h" +#include "Hungarian.h" + +namespace ascendVehicleTracking +{ + namespace + { + // convert double to int + const int FLOAT_TO_INT = 1000; + const int MULTIPLE = 0; + const double SIMILARITY_THRESHOLD = 0.66; + const int MULTIPLE_IOU = 6; + const float NORM_EPS = 1e-10; + const double TIME_COUNTS = 1000.0; + const double COST_TIME_MS_THRESHOLD = 10.; + const float WIDTH_RATE_THRESH = 1.f; + const float HEIGHT_RATE_THRESH = 1.f; + const float X_DIST_RATE_THRESH = 1.3f; + const float Y_DIST_RATE_THRESH = 1.f; + } // namespace + + // 计算bounding box的交并比 + float CalIOU(MxBase::ObjectInfo detect1, MxBase::ObjectInfo detect2) + { + cv::Rect_ bbox1(detect1.x0, detect1.y0, detect1.x1 - detect1.x0, detect1.y1 - detect1.y0); + cv::Rect_ bbox2(detect2.x0, detect2.y0, detect2.x1 - detect2.x0, detect2.y1 - detect2.y0); + float intersectionArea = (bbox1 & bbox2).area(); + float unionArea = bbox1.area() + bbox2.area() - intersectionArea; + if (unionArea < DBL_EPSILON) + { + return 0.f; + } + return (intersectionArea / unionArea); + } + + // 计算前后两帧的两个bounding box的相似度 + float CalSimilarity(const TraceLet &traceLet, const MxBase::ObjectInfo &objectInfo, const int &method, const double &kIOU) + { + return CalIOU(traceLet.detectInfo, objectInfo); + } + + // 过滤掉交并比小于阈值的匹配 + void MOTConnection::FilterLowThreshold(const HungarianHandle &hungarianHandleObj, + const std::vector> &disMatrix, std::vector &matchedTracedDetected, + std::vector &detectVehicleFlagVec) + { + for (unsigned int i = 0; i < traceList_.size(); ++i) + { + if ((hungarianHandleObj.resX[i] != -1) && + (disMatrix[i][hungarianHandleObj.resX[i]] >= (trackThreshold_ * FLOAT_TO_INT))) + { + matchedTracedDetected.push_back(cv::Point(i, hungarianHandleObj.resX[i])); + detectVehicleFlagVec[hungarianHandleObj.resX[i]] = true; + } + else + { + traceList_[i].info.flag = LOST_VEHICLE; + } + } + } + + // 更新没有匹配上的跟踪器 + void MOTConnection::UpdateUnmatchedTraceLet(const std::vector> &objInfos) + { + for (auto itr = traceList_.begin(); itr != traceList_.end();) + { + if ((*itr).info.flag != LOST_VEHICLE) + { + ++itr; + continue; + } + + (*itr).lostAge++; + (*itr).kalman.Update((*itr).detectInfo); + + if ((*itr).lostAge < lostThreshold_) + { + continue; + } + + itr = traceList_.erase(itr); + } + } + + // 更新匹配上的跟踪器 + void MOTConnection::UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, + std::vector> &objInfos) + { + for (unsigned int i = 0; i < matchedTracedDetected.size(); ++i) + { + int traceIndex = matchedTracedDetected[i].x; + int detectIndex = matchedTracedDetected[i].y; + if (traceList_[traceIndex].info.survivalTime > MULTIPLE) + { + traceList_[traceIndex].info.flag = TRACkED_VEHICLE; + } + traceList_[traceIndex].info.survivalTime++; + traceList_[traceIndex].info.detectedTime++; + traceList_[traceIndex].lostAge = 0; + traceList_[traceIndex].detectInfo = objInfos[0][detectIndex]; + traceList_[traceIndex].kalman.Update(objInfos[0][detectIndex]); + } + } + // 将没有匹配上的检测更新为新的检测器 + void MOTConnection::AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue) + { + using Time = std::chrono::high_resolution_clock; + for (auto &vehicleObject : unmatchedVehicleObjectQueue) + { + // add new detected into traceList + TraceLet traceLet; + generatedId_++; + traceLet.info.id = generatedId_; + traceLet.info.survivalTime = 1; + traceLet.info.detectedTime = 1; + traceLet.lostAge = 0; + traceLet.info.flag = NEW_VEHICLE; + traceLet.detectInfo = vehicleObject; + traceLet.info.createTime = Time::now(); + + traceLet.kalman.CvKalmanInit(vehicleObject); + traceList_.push_back(traceLet); + } + } + + void MOTConnection::UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, + std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue) + { + UpdateMatchedTraceLet(matchedTracedDetected, objInfos); // 更新匹配上的跟踪器 + AddNewDetectedVehicle(unmatchedVehicleObjectQueue); // 将没有匹配上的检测更新为新的检测器 + UpdateUnmatchedTraceLet(objInfos); // 更新没有匹配上的跟踪器 + } + + void MOTConnection::TrackObjectPredict() + { + // every traceLet should do kalman predict + for (auto &traceLet : traceList_) + { + traceLet.detectInfo = traceLet.kalman.Predict(); // 卡尔曼滤波预测的框 + } + } + + void MOTConnection::TrackObjectUpdate(const std::vector> &objInfos, + std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue) + { + if (objInfos[0].size() > 0) + { + LogDebug << "[frame id = " << 1 << "], trace size =" << traceList_.size() << "detect size = " << objInfos[0].size() << ""; + // init vehicle matched flag + std::vector detectVehicleFlagVec; + for (unsigned int i = 0; i < objInfos[0].size(); ++i) + { + detectVehicleFlagVec.push_back(false); + } + // calculate the associated matrix + std::vector> disMatrix; + disMatrix.clear(); + disMatrix.resize(traceList_.size(), std::vector(objInfos[0].size(), 0)); + for (unsigned int j = 0; j < objInfos[0].size(); ++j) + { + for (unsigned int i = 0; i < traceList_.size(); ++i) + { + // 计算交并比 + float sim = CalSimilarity(traceList_[i], objInfos[0][j], method_, kIOU_); // method_=1, kIOU_=1.0 + disMatrix[i][j] = (int)(sim * FLOAT_TO_INT); + } + } + + // solve the assignment problem using hungarian 匈牙利算法进行匹配 + HungarianHandle hungarianHandleObj = {}; + HungarianHandleInit(hungarianHandleObj, traceList_.size(), objInfos[0].size()); + HungarianSolve(hungarianHandleObj, disMatrix, traceList_.size(), objInfos[0].size()); + // filter out matched but with low distance 过滤掉匹配上但是交并比较小的 + FilterLowThreshold(hungarianHandleObj, disMatrix, matchedTracedDetected, detectVehicleFlagVec); + LogDebug << "matchedTracedDetected = " << matchedTracedDetected.size() << ""; + // fill unmatchedVehicleObjectQueue + for (unsigned int i = 0; i < detectVehicleFlagVec.size(); ++i) + { + if (detectVehicleFlagVec[i] == false) + { + unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); + } + } + } + } + + APP_ERROR MOTConnection::ProcessSort(std::vector> &objInfos, size_t frameId) + { + std::vector unmatchedVehicleObjectQueue; + std::vector matchedTracedDetected; + if (objInfos[0].size() == 0) + { + return APP_ERR_COMM_FAILURE; + } + + if (traceList_.size() > 0) + { + // every traceLet should do kalman predict + TrackObjectPredict(); // 卡尔曼滤波预测 + TrackObjectUpdate(objInfos, matchedTracedDetected, unmatchedVehicleObjectQueue); // 选出matched track、unmatched detection + } + else + { + // traceList is empty, all the vehicle detected in the new frame are unmatched. + if (objInfos[0].size() > 0) + { + for (unsigned int i = 0; i < objInfos[0].size(); ++i) + { + unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); + } + } + } + + // update all the tracelet in the tracelist per frame + UpdateTraceLetAndFrame(matchedTracedDetected, objInfos, unmatchedVehicleObjectQueue); // 用matched track、unmatched detection更新跟踪器 + return APP_ERR_OK; + } + + // 获取跟踪后的检测框 + APP_ERROR MOTConnection::GettrackResult(std::vector &objInfos_) + { + if (traceList_.size() > 0) + { + for (auto &traceLet : traceList_) + { + traceLet.detectInfo.classId = traceLet.info.id; + objInfos_.push_back(traceLet.detectInfo); + } + } + else + { + return APP_ERR_COMM_FAILURE; + } + return APP_ERR_OK; + } +} +// namespace ascendVehicleTracking \ No newline at end of file diff --git a/HelmetIdentification_V2/src/MOTConnection.h b/HelmetIdentification_V2/src/MOTConnection.h new file mode 100644 index 0000000..be54f17 --- /dev/null +++ b/HelmetIdentification_V2/src/MOTConnection.h @@ -0,0 +1,78 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H +#define MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H + +#include +#include +#include +#include "KalmanTracker.h" +#include "Hungarian.h" +#include "DataType.h" +#include "MxBase/ErrorCode/ErrorCodes.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/Tensor/TensorBase/TensorBase.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +namespace ascendVehicleTracking { +struct TraceLet { + TraceInfo info = {}; + int32_t lostAge = 0; + KalmanTracker kalman; + std::list> shortFeatureQueue; + MxBase::ObjectInfo detectInfo = {}; +}; + +class MOTConnection { +public: + APP_ERROR ProcessSort(std::vector> &objInfos, size_t frameId); + APP_ERROR GettrackResult(std::vector &objInfos_); + +private: + double trackThreshold_ = 0.3; + double kIOU_ = 1.0; + int32_t method_ = 1; + int32_t lostThreshold_ = 3; + uint32_t maxNumberFeature_ = 0; + int32_t generatedId_ = 0; + std::vector traceList_ = {}; + +private: + + void FilterLowThreshold(const HungarianHandle &hungarianHandleObj, const std::vector> &disMatrix, + std::vector &matchedTracedDetected, std::vector &detectVehicleFlagVec); + + void UpdateUnmatchedTraceLet(const std::vector> &objInfos); + + void UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, + std::vector> &objInfos); + + void AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue); + + void UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, + std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue); + + void TrackObjectPredict(); + void TrackObjectUpdate(const std::vector> &objInfos, + std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue); +}; +} // namespace ascendVehicleTracking + +#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/cropResizePaste.hpp b/HelmetIdentification_V2/src/cropResizePaste.hpp new file mode 100644 index 0000000..b3648ec --- /dev/null +++ b/HelmetIdentification_V2/src/cropResizePaste.hpp @@ -0,0 +1,114 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef CRP_H +#define CRP_H + +#include "MxBase/E2eInfer/Image/Image.h" +#include "MxBase/E2eInfer/Rect/Rect.h" +#include "MxBase/E2eInfer/Size/Size.h" + +#include "acl/dvpp/hi_dvpp.h" +#include "acl/acl.h" +#include "acl/acl_rt.h" + +#define CONVER_TO_PODD(NUM) (((NUM) % 2 != 0) ? (NUM) : ((NUM)-1)) +#define CONVER_TO_EVEN(NUM) (((NUM) % 2 == 0) ? (NUM) : ((NUM)-1)) +#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) + +MxBase::Rect GetPasteRect(uint32_t inputWidth, uint32_t inputHeight, uint32_t outputWidth, uint32_t outputHeight) +{ + bool widthRatioLarger = true; + float resizeRatio = static_cast(inputWidth) / outputWidth; + if (resizeRatio < (static_cast(inputHeight) / outputHeight)) + { + resizeRatio = static_cast(inputHeight) / outputHeight; + widthRatioLarger = false; + } + + // (x0, y0)是左上角坐标,(x1, y1)是右下角坐标,采用图片坐标系 + uint32_t x0; + uint32_t y0; + uint32_t x1; + uint32_t y1; + if (widthRatioLarger) + { + // 原图width大于height + x0 = 0; + y0 = 0; + x1 = outputWidth-1; + y1 = inputHeight / resizeRatio - 1; + } + else + { + // 原图height大于width + x0 = 0; + y0 = 0; + x1 = inputWidth / resizeRatio - 1; + y1 = outputHeight - 1; + } + x0 = DVPP_ALIGN_UP(CONVER_TO_EVEN(x0), 16); // 16对齐 + x1 = DVPP_ALIGN_UP((x1 - x0 + 1), 16) + x0 - 1; + y0 = CONVER_TO_EVEN(y0); + y1 = CONVER_TO_PODD(y1); + MxBase::Rect res(x0, y0, x1, y1); + return res; +} + +MxBase::Image ConstructImage(uint32_t resizeWidth, uint32_t resizeHeight) +{ + void *addr; + uint32_t dataSize = resizeWidth * resizeHeight * 3 / 2; + auto ret = hi_mpi_dvpp_malloc(0, &addr, dataSize); + if (ret != APP_ERR_OK) + { + LogError << "hi_mpi_dvpp_malloc fail :" << ret; + } + // 第三个参数从128改成了0 + ret = aclrtMemset(addr, dataSize, 0, dataSize); + if (ret != APP_ERR_OK) + { + LogError << "aclrtMemset fail :" << ret; + } + std::shared_ptr data((uint8_t *)addr, hi_mpi_dvpp_free); + MxBase::Size imageSize(resizeWidth, resizeHeight); + MxBase::Image pastedImg(data, dataSize, 0, imageSize); + return pastedImg; +} + +std::pair GenerateRect(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight) +{ + uint32_t x1 = CONVER_TO_PODD(originalWidth - 1); + uint32_t y1 = CONVER_TO_PODD(originalHeight - 1); + MxBase::Rect cropRect(0, 0, x1, y1); + MxBase::Rect pasteRect = GetPasteRect(originalWidth, originalHeight, resizeWidth, resizeHeight); + std::pair cropPasteRect(cropRect, pasteRect); + return cropPasteRect; +} + +MxBase::Image resizeKeepAspectRatioFit(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight, MxBase::Image &decodeImage, MxBase::ImageProcessor& imageProcessor) +{ + std::pair cropPasteRect = GenerateRect(originalWidth, originalHeight, resizeWidth, resizeHeight); + MxBase::Image resizeImage = ConstructImage(resizeWidth, resizeHeight); + auto ret = imageProcessor.CropAndPaste(decodeImage, cropPasteRect, resizeImage); + if (ret != APP_ERR_OK) + { + LogError << "CropAndPaste fail :" << ret; + } + return resizeImage; +} + +#endif // CRP_H \ No newline at end of file diff --git a/HelmetIdentification_V2/src/main-image.cpp b/HelmetIdentification_V2/src/main-image.cpp new file mode 100644 index 0000000..a764a67 --- /dev/null +++ b/HelmetIdentification_V2/src/main-image.cpp @@ -0,0 +1,170 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils.h" + +#include +#include +#include +using namespace std; + +// 如果在200DK上运行就改为 USE_200DK +#define USE_DVPP + +APP_ERROR readImage(std::string imgPath, MxBase::Image& image, MxBase::ImageProcessor& imageProcessor) +{ + APP_ERROR ret; +#ifdef USE_DVPP + // if USE DVPP + ret = imageProcessor.Decode(imgPath, image); +#endif +#ifdef USE_200DK + std::shared_ptr dataPtr; + uint32_t dataSize; + // Get image data to memory, this method can be substituted or designed by yourself! + std::ifstream file; + file.open(imgPath.c_str(), std::ios::binary); + if (!file) + { + LogInfo << "Invalid file."; + return APP_ERR_COMM_INVALID_PARAM; + } + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); + + char *p = (char *)malloc(content.size()); + memcpy(p, content.data(), content.size()); + auto deleter = [](void *p) -> void + { + free(p); + p = nullptr; + }; + + dataPtr.reset(static_cast((void *)(p)), deleter); + dataSize = content.size(); + file.close(); + if (ret != APP_ERR_OK) + { + LogError << "Getimage failed, ret=" << ret; + return ret; + } + ret = imageProcessor.Decode(dataPtr, dataSize, image, MxBase::ImageFormat::YUV_SP_420); + // endif +#endif + if (ret != APP_ERR_OK) + { + LogError << "Decode failed, ret=" << ret; + return ret; + } +} + +void postProcess(std::vector modelOutputs, std::shared_ptr postProcessorDptr, MxBase::Size originalSize, MxBase::Size resizeSize) +{ + MxBase::ResizedImageInfo imgInfo; + auto shape = modelOutputs[0].GetShape(); + imgInfo.widthOriginal = originalSize.width; + imgInfo.heightOriginal = originalSize.height; + imgInfo.widthResize = resizeSize.width; + imgInfo.heightResize = resizeSize.height; + imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; + float resizeRate = originalSize.width > originalSize.height ? (originalSize.width * 1.0 / videoInfo::YOLOV5_RESIZE) : (originalSize.height * 1.0 / videoInfo::YOLOV5_RESIZE); + imgInfo.keepAspectRatioScaling = 1 / resizeRate; + std::vector imageInfoVec = {}; + imageInfoVec.push_back(imgInfo); + // make postProcess inputs + std::vector tensors; + for (size_t i = 0; i < modelOutputs.size(); i++) + { + MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); + MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); + tensors.push_back(tensorBase); + } + std::vector> objectInfos; + postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); + std::cout << "===---> Size of objectInfos is " << objectInfos.size() << std::endl; + for (size_t i = 0; i < objectInfos.size(); i++) + { + std::cout << "objectInfo-" << i << " , Size:" << objectInfos[i].size() << std::endl; + for (size_t j = 0; j < objectInfos[i].size(); j++) + { + std::cout << std::endl + << "*****objectInfo-" << i << ":" << j << std::endl; + std::cout << "x0 is " << objectInfos[i][j].x0 << std::endl; + std::cout << "y0 is " << objectInfos[i][j].y0 << std::endl; + std::cout << "x1 is " << objectInfos[i][j].x1 << std::endl; + std::cout << "y1 is " << objectInfos[i][j].y1 << std::endl; + std::cout << "confidence is " << objectInfos[i][j].confidence << std::endl; + std::cout << "classId is " << objectInfos[i][j].classId << std::endl; + std::cout << "className is " << objectInfos[i][j].className << std::endl; + } + } +} + +APP_ERROR main(int argc, char *argv[]) +{ + APP_ERROR ret; + + // global init + ret = MxBase::MxInit(); + if (ret != APP_ERR_OK) + { + LogError << "MxInit failed, ret=" << ret << "."; + } + // 检测是否输入了文件路径 + if (argc <= 1) + { + LogWarn << "Please input image path, such as 'test.jpg'."; + return APP_ERR_OK; + } + + // imageProcess init + MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); + // model init + MxBase::Model yoloModel(videoInfo::modelPath, videoInfo::DEVICE_ID); + // postprocessor init + std::map postConfig; + postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); + postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); + std::shared_ptr postProcessorDptr = std::make_shared(); + postProcessorDptr->Init(postConfig); + + std::string imgPath = argv[1]; + // 读取图片 + MxBase::Image image; + readImage(imgPath, image, imageProcessor); + + // 缩放图片 + MxBase::Size originalSize = image.GetOriginalSize(); + MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); + MxBase::Image resizedImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); + + // 模型推理 + MxBase::Tensor tensorImg = resizedImage.ConvertToTensor(); + tensorImg.ToDevice(videoInfo::DEVICE_ID); + std::vector inputs; + inputs.push_back(tensorImg); + std::vector modelOutputs = yoloModel.Infer(inputs); + for (auto output : modelOutputs) + { + output.ToHost(); + } + + // 后处理 + postProcess(modelOutputs, postProcessorDptr, originalSize, resizeSize); + + return APP_ERR_OK; +} \ No newline at end of file diff --git a/HelmetIdentification_V2/src/main.cpp b/HelmetIdentification_V2/src/main.cpp new file mode 100644 index 0000000..2eb3af9 --- /dev/null +++ b/HelmetIdentification_V2/src/main.cpp @@ -0,0 +1,330 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils.h" + +extern "C" +{ +#include "libavformat/avformat.h" +} + +#include +#include +#include +#include "unistd.h" +#include +#include +#include +#include "boost/filesystem.hpp" + +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "opencv2/opencv.hpp" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" +#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" +#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" +#include "MxBase/E2eInfer/DataType.h" + +#include +#include +using namespace std; +using namespace videoInfo; +namespace frameConfig +{ + size_t channelId0 = 0; + size_t channelId1 = 1; + size_t frameCountChannel0 = 300; + size_t frameCountChannel1 = 300; + size_t skipIntervalChannel0 = 2; + size_t skipIntervalChannel1 = 2; + + // channel0对应文件的指针 + AVFormatContext *pFormatCtx0 = nullptr; + // channel1对应文件的指针 + AVFormatContext *pFormatCtx1 = nullptr; +} + +// 检查文件(是否存在、是否为文件夹) +bool checkFile(std::string &fileName) +{ + // 判断视频文件是否存在 + ifstream f(fileName.c_str()); + if (!f.good()) + { + LogError << "file not exists! " << fileName; + return false; + } + else + { + // 如果存在还需要判断是否是文件夹 + struct stat s; + if (stat(fileName.c_str(), &s) == 0) + { + if (s.st_mode & S_IFDIR) + { + LogError << fileName << " is a directory!"; + return false; + } + } + } + return true; +} + +// ffmpeg拉流 +AVFormatContext *CreateFormatContext(std::string filePath) +{ + LogInfo << "start to CreatFormatContext!"; + // creat message for stream pull + AVFormatContext *formatContext = nullptr; + AVDictionary *options = nullptr; + + LogInfo << "start to avformat_open_input!"; + int ret = avformat_open_input(&formatContext, filePath.c_str(), nullptr, &options); + if (options != nullptr) + { + av_dict_free(&options); + } + if (ret != 0) + { + LogError << "Couldn't open input stream " << filePath.c_str() << " ret=" << ret; + return nullptr; + } + ret = avformat_find_stream_info(formatContext, nullptr); + if (ret != 0) + { + LogError << "Couldn't open input stream information"; + return nullptr; + } + return formatContext; +} + +// 真正的拉流函数 +void PullStream0(std::string filePath) +{ + av_register_all(); + avformat_network_init(); + frameConfig::pFormatCtx0 = avformat_alloc_context(); + frameConfig::pFormatCtx0 = CreateFormatContext(filePath); + av_dump_format(frameConfig::pFormatCtx0, 0, filePath.c_str(), 0); +} +void PullStream1(std::string filePath) +{ + av_register_all(); + avformat_network_init(); + frameConfig::pFormatCtx1 = avformat_alloc_context(); + frameConfig::pFormatCtx1 = CreateFormatContext(filePath); + av_dump_format(frameConfig::pFormatCtx1, 0, filePath.c_str(), 0); +} + +// 视频解码回调(样例代码,测试可以跑通,但是不能直接复用) +APP_ERROR CallBackVdec(MxBase::Image &decodedImage, uint32_t channelId, uint32_t frameId, void *userData) +{ + FrameImage frameImage; + frameImage.image = decodedImage; + frameImage.channelId = channelId; + frameImage.frameId = frameId; + + videoInfo::g_threadsMutex_frameImageQueue.lock(); + videoInfo::frameImageQueue.push(frameImage); + videoInfo::g_threadsMutex_frameImageQueue.unlock(); + + return APP_ERR_OK; +} + +// 获取H264中的帧 +void GetFrame(AVPacket &pkt, FrameImage &frameImage, AVFormatContext *pFormatCtx) +{ + av_init_packet(&pkt); + int ret = av_read_frame(pFormatCtx, &pkt); + if (ret != 0) + { + LogInfo << "[StreamPuller] channel Read frame failed, continue!"; + if (ret == AVERROR_EOF) + { + LogInfo << "[StreamPuller] channel StreamPuller is EOF, over!"; + return; + } + return; + } + else + { + if (pkt.size <= 0) + { + LogError << "Invalid pkt.size: " << pkt.size; + return; + } + + // send to the device + auto hostDeleter = [](void *dataPtr) -> void {}; + MxBase::MemoryData data(pkt.size, MxBase::MemoryData::MEMORY_HOST); + MxBase::MemoryData src((void *)(pkt.data), pkt.size, MxBase::MemoryData::MEMORY_HOST); + APP_ERROR ret = MxBase::MemoryHelper::MxbsMallocAndCopy(data, src); + if (ret != APP_ERR_OK) + { + LogError << "MxbsMallocAndCopy failed!"; + } + std::shared_ptr imageData((uint8_t *)data.ptrData, hostDeleter); + + MxBase::Image subImage(imageData, pkt.size); + frameImage.image = subImage; + + LogDebug << "'channelId = " << frameImage.channelId << ", frameId = " << frameImage.frameId << " , dataSize = " << frameImage.image.GetDataSize(); + + av_packet_unref(&pkt); + } + return; +} + +// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 +void VdecThread0(size_t frameCount, size_t skipInterval, int32_t channelId) +{ + AVPacket pkt; + uint32_t frameId = 0; + // 解码器参数 + MxBase::VideoDecodeConfig config; + MxBase::VideoDecodeCallBack cPtr = CallBackVdec; + config.width = videoInfo::SRC_WIDTH; + config.height = videoInfo::SRC_HEIGHT; + config.callbackFunc = cPtr; + config.skipInterval = skipInterval; // 跳帧控制 + + std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); + for (size_t i = 0; i < frameCount; i++) + { + MxBase::Image subImage; + FrameImage frame; + frame.channelId = channelId; + frame.frameId = frameId; + frame.image = subImage; + GetFrame(pkt, frame, frameConfig::pFormatCtx0); + APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); + if (ret != APP_ERR_OK) + { + LogError << "videoDecoder Decode failed. ret is: " << ret; + } + frameId += 1; + } +} + +// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 +void VdecThread1(size_t frameCount, size_t skipInterval, int32_t channelId) +{ + AVPacket pkt; + uint32_t frameId = 0; + // 解码器参数 + MxBase::VideoDecodeConfig config; + MxBase::VideoDecodeCallBack cPtr = CallBackVdec; + config.width = videoInfo::SRC_WIDTH; + config.height = videoInfo::SRC_HEIGHT; + config.callbackFunc = cPtr; + // 跳帧控制 + config.skipInterval = skipInterval; + + std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); + for (size_t i = 0; i < frameCount; i++) + { + MxBase::Image subImage; + FrameImage frame; + frame.channelId = channelId; + frame.frameId = frameId; + frame.image = subImage; + GetFrame(pkt, frame, frameConfig::pFormatCtx1); + APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); + if (ret != APP_ERR_OK) + { + LogError << "videoDecoder Decode failed. ret is: " << ret; + } + frameId += 1; + } +} + +APP_ERROR main(int argc, char *argv[]) +{ + // 初始化 + APP_ERROR ret = MxBase::MxInit(); + if (ret != APP_ERR_OK) + { + LogError << "MxInit failed, ret=" << ret << "."; + } + // 检测是否输入了文件路径 + if (argc <= 1) + { + LogWarn << "Please input video path, such as './video_sample test.264'."; + return APP_ERR_OK; + } + + std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); + + // 视频流解码线程(根据输入视频的数量确定是一路还是两路) + if (argc == 2) + { + std::string videoPath0 = argv[1]; + if (!checkFile(videoPath0)) + { + return APP_ERR_OK; + } + PullStream0(videoPath0); + std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0); + threadVdec0.join(); + } + else if (argc == 3) + { + std::string videoPath0 = argv[1]; + std::string videoPath1 = argv[2]; + if (!checkFile(videoPath0)) + { + return APP_ERR_OK; + } + if (!checkFile(videoPath1)) + { + return APP_ERR_OK; + } + PullStream0(videoPath0); + std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0); + PullStream1(videoPath1); + std::thread threadVdec1(VdecThread1, frameConfig::frameCountChannel1, frameConfig::skipIntervalChannel1, frameConfig::channelId1); + threadVdec0.join(); + threadVdec1.join(); + } + + size_t target_count = videoInfo::frameImageQueue.size(); + LogInfo << "frameImageQueue.size: " << videoInfo::frameImageQueue.size(); + + // resize线程 + std::thread resizeThread(resizeMethod, start_time, target_count); + resizeThread.join(); + + // 推理线程 + std::thread inferThread(inferMethod, start_time, target_count); + inferThread.join(); + + // 后处理线程 + std::thread postprocessThread(postprocessMethod, start_time, target_count); + postprocessThread.join(); + + // 跟踪去重线程 + std::thread trackThread(trackMethod, start_time, target_count); + trackThread.join(); + + std::chrono::high_resolution_clock::time_point end_time = std::chrono::high_resolution_clock::now(); + double_t cost_time = std::chrono::duration_cast(end_time - start_time).count(); + LogInfo << "端到端时间为" << cost_time / videoInfo::MS_PPE_SECOND; + + return APP_ERR_OK; +} \ No newline at end of file diff --git a/HelmetIdentification_V2/src/utils.h b/HelmetIdentification_V2/src/utils.h new file mode 100644 index 0000000..62ac822 --- /dev/null +++ b/HelmetIdentification_V2/src/utils.h @@ -0,0 +1,350 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_UTILS_H +#define MXBASE_HELMETIDENTIFICATION_UTILS_H + +#include +#include +#include +#include "unistd.h" +#include +#include +#include +#include "boost/filesystem.hpp" + +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "opencv2/opencv.hpp" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" +#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" +#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" +#include "MxBase/E2eInfer/DataType.h" + +#include "MxBase/MxBase.h" +#include "MOTConnection.h" +#include "cropResizePaste.hpp" +#include "chrono" +#include "time.h" + +struct FrameImage +{ + MxBase::Image image; + uint32_t frameId = 0; + uint32_t channelId = 0; +}; + +namespace videoInfo +{ + const uint32_t SRC_WIDTH = 1920; + const uint32_t SRC_HEIGHT = 1080; + + const uint32_t YUV_BYTE_NU = 3; + const uint32_t YUV_BYTE_DE = 2; + const uint32_t YOLOV5_RESIZE = 640; + + // 要检测的目标类别的标签 + const std::string TARGET_CLASS_NAME = "head"; + // 使用chrono计数结果为毫秒,需要除以1000转换为秒 + double MS_PPE_SECOND = 1000.0; + + const uint32_t DEVICE_ID = 0; + std::string labelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/imgclass.names"; + std::string configPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/Helmet_yolov5.cfg"; + std::string modelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/YOLOv5_s.om"; + + // 读入视频帧Image的队列 + std::queue frameImageQueue; + // 读入视频帧Image队列的线程锁 + std::mutex g_threadsMutex_frameImageQueue; + + // resize前即原始图像的vector + std::vector realImageVector; + // resize后Image的vector + std::vector resizedImageVector; + // resize后Image队列的线程锁 + std::mutex g_threadsMutex_resizedImageVector; + + // 推理后tensor的vector + std::vector> inferOutputVector; + // 推理后tensor队列的线程锁 + std::mutex g_threadsMutex_inferOutputVector; + + // 后处理后objectInfos的队列 map> + std::vector>> postprocessOutputVector; + // 后处理后objectInfos队列的线程锁 + std::mutex g_threadsMutex_postprocessOutputVector; +} +namespace fs = boost::filesystem; + +// resize线程 +void resizeMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point resize_start_time = std::chrono::high_resolution_clock::now(); + + MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); + + size_t resize_count = 0; + while (resize_count < target_count) + { + if (videoInfo::frameImageQueue.empty()) + { + continue; + } + // 取图像并resize + FrameImage frame = videoInfo::frameImageQueue.front(); + MxBase::Image image = frame.image; + MxBase::Size originalSize = image.GetOriginalSize(); + MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); + MxBase::Image outputImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); + // 先将缩放后的图像放入resizeImage的队列 + FrameImage resizedFrame; + resizedFrame.channelId = frame.channelId; + resizedFrame.frameId = frame.frameId; + resizedFrame.image = outputImage; + videoInfo::g_threadsMutex_resizedImageVector.lock(); + frame.image.ToHost(); + videoInfo::realImageVector.push_back(frame); + videoInfo::resizedImageVector.push_back(resizedFrame); + videoInfo::g_threadsMutex_resizedImageVector.unlock(); + // 然后再将原图pop出去 + videoInfo::g_threadsMutex_frameImageQueue.lock(); + videoInfo::frameImageQueue.pop(); + videoInfo::g_threadsMutex_frameImageQueue.unlock(); + // 计数 + resize_count++; + } + std::chrono::high_resolution_clock::time_point resize_end_time = std::chrono::high_resolution_clock::now(); + double_t resize_cost_time = std::chrono::duration_cast(resize_end_time - resize_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t resize_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到缩放全部完成一共花费: " << resize_finish_time << ", 缩放本身花费: " << resize_cost_time; +} + +// 推理线程 +void inferMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point infer_start_time = std::chrono::high_resolution_clock::now(); + + std::shared_ptr modelDptr = std::make_shared(videoInfo::modelPath, videoInfo::DEVICE_ID); + + size_t infer_count = 0; + while (infer_count < target_count) + { + if (infer_count >= videoInfo::resizedImageVector.size()) + { + continue; + } + // 从resize后的队列中取图片 + FrameImage resizedFrame = videoInfo::resizedImageVector[infer_count]; + std::vector modelOutputs; + MxBase::Tensor tensorImg = resizedFrame.image.ConvertToTensor(); + tensorImg.ToDevice(videoInfo::DEVICE_ID); + std::vector inputs; + inputs.push_back(tensorImg); + modelOutputs = modelDptr->Infer(inputs); + for (auto output : modelOutputs) + { + output.ToHost(); + } + + // 将推理结果存入队列 + videoInfo::g_threadsMutex_inferOutputVector.lock(); + videoInfo::inferOutputVector.push_back(modelOutputs); + videoInfo::g_threadsMutex_inferOutputVector.unlock(); + // 计数 + infer_count++; + } + std::chrono::high_resolution_clock::time_point infer_end_time = std::chrono::high_resolution_clock::now(); + double_t infer_cost_time = std::chrono::duration_cast(infer_end_time - infer_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t infer_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到推理全部完成一共花费: " << infer_finish_time << ", 推理本身花费: " << infer_cost_time; +} + +// 后处理线程 +void postprocessMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point postprocess_start_time = std::chrono::high_resolution_clock::now(); + + std::map postConfig; + postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); + postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); + std::shared_ptr postProcessorDptr = std::make_shared(); + if (postProcessorDptr == nullptr) + { + LogError << "init postProcessor failed, nullptr"; + } + postProcessorDptr->Init(postConfig); + + size_t postprocess_count = 0; + while (postprocess_count < target_count) + { + if (postprocess_count >= videoInfo::inferOutputVector.size()) + { + continue; + } + // 取原图信息用于计算 + MxBase::Size originalSize = videoInfo::realImageVector[postprocess_count].image.GetOriginalSize(); + // 从推理结果的队列里面取出推理结果 + std::vector modelOutputs = videoInfo::inferOutputVector[postprocess_count]; + FrameImage resizedFrame = videoInfo::resizedImageVector[postprocess_count]; + // 新的后处理过程 + MxBase::ResizedImageInfo imgInfo; + auto shape = modelOutputs[0].GetShape(); + imgInfo.widthOriginal = originalSize.width; + imgInfo.heightOriginal = originalSize.height; + imgInfo.widthResize = videoInfo::YOLOV5_RESIZE; + imgInfo.heightResize = videoInfo::YOLOV5_RESIZE; + imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; + // 因为yolov5要求输入图像为640*640,所以直接比较原图的height和width就好(如果不理解就去看cropResizePaste.hpp里的GetPasteRect函数) + float resizeRate = originalSize.width > originalSize.height ? (originalSize.width * 1.0 / videoInfo::YOLOV5_RESIZE) : (originalSize.height * 1.0 / videoInfo::YOLOV5_RESIZE); + imgInfo.keepAspectRatioScaling = 1 / resizeRate; + std::vector imageInfoVec = {}; + imageInfoVec.push_back(imgInfo); + // make postProcess inputs + std::vector tensors; + for (size_t i = 0; i < modelOutputs.size(); i++) + { + MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); + MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); + tensors.push_back(tensorBase); + } + // 后处理 + std::vector> objectInfos; + postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); + + // 将后处理结果存入队列 + videoInfo::g_threadsMutex_postprocessOutputVector.lock(); + videoInfo::postprocessOutputVector.push_back(objectInfos); + videoInfo::g_threadsMutex_postprocessOutputVector.unlock(); + // 计数 + postprocess_count++; + } + std::chrono::high_resolution_clock::time_point postprocess_end_time = std::chrono::high_resolution_clock::now(); + double_t postprocess_cost_time = std::chrono::duration_cast(postprocess_end_time - postprocess_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t postprocess_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到后处理全部完成一共花费: " << postprocess_finish_time << ", 后处理本身花费: " << postprocess_cost_time; +} + +// 跟踪去重线程 +void trackMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point track_start_time = std::chrono::high_resolution_clock::now(); + + std::shared_ptr tracker0 = std::make_shared(); + if (tracker0 == nullptr) + { + LogError << "init tracker0 failed, nullptr"; + } + std::shared_ptr tracker1 = std::make_shared(); + if (tracker1 == nullptr) + { + LogError << "init tracker1 failed, nullptr"; + } + // 用于计算帧率 + size_t old_count = 0; + std::chrono::high_resolution_clock::time_point count_time; + std::chrono::high_resolution_clock::time_point old_count_time = std::chrono::high_resolution_clock::now(); + size_t one_step = 2; + size_t track_count = 0; + while (track_count < target_count) + { + // 计算帧率 + // 如果count_time-old_count_time的值大于one_step,就计算一下这个step里面的帧数,然后除以step的值 + count_time = std::chrono::high_resolution_clock::now(); + if (std::chrono::duration_cast(count_time - old_count_time).count() / videoInfo::MS_PPE_SECOND > one_step) + { + old_count_time = count_time; + LogInfo << "rate: " << (track_count - old_count) / one_step * 1.0; + old_count = track_count; + } + // 下面是业务循环 + if (track_count >= videoInfo::postprocessOutputVector.size()) + { + continue; + } + // 从后处理结果的队列中取结果用于跟踪去重 + std::vector> objectInfos = videoInfo::postprocessOutputVector[track_count]; + FrameImage frame = videoInfo::realImageVector[track_count]; + MxBase::Size originalSize = frame.image.GetOriginalSize(); + + // 根据channelId的不同使用不同的tracker + std::vector objInfos_ = {}; + if (frame.channelId == 0) + { + tracker0->ProcessSort(objectInfos, frame.frameId); + APP_ERROR ret = tracker0->GettrackResult(objInfos_); + if (ret != APP_ERR_OK) + { + LogError << "No tracker0"; + } + } + else + { + tracker1->ProcessSort(objectInfos, frame.frameId); + APP_ERROR ret = tracker1->GettrackResult(objInfos_); + if (ret != APP_ERR_OK) + { + LogError << "No tracker1"; + } + } + + uint32_t video_height = originalSize.height; + uint32_t video_width = originalSize.width; + // 初始化OpenCV图像信息矩阵 + cv::Mat imgYuv = cv::Mat(video_height * videoInfo::YUV_BYTE_NU / videoInfo::YUV_BYTE_DE, video_width, CV_8UC1, frame.image.GetData().get()); + cv::Mat imgBgr = cv::Mat(video_height, video_width, CV_8UC3); + // 颜色空间转换 + cv::cvtColor(imgYuv, imgBgr, cv::COLOR_YUV420sp2RGB); + std::vector info; + bool headFlag = false; + for (uint32_t i = 0; i < objInfos_.size(); i++) + { + if (objInfos_[i].className == videoInfo::TARGET_CLASS_NAME) + { + headFlag = true; + LogWarn << "Warning:Not wearing a helmet, channelId:" << frame.channelId << ", frameId:" << frame.frameId; + // (blue, green, red) + const cv::Scalar color = cv::Scalar(0, 0, 255); + // width for rectangle + const uint32_t thickness = 2; + // draw the rectangle + cv::rectangle(imgBgr, + cv::Rect(objInfos_[i].x0, objInfos_[i].y0, objInfos_[i].x1 - objInfos_[i].x0, objInfos_[i].y1 - objInfos_[i].y0), + color, thickness); + } + } + // 如果检测结果中有head标签,就保存为图片 + if (headFlag) + { + // 把Mat类型的图像矩阵保存为图像到指定位置。 + std::string outPath = frame.channelId == 0 ? "one" : "two"; + std::string fileName = "./result/" + outPath + "/result" + std::to_string(frame.frameId) + ".jpg"; + cv::imwrite(fileName, imgBgr); + } + + // 计数 + track_count++; + } + std::chrono::high_resolution_clock::time_point track_end_time = std::chrono::high_resolution_clock::now(); + double_t track_cost_time = std::chrono::duration_cast(track_end_time - track_start_time).count() / videoInfo::MS_PPE_SECOND; + double_t track_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; + LogInfo << "总共" << target_count << "帧, 到跟踪去重全部完成一共花费: " << track_finish_time << ", 跟踪去重本身花费: " << track_cost_time; +} + +#endif From 73b7b8d8c1692286d1fd4c9899ed38b7af6f8b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 05:51:30 +0000 Subject: [PATCH 29/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/src/.keep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/src/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 HelmetIdentification_V2/src/.keep diff --git a/HelmetIdentification_V2/src/.keep b/HelmetIdentification_V2/src/.keep deleted file mode 100644 index e69de29..0000000 From 7b45abcce280a7060a6653d80fefa6bf207df3e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 08:13:04 +0000 Subject: [PATCH 30/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/src?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/src/CMakeLists.txt | 47 --- HelmetIdentification_V2/src/DataType.h | 94 ----- HelmetIdentification_V2/src/Hungarian.cpp | 165 --------- HelmetIdentification_V2/src/Hungarian.h | 46 --- HelmetIdentification_V2/src/KalmanTracker.cpp | 143 ------- HelmetIdentification_V2/src/KalmanTracker.h | 39 -- HelmetIdentification_V2/src/MOTConnection.cpp | 262 ------------- HelmetIdentification_V2/src/MOTConnection.h | 78 ---- .../src/cropResizePaste.hpp | 114 ------ HelmetIdentification_V2/src/main-image.cpp | 170 --------- HelmetIdentification_V2/src/main.cpp | 330 ----------------- HelmetIdentification_V2/src/utils.h | 350 ------------------ 12 files changed, 1838 deletions(-) delete mode 100644 HelmetIdentification_V2/src/CMakeLists.txt delete mode 100644 HelmetIdentification_V2/src/DataType.h delete mode 100644 HelmetIdentification_V2/src/Hungarian.cpp delete mode 100644 HelmetIdentification_V2/src/Hungarian.h delete mode 100644 HelmetIdentification_V2/src/KalmanTracker.cpp delete mode 100644 HelmetIdentification_V2/src/KalmanTracker.h delete mode 100644 HelmetIdentification_V2/src/MOTConnection.cpp delete mode 100644 HelmetIdentification_V2/src/MOTConnection.h delete mode 100644 HelmetIdentification_V2/src/cropResizePaste.hpp delete mode 100644 HelmetIdentification_V2/src/main-image.cpp delete mode 100644 HelmetIdentification_V2/src/main.cpp delete mode 100644 HelmetIdentification_V2/src/utils.h diff --git a/HelmetIdentification_V2/src/CMakeLists.txt b/HelmetIdentification_V2/src/CMakeLists.txt deleted file mode 100644 index 6fcd298..0000000 --- a/HelmetIdentification_V2/src/CMakeLists.txt +++ /dev/null @@ -1,47 +0,0 @@ -# CMake lowest version requirement -cmake_minimum_required(VERSION 3.5.1) -# project information -project(Individual) - -# Compile options -add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0 -Dgoogle=mindxsdk_private) -add_compile_options(-std=c++11 -fPIC -fstack-protector-all -Wall -D_FORTIFY_SOURCE=2 -O2) - -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../../") -set(CMAKE_CXX_FLAGS_DEBUG "-g") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now,-z,noexecstack -s -pie -pthread") -set(CMAKE_SKIP_RPATH TRUE) - -SET(CMAKE_BUILD_TYPE "Debug") -SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb") -SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall") - -# Header path -include_directories( - ${MX_SDK_HOME}/include/ - ${MX_SDK_HOME}/opensource/include/ - ${MX_SDK_HOME}/opensource/include/opencv4/ - /home/HwHiAiUser/Ascend/ascend-toolkit/latest/include/ - ./ -) - -# add host lib path -link_directories( - ${MX_SDK_HOME}/lib/ - ${MX_SDK_HOME}/lib/modelpostprocessors - ${MX_SDK_HOME}/opensource/lib/ - ${MX_SDK_HOME}/opensource/lib64/ - /usr/lib/aarch64-linux-gnu/ - /home/HwHiAiUser/Ascend/ascend-toolkit/latest/lib64/ - /usr/local/Ascend/driver/lib64/ - ./ -) - - -aux_source_directory(. sourceList) - -add_executable(main ${sourceList}) - -target_link_libraries(main mxbase opencv_world boost_filesystem glog avformat avcodec avutil cpprest yolov3postprocess ascendcl acl_dvpp_mpi) - -install(TARGETS main DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/HelmetIdentification_V2/src/DataType.h b/HelmetIdentification_V2/src/DataType.h deleted file mode 100644 index dc34a0a..0000000 --- a/HelmetIdentification_V2/src/DataType.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_DATATYPE_H -#define MXBASE_HELMETIDENTIFICATION_DATATYPE_H - -#include -#include -#include -#include -#include -#include -#include "opencv2/highgui.hpp" -#include "opencv2/imgcodecs.hpp" -#include "opencv2/imgproc.hpp" - -namespace ascendVehicleTracking { -#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) - - const int MODULE_QUEUE_SIZE = 1000; - - enum FrameMode { - FRAME_MODE_SEARCH = 0, - FRAME_MODE_REG - }; - - struct DataBuffer { - std::shared_ptr deviceData; - std::shared_ptr hostData; - uint32_t dataSize; // buffer size - DataBuffer() : deviceData(nullptr), hostData(nullptr), dataSize(0) {} - }; - - struct DetectInfo { - int32_t classId; - float confidence; - float minx; // x value of left-top point - float miny; // y value of left-top point - float height; - float width; - }; - - enum TraceFlag { - NEW_VEHICLE = 0, - TRACkED_VEHICLE, - LOST_VEHICLE - }; - - struct TraceInfo { - int32_t id; - TraceFlag flag; - int32_t survivalTime; // How long is it been since the first time, unit: detection period - int32_t detectedTime; // How long is the vehicle detected, unit: detection period - std::chrono::time_point createTime; - }; - - struct TrackLet { - TraceInfo info; - // reserved: kalman status parameter - int32_t lostTime; // undetected time for tracked vehicle - std::vector shortFeature; // nearest 10 frame - }; - - struct VehicleQuality { - float score; - }; - - struct Coordinate2D { - uint32_t x; - uint32_t y; - }; -} -// namespace ascendVehicleTracking - -struct AttrT { - AttrT(std::string name, std::string value) : name(std::move(name)), value(std::move(value)) {} - std::string name = {}; - std::string value = {}; -}; - -#endif diff --git a/HelmetIdentification_V2/src/Hungarian.cpp b/HelmetIdentification_V2/src/Hungarian.cpp deleted file mode 100644 index 3f6fcd2..0000000 --- a/HelmetIdentification_V2/src/Hungarian.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Hungarian.h" -#include -#include -#include "MxBase/Log/Log.h" - -namespace { - const int INF = 0x3f3f3f3f; - const int VISITED = 1; - const int HUNGARIAN_CONTENT = 7; - const int X_MATCH_OFFSET = 0; - const int Y_MATCH_OFFSET = 1; - const int X_VALUE_OFFSET = 2; - const int Y_VALUE_OFFSET = 3; - const int SLACK_OFFSET = 4; - const int X_VISIT_OFFSET = 5; - const int Y_VISIT_OFFSET = 6; -} - -APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols) -{ - handle.max = (row > cols) ? row : cols; - auto adjMat = std::shared_ptr(); - adjMat.reset(new int[handle.max * handle.max], std::default_delete()); - if (adjMat == nullptr) { - LogFatal << "HungarianHandleInit new failed"; - return APP_ERR_ACL_FAILURE; - } - - handle.adjMat = adjMat; - - void* ptr[HUNGARIAN_CONTENT] = {nullptr}; - for (int i = 0; i < HUNGARIAN_CONTENT; ++i) { - ptr[i] = malloc(handle.max * sizeof(int)); - if (ptr[i] == nullptr) { - LogFatal << "HungarianHandleInit Malloc failed"; - return APP_ERR_ACL_FAILURE; - } - } - - handle.xMatch.reset((int *)ptr[X_MATCH_OFFSET], free); - handle.yMatch.reset((int *)ptr[Y_MATCH_OFFSET], free); - handle.xValue.reset((int *)ptr[X_VALUE_OFFSET], free); - handle.yValue.reset((int *)ptr[Y_VALUE_OFFSET], free); - handle.slack.reset((int *)ptr[SLACK_OFFSET], free); - handle.xVisit.reset((int *)ptr[X_VISIT_OFFSET], free); - handle.yVisit.reset((int *)ptr[Y_VISIT_OFFSET], free); - return APP_ERR_OK; -} - -static void HungarianInit(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) -{ - int i, j, value; - if (rows > cols) { - handle.transpose = true; - handle.cols = rows; - handle.rows = cols; - handle.resX = handle.yMatch.get(); - handle.resY = handle.xMatch.get(); - } else { - handle.transpose = false; - handle.rows = rows; - handle.cols = cols; - handle.resX = handle.xMatch.get(); - handle.resY = handle.yMatch.get(); - } - - for (i = 0; i < handle.rows; ++i) { - handle.xValue.get()[i] = 0; - handle.xMatch.get()[i] = -1; - for (j = 0; j < handle.cols; ++j) { - if (handle.transpose) { - value = cost[j][i]; - } else { - value = cost[i][j]; - } - handle.adjMat.get()[i * handle.cols + j] = value; - if (handle.xValue.get()[i] < value) { - handle.xValue.get()[i] = value; - } - } - } - - for (i = 0; i < handle.cols; ++i) { - handle.yValue.get()[i] = 0; - handle.yMatch.get()[i] = -1; - } -} - -static bool Match(HungarianHandle &handle, int id) -{ - int j, delta; - handle.xVisit.get()[id] = VISITED; - for (j = 0; j < handle.cols; ++j) { - if (handle.yVisit.get()[j] == VISITED) { - continue; - } - delta = handle.xValue.get()[id] + handle.yValue.get()[j] - handle.adjMat.get()[id * handle.cols + j]; - if (delta == 0) { - handle.yVisit.get()[j] = VISITED; - if (handle.yMatch.get()[j] == -1 || Match(handle, handle.yMatch.get()[j])) { - handle.yMatch.get()[j] = id; - handle.xMatch.get()[id] = j; - return true; - } - } else if (delta < handle.slack.get()[j]) { - handle.slack.get()[j] = delta; - } - } - return false; -} - -int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) -{ - HungarianInit(handle, cost, rows, cols); - int i, j, delta; - for (i = 0; i < handle.rows; ++i) { - while (true) { - std::fill(handle.xVisit.get(), handle.xVisit.get() + handle.rows, 0); - std::fill(handle.yVisit.get(), handle.yVisit.get() + handle.cols, 0); - for (j = 0; j < handle.cols; ++j) { - handle.slack.get()[j] = INF; - } - if (Match(handle, i)) { - break; - } - delta = INF; - for (j = 0; j < handle.cols; ++j) { - if (handle.yVisit.get()[j] != VISITED && delta > handle.slack.get()[j]) { - delta = handle.slack.get()[j]; - } - } - if (delta == INF) { - LogDebug << "Hungarian solve is invalid!"; - return -1; - } - for (j = 0; j < handle.rows; ++j) { - if (handle.xVisit.get()[j] == VISITED) { - handle.xValue.get()[j] -= delta; - } - } - for (j = 0; j < handle.cols; ++j) { - if (handle.yVisit.get()[j] == VISITED) { - handle.yValue.get()[j] += delta; - } - } - } - } - return handle.rows; -} diff --git a/HelmetIdentification_V2/src/Hungarian.h b/HelmetIdentification_V2/src/Hungarian.h deleted file mode 100644 index 2ae6a33..0000000 --- a/HelmetIdentification_V2/src/Hungarian.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H -#define MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H - -#include -#include -#include "DataType.h" -#include "MxBase/PostProcessBases/PostProcessDataType.h" -#include "MxBase/ErrorCode/ErrorCodes.h" - -struct HungarianHandle { - int rows; - int cols; - int max; - int *resX; - int *resY; - bool transpose; - std::shared_ptr adjMat; - std::shared_ptr xMatch; - std::shared_ptr yMatch; - std::shared_ptr xValue; - std::shared_ptr yValue; - std::shared_ptr slack; - std::shared_ptr xVisit; - std::shared_ptr yVisit; -}; - -APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols); -int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols); - -#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/KalmanTracker.cpp b/HelmetIdentification_V2/src/KalmanTracker.cpp deleted file mode 100644 index 69362f9..0000000 --- a/HelmetIdentification_V2/src/KalmanTracker.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "KalmanTracker.h" - -namespace ascendVehicleTracking -{ - namespace - { - const int OFFSET = 2; - const int MULTIPLE = 2; - } - - /* - * The SORT algorithm uses a linear constant velocity model,which assumes 7 - * states, including - * x coordinate of bounding box center - * y coordinate of bounding box center - * area of bounding box - * aspect ratio of w to h - * velocity of x - * velocity of y - * variation rate of area - * - * The aspect ratio is considered to be unchanged, so there is no additive item - * for aspect ratio in the transitionMatrix - * - * - * Kalman filter equation step by step - * (1) X(k|k-1)=AX(k-1|k-1)+BU(k) - * X(k|k-1) is the predicted state(statePre),X(k-1|k-1) is the k-1 statePost,A - * is transitionMatrix, B is controlMatrix, U(k) is control state, in SORT U(k) is 0. - * - * (2) P(k|k-1)=AP(k-1|k-1)A'+Q - * P(k|k-1) is the predicted errorCovPre, P(k-1|k-1) is the k-1 errorCovPost, - * Q is processNoiseCov - * - * (3) Kg(k)=P(k|k-1)H'/(HP(k|k-1))H'+R - * Kg(k) is the kalman gain, the ratio of estimate variance in total variance, - * H is the measurementMatrix,R is the measurementNoiseCov - * - * (4) X(k|k)=X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) - * X(k|k) is the k statePost, Z(k) is the measurement of K, in SORT Z(k) is - * the detection result of k - * - * (5) P(k|k)=(1-Kg(k)H)P(k|k-1) - * P(k|k) is the errorCovPost - */ - void KalmanTracker::CvKalmanInit(MxBase::ObjectInfo initRect) - { - const int stateDim = 7; - const int measureDim = 4; - cvkalmanfilter_ = cv::KalmanFilter(stateDim, measureDim, 0); // zero control - measurement_ = cv::Mat::zeros(measureDim, 1, CV_32F); // 4 measurements, Z(k), according to detection results - // A, will not be updated - cvkalmanfilter_.transitionMatrix = (cv::Mat_(stateDim, stateDim) << 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1); - cvkalmanfilter_.measurementMatrix = (cv::Mat_(measureDim, stateDim) << 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0); - cv::setIdentity(cvkalmanfilter_.measurementMatrix); // H, will not be updated - cv::setIdentity(cvkalmanfilter_.processNoiseCov, cv::Scalar::all(1e-2)); // Q, will not be updated - cv::setIdentity(cvkalmanfilter_.measurementNoiseCov, cv::Scalar::all(1e-1)); // R, will bot be updated - cv::setIdentity(cvkalmanfilter_.errorCovPost, cv::Scalar::all(1)); // P(k-1|k-1), will be updated - - // initialize state vector with bounding box in - // [center_x,center_y,area,ratio] - // style, the velocity is 0 - // X(k-1|k-1) - cvkalmanfilter_.statePost.at(0, 0) = initRect.x0 + (initRect.x1 - initRect.x0) / MULTIPLE; - cvkalmanfilter_.statePost.at(1, 0) = initRect.y0 + (initRect.y1 - initRect.y0) / MULTIPLE; - cvkalmanfilter_.statePost.at(OFFSET, 0) = (initRect.x1 - initRect.x0) * (initRect.y1 - initRect.y0); - cvkalmanfilter_.statePost.at(OFFSET + 1, 0) = (initRect.x1 - initRect.x0) / (initRect.y1 - initRect.y0); - } - - // Predict the bounding box. - MxBase::ObjectInfo KalmanTracker::Predict() - { - // predict - // return X(k|k-1)=AX(k-1|k-1), and update - // P(k|k-1) <- AP(k-1|k-1)A'+Q - MxBase::ObjectInfo detectInfo = {}; - cv::Mat predictState = cvkalmanfilter_.predict(); - float *pData = (float *)(predictState.data); - float w = sqrt((*(pData + OFFSET)) * (*(pData + OFFSET + 1))); - if (w < DBL_EPSILON) - { - detectInfo.x0 = 0; - detectInfo.y0 = 0; - detectInfo.x1 = 0; - detectInfo.y1 = 0; - detectInfo.classId = 0; - return detectInfo; - } - if (w == 0) - { - MxBase::ObjectInfo w0DetectInfo = {}; - return w0DetectInfo; - } - float h = (*(pData + OFFSET)) / w; - float x = (*pData) - w / MULTIPLE; - float y = (*(pData + 1)) - h / MULTIPLE; - if (x < 0 && (*pData) > 0) - { - x = 0; - } - if (y < 0 && (*(pData + 1)) > 0) - { - y = 0; - } - detectInfo.x0 = x; - detectInfo.y0 = y; - detectInfo.x1 = x + w; - detectInfo.y1 = y + h; - return detectInfo; - } - - // Update the state using observed bounding box - void KalmanTracker::Update(MxBase::ObjectInfo stateMat) - { - // measurement_, update Z(k) - float *pData = (float *)(measurement_.data); - *pData = stateMat.x0 + (stateMat.x1 - stateMat.x0) / MULTIPLE; - *(pData + 1) = stateMat.y0 + (stateMat.y1 - stateMat.y0) / MULTIPLE; - *(pData + OFFSET) = (stateMat.x1 - stateMat.x0) * (stateMat.y1 - stateMat.y0); - *(pData + OFFSET + 1) = (stateMat.x1 - stateMat.x0) / (stateMat.y1 - stateMat.y0); - // update, do the following steps: - // Kg(k): P(k|k-1)H'/(HP(k|k-1))H'+R - // X(k|k): X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) - // P(k|k): (1-Kg(k)H)P(k|k-1) - cvkalmanfilter_.correct(measurement_); - } -} // namespace diff --git a/HelmetIdentification_V2/src/KalmanTracker.h b/HelmetIdentification_V2/src/KalmanTracker.h deleted file mode 100644 index f5f3465..0000000 --- a/HelmetIdentification_V2/src/KalmanTracker.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H -#define MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H - -#include "opencv2/highgui/highgui.hpp" -#include "opencv2/video/tracking.hpp" -#include "DataType.h" -#include "MxBase/PostProcessBases/PostProcessDataType.h" - -namespace ascendVehicleTracking { -class KalmanTracker { -public: - KalmanTracker() {} - ~KalmanTracker() {} - void CvKalmanInit(MxBase::ObjectInfo initRect); - MxBase::ObjectInfo Predict(); - void Update(MxBase::ObjectInfo stateMat); -private: - cv::KalmanFilter cvkalmanfilter_ = {}; - cv::Mat measurement_ = {}; -}; -} // namesapce ascendVehicleTracking - -#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/MOTConnection.cpp b/HelmetIdentification_V2/src/MOTConnection.cpp deleted file mode 100644 index 14e4341..0000000 --- a/HelmetIdentification_V2/src/MOTConnection.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "MOTConnection.h" -#include -#include -#include -#include -#include "opencv2/highgui.hpp" -#include "opencv2/imgcodecs.hpp" -#include "opencv2/imgproc.hpp" -#include "MxBase/Log/Log.h" -#include "MxBase/ErrorCode/ErrorCodes.h" -#include "Hungarian.h" - -namespace ascendVehicleTracking -{ - namespace - { - // convert double to int - const int FLOAT_TO_INT = 1000; - const int MULTIPLE = 0; - const double SIMILARITY_THRESHOLD = 0.66; - const int MULTIPLE_IOU = 6; - const float NORM_EPS = 1e-10; - const double TIME_COUNTS = 1000.0; - const double COST_TIME_MS_THRESHOLD = 10.; - const float WIDTH_RATE_THRESH = 1.f; - const float HEIGHT_RATE_THRESH = 1.f; - const float X_DIST_RATE_THRESH = 1.3f; - const float Y_DIST_RATE_THRESH = 1.f; - } // namespace - - // 计算bounding box的交并比 - float CalIOU(MxBase::ObjectInfo detect1, MxBase::ObjectInfo detect2) - { - cv::Rect_ bbox1(detect1.x0, detect1.y0, detect1.x1 - detect1.x0, detect1.y1 - detect1.y0); - cv::Rect_ bbox2(detect2.x0, detect2.y0, detect2.x1 - detect2.x0, detect2.y1 - detect2.y0); - float intersectionArea = (bbox1 & bbox2).area(); - float unionArea = bbox1.area() + bbox2.area() - intersectionArea; - if (unionArea < DBL_EPSILON) - { - return 0.f; - } - return (intersectionArea / unionArea); - } - - // 计算前后两帧的两个bounding box的相似度 - float CalSimilarity(const TraceLet &traceLet, const MxBase::ObjectInfo &objectInfo, const int &method, const double &kIOU) - { - return CalIOU(traceLet.detectInfo, objectInfo); - } - - // 过滤掉交并比小于阈值的匹配 - void MOTConnection::FilterLowThreshold(const HungarianHandle &hungarianHandleObj, - const std::vector> &disMatrix, std::vector &matchedTracedDetected, - std::vector &detectVehicleFlagVec) - { - for (unsigned int i = 0; i < traceList_.size(); ++i) - { - if ((hungarianHandleObj.resX[i] != -1) && - (disMatrix[i][hungarianHandleObj.resX[i]] >= (trackThreshold_ * FLOAT_TO_INT))) - { - matchedTracedDetected.push_back(cv::Point(i, hungarianHandleObj.resX[i])); - detectVehicleFlagVec[hungarianHandleObj.resX[i]] = true; - } - else - { - traceList_[i].info.flag = LOST_VEHICLE; - } - } - } - - // 更新没有匹配上的跟踪器 - void MOTConnection::UpdateUnmatchedTraceLet(const std::vector> &objInfos) - { - for (auto itr = traceList_.begin(); itr != traceList_.end();) - { - if ((*itr).info.flag != LOST_VEHICLE) - { - ++itr; - continue; - } - - (*itr).lostAge++; - (*itr).kalman.Update((*itr).detectInfo); - - if ((*itr).lostAge < lostThreshold_) - { - continue; - } - - itr = traceList_.erase(itr); - } - } - - // 更新匹配上的跟踪器 - void MOTConnection::UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, - std::vector> &objInfos) - { - for (unsigned int i = 0; i < matchedTracedDetected.size(); ++i) - { - int traceIndex = matchedTracedDetected[i].x; - int detectIndex = matchedTracedDetected[i].y; - if (traceList_[traceIndex].info.survivalTime > MULTIPLE) - { - traceList_[traceIndex].info.flag = TRACkED_VEHICLE; - } - traceList_[traceIndex].info.survivalTime++; - traceList_[traceIndex].info.detectedTime++; - traceList_[traceIndex].lostAge = 0; - traceList_[traceIndex].detectInfo = objInfos[0][detectIndex]; - traceList_[traceIndex].kalman.Update(objInfos[0][detectIndex]); - } - } - // 将没有匹配上的检测更新为新的检测器 - void MOTConnection::AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue) - { - using Time = std::chrono::high_resolution_clock; - for (auto &vehicleObject : unmatchedVehicleObjectQueue) - { - // add new detected into traceList - TraceLet traceLet; - generatedId_++; - traceLet.info.id = generatedId_; - traceLet.info.survivalTime = 1; - traceLet.info.detectedTime = 1; - traceLet.lostAge = 0; - traceLet.info.flag = NEW_VEHICLE; - traceLet.detectInfo = vehicleObject; - traceLet.info.createTime = Time::now(); - - traceLet.kalman.CvKalmanInit(vehicleObject); - traceList_.push_back(traceLet); - } - } - - void MOTConnection::UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, - std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue) - { - UpdateMatchedTraceLet(matchedTracedDetected, objInfos); // 更新匹配上的跟踪器 - AddNewDetectedVehicle(unmatchedVehicleObjectQueue); // 将没有匹配上的检测更新为新的检测器 - UpdateUnmatchedTraceLet(objInfos); // 更新没有匹配上的跟踪器 - } - - void MOTConnection::TrackObjectPredict() - { - // every traceLet should do kalman predict - for (auto &traceLet : traceList_) - { - traceLet.detectInfo = traceLet.kalman.Predict(); // 卡尔曼滤波预测的框 - } - } - - void MOTConnection::TrackObjectUpdate(const std::vector> &objInfos, - std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue) - { - if (objInfos[0].size() > 0) - { - LogDebug << "[frame id = " << 1 << "], trace size =" << traceList_.size() << "detect size = " << objInfos[0].size() << ""; - // init vehicle matched flag - std::vector detectVehicleFlagVec; - for (unsigned int i = 0; i < objInfos[0].size(); ++i) - { - detectVehicleFlagVec.push_back(false); - } - // calculate the associated matrix - std::vector> disMatrix; - disMatrix.clear(); - disMatrix.resize(traceList_.size(), std::vector(objInfos[0].size(), 0)); - for (unsigned int j = 0; j < objInfos[0].size(); ++j) - { - for (unsigned int i = 0; i < traceList_.size(); ++i) - { - // 计算交并比 - float sim = CalSimilarity(traceList_[i], objInfos[0][j], method_, kIOU_); // method_=1, kIOU_=1.0 - disMatrix[i][j] = (int)(sim * FLOAT_TO_INT); - } - } - - // solve the assignment problem using hungarian 匈牙利算法进行匹配 - HungarianHandle hungarianHandleObj = {}; - HungarianHandleInit(hungarianHandleObj, traceList_.size(), objInfos[0].size()); - HungarianSolve(hungarianHandleObj, disMatrix, traceList_.size(), objInfos[0].size()); - // filter out matched but with low distance 过滤掉匹配上但是交并比较小的 - FilterLowThreshold(hungarianHandleObj, disMatrix, matchedTracedDetected, detectVehicleFlagVec); - LogDebug << "matchedTracedDetected = " << matchedTracedDetected.size() << ""; - // fill unmatchedVehicleObjectQueue - for (unsigned int i = 0; i < detectVehicleFlagVec.size(); ++i) - { - if (detectVehicleFlagVec[i] == false) - { - unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); - } - } - } - } - - APP_ERROR MOTConnection::ProcessSort(std::vector> &objInfos, size_t frameId) - { - std::vector unmatchedVehicleObjectQueue; - std::vector matchedTracedDetected; - if (objInfos[0].size() == 0) - { - return APP_ERR_COMM_FAILURE; - } - - if (traceList_.size() > 0) - { - // every traceLet should do kalman predict - TrackObjectPredict(); // 卡尔曼滤波预测 - TrackObjectUpdate(objInfos, matchedTracedDetected, unmatchedVehicleObjectQueue); // 选出matched track、unmatched detection - } - else - { - // traceList is empty, all the vehicle detected in the new frame are unmatched. - if (objInfos[0].size() > 0) - { - for (unsigned int i = 0; i < objInfos[0].size(); ++i) - { - unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); - } - } - } - - // update all the tracelet in the tracelist per frame - UpdateTraceLetAndFrame(matchedTracedDetected, objInfos, unmatchedVehicleObjectQueue); // 用matched track、unmatched detection更新跟踪器 - return APP_ERR_OK; - } - - // 获取跟踪后的检测框 - APP_ERROR MOTConnection::GettrackResult(std::vector &objInfos_) - { - if (traceList_.size() > 0) - { - for (auto &traceLet : traceList_) - { - traceLet.detectInfo.classId = traceLet.info.id; - objInfos_.push_back(traceLet.detectInfo); - } - } - else - { - return APP_ERR_COMM_FAILURE; - } - return APP_ERR_OK; - } -} -// namespace ascendVehicleTracking \ No newline at end of file diff --git a/HelmetIdentification_V2/src/MOTConnection.h b/HelmetIdentification_V2/src/MOTConnection.h deleted file mode 100644 index be54f17..0000000 --- a/HelmetIdentification_V2/src/MOTConnection.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H -#define MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H - -#include -#include -#include -#include "KalmanTracker.h" -#include "Hungarian.h" -#include "DataType.h" -#include "MxBase/ErrorCode/ErrorCodes.h" -#include "MxBase/DvppWrapper/DvppWrapper.h" -#include "MxBase/MemoryHelper/MemoryHelper.h" -#include "MxBase/DeviceManager/DeviceManager.h" -#include "MxBase/Tensor/TensorBase/TensorBase.h" -#include "MxBase/PostProcessBases/PostProcessDataType.h" -#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" - -namespace ascendVehicleTracking { -struct TraceLet { - TraceInfo info = {}; - int32_t lostAge = 0; - KalmanTracker kalman; - std::list> shortFeatureQueue; - MxBase::ObjectInfo detectInfo = {}; -}; - -class MOTConnection { -public: - APP_ERROR ProcessSort(std::vector> &objInfos, size_t frameId); - APP_ERROR GettrackResult(std::vector &objInfos_); - -private: - double trackThreshold_ = 0.3; - double kIOU_ = 1.0; - int32_t method_ = 1; - int32_t lostThreshold_ = 3; - uint32_t maxNumberFeature_ = 0; - int32_t generatedId_ = 0; - std::vector traceList_ = {}; - -private: - - void FilterLowThreshold(const HungarianHandle &hungarianHandleObj, const std::vector> &disMatrix, - std::vector &matchedTracedDetected, std::vector &detectVehicleFlagVec); - - void UpdateUnmatchedTraceLet(const std::vector> &objInfos); - - void UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, - std::vector> &objInfos); - - void AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue); - - void UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, - std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue); - - void TrackObjectPredict(); - void TrackObjectUpdate(const std::vector> &objInfos, - std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue); -}; -} // namespace ascendVehicleTracking - -#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/cropResizePaste.hpp b/HelmetIdentification_V2/src/cropResizePaste.hpp deleted file mode 100644 index b3648ec..0000000 --- a/HelmetIdentification_V2/src/cropResizePaste.hpp +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef CRP_H -#define CRP_H - -#include "MxBase/E2eInfer/Image/Image.h" -#include "MxBase/E2eInfer/Rect/Rect.h" -#include "MxBase/E2eInfer/Size/Size.h" - -#include "acl/dvpp/hi_dvpp.h" -#include "acl/acl.h" -#include "acl/acl_rt.h" - -#define CONVER_TO_PODD(NUM) (((NUM) % 2 != 0) ? (NUM) : ((NUM)-1)) -#define CONVER_TO_EVEN(NUM) (((NUM) % 2 == 0) ? (NUM) : ((NUM)-1)) -#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) - -MxBase::Rect GetPasteRect(uint32_t inputWidth, uint32_t inputHeight, uint32_t outputWidth, uint32_t outputHeight) -{ - bool widthRatioLarger = true; - float resizeRatio = static_cast(inputWidth) / outputWidth; - if (resizeRatio < (static_cast(inputHeight) / outputHeight)) - { - resizeRatio = static_cast(inputHeight) / outputHeight; - widthRatioLarger = false; - } - - // (x0, y0)是左上角坐标,(x1, y1)是右下角坐标,采用图片坐标系 - uint32_t x0; - uint32_t y0; - uint32_t x1; - uint32_t y1; - if (widthRatioLarger) - { - // 原图width大于height - x0 = 0; - y0 = 0; - x1 = outputWidth-1; - y1 = inputHeight / resizeRatio - 1; - } - else - { - // 原图height大于width - x0 = 0; - y0 = 0; - x1 = inputWidth / resizeRatio - 1; - y1 = outputHeight - 1; - } - x0 = DVPP_ALIGN_UP(CONVER_TO_EVEN(x0), 16); // 16对齐 - x1 = DVPP_ALIGN_UP((x1 - x0 + 1), 16) + x0 - 1; - y0 = CONVER_TO_EVEN(y0); - y1 = CONVER_TO_PODD(y1); - MxBase::Rect res(x0, y0, x1, y1); - return res; -} - -MxBase::Image ConstructImage(uint32_t resizeWidth, uint32_t resizeHeight) -{ - void *addr; - uint32_t dataSize = resizeWidth * resizeHeight * 3 / 2; - auto ret = hi_mpi_dvpp_malloc(0, &addr, dataSize); - if (ret != APP_ERR_OK) - { - LogError << "hi_mpi_dvpp_malloc fail :" << ret; - } - // 第三个参数从128改成了0 - ret = aclrtMemset(addr, dataSize, 0, dataSize); - if (ret != APP_ERR_OK) - { - LogError << "aclrtMemset fail :" << ret; - } - std::shared_ptr data((uint8_t *)addr, hi_mpi_dvpp_free); - MxBase::Size imageSize(resizeWidth, resizeHeight); - MxBase::Image pastedImg(data, dataSize, 0, imageSize); - return pastedImg; -} - -std::pair GenerateRect(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight) -{ - uint32_t x1 = CONVER_TO_PODD(originalWidth - 1); - uint32_t y1 = CONVER_TO_PODD(originalHeight - 1); - MxBase::Rect cropRect(0, 0, x1, y1); - MxBase::Rect pasteRect = GetPasteRect(originalWidth, originalHeight, resizeWidth, resizeHeight); - std::pair cropPasteRect(cropRect, pasteRect); - return cropPasteRect; -} - -MxBase::Image resizeKeepAspectRatioFit(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight, MxBase::Image &decodeImage, MxBase::ImageProcessor& imageProcessor) -{ - std::pair cropPasteRect = GenerateRect(originalWidth, originalHeight, resizeWidth, resizeHeight); - MxBase::Image resizeImage = ConstructImage(resizeWidth, resizeHeight); - auto ret = imageProcessor.CropAndPaste(decodeImage, cropPasteRect, resizeImage); - if (ret != APP_ERR_OK) - { - LogError << "CropAndPaste fail :" << ret; - } - return resizeImage; -} - -#endif // CRP_H \ No newline at end of file diff --git a/HelmetIdentification_V2/src/main-image.cpp b/HelmetIdentification_V2/src/main-image.cpp deleted file mode 100644 index a764a67..0000000 --- a/HelmetIdentification_V2/src/main-image.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "utils.h" - -#include -#include -#include -using namespace std; - -// 如果在200DK上运行就改为 USE_200DK -#define USE_DVPP - -APP_ERROR readImage(std::string imgPath, MxBase::Image& image, MxBase::ImageProcessor& imageProcessor) -{ - APP_ERROR ret; -#ifdef USE_DVPP - // if USE DVPP - ret = imageProcessor.Decode(imgPath, image); -#endif -#ifdef USE_200DK - std::shared_ptr dataPtr; - uint32_t dataSize; - // Get image data to memory, this method can be substituted or designed by yourself! - std::ifstream file; - file.open(imgPath.c_str(), std::ios::binary); - if (!file) - { - LogInfo << "Invalid file."; - return APP_ERR_COMM_INVALID_PARAM; - } - std::stringstream buffer; - buffer << file.rdbuf(); - std::string content = buffer.str(); - - char *p = (char *)malloc(content.size()); - memcpy(p, content.data(), content.size()); - auto deleter = [](void *p) -> void - { - free(p); - p = nullptr; - }; - - dataPtr.reset(static_cast((void *)(p)), deleter); - dataSize = content.size(); - file.close(); - if (ret != APP_ERR_OK) - { - LogError << "Getimage failed, ret=" << ret; - return ret; - } - ret = imageProcessor.Decode(dataPtr, dataSize, image, MxBase::ImageFormat::YUV_SP_420); - // endif -#endif - if (ret != APP_ERR_OK) - { - LogError << "Decode failed, ret=" << ret; - return ret; - } -} - -void postProcess(std::vector modelOutputs, std::shared_ptr postProcessorDptr, MxBase::Size originalSize, MxBase::Size resizeSize) -{ - MxBase::ResizedImageInfo imgInfo; - auto shape = modelOutputs[0].GetShape(); - imgInfo.widthOriginal = originalSize.width; - imgInfo.heightOriginal = originalSize.height; - imgInfo.widthResize = resizeSize.width; - imgInfo.heightResize = resizeSize.height; - imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; - float resizeRate = originalSize.width > originalSize.height ? (originalSize.width * 1.0 / videoInfo::YOLOV5_RESIZE) : (originalSize.height * 1.0 / videoInfo::YOLOV5_RESIZE); - imgInfo.keepAspectRatioScaling = 1 / resizeRate; - std::vector imageInfoVec = {}; - imageInfoVec.push_back(imgInfo); - // make postProcess inputs - std::vector tensors; - for (size_t i = 0; i < modelOutputs.size(); i++) - { - MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); - MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); - tensors.push_back(tensorBase); - } - std::vector> objectInfos; - postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); - std::cout << "===---> Size of objectInfos is " << objectInfos.size() << std::endl; - for (size_t i = 0; i < objectInfos.size(); i++) - { - std::cout << "objectInfo-" << i << " , Size:" << objectInfos[i].size() << std::endl; - for (size_t j = 0; j < objectInfos[i].size(); j++) - { - std::cout << std::endl - << "*****objectInfo-" << i << ":" << j << std::endl; - std::cout << "x0 is " << objectInfos[i][j].x0 << std::endl; - std::cout << "y0 is " << objectInfos[i][j].y0 << std::endl; - std::cout << "x1 is " << objectInfos[i][j].x1 << std::endl; - std::cout << "y1 is " << objectInfos[i][j].y1 << std::endl; - std::cout << "confidence is " << objectInfos[i][j].confidence << std::endl; - std::cout << "classId is " << objectInfos[i][j].classId << std::endl; - std::cout << "className is " << objectInfos[i][j].className << std::endl; - } - } -} - -APP_ERROR main(int argc, char *argv[]) -{ - APP_ERROR ret; - - // global init - ret = MxBase::MxInit(); - if (ret != APP_ERR_OK) - { - LogError << "MxInit failed, ret=" << ret << "."; - } - // 检测是否输入了文件路径 - if (argc <= 1) - { - LogWarn << "Please input image path, such as 'test.jpg'."; - return APP_ERR_OK; - } - - // imageProcess init - MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); - // model init - MxBase::Model yoloModel(videoInfo::modelPath, videoInfo::DEVICE_ID); - // postprocessor init - std::map postConfig; - postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); - postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); - std::shared_ptr postProcessorDptr = std::make_shared(); - postProcessorDptr->Init(postConfig); - - std::string imgPath = argv[1]; - // 读取图片 - MxBase::Image image; - readImage(imgPath, image, imageProcessor); - - // 缩放图片 - MxBase::Size originalSize = image.GetOriginalSize(); - MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); - MxBase::Image resizedImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); - - // 模型推理 - MxBase::Tensor tensorImg = resizedImage.ConvertToTensor(); - tensorImg.ToDevice(videoInfo::DEVICE_ID); - std::vector inputs; - inputs.push_back(tensorImg); - std::vector modelOutputs = yoloModel.Infer(inputs); - for (auto output : modelOutputs) - { - output.ToHost(); - } - - // 后处理 - postProcess(modelOutputs, postProcessorDptr, originalSize, resizeSize); - - return APP_ERR_OK; -} \ No newline at end of file diff --git a/HelmetIdentification_V2/src/main.cpp b/HelmetIdentification_V2/src/main.cpp deleted file mode 100644 index 2eb3af9..0000000 --- a/HelmetIdentification_V2/src/main.cpp +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "utils.h" - -extern "C" -{ -#include "libavformat/avformat.h" -} - -#include -#include -#include -#include "unistd.h" -#include -#include -#include -#include "boost/filesystem.hpp" - -#include "MxBase/DeviceManager/DeviceManager.h" -#include "MxBase/DvppWrapper/DvppWrapper.h" -#include "MxBase/MemoryHelper/MemoryHelper.h" -#include "opencv2/opencv.hpp" -#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" - -#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" -#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" -#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" -#include "MxBase/E2eInfer/DataType.h" - -#include -#include -using namespace std; -using namespace videoInfo; -namespace frameConfig -{ - size_t channelId0 = 0; - size_t channelId1 = 1; - size_t frameCountChannel0 = 300; - size_t frameCountChannel1 = 300; - size_t skipIntervalChannel0 = 2; - size_t skipIntervalChannel1 = 2; - - // channel0对应文件的指针 - AVFormatContext *pFormatCtx0 = nullptr; - // channel1对应文件的指针 - AVFormatContext *pFormatCtx1 = nullptr; -} - -// 检查文件(是否存在、是否为文件夹) -bool checkFile(std::string &fileName) -{ - // 判断视频文件是否存在 - ifstream f(fileName.c_str()); - if (!f.good()) - { - LogError << "file not exists! " << fileName; - return false; - } - else - { - // 如果存在还需要判断是否是文件夹 - struct stat s; - if (stat(fileName.c_str(), &s) == 0) - { - if (s.st_mode & S_IFDIR) - { - LogError << fileName << " is a directory!"; - return false; - } - } - } - return true; -} - -// ffmpeg拉流 -AVFormatContext *CreateFormatContext(std::string filePath) -{ - LogInfo << "start to CreatFormatContext!"; - // creat message for stream pull - AVFormatContext *formatContext = nullptr; - AVDictionary *options = nullptr; - - LogInfo << "start to avformat_open_input!"; - int ret = avformat_open_input(&formatContext, filePath.c_str(), nullptr, &options); - if (options != nullptr) - { - av_dict_free(&options); - } - if (ret != 0) - { - LogError << "Couldn't open input stream " << filePath.c_str() << " ret=" << ret; - return nullptr; - } - ret = avformat_find_stream_info(formatContext, nullptr); - if (ret != 0) - { - LogError << "Couldn't open input stream information"; - return nullptr; - } - return formatContext; -} - -// 真正的拉流函数 -void PullStream0(std::string filePath) -{ - av_register_all(); - avformat_network_init(); - frameConfig::pFormatCtx0 = avformat_alloc_context(); - frameConfig::pFormatCtx0 = CreateFormatContext(filePath); - av_dump_format(frameConfig::pFormatCtx0, 0, filePath.c_str(), 0); -} -void PullStream1(std::string filePath) -{ - av_register_all(); - avformat_network_init(); - frameConfig::pFormatCtx1 = avformat_alloc_context(); - frameConfig::pFormatCtx1 = CreateFormatContext(filePath); - av_dump_format(frameConfig::pFormatCtx1, 0, filePath.c_str(), 0); -} - -// 视频解码回调(样例代码,测试可以跑通,但是不能直接复用) -APP_ERROR CallBackVdec(MxBase::Image &decodedImage, uint32_t channelId, uint32_t frameId, void *userData) -{ - FrameImage frameImage; - frameImage.image = decodedImage; - frameImage.channelId = channelId; - frameImage.frameId = frameId; - - videoInfo::g_threadsMutex_frameImageQueue.lock(); - videoInfo::frameImageQueue.push(frameImage); - videoInfo::g_threadsMutex_frameImageQueue.unlock(); - - return APP_ERR_OK; -} - -// 获取H264中的帧 -void GetFrame(AVPacket &pkt, FrameImage &frameImage, AVFormatContext *pFormatCtx) -{ - av_init_packet(&pkt); - int ret = av_read_frame(pFormatCtx, &pkt); - if (ret != 0) - { - LogInfo << "[StreamPuller] channel Read frame failed, continue!"; - if (ret == AVERROR_EOF) - { - LogInfo << "[StreamPuller] channel StreamPuller is EOF, over!"; - return; - } - return; - } - else - { - if (pkt.size <= 0) - { - LogError << "Invalid pkt.size: " << pkt.size; - return; - } - - // send to the device - auto hostDeleter = [](void *dataPtr) -> void {}; - MxBase::MemoryData data(pkt.size, MxBase::MemoryData::MEMORY_HOST); - MxBase::MemoryData src((void *)(pkt.data), pkt.size, MxBase::MemoryData::MEMORY_HOST); - APP_ERROR ret = MxBase::MemoryHelper::MxbsMallocAndCopy(data, src); - if (ret != APP_ERR_OK) - { - LogError << "MxbsMallocAndCopy failed!"; - } - std::shared_ptr imageData((uint8_t *)data.ptrData, hostDeleter); - - MxBase::Image subImage(imageData, pkt.size); - frameImage.image = subImage; - - LogDebug << "'channelId = " << frameImage.channelId << ", frameId = " << frameImage.frameId << " , dataSize = " << frameImage.image.GetDataSize(); - - av_packet_unref(&pkt); - } - return; -} - -// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 -void VdecThread0(size_t frameCount, size_t skipInterval, int32_t channelId) -{ - AVPacket pkt; - uint32_t frameId = 0; - // 解码器参数 - MxBase::VideoDecodeConfig config; - MxBase::VideoDecodeCallBack cPtr = CallBackVdec; - config.width = videoInfo::SRC_WIDTH; - config.height = videoInfo::SRC_HEIGHT; - config.callbackFunc = cPtr; - config.skipInterval = skipInterval; // 跳帧控制 - - std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); - for (size_t i = 0; i < frameCount; i++) - { - MxBase::Image subImage; - FrameImage frame; - frame.channelId = channelId; - frame.frameId = frameId; - frame.image = subImage; - GetFrame(pkt, frame, frameConfig::pFormatCtx0); - APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); - if (ret != APP_ERR_OK) - { - LogError << "videoDecoder Decode failed. ret is: " << ret; - } - frameId += 1; - } -} - -// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 -void VdecThread1(size_t frameCount, size_t skipInterval, int32_t channelId) -{ - AVPacket pkt; - uint32_t frameId = 0; - // 解码器参数 - MxBase::VideoDecodeConfig config; - MxBase::VideoDecodeCallBack cPtr = CallBackVdec; - config.width = videoInfo::SRC_WIDTH; - config.height = videoInfo::SRC_HEIGHT; - config.callbackFunc = cPtr; - // 跳帧控制 - config.skipInterval = skipInterval; - - std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); - for (size_t i = 0; i < frameCount; i++) - { - MxBase::Image subImage; - FrameImage frame; - frame.channelId = channelId; - frame.frameId = frameId; - frame.image = subImage; - GetFrame(pkt, frame, frameConfig::pFormatCtx1); - APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); - if (ret != APP_ERR_OK) - { - LogError << "videoDecoder Decode failed. ret is: " << ret; - } - frameId += 1; - } -} - -APP_ERROR main(int argc, char *argv[]) -{ - // 初始化 - APP_ERROR ret = MxBase::MxInit(); - if (ret != APP_ERR_OK) - { - LogError << "MxInit failed, ret=" << ret << "."; - } - // 检测是否输入了文件路径 - if (argc <= 1) - { - LogWarn << "Please input video path, such as './video_sample test.264'."; - return APP_ERR_OK; - } - - std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); - - // 视频流解码线程(根据输入视频的数量确定是一路还是两路) - if (argc == 2) - { - std::string videoPath0 = argv[1]; - if (!checkFile(videoPath0)) - { - return APP_ERR_OK; - } - PullStream0(videoPath0); - std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0); - threadVdec0.join(); - } - else if (argc == 3) - { - std::string videoPath0 = argv[1]; - std::string videoPath1 = argv[2]; - if (!checkFile(videoPath0)) - { - return APP_ERR_OK; - } - if (!checkFile(videoPath1)) - { - return APP_ERR_OK; - } - PullStream0(videoPath0); - std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0); - PullStream1(videoPath1); - std::thread threadVdec1(VdecThread1, frameConfig::frameCountChannel1, frameConfig::skipIntervalChannel1, frameConfig::channelId1); - threadVdec0.join(); - threadVdec1.join(); - } - - size_t target_count = videoInfo::frameImageQueue.size(); - LogInfo << "frameImageQueue.size: " << videoInfo::frameImageQueue.size(); - - // resize线程 - std::thread resizeThread(resizeMethod, start_time, target_count); - resizeThread.join(); - - // 推理线程 - std::thread inferThread(inferMethod, start_time, target_count); - inferThread.join(); - - // 后处理线程 - std::thread postprocessThread(postprocessMethod, start_time, target_count); - postprocessThread.join(); - - // 跟踪去重线程 - std::thread trackThread(trackMethod, start_time, target_count); - trackThread.join(); - - std::chrono::high_resolution_clock::time_point end_time = std::chrono::high_resolution_clock::now(); - double_t cost_time = std::chrono::duration_cast(end_time - start_time).count(); - LogInfo << "端到端时间为" << cost_time / videoInfo::MS_PPE_SECOND; - - return APP_ERR_OK; -} \ No newline at end of file diff --git a/HelmetIdentification_V2/src/utils.h b/HelmetIdentification_V2/src/utils.h deleted file mode 100644 index 62ac822..0000000 --- a/HelmetIdentification_V2/src/utils.h +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. - */ - -#ifndef MXBASE_HELMETIDENTIFICATION_UTILS_H -#define MXBASE_HELMETIDENTIFICATION_UTILS_H - -#include -#include -#include -#include "unistd.h" -#include -#include -#include -#include "boost/filesystem.hpp" - -#include "MxBase/DeviceManager/DeviceManager.h" -#include "MxBase/DvppWrapper/DvppWrapper.h" -#include "MxBase/MemoryHelper/MemoryHelper.h" -#include "opencv2/opencv.hpp" -#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" - -#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" -#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" -#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" -#include "MxBase/E2eInfer/DataType.h" - -#include "MxBase/MxBase.h" -#include "MOTConnection.h" -#include "cropResizePaste.hpp" -#include "chrono" -#include "time.h" - -struct FrameImage -{ - MxBase::Image image; - uint32_t frameId = 0; - uint32_t channelId = 0; -}; - -namespace videoInfo -{ - const uint32_t SRC_WIDTH = 1920; - const uint32_t SRC_HEIGHT = 1080; - - const uint32_t YUV_BYTE_NU = 3; - const uint32_t YUV_BYTE_DE = 2; - const uint32_t YOLOV5_RESIZE = 640; - - // 要检测的目标类别的标签 - const std::string TARGET_CLASS_NAME = "head"; - // 使用chrono计数结果为毫秒,需要除以1000转换为秒 - double MS_PPE_SECOND = 1000.0; - - const uint32_t DEVICE_ID = 0; - std::string labelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/imgclass.names"; - std::string configPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/Helmet_yolov5.cfg"; - std::string modelPath = "/home/HwHiAiUser/testmain/HelmetIdentification_V2/model/YOLOv5_s.om"; - - // 读入视频帧Image的队列 - std::queue frameImageQueue; - // 读入视频帧Image队列的线程锁 - std::mutex g_threadsMutex_frameImageQueue; - - // resize前即原始图像的vector - std::vector realImageVector; - // resize后Image的vector - std::vector resizedImageVector; - // resize后Image队列的线程锁 - std::mutex g_threadsMutex_resizedImageVector; - - // 推理后tensor的vector - std::vector> inferOutputVector; - // 推理后tensor队列的线程锁 - std::mutex g_threadsMutex_inferOutputVector; - - // 后处理后objectInfos的队列 map> - std::vector>> postprocessOutputVector; - // 后处理后objectInfos队列的线程锁 - std::mutex g_threadsMutex_postprocessOutputVector; -} -namespace fs = boost::filesystem; - -// resize线程 -void resizeMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point resize_start_time = std::chrono::high_resolution_clock::now(); - - MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); - - size_t resize_count = 0; - while (resize_count < target_count) - { - if (videoInfo::frameImageQueue.empty()) - { - continue; - } - // 取图像并resize - FrameImage frame = videoInfo::frameImageQueue.front(); - MxBase::Image image = frame.image; - MxBase::Size originalSize = image.GetOriginalSize(); - MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); - MxBase::Image outputImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); - // 先将缩放后的图像放入resizeImage的队列 - FrameImage resizedFrame; - resizedFrame.channelId = frame.channelId; - resizedFrame.frameId = frame.frameId; - resizedFrame.image = outputImage; - videoInfo::g_threadsMutex_resizedImageVector.lock(); - frame.image.ToHost(); - videoInfo::realImageVector.push_back(frame); - videoInfo::resizedImageVector.push_back(resizedFrame); - videoInfo::g_threadsMutex_resizedImageVector.unlock(); - // 然后再将原图pop出去 - videoInfo::g_threadsMutex_frameImageQueue.lock(); - videoInfo::frameImageQueue.pop(); - videoInfo::g_threadsMutex_frameImageQueue.unlock(); - // 计数 - resize_count++; - } - std::chrono::high_resolution_clock::time_point resize_end_time = std::chrono::high_resolution_clock::now(); - double_t resize_cost_time = std::chrono::duration_cast(resize_end_time - resize_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t resize_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到缩放全部完成一共花费: " << resize_finish_time << ", 缩放本身花费: " << resize_cost_time; -} - -// 推理线程 -void inferMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point infer_start_time = std::chrono::high_resolution_clock::now(); - - std::shared_ptr modelDptr = std::make_shared(videoInfo::modelPath, videoInfo::DEVICE_ID); - - size_t infer_count = 0; - while (infer_count < target_count) - { - if (infer_count >= videoInfo::resizedImageVector.size()) - { - continue; - } - // 从resize后的队列中取图片 - FrameImage resizedFrame = videoInfo::resizedImageVector[infer_count]; - std::vector modelOutputs; - MxBase::Tensor tensorImg = resizedFrame.image.ConvertToTensor(); - tensorImg.ToDevice(videoInfo::DEVICE_ID); - std::vector inputs; - inputs.push_back(tensorImg); - modelOutputs = modelDptr->Infer(inputs); - for (auto output : modelOutputs) - { - output.ToHost(); - } - - // 将推理结果存入队列 - videoInfo::g_threadsMutex_inferOutputVector.lock(); - videoInfo::inferOutputVector.push_back(modelOutputs); - videoInfo::g_threadsMutex_inferOutputVector.unlock(); - // 计数 - infer_count++; - } - std::chrono::high_resolution_clock::time_point infer_end_time = std::chrono::high_resolution_clock::now(); - double_t infer_cost_time = std::chrono::duration_cast(infer_end_time - infer_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t infer_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到推理全部完成一共花费: " << infer_finish_time << ", 推理本身花费: " << infer_cost_time; -} - -// 后处理线程 -void postprocessMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point postprocess_start_time = std::chrono::high_resolution_clock::now(); - - std::map postConfig; - postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); - postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); - std::shared_ptr postProcessorDptr = std::make_shared(); - if (postProcessorDptr == nullptr) - { - LogError << "init postProcessor failed, nullptr"; - } - postProcessorDptr->Init(postConfig); - - size_t postprocess_count = 0; - while (postprocess_count < target_count) - { - if (postprocess_count >= videoInfo::inferOutputVector.size()) - { - continue; - } - // 取原图信息用于计算 - MxBase::Size originalSize = videoInfo::realImageVector[postprocess_count].image.GetOriginalSize(); - // 从推理结果的队列里面取出推理结果 - std::vector modelOutputs = videoInfo::inferOutputVector[postprocess_count]; - FrameImage resizedFrame = videoInfo::resizedImageVector[postprocess_count]; - // 新的后处理过程 - MxBase::ResizedImageInfo imgInfo; - auto shape = modelOutputs[0].GetShape(); - imgInfo.widthOriginal = originalSize.width; - imgInfo.heightOriginal = originalSize.height; - imgInfo.widthResize = videoInfo::YOLOV5_RESIZE; - imgInfo.heightResize = videoInfo::YOLOV5_RESIZE; - imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; - // 因为yolov5要求输入图像为640*640,所以直接比较原图的height和width就好(如果不理解就去看cropResizePaste.hpp里的GetPasteRect函数) - float resizeRate = originalSize.width > originalSize.height ? (originalSize.width * 1.0 / videoInfo::YOLOV5_RESIZE) : (originalSize.height * 1.0 / videoInfo::YOLOV5_RESIZE); - imgInfo.keepAspectRatioScaling = 1 / resizeRate; - std::vector imageInfoVec = {}; - imageInfoVec.push_back(imgInfo); - // make postProcess inputs - std::vector tensors; - for (size_t i = 0; i < modelOutputs.size(); i++) - { - MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); - MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); - tensors.push_back(tensorBase); - } - // 后处理 - std::vector> objectInfos; - postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); - - // 将后处理结果存入队列 - videoInfo::g_threadsMutex_postprocessOutputVector.lock(); - videoInfo::postprocessOutputVector.push_back(objectInfos); - videoInfo::g_threadsMutex_postprocessOutputVector.unlock(); - // 计数 - postprocess_count++; - } - std::chrono::high_resolution_clock::time_point postprocess_end_time = std::chrono::high_resolution_clock::now(); - double_t postprocess_cost_time = std::chrono::duration_cast(postprocess_end_time - postprocess_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t postprocess_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到后处理全部完成一共花费: " << postprocess_finish_time << ", 后处理本身花费: " << postprocess_cost_time; -} - -// 跟踪去重线程 -void trackMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) -{ - std::chrono::high_resolution_clock::time_point track_start_time = std::chrono::high_resolution_clock::now(); - - std::shared_ptr tracker0 = std::make_shared(); - if (tracker0 == nullptr) - { - LogError << "init tracker0 failed, nullptr"; - } - std::shared_ptr tracker1 = std::make_shared(); - if (tracker1 == nullptr) - { - LogError << "init tracker1 failed, nullptr"; - } - // 用于计算帧率 - size_t old_count = 0; - std::chrono::high_resolution_clock::time_point count_time; - std::chrono::high_resolution_clock::time_point old_count_time = std::chrono::high_resolution_clock::now(); - size_t one_step = 2; - size_t track_count = 0; - while (track_count < target_count) - { - // 计算帧率 - // 如果count_time-old_count_time的值大于one_step,就计算一下这个step里面的帧数,然后除以step的值 - count_time = std::chrono::high_resolution_clock::now(); - if (std::chrono::duration_cast(count_time - old_count_time).count() / videoInfo::MS_PPE_SECOND > one_step) - { - old_count_time = count_time; - LogInfo << "rate: " << (track_count - old_count) / one_step * 1.0; - old_count = track_count; - } - // 下面是业务循环 - if (track_count >= videoInfo::postprocessOutputVector.size()) - { - continue; - } - // 从后处理结果的队列中取结果用于跟踪去重 - std::vector> objectInfos = videoInfo::postprocessOutputVector[track_count]; - FrameImage frame = videoInfo::realImageVector[track_count]; - MxBase::Size originalSize = frame.image.GetOriginalSize(); - - // 根据channelId的不同使用不同的tracker - std::vector objInfos_ = {}; - if (frame.channelId == 0) - { - tracker0->ProcessSort(objectInfos, frame.frameId); - APP_ERROR ret = tracker0->GettrackResult(objInfos_); - if (ret != APP_ERR_OK) - { - LogError << "No tracker0"; - } - } - else - { - tracker1->ProcessSort(objectInfos, frame.frameId); - APP_ERROR ret = tracker1->GettrackResult(objInfos_); - if (ret != APP_ERR_OK) - { - LogError << "No tracker1"; - } - } - - uint32_t video_height = originalSize.height; - uint32_t video_width = originalSize.width; - // 初始化OpenCV图像信息矩阵 - cv::Mat imgYuv = cv::Mat(video_height * videoInfo::YUV_BYTE_NU / videoInfo::YUV_BYTE_DE, video_width, CV_8UC1, frame.image.GetData().get()); - cv::Mat imgBgr = cv::Mat(video_height, video_width, CV_8UC3); - // 颜色空间转换 - cv::cvtColor(imgYuv, imgBgr, cv::COLOR_YUV420sp2RGB); - std::vector info; - bool headFlag = false; - for (uint32_t i = 0; i < objInfos_.size(); i++) - { - if (objInfos_[i].className == videoInfo::TARGET_CLASS_NAME) - { - headFlag = true; - LogWarn << "Warning:Not wearing a helmet, channelId:" << frame.channelId << ", frameId:" << frame.frameId; - // (blue, green, red) - const cv::Scalar color = cv::Scalar(0, 0, 255); - // width for rectangle - const uint32_t thickness = 2; - // draw the rectangle - cv::rectangle(imgBgr, - cv::Rect(objInfos_[i].x0, objInfos_[i].y0, objInfos_[i].x1 - objInfos_[i].x0, objInfos_[i].y1 - objInfos_[i].y0), - color, thickness); - } - } - // 如果检测结果中有head标签,就保存为图片 - if (headFlag) - { - // 把Mat类型的图像矩阵保存为图像到指定位置。 - std::string outPath = frame.channelId == 0 ? "one" : "two"; - std::string fileName = "./result/" + outPath + "/result" + std::to_string(frame.frameId) + ".jpg"; - cv::imwrite(fileName, imgBgr); - } - - // 计数 - track_count++; - } - std::chrono::high_resolution_clock::time_point track_end_time = std::chrono::high_resolution_clock::now(); - double_t track_cost_time = std::chrono::duration_cast(track_end_time - track_start_time).count() / videoInfo::MS_PPE_SECOND; - double_t track_finish_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() / videoInfo::MS_PPE_SECOND; - LogInfo << "总共" << target_count << "帧, 到跟踪去重全部完成一共花费: " << track_finish_time << ", 跟踪去重本身花费: " << track_cost_time; -} - -#endif From 534d2d7aafa333eda6538804f71b9838969352f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 08:13:16 +0000 Subject: [PATCH 31/58] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20src?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/src/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 HelmetIdentification_V2/src/.keep diff --git a/HelmetIdentification_V2/src/.keep b/HelmetIdentification_V2/src/.keep new file mode 100644 index 0000000..e69de29 From 351f52fd864755649852b2e9cb33786e9e2bcb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 08:13:45 +0000 Subject: [PATCH 32/58] =?UTF-8?q?=E4=BA=A4=E4=BB=98=E7=94=A8=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E5=8C=85=E6=8B=AC=E8=AF=BB=E5=8F=96=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E5=92=8C=E8=AF=BB=E5=8F=96=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/src/CMakeLists.txt | 47 +++ HelmetIdentification_V2/src/DataType.h | 94 +++++ HelmetIdentification_V2/src/Hungarian.cpp | 165 ++++++++ HelmetIdentification_V2/src/Hungarian.h | 46 +++ HelmetIdentification_V2/src/KalmanTracker.cpp | 143 +++++++ HelmetIdentification_V2/src/KalmanTracker.h | 39 ++ HelmetIdentification_V2/src/MOTConnection.cpp | 262 +++++++++++++ HelmetIdentification_V2/src/MOTConnection.h | 78 ++++ .../src/cropResizePaste.hpp | 114 ++++++ HelmetIdentification_V2/src/main-image.cpp | 170 +++++++++ HelmetIdentification_V2/src/main.cpp | 358 ++++++++++++++++++ HelmetIdentification_V2/src/utils.h | 332 ++++++++++++++++ 12 files changed, 1848 insertions(+) create mode 100644 HelmetIdentification_V2/src/CMakeLists.txt create mode 100644 HelmetIdentification_V2/src/DataType.h create mode 100644 HelmetIdentification_V2/src/Hungarian.cpp create mode 100644 HelmetIdentification_V2/src/Hungarian.h create mode 100644 HelmetIdentification_V2/src/KalmanTracker.cpp create mode 100644 HelmetIdentification_V2/src/KalmanTracker.h create mode 100644 HelmetIdentification_V2/src/MOTConnection.cpp create mode 100644 HelmetIdentification_V2/src/MOTConnection.h create mode 100644 HelmetIdentification_V2/src/cropResizePaste.hpp create mode 100644 HelmetIdentification_V2/src/main-image.cpp create mode 100644 HelmetIdentification_V2/src/main.cpp create mode 100644 HelmetIdentification_V2/src/utils.h diff --git a/HelmetIdentification_V2/src/CMakeLists.txt b/HelmetIdentification_V2/src/CMakeLists.txt new file mode 100644 index 0000000..6fcd298 --- /dev/null +++ b/HelmetIdentification_V2/src/CMakeLists.txt @@ -0,0 +1,47 @@ +# CMake lowest version requirement +cmake_minimum_required(VERSION 3.5.1) +# project information +project(Individual) + +# Compile options +add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0 -Dgoogle=mindxsdk_private) +add_compile_options(-std=c++11 -fPIC -fstack-protector-all -Wall -D_FORTIFY_SOURCE=2 -O2) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../../") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now,-z,noexecstack -s -pie -pthread") +set(CMAKE_SKIP_RPATH TRUE) + +SET(CMAKE_BUILD_TYPE "Debug") +SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb") +SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall") + +# Header path +include_directories( + ${MX_SDK_HOME}/include/ + ${MX_SDK_HOME}/opensource/include/ + ${MX_SDK_HOME}/opensource/include/opencv4/ + /home/HwHiAiUser/Ascend/ascend-toolkit/latest/include/ + ./ +) + +# add host lib path +link_directories( + ${MX_SDK_HOME}/lib/ + ${MX_SDK_HOME}/lib/modelpostprocessors + ${MX_SDK_HOME}/opensource/lib/ + ${MX_SDK_HOME}/opensource/lib64/ + /usr/lib/aarch64-linux-gnu/ + /home/HwHiAiUser/Ascend/ascend-toolkit/latest/lib64/ + /usr/local/Ascend/driver/lib64/ + ./ +) + + +aux_source_directory(. sourceList) + +add_executable(main ${sourceList}) + +target_link_libraries(main mxbase opencv_world boost_filesystem glog avformat avcodec avutil cpprest yolov3postprocess ascendcl acl_dvpp_mpi) + +install(TARGETS main DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/HelmetIdentification_V2/src/DataType.h b/HelmetIdentification_V2/src/DataType.h new file mode 100644 index 0000000..dc34a0a --- /dev/null +++ b/HelmetIdentification_V2/src/DataType.h @@ -0,0 +1,94 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_DATATYPE_H +#define MXBASE_HELMETIDENTIFICATION_DATATYPE_H + +#include +#include +#include +#include +#include +#include +#include "opencv2/highgui.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" + +namespace ascendVehicleTracking { +#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) + + const int MODULE_QUEUE_SIZE = 1000; + + enum FrameMode { + FRAME_MODE_SEARCH = 0, + FRAME_MODE_REG + }; + + struct DataBuffer { + std::shared_ptr deviceData; + std::shared_ptr hostData; + uint32_t dataSize; // buffer size + DataBuffer() : deviceData(nullptr), hostData(nullptr), dataSize(0) {} + }; + + struct DetectInfo { + int32_t classId; + float confidence; + float minx; // x value of left-top point + float miny; // y value of left-top point + float height; + float width; + }; + + enum TraceFlag { + NEW_VEHICLE = 0, + TRACkED_VEHICLE, + LOST_VEHICLE + }; + + struct TraceInfo { + int32_t id; + TraceFlag flag; + int32_t survivalTime; // How long is it been since the first time, unit: detection period + int32_t detectedTime; // How long is the vehicle detected, unit: detection period + std::chrono::time_point createTime; + }; + + struct TrackLet { + TraceInfo info; + // reserved: kalman status parameter + int32_t lostTime; // undetected time for tracked vehicle + std::vector shortFeature; // nearest 10 frame + }; + + struct VehicleQuality { + float score; + }; + + struct Coordinate2D { + uint32_t x; + uint32_t y; + }; +} +// namespace ascendVehicleTracking + +struct AttrT { + AttrT(std::string name, std::string value) : name(std::move(name)), value(std::move(value)) {} + std::string name = {}; + std::string value = {}; +}; + +#endif diff --git a/HelmetIdentification_V2/src/Hungarian.cpp b/HelmetIdentification_V2/src/Hungarian.cpp new file mode 100644 index 0000000..3f6fcd2 --- /dev/null +++ b/HelmetIdentification_V2/src/Hungarian.cpp @@ -0,0 +1,165 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Hungarian.h" +#include +#include +#include "MxBase/Log/Log.h" + +namespace { + const int INF = 0x3f3f3f3f; + const int VISITED = 1; + const int HUNGARIAN_CONTENT = 7; + const int X_MATCH_OFFSET = 0; + const int Y_MATCH_OFFSET = 1; + const int X_VALUE_OFFSET = 2; + const int Y_VALUE_OFFSET = 3; + const int SLACK_OFFSET = 4; + const int X_VISIT_OFFSET = 5; + const int Y_VISIT_OFFSET = 6; +} + +APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols) +{ + handle.max = (row > cols) ? row : cols; + auto adjMat = std::shared_ptr(); + adjMat.reset(new int[handle.max * handle.max], std::default_delete()); + if (adjMat == nullptr) { + LogFatal << "HungarianHandleInit new failed"; + return APP_ERR_ACL_FAILURE; + } + + handle.adjMat = adjMat; + + void* ptr[HUNGARIAN_CONTENT] = {nullptr}; + for (int i = 0; i < HUNGARIAN_CONTENT; ++i) { + ptr[i] = malloc(handle.max * sizeof(int)); + if (ptr[i] == nullptr) { + LogFatal << "HungarianHandleInit Malloc failed"; + return APP_ERR_ACL_FAILURE; + } + } + + handle.xMatch.reset((int *)ptr[X_MATCH_OFFSET], free); + handle.yMatch.reset((int *)ptr[Y_MATCH_OFFSET], free); + handle.xValue.reset((int *)ptr[X_VALUE_OFFSET], free); + handle.yValue.reset((int *)ptr[Y_VALUE_OFFSET], free); + handle.slack.reset((int *)ptr[SLACK_OFFSET], free); + handle.xVisit.reset((int *)ptr[X_VISIT_OFFSET], free); + handle.yVisit.reset((int *)ptr[Y_VISIT_OFFSET], free); + return APP_ERR_OK; +} + +static void HungarianInit(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) +{ + int i, j, value; + if (rows > cols) { + handle.transpose = true; + handle.cols = rows; + handle.rows = cols; + handle.resX = handle.yMatch.get(); + handle.resY = handle.xMatch.get(); + } else { + handle.transpose = false; + handle.rows = rows; + handle.cols = cols; + handle.resX = handle.xMatch.get(); + handle.resY = handle.yMatch.get(); + } + + for (i = 0; i < handle.rows; ++i) { + handle.xValue.get()[i] = 0; + handle.xMatch.get()[i] = -1; + for (j = 0; j < handle.cols; ++j) { + if (handle.transpose) { + value = cost[j][i]; + } else { + value = cost[i][j]; + } + handle.adjMat.get()[i * handle.cols + j] = value; + if (handle.xValue.get()[i] < value) { + handle.xValue.get()[i] = value; + } + } + } + + for (i = 0; i < handle.cols; ++i) { + handle.yValue.get()[i] = 0; + handle.yMatch.get()[i] = -1; + } +} + +static bool Match(HungarianHandle &handle, int id) +{ + int j, delta; + handle.xVisit.get()[id] = VISITED; + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] == VISITED) { + continue; + } + delta = handle.xValue.get()[id] + handle.yValue.get()[j] - handle.adjMat.get()[id * handle.cols + j]; + if (delta == 0) { + handle.yVisit.get()[j] = VISITED; + if (handle.yMatch.get()[j] == -1 || Match(handle, handle.yMatch.get()[j])) { + handle.yMatch.get()[j] = id; + handle.xMatch.get()[id] = j; + return true; + } + } else if (delta < handle.slack.get()[j]) { + handle.slack.get()[j] = delta; + } + } + return false; +} + +int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols) +{ + HungarianInit(handle, cost, rows, cols); + int i, j, delta; + for (i = 0; i < handle.rows; ++i) { + while (true) { + std::fill(handle.xVisit.get(), handle.xVisit.get() + handle.rows, 0); + std::fill(handle.yVisit.get(), handle.yVisit.get() + handle.cols, 0); + for (j = 0; j < handle.cols; ++j) { + handle.slack.get()[j] = INF; + } + if (Match(handle, i)) { + break; + } + delta = INF; + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] != VISITED && delta > handle.slack.get()[j]) { + delta = handle.slack.get()[j]; + } + } + if (delta == INF) { + LogDebug << "Hungarian solve is invalid!"; + return -1; + } + for (j = 0; j < handle.rows; ++j) { + if (handle.xVisit.get()[j] == VISITED) { + handle.xValue.get()[j] -= delta; + } + } + for (j = 0; j < handle.cols; ++j) { + if (handle.yVisit.get()[j] == VISITED) { + handle.yValue.get()[j] += delta; + } + } + } + } + return handle.rows; +} diff --git a/HelmetIdentification_V2/src/Hungarian.h b/HelmetIdentification_V2/src/Hungarian.h new file mode 100644 index 0000000..2ae6a33 --- /dev/null +++ b/HelmetIdentification_V2/src/Hungarian.h @@ -0,0 +1,46 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H +#define MXBASE_HELMETIDENTIFICATION_HUNGARIAN_H + +#include +#include +#include "DataType.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" +#include "MxBase/ErrorCode/ErrorCodes.h" + +struct HungarianHandle { + int rows; + int cols; + int max; + int *resX; + int *resY; + bool transpose; + std::shared_ptr adjMat; + std::shared_ptr xMatch; + std::shared_ptr yMatch; + std::shared_ptr xValue; + std::shared_ptr yValue; + std::shared_ptr slack; + std::shared_ptr xVisit; + std::shared_ptr yVisit; +}; + +APP_ERROR HungarianHandleInit(HungarianHandle &handle, int row, int cols); +int HungarianSolve(HungarianHandle &handle, const std::vector> &cost, int rows, int cols); + +#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/KalmanTracker.cpp b/HelmetIdentification_V2/src/KalmanTracker.cpp new file mode 100644 index 0000000..69362f9 --- /dev/null +++ b/HelmetIdentification_V2/src/KalmanTracker.cpp @@ -0,0 +1,143 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "KalmanTracker.h" + +namespace ascendVehicleTracking +{ + namespace + { + const int OFFSET = 2; + const int MULTIPLE = 2; + } + + /* + * The SORT algorithm uses a linear constant velocity model,which assumes 7 + * states, including + * x coordinate of bounding box center + * y coordinate of bounding box center + * area of bounding box + * aspect ratio of w to h + * velocity of x + * velocity of y + * variation rate of area + * + * The aspect ratio is considered to be unchanged, so there is no additive item + * for aspect ratio in the transitionMatrix + * + * + * Kalman filter equation step by step + * (1) X(k|k-1)=AX(k-1|k-1)+BU(k) + * X(k|k-1) is the predicted state(statePre),X(k-1|k-1) is the k-1 statePost,A + * is transitionMatrix, B is controlMatrix, U(k) is control state, in SORT U(k) is 0. + * + * (2) P(k|k-1)=AP(k-1|k-1)A'+Q + * P(k|k-1) is the predicted errorCovPre, P(k-1|k-1) is the k-1 errorCovPost, + * Q is processNoiseCov + * + * (3) Kg(k)=P(k|k-1)H'/(HP(k|k-1))H'+R + * Kg(k) is the kalman gain, the ratio of estimate variance in total variance, + * H is the measurementMatrix,R is the measurementNoiseCov + * + * (4) X(k|k)=X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) + * X(k|k) is the k statePost, Z(k) is the measurement of K, in SORT Z(k) is + * the detection result of k + * + * (5) P(k|k)=(1-Kg(k)H)P(k|k-1) + * P(k|k) is the errorCovPost + */ + void KalmanTracker::CvKalmanInit(MxBase::ObjectInfo initRect) + { + const int stateDim = 7; + const int measureDim = 4; + cvkalmanfilter_ = cv::KalmanFilter(stateDim, measureDim, 0); // zero control + measurement_ = cv::Mat::zeros(measureDim, 1, CV_32F); // 4 measurements, Z(k), according to detection results + // A, will not be updated + cvkalmanfilter_.transitionMatrix = (cv::Mat_(stateDim, stateDim) << 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1); + cvkalmanfilter_.measurementMatrix = (cv::Mat_(measureDim, stateDim) << 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0); + cv::setIdentity(cvkalmanfilter_.measurementMatrix); // H, will not be updated + cv::setIdentity(cvkalmanfilter_.processNoiseCov, cv::Scalar::all(1e-2)); // Q, will not be updated + cv::setIdentity(cvkalmanfilter_.measurementNoiseCov, cv::Scalar::all(1e-1)); // R, will bot be updated + cv::setIdentity(cvkalmanfilter_.errorCovPost, cv::Scalar::all(1)); // P(k-1|k-1), will be updated + + // initialize state vector with bounding box in + // [center_x,center_y,area,ratio] + // style, the velocity is 0 + // X(k-1|k-1) + cvkalmanfilter_.statePost.at(0, 0) = initRect.x0 + (initRect.x1 - initRect.x0) / MULTIPLE; + cvkalmanfilter_.statePost.at(1, 0) = initRect.y0 + (initRect.y1 - initRect.y0) / MULTIPLE; + cvkalmanfilter_.statePost.at(OFFSET, 0) = (initRect.x1 - initRect.x0) * (initRect.y1 - initRect.y0); + cvkalmanfilter_.statePost.at(OFFSET + 1, 0) = (initRect.x1 - initRect.x0) / (initRect.y1 - initRect.y0); + } + + // Predict the bounding box. + MxBase::ObjectInfo KalmanTracker::Predict() + { + // predict + // return X(k|k-1)=AX(k-1|k-1), and update + // P(k|k-1) <- AP(k-1|k-1)A'+Q + MxBase::ObjectInfo detectInfo = {}; + cv::Mat predictState = cvkalmanfilter_.predict(); + float *pData = (float *)(predictState.data); + float w = sqrt((*(pData + OFFSET)) * (*(pData + OFFSET + 1))); + if (w < DBL_EPSILON) + { + detectInfo.x0 = 0; + detectInfo.y0 = 0; + detectInfo.x1 = 0; + detectInfo.y1 = 0; + detectInfo.classId = 0; + return detectInfo; + } + if (w == 0) + { + MxBase::ObjectInfo w0DetectInfo = {}; + return w0DetectInfo; + } + float h = (*(pData + OFFSET)) / w; + float x = (*pData) - w / MULTIPLE; + float y = (*(pData + 1)) - h / MULTIPLE; + if (x < 0 && (*pData) > 0) + { + x = 0; + } + if (y < 0 && (*(pData + 1)) > 0) + { + y = 0; + } + detectInfo.x0 = x; + detectInfo.y0 = y; + detectInfo.x1 = x + w; + detectInfo.y1 = y + h; + return detectInfo; + } + + // Update the state using observed bounding box + void KalmanTracker::Update(MxBase::ObjectInfo stateMat) + { + // measurement_, update Z(k) + float *pData = (float *)(measurement_.data); + *pData = stateMat.x0 + (stateMat.x1 - stateMat.x0) / MULTIPLE; + *(pData + 1) = stateMat.y0 + (stateMat.y1 - stateMat.y0) / MULTIPLE; + *(pData + OFFSET) = (stateMat.x1 - stateMat.x0) * (stateMat.y1 - stateMat.y0); + *(pData + OFFSET + 1) = (stateMat.x1 - stateMat.x0) / (stateMat.y1 - stateMat.y0); + // update, do the following steps: + // Kg(k): P(k|k-1)H'/(HP(k|k-1))H'+R + // X(k|k): X(k|k-1)+Kg(k)(Z(k)-HX(k|k-1)) + // P(k|k): (1-Kg(k)H)P(k|k-1) + cvkalmanfilter_.correct(measurement_); + } +} // namespace diff --git a/HelmetIdentification_V2/src/KalmanTracker.h b/HelmetIdentification_V2/src/KalmanTracker.h new file mode 100644 index 0000000..f5f3465 --- /dev/null +++ b/HelmetIdentification_V2/src/KalmanTracker.h @@ -0,0 +1,39 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H +#define MXBASE_HELMETIDENTIFICATION_KALMANTRACKER_H + +#include "opencv2/highgui/highgui.hpp" +#include "opencv2/video/tracking.hpp" +#include "DataType.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" + +namespace ascendVehicleTracking { +class KalmanTracker { +public: + KalmanTracker() {} + ~KalmanTracker() {} + void CvKalmanInit(MxBase::ObjectInfo initRect); + MxBase::ObjectInfo Predict(); + void Update(MxBase::ObjectInfo stateMat); +private: + cv::KalmanFilter cvkalmanfilter_ = {}; + cv::Mat measurement_ = {}; +}; +} // namesapce ascendVehicleTracking + +#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/MOTConnection.cpp b/HelmetIdentification_V2/src/MOTConnection.cpp new file mode 100644 index 0000000..14e4341 --- /dev/null +++ b/HelmetIdentification_V2/src/MOTConnection.cpp @@ -0,0 +1,262 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MOTConnection.h" +#include +#include +#include +#include +#include "opencv2/highgui.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include "MxBase/Log/Log.h" +#include "MxBase/ErrorCode/ErrorCodes.h" +#include "Hungarian.h" + +namespace ascendVehicleTracking +{ + namespace + { + // convert double to int + const int FLOAT_TO_INT = 1000; + const int MULTIPLE = 0; + const double SIMILARITY_THRESHOLD = 0.66; + const int MULTIPLE_IOU = 6; + const float NORM_EPS = 1e-10; + const double TIME_COUNTS = 1000.0; + const double COST_TIME_MS_THRESHOLD = 10.; + const float WIDTH_RATE_THRESH = 1.f; + const float HEIGHT_RATE_THRESH = 1.f; + const float X_DIST_RATE_THRESH = 1.3f; + const float Y_DIST_RATE_THRESH = 1.f; + } // namespace + + // 计算bounding box的交并比 + float CalIOU(MxBase::ObjectInfo detect1, MxBase::ObjectInfo detect2) + { + cv::Rect_ bbox1(detect1.x0, detect1.y0, detect1.x1 - detect1.x0, detect1.y1 - detect1.y0); + cv::Rect_ bbox2(detect2.x0, detect2.y0, detect2.x1 - detect2.x0, detect2.y1 - detect2.y0); + float intersectionArea = (bbox1 & bbox2).area(); + float unionArea = bbox1.area() + bbox2.area() - intersectionArea; + if (unionArea < DBL_EPSILON) + { + return 0.f; + } + return (intersectionArea / unionArea); + } + + // 计算前后两帧的两个bounding box的相似度 + float CalSimilarity(const TraceLet &traceLet, const MxBase::ObjectInfo &objectInfo, const int &method, const double &kIOU) + { + return CalIOU(traceLet.detectInfo, objectInfo); + } + + // 过滤掉交并比小于阈值的匹配 + void MOTConnection::FilterLowThreshold(const HungarianHandle &hungarianHandleObj, + const std::vector> &disMatrix, std::vector &matchedTracedDetected, + std::vector &detectVehicleFlagVec) + { + for (unsigned int i = 0; i < traceList_.size(); ++i) + { + if ((hungarianHandleObj.resX[i] != -1) && + (disMatrix[i][hungarianHandleObj.resX[i]] >= (trackThreshold_ * FLOAT_TO_INT))) + { + matchedTracedDetected.push_back(cv::Point(i, hungarianHandleObj.resX[i])); + detectVehicleFlagVec[hungarianHandleObj.resX[i]] = true; + } + else + { + traceList_[i].info.flag = LOST_VEHICLE; + } + } + } + + // 更新没有匹配上的跟踪器 + void MOTConnection::UpdateUnmatchedTraceLet(const std::vector> &objInfos) + { + for (auto itr = traceList_.begin(); itr != traceList_.end();) + { + if ((*itr).info.flag != LOST_VEHICLE) + { + ++itr; + continue; + } + + (*itr).lostAge++; + (*itr).kalman.Update((*itr).detectInfo); + + if ((*itr).lostAge < lostThreshold_) + { + continue; + } + + itr = traceList_.erase(itr); + } + } + + // 更新匹配上的跟踪器 + void MOTConnection::UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, + std::vector> &objInfos) + { + for (unsigned int i = 0; i < matchedTracedDetected.size(); ++i) + { + int traceIndex = matchedTracedDetected[i].x; + int detectIndex = matchedTracedDetected[i].y; + if (traceList_[traceIndex].info.survivalTime > MULTIPLE) + { + traceList_[traceIndex].info.flag = TRACkED_VEHICLE; + } + traceList_[traceIndex].info.survivalTime++; + traceList_[traceIndex].info.detectedTime++; + traceList_[traceIndex].lostAge = 0; + traceList_[traceIndex].detectInfo = objInfos[0][detectIndex]; + traceList_[traceIndex].kalman.Update(objInfos[0][detectIndex]); + } + } + // 将没有匹配上的检测更新为新的检测器 + void MOTConnection::AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue) + { + using Time = std::chrono::high_resolution_clock; + for (auto &vehicleObject : unmatchedVehicleObjectQueue) + { + // add new detected into traceList + TraceLet traceLet; + generatedId_++; + traceLet.info.id = generatedId_; + traceLet.info.survivalTime = 1; + traceLet.info.detectedTime = 1; + traceLet.lostAge = 0; + traceLet.info.flag = NEW_VEHICLE; + traceLet.detectInfo = vehicleObject; + traceLet.info.createTime = Time::now(); + + traceLet.kalman.CvKalmanInit(vehicleObject); + traceList_.push_back(traceLet); + } + } + + void MOTConnection::UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, + std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue) + { + UpdateMatchedTraceLet(matchedTracedDetected, objInfos); // 更新匹配上的跟踪器 + AddNewDetectedVehicle(unmatchedVehicleObjectQueue); // 将没有匹配上的检测更新为新的检测器 + UpdateUnmatchedTraceLet(objInfos); // 更新没有匹配上的跟踪器 + } + + void MOTConnection::TrackObjectPredict() + { + // every traceLet should do kalman predict + for (auto &traceLet : traceList_) + { + traceLet.detectInfo = traceLet.kalman.Predict(); // 卡尔曼滤波预测的框 + } + } + + void MOTConnection::TrackObjectUpdate(const std::vector> &objInfos, + std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue) + { + if (objInfos[0].size() > 0) + { + LogDebug << "[frame id = " << 1 << "], trace size =" << traceList_.size() << "detect size = " << objInfos[0].size() << ""; + // init vehicle matched flag + std::vector detectVehicleFlagVec; + for (unsigned int i = 0; i < objInfos[0].size(); ++i) + { + detectVehicleFlagVec.push_back(false); + } + // calculate the associated matrix + std::vector> disMatrix; + disMatrix.clear(); + disMatrix.resize(traceList_.size(), std::vector(objInfos[0].size(), 0)); + for (unsigned int j = 0; j < objInfos[0].size(); ++j) + { + for (unsigned int i = 0; i < traceList_.size(); ++i) + { + // 计算交并比 + float sim = CalSimilarity(traceList_[i], objInfos[0][j], method_, kIOU_); // method_=1, kIOU_=1.0 + disMatrix[i][j] = (int)(sim * FLOAT_TO_INT); + } + } + + // solve the assignment problem using hungarian 匈牙利算法进行匹配 + HungarianHandle hungarianHandleObj = {}; + HungarianHandleInit(hungarianHandleObj, traceList_.size(), objInfos[0].size()); + HungarianSolve(hungarianHandleObj, disMatrix, traceList_.size(), objInfos[0].size()); + // filter out matched but with low distance 过滤掉匹配上但是交并比较小的 + FilterLowThreshold(hungarianHandleObj, disMatrix, matchedTracedDetected, detectVehicleFlagVec); + LogDebug << "matchedTracedDetected = " << matchedTracedDetected.size() << ""; + // fill unmatchedVehicleObjectQueue + for (unsigned int i = 0; i < detectVehicleFlagVec.size(); ++i) + { + if (detectVehicleFlagVec[i] == false) + { + unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); + } + } + } + } + + APP_ERROR MOTConnection::ProcessSort(std::vector> &objInfos, size_t frameId) + { + std::vector unmatchedVehicleObjectQueue; + std::vector matchedTracedDetected; + if (objInfos[0].size() == 0) + { + return APP_ERR_COMM_FAILURE; + } + + if (traceList_.size() > 0) + { + // every traceLet should do kalman predict + TrackObjectPredict(); // 卡尔曼滤波预测 + TrackObjectUpdate(objInfos, matchedTracedDetected, unmatchedVehicleObjectQueue); // 选出matched track、unmatched detection + } + else + { + // traceList is empty, all the vehicle detected in the new frame are unmatched. + if (objInfos[0].size() > 0) + { + for (unsigned int i = 0; i < objInfos[0].size(); ++i) + { + unmatchedVehicleObjectQueue.push_back(objInfos[0][i]); + } + } + } + + // update all the tracelet in the tracelist per frame + UpdateTraceLetAndFrame(matchedTracedDetected, objInfos, unmatchedVehicleObjectQueue); // 用matched track、unmatched detection更新跟踪器 + return APP_ERR_OK; + } + + // 获取跟踪后的检测框 + APP_ERROR MOTConnection::GettrackResult(std::vector &objInfos_) + { + if (traceList_.size() > 0) + { + for (auto &traceLet : traceList_) + { + traceLet.detectInfo.classId = traceLet.info.id; + objInfos_.push_back(traceLet.detectInfo); + } + } + else + { + return APP_ERR_COMM_FAILURE; + } + return APP_ERR_OK; + } +} +// namespace ascendVehicleTracking \ No newline at end of file diff --git a/HelmetIdentification_V2/src/MOTConnection.h b/HelmetIdentification_V2/src/MOTConnection.h new file mode 100644 index 0000000..be54f17 --- /dev/null +++ b/HelmetIdentification_V2/src/MOTConnection.h @@ -0,0 +1,78 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H +#define MXBASE_HELMETIDENTIFICATION_MOTCONNECTION_H + +#include +#include +#include +#include "KalmanTracker.h" +#include "Hungarian.h" +#include "DataType.h" +#include "MxBase/ErrorCode/ErrorCodes.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/Tensor/TensorBase/TensorBase.h" +#include "MxBase/PostProcessBases/PostProcessDataType.h" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +namespace ascendVehicleTracking { +struct TraceLet { + TraceInfo info = {}; + int32_t lostAge = 0; + KalmanTracker kalman; + std::list> shortFeatureQueue; + MxBase::ObjectInfo detectInfo = {}; +}; + +class MOTConnection { +public: + APP_ERROR ProcessSort(std::vector> &objInfos, size_t frameId); + APP_ERROR GettrackResult(std::vector &objInfos_); + +private: + double trackThreshold_ = 0.3; + double kIOU_ = 1.0; + int32_t method_ = 1; + int32_t lostThreshold_ = 3; + uint32_t maxNumberFeature_ = 0; + int32_t generatedId_ = 0; + std::vector traceList_ = {}; + +private: + + void FilterLowThreshold(const HungarianHandle &hungarianHandleObj, const std::vector> &disMatrix, + std::vector &matchedTracedDetected, std::vector &detectVehicleFlagVec); + + void UpdateUnmatchedTraceLet(const std::vector> &objInfos); + + void UpdateMatchedTraceLet(const std::vector &matchedTracedDetected, + std::vector> &objInfos); + + void AddNewDetectedVehicle(std::vector &unmatchedVehicleObjectQueue); + + void UpdateTraceLetAndFrame(const std::vector &matchedTracedDetected, + std::vector> &objInfos, std::vector &unmatchedVehicleObjectQueue); + + void TrackObjectPredict(); + void TrackObjectUpdate(const std::vector> &objInfos, + std::vector &matchedTracedDetected, std::vector &unmatchedVehicleObjectQueue); +}; +} // namespace ascendVehicleTracking + +#endif \ No newline at end of file diff --git a/HelmetIdentification_V2/src/cropResizePaste.hpp b/HelmetIdentification_V2/src/cropResizePaste.hpp new file mode 100644 index 0000000..b3648ec --- /dev/null +++ b/HelmetIdentification_V2/src/cropResizePaste.hpp @@ -0,0 +1,114 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef CRP_H +#define CRP_H + +#include "MxBase/E2eInfer/Image/Image.h" +#include "MxBase/E2eInfer/Rect/Rect.h" +#include "MxBase/E2eInfer/Size/Size.h" + +#include "acl/dvpp/hi_dvpp.h" +#include "acl/acl.h" +#include "acl/acl_rt.h" + +#define CONVER_TO_PODD(NUM) (((NUM) % 2 != 0) ? (NUM) : ((NUM)-1)) +#define CONVER_TO_EVEN(NUM) (((NUM) % 2 == 0) ? (NUM) : ((NUM)-1)) +#define DVPP_ALIGN_UP(x, align) ((((x) + ((align)-1)) / (align)) * (align)) + +MxBase::Rect GetPasteRect(uint32_t inputWidth, uint32_t inputHeight, uint32_t outputWidth, uint32_t outputHeight) +{ + bool widthRatioLarger = true; + float resizeRatio = static_cast(inputWidth) / outputWidth; + if (resizeRatio < (static_cast(inputHeight) / outputHeight)) + { + resizeRatio = static_cast(inputHeight) / outputHeight; + widthRatioLarger = false; + } + + // (x0, y0)是左上角坐标,(x1, y1)是右下角坐标,采用图片坐标系 + uint32_t x0; + uint32_t y0; + uint32_t x1; + uint32_t y1; + if (widthRatioLarger) + { + // 原图width大于height + x0 = 0; + y0 = 0; + x1 = outputWidth-1; + y1 = inputHeight / resizeRatio - 1; + } + else + { + // 原图height大于width + x0 = 0; + y0 = 0; + x1 = inputWidth / resizeRatio - 1; + y1 = outputHeight - 1; + } + x0 = DVPP_ALIGN_UP(CONVER_TO_EVEN(x0), 16); // 16对齐 + x1 = DVPP_ALIGN_UP((x1 - x0 + 1), 16) + x0 - 1; + y0 = CONVER_TO_EVEN(y0); + y1 = CONVER_TO_PODD(y1); + MxBase::Rect res(x0, y0, x1, y1); + return res; +} + +MxBase::Image ConstructImage(uint32_t resizeWidth, uint32_t resizeHeight) +{ + void *addr; + uint32_t dataSize = resizeWidth * resizeHeight * 3 / 2; + auto ret = hi_mpi_dvpp_malloc(0, &addr, dataSize); + if (ret != APP_ERR_OK) + { + LogError << "hi_mpi_dvpp_malloc fail :" << ret; + } + // 第三个参数从128改成了0 + ret = aclrtMemset(addr, dataSize, 0, dataSize); + if (ret != APP_ERR_OK) + { + LogError << "aclrtMemset fail :" << ret; + } + std::shared_ptr data((uint8_t *)addr, hi_mpi_dvpp_free); + MxBase::Size imageSize(resizeWidth, resizeHeight); + MxBase::Image pastedImg(data, dataSize, 0, imageSize); + return pastedImg; +} + +std::pair GenerateRect(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight) +{ + uint32_t x1 = CONVER_TO_PODD(originalWidth - 1); + uint32_t y1 = CONVER_TO_PODD(originalHeight - 1); + MxBase::Rect cropRect(0, 0, x1, y1); + MxBase::Rect pasteRect = GetPasteRect(originalWidth, originalHeight, resizeWidth, resizeHeight); + std::pair cropPasteRect(cropRect, pasteRect); + return cropPasteRect; +} + +MxBase::Image resizeKeepAspectRatioFit(uint32_t originalWidth, uint32_t originalHeight, uint32_t resizeWidth, uint32_t resizeHeight, MxBase::Image &decodeImage, MxBase::ImageProcessor& imageProcessor) +{ + std::pair cropPasteRect = GenerateRect(originalWidth, originalHeight, resizeWidth, resizeHeight); + MxBase::Image resizeImage = ConstructImage(resizeWidth, resizeHeight); + auto ret = imageProcessor.CropAndPaste(decodeImage, cropPasteRect, resizeImage); + if (ret != APP_ERR_OK) + { + LogError << "CropAndPaste fail :" << ret; + } + return resizeImage; +} + +#endif // CRP_H \ No newline at end of file diff --git a/HelmetIdentification_V2/src/main-image.cpp b/HelmetIdentification_V2/src/main-image.cpp new file mode 100644 index 0000000..a764a67 --- /dev/null +++ b/HelmetIdentification_V2/src/main-image.cpp @@ -0,0 +1,170 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils.h" + +#include +#include +#include +using namespace std; + +// 如果在200DK上运行就改为 USE_200DK +#define USE_DVPP + +APP_ERROR readImage(std::string imgPath, MxBase::Image& image, MxBase::ImageProcessor& imageProcessor) +{ + APP_ERROR ret; +#ifdef USE_DVPP + // if USE DVPP + ret = imageProcessor.Decode(imgPath, image); +#endif +#ifdef USE_200DK + std::shared_ptr dataPtr; + uint32_t dataSize; + // Get image data to memory, this method can be substituted or designed by yourself! + std::ifstream file; + file.open(imgPath.c_str(), std::ios::binary); + if (!file) + { + LogInfo << "Invalid file."; + return APP_ERR_COMM_INVALID_PARAM; + } + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); + + char *p = (char *)malloc(content.size()); + memcpy(p, content.data(), content.size()); + auto deleter = [](void *p) -> void + { + free(p); + p = nullptr; + }; + + dataPtr.reset(static_cast((void *)(p)), deleter); + dataSize = content.size(); + file.close(); + if (ret != APP_ERR_OK) + { + LogError << "Getimage failed, ret=" << ret; + return ret; + } + ret = imageProcessor.Decode(dataPtr, dataSize, image, MxBase::ImageFormat::YUV_SP_420); + // endif +#endif + if (ret != APP_ERR_OK) + { + LogError << "Decode failed, ret=" << ret; + return ret; + } +} + +void postProcess(std::vector modelOutputs, std::shared_ptr postProcessorDptr, MxBase::Size originalSize, MxBase::Size resizeSize) +{ + MxBase::ResizedImageInfo imgInfo; + auto shape = modelOutputs[0].GetShape(); + imgInfo.widthOriginal = originalSize.width; + imgInfo.heightOriginal = originalSize.height; + imgInfo.widthResize = resizeSize.width; + imgInfo.heightResize = resizeSize.height; + imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; + float resizeRate = originalSize.width > originalSize.height ? (originalSize.width * 1.0 / videoInfo::YOLOV5_RESIZE) : (originalSize.height * 1.0 / videoInfo::YOLOV5_RESIZE); + imgInfo.keepAspectRatioScaling = 1 / resizeRate; + std::vector imageInfoVec = {}; + imageInfoVec.push_back(imgInfo); + // make postProcess inputs + std::vector tensors; + for (size_t i = 0; i < modelOutputs.size(); i++) + { + MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); + MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); + tensors.push_back(tensorBase); + } + std::vector> objectInfos; + postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); + std::cout << "===---> Size of objectInfos is " << objectInfos.size() << std::endl; + for (size_t i = 0; i < objectInfos.size(); i++) + { + std::cout << "objectInfo-" << i << " , Size:" << objectInfos[i].size() << std::endl; + for (size_t j = 0; j < objectInfos[i].size(); j++) + { + std::cout << std::endl + << "*****objectInfo-" << i << ":" << j << std::endl; + std::cout << "x0 is " << objectInfos[i][j].x0 << std::endl; + std::cout << "y0 is " << objectInfos[i][j].y0 << std::endl; + std::cout << "x1 is " << objectInfos[i][j].x1 << std::endl; + std::cout << "y1 is " << objectInfos[i][j].y1 << std::endl; + std::cout << "confidence is " << objectInfos[i][j].confidence << std::endl; + std::cout << "classId is " << objectInfos[i][j].classId << std::endl; + std::cout << "className is " << objectInfos[i][j].className << std::endl; + } + } +} + +APP_ERROR main(int argc, char *argv[]) +{ + APP_ERROR ret; + + // global init + ret = MxBase::MxInit(); + if (ret != APP_ERR_OK) + { + LogError << "MxInit failed, ret=" << ret << "."; + } + // 检测是否输入了文件路径 + if (argc <= 1) + { + LogWarn << "Please input image path, such as 'test.jpg'."; + return APP_ERR_OK; + } + + // imageProcess init + MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); + // model init + MxBase::Model yoloModel(videoInfo::modelPath, videoInfo::DEVICE_ID); + // postprocessor init + std::map postConfig; + postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); + postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); + std::shared_ptr postProcessorDptr = std::make_shared(); + postProcessorDptr->Init(postConfig); + + std::string imgPath = argv[1]; + // 读取图片 + MxBase::Image image; + readImage(imgPath, image, imageProcessor); + + // 缩放图片 + MxBase::Size originalSize = image.GetOriginalSize(); + MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); + MxBase::Image resizedImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); + + // 模型推理 + MxBase::Tensor tensorImg = resizedImage.ConvertToTensor(); + tensorImg.ToDevice(videoInfo::DEVICE_ID); + std::vector inputs; + inputs.push_back(tensorImg); + std::vector modelOutputs = yoloModel.Infer(inputs); + for (auto output : modelOutputs) + { + output.ToHost(); + } + + // 后处理 + postProcess(modelOutputs, postProcessorDptr, originalSize, resizeSize); + + return APP_ERR_OK; +} \ No newline at end of file diff --git a/HelmetIdentification_V2/src/main.cpp b/HelmetIdentification_V2/src/main.cpp new file mode 100644 index 0000000..83a03a8 --- /dev/null +++ b/HelmetIdentification_V2/src/main.cpp @@ -0,0 +1,358 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils.h" + +extern "C" +{ +#include "libavformat/avformat.h" +} + +#include +#include +#include +#include "unistd.h" +#include +#include +#include +#include "boost/filesystem.hpp" + +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "opencv2/opencv.hpp" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" +#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" +#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" +#include "MxBase/E2eInfer/DataType.h" + +#include +#include +using namespace std; +using namespace videoInfo; +namespace frameConfig +{ + size_t channelId0 = 0; + size_t channelId1 = 1; + size_t frameCountChannel0 = 300; + size_t frameCountChannel1 = 300; + size_t skipIntervalChannel0 = 2; + size_t skipIntervalChannel1 = 2; + + int arg_one_video = 4; + int arg_two_video = 7; + int arg1 = 1; + int arg2 = 2; + int arg3 = 3; + int arg4 = 4; + int arg5 = 5; + int arg6 = 6; + int arg7 = 7; + + // channel0对应文件的指针 + AVFormatContext *pFormatCtx0 = nullptr; + // channel1对应文件的指针 + AVFormatContext *pFormatCtx1 = nullptr; +} + +// ffmpeg拉流 +AVFormatContext *CreateFormatContext(std::string filePath) +{ + LogInfo << "start to CreatFormatContext!"; + // creat message for stream pull + AVFormatContext *formatContext = nullptr; + AVDictionary *options = nullptr; + + LogInfo << "start to avformat_open_input!"; + int ret = avformat_open_input(&formatContext, filePath.c_str(), nullptr, &options); + if (options != nullptr) + { + av_dict_free(&options); + } + if (ret != 0) + { + LogError << "Couldn't open input stream " << filePath.c_str() << " ret=" << ret; + return nullptr; + } + ret = avformat_find_stream_info(formatContext, nullptr); + if (ret != 0) + { + LogError << "Couldn't open input stream information"; + return nullptr; + } + return formatContext; +} + +// 真正的拉流函数 +void PullStream0(std::string filePath) +{ + av_register_all(); + avformat_network_init(); + frameConfig::pFormatCtx0 = avformat_alloc_context(); + frameConfig::pFormatCtx0 = CreateFormatContext(filePath); + av_dump_format(frameConfig::pFormatCtx0, 0, filePath.c_str(), 0); +} +void PullStream1(std::string filePath) +{ + av_register_all(); + avformat_network_init(); + frameConfig::pFormatCtx1 = avformat_alloc_context(); + frameConfig::pFormatCtx1 = CreateFormatContext(filePath); + av_dump_format(frameConfig::pFormatCtx1, 0, filePath.c_str(), 0); +} + +// 视频解码回调(样例代码,测试可以跑通,但是不能直接复用) +APP_ERROR CallBackVdec(MxBase::Image &decodedImage, uint32_t channelId, uint32_t frameId, void *userData) +{ + FrameImage frameImage; + frameImage.image = decodedImage; + frameImage.channelId = channelId; + frameImage.frameId = frameId; + + videoInfo::g_threadsMutex_frameImageQueue.lock(); + videoInfo::frameImageQueue.push(frameImage); + videoInfo::g_threadsMutex_frameImageQueue.unlock(); + + return APP_ERR_OK; +} + +// 获取H264中的帧 +void GetFrame(AVPacket &pkt, FrameImage &frameImage, AVFormatContext *pFormatCtx) +{ + av_init_packet(&pkt); + int ret = av_read_frame(pFormatCtx, &pkt); + if (ret != 0) + { + LogInfo << "[StreamPuller] channel Read frame failed, continue!"; + if (ret == AVERROR_EOF) + { + LogInfo << "[StreamPuller] channel StreamPuller is EOF, over!"; + return; + } + return; + } + else + { + if (pkt.size <= 0) + { + LogError << "Invalid pkt.size: " << pkt.size; + return; + } + + // send to the device + auto hostDeleter = [](void *dataPtr) -> void {}; + MxBase::MemoryData data(pkt.size, MxBase::MemoryData::MEMORY_HOST); + MxBase::MemoryData src((void *)(pkt.data), pkt.size, MxBase::MemoryData::MEMORY_HOST); + APP_ERROR ret = MxBase::MemoryHelper::MxbsMallocAndCopy(data, src); + if (ret != APP_ERR_OK) + { + LogError << "MxbsMallocAndCopy failed!"; + } + std::shared_ptr imageData((uint8_t *)data.ptrData, hostDeleter); + + MxBase::Image subImage(imageData, pkt.size); + frameImage.image = subImage; + + LogDebug << "'channelId = " << frameImage.channelId << ", frameId = " << frameImage.frameId << " , dataSize = " << frameImage.image.GetDataSize(); + + av_packet_unref(&pkt); + } + return; +} + +// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 +void VdecThread0(size_t frameCount, size_t skipInterval, int32_t channelId, uint32_t src_width, uint32_t src_height) +{ + AVPacket pkt; + uint32_t frameId = 0; + // 解码器参数 + MxBase::VideoDecodeConfig config; + MxBase::VideoDecodeCallBack cPtr = CallBackVdec; + config.width = src_width; + config.height = src_height; + config.callbackFunc = cPtr; + // 跳帧控制 + config.skipInterval = skipInterval; + + std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); + for (size_t i = 0; i < frameCount; i++) + { + MxBase::Image subImage; + FrameImage frame; + frame.channelId = channelId; + frame.frameId = frameId; + frame.image = subImage; + GetFrame(pkt, frame, frameConfig::pFormatCtx0); + APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); + if (ret != APP_ERR_OK) + { + LogError << "videoDecoder Decode failed. ret is: " << ret; + } + frameId += 1; + } +} + +// 视频流解码线程 frameCount:要求遍历的帧的总数 skipInterval:跳帧的间隔 +void VdecThread1(size_t frameCount, size_t skipInterval, int32_t channelId, uint32_t src_width, uint32_t src_height) +{ + AVPacket pkt; + uint32_t frameId = 0; + // 解码器参数 + MxBase::VideoDecodeConfig config; + MxBase::VideoDecodeCallBack cPtr = CallBackVdec; + config.width = src_width; + config.height = src_height; + config.callbackFunc = cPtr; + // 跳帧控制 + config.skipInterval = skipInterval; + + std::shared_ptr videoDecoder = std::make_shared(config, videoInfo::DEVICE_ID, channelId); + for (size_t i = 0; i < frameCount; i++) + { + MxBase::Image subImage; + FrameImage frame; + frame.channelId = channelId; + frame.frameId = frameId; + frame.image = subImage; + GetFrame(pkt, frame, frameConfig::pFormatCtx1); + APP_ERROR ret = videoDecoder->Decode(frame.image.GetData(), frame.image.GetDataSize(), frameId, &videoInfo::frameImageQueue); + if (ret != APP_ERR_OK) + { + LogError << "videoDecoder Decode failed. ret is: " << ret; + } + frameId += 1; + } +} + +// 检查文件(是否存在、是否为文件夹) +bool checkFile(std::string &fileName) +{ + // 判断视频文件是否存在 + ifstream f(fileName.c_str()); + if (!f.good()) + { + LogError << "file not exists! " << fileName; + return false; + } + else + { + // 如果存在还需要判断是否是文件夹 + struct stat s; + if (stat(fileName.c_str(), &s) == 0) + { + if (s.st_mode & S_IFDIR) + { + LogError << fileName << " is a directory!"; + return false; + } + } + } + return true; +} + +bool checkArg(int argc, char *argv[]) +{ + // 检测是否输入了文件路径 + if (argc <= 1) + { + LogWarn << "Please input video path, such as './video_sample test.264'."; + return false; + } + else if(argc == arg_one_video) + { + std::string videoPath0 = argv[arg1]; + if(!checkFile(videoPath0)) + { + return false; + } + uint32_t src_width0 = (uint32_t)stoul(argv[arg2]); + uint32_t src_height0 = (uint32_t)stoul(argv[arg3]); + LogWarn << "videoPath0: " << videoPath0 << ", src_width0: " << src_width0 << ", src_height0: " << src_height0; + PullStream0(videoPath0); + std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0, src_width0, src_height0); + threadVdec0.join(); + } + else if(argc == arg_two_video) + { + std::string videoPath0 = argv[arg1]; + std::string videoPath1 = argv[arg4]; + if(!checkFile(videoPath0)) + { + return false; + } + if(!checkFile(videoPath1)) + { + return false; + } + + uint32_t src_width0 = (uint32_t)stoul(argv[arg2]); + uint32_t src_height0 = (uint32_t)stoul(argv[arg3]); + LogWarn << "videoPath0: " << videoPath0 << ", src_width0: " << src_width0 << ", src_height0: " << src_height0; + PullStream0(videoPath0); + std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0, src_width0, src_height0); + threadVdec0.join(); + + uint32_t src_width1 = (uint32_t)stoul(argv[arg5]); + uint32_t src_height1 = (uint32_t)stoul(argv[arg6]); + LogWarn << "videoPath1: " << videoPath1 << ", src_width1: " << src_width1 << ", src_height1: " << src_height1; + PullStream1(videoPath1); + std::thread threadVdec1(VdecThread1, frameConfig::frameCountChannel1, frameConfig::skipIntervalChannel1, frameConfig::channelId1, src_width1, src_height1); + threadVdec1.join(); + } + else + { + LogWarn << "usage: ./main test1.264 test1_width test1_height [test2.264 test2_width test2_height]"; + return false; + } + return true; +} + +APP_ERROR main(int argc, char *argv[]) +{ + // 初始化 + APP_ERROR ret = MxBase::MxInit(); + if (ret != APP_ERR_OK) + { + LogError << "MxInit failed, ret=" << ret << "."; + } + if(!checkArg(argc, argv)) + { + return APP_ERR_OK; + } + + std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); + size_t target_count = videoInfo::frameImageQueue.size(); + + // resize线程 + std::thread resizeThread(resizeMethod, start_time, target_count); + resizeThread.join(); + + // 推理线程 + std::thread inferThread(inferMethod, start_time, target_count); + inferThread.join(); + + // 后处理线程 + std::thread postprocessThread(postprocessMethod, start_time, target_count); + postprocessThread.join(); + + // 跟踪去重线程 + std::thread trackThread(trackMethod, start_time, target_count); + trackThread.join(); + + return APP_ERR_OK; +} \ No newline at end of file diff --git a/HelmetIdentification_V2/src/utils.h b/HelmetIdentification_V2/src/utils.h new file mode 100644 index 0000000..1760121 --- /dev/null +++ b/HelmetIdentification_V2/src/utils.h @@ -0,0 +1,332 @@ +/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. 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. + */ + +#ifndef MXBASE_HELMETIDENTIFICATION_UTILS_H +#define MXBASE_HELMETIDENTIFICATION_UTILS_H + +#include +#include +#include +#include "unistd.h" +#include +#include +#include +#include "boost/filesystem.hpp" + +#include "MxBase/DeviceManager/DeviceManager.h" +#include "MxBase/DvppWrapper/DvppWrapper.h" +#include "MxBase/MemoryHelper/MemoryHelper.h" +#include "opencv2/opencv.hpp" +#include "MxBase/postprocess/include/ObjectPostProcessors/Yolov3PostProcess.h" + +#include "MxBase/E2eInfer/ImageProcessor/ImageProcessor.h" +#include "MxBase/E2eInfer/VideoDecoder/VideoDecoder.h" +#include "MxBase/E2eInfer/VideoEncoder/VideoEncoder.h" +#include "MxBase/E2eInfer/DataType.h" + +#include "MxBase/MxBase.h" +#include "MOTConnection.h" +#include "cropResizePaste.hpp" +#include "chrono" +#include "time.h" + +struct FrameImage +{ + MxBase::Image image; + uint32_t frameId = 0; + uint32_t channelId = 0; +}; + +namespace videoInfo +{ + const uint32_t YUV_BYTE_NU = 3; + const uint32_t YUV_BYTE_DE = 2; + const uint32_t YOLOV5_RESIZE = 640; + + // 要检测的目标类别的标签 + const std::string TARGET_CLASS_NAME = "head"; + // 使用chrono计数结果为毫秒,需要除以1000转换为秒 + double MS_PPE_SECOND = 1000.0; + + const uint32_t DEVICE_ID = 0; + // 相对编译出的可执行文件的路径 + std::string labelPath = "./model/imgclass.names"; + std::string configPath = "./model/Helmet_yolov5.cfg"; + std::string modelPath = "./model/YOLOv5_s.om"; + + // 读入视频帧Image的队列 + std::queue frameImageQueue; + // 读入视频帧Image队列的线程锁 + std::mutex g_threadsMutex_frameImageQueue; + + // resize前即原始图像的vector + std::vector realImageVector; + // resize后Image的vector + std::vector resizedImageVector; + // resize后Image队列的线程锁 + std::mutex g_threadsMutex_resizedImageVector; + + // 推理后tensor的vector + std::vector> inferOutputVector; + // 推理后tensor队列的线程锁 + std::mutex g_threadsMutex_inferOutputVector; + + // 后处理后objectInfos的队列 map> + std::vector>> postprocessOutputVector; + // 后处理后objectInfos队列的线程锁 + std::mutex g_threadsMutex_postprocessOutputVector; +} +namespace fs = boost::filesystem; + +// resize线程 +void resizeMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point resize_start_time = std::chrono::high_resolution_clock::now(); + + MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); + + size_t resize_count = 0; + while (resize_count < target_count) + { + if (videoInfo::frameImageQueue.empty()) + { + continue; + } + // 取图像并resize + FrameImage frame = videoInfo::frameImageQueue.front(); + MxBase::Image image = frame.image; + MxBase::Size originalSize = image.GetOriginalSize(); + MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); + MxBase::Image outputImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); + // 先将缩放后的图像放入resizeImage的队列 + FrameImage resizedFrame; + resizedFrame.channelId = frame.channelId; + resizedFrame.frameId = frame.frameId; + resizedFrame.image = outputImage; + videoInfo::g_threadsMutex_resizedImageVector.lock(); + frame.image.ToHost(); + videoInfo::realImageVector.push_back(frame); + videoInfo::resizedImageVector.push_back(resizedFrame); + videoInfo::g_threadsMutex_resizedImageVector.unlock(); + // 然后再将原图pop出去 + videoInfo::g_threadsMutex_frameImageQueue.lock(); + videoInfo::frameImageQueue.pop(); + videoInfo::g_threadsMutex_frameImageQueue.unlock(); + // 计数 + resize_count++; + } +} + +// 推理线程 +void inferMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point infer_start_time = std::chrono::high_resolution_clock::now(); + + std::shared_ptr modelDptr = std::make_shared(videoInfo::modelPath, videoInfo::DEVICE_ID); + + size_t infer_count = 0; + while (infer_count < target_count) + { + if (infer_count >= videoInfo::resizedImageVector.size()) + { + continue; + } + // 从resize后的队列中取图片 + FrameImage resizedFrame = videoInfo::resizedImageVector[infer_count]; + std::vector modelOutputs; + MxBase::Tensor tensorImg = resizedFrame.image.ConvertToTensor(); + tensorImg.ToDevice(videoInfo::DEVICE_ID); + std::vector inputs; + inputs.push_back(tensorImg); + modelOutputs = modelDptr->Infer(inputs); + for (auto output : modelOutputs) + { + output.ToHost(); + } + + // 将推理结果存入队列 + videoInfo::g_threadsMutex_inferOutputVector.lock(); + videoInfo::inferOutputVector.push_back(modelOutputs); + videoInfo::g_threadsMutex_inferOutputVector.unlock(); + // 计数 + infer_count++; + } +} + +// 后处理线程 +void postprocessMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point postprocess_start_time = std::chrono::high_resolution_clock::now(); + + std::map postConfig; + postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); + postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); + std::shared_ptr postProcessorDptr = std::make_shared(); + if (postProcessorDptr == nullptr) + { + LogError << "init postProcessor failed, nullptr"; + } + postProcessorDptr->Init(postConfig); + + size_t postprocess_count = 0; + while (postprocess_count < target_count) + { + if (postprocess_count >= videoInfo::inferOutputVector.size()) + { + continue; + } + // 取原图信息用于计算 + MxBase::Size originalSize = videoInfo::realImageVector[postprocess_count].image.GetOriginalSize(); + // 从推理结果的队列里面取出推理结果 + std::vector modelOutputs = videoInfo::inferOutputVector[postprocess_count]; + FrameImage resizedFrame = videoInfo::resizedImageVector[postprocess_count]; + // 新的后处理过程 + MxBase::ResizedImageInfo imgInfo; + auto shape = modelOutputs[0].GetShape(); + imgInfo.widthOriginal = originalSize.width; + imgInfo.heightOriginal = originalSize.height; + imgInfo.widthResize = videoInfo::YOLOV5_RESIZE; + imgInfo.heightResize = videoInfo::YOLOV5_RESIZE; + imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; + // 因为yolov5要求输入图像为640*640,所以直接比较原图的height和width就好(如果不理解就去看cropResizePaste.hpp里的GetPasteRect函数) + float resizeRate = originalSize.width > originalSize.height ? (originalSize.width * 1.0 / videoInfo::YOLOV5_RESIZE) : (originalSize.height * 1.0 / videoInfo::YOLOV5_RESIZE); + imgInfo.keepAspectRatioScaling = 1 / resizeRate; + std::vector imageInfoVec = {}; + imageInfoVec.push_back(imgInfo); + // make postProcess inputs + std::vector tensors; + for (size_t i = 0; i < modelOutputs.size(); i++) + { + MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); + MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); + tensors.push_back(tensorBase); + } + // 后处理 + std::vector> objectInfos; + postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); + + // 将后处理结果存入队列 + videoInfo::g_threadsMutex_postprocessOutputVector.lock(); + videoInfo::postprocessOutputVector.push_back(objectInfos); + videoInfo::g_threadsMutex_postprocessOutputVector.unlock(); + // 计数 + postprocess_count++; + } +} + +// 跟踪去重线程 +void trackMethod(std::chrono::high_resolution_clock::time_point start_time, size_t target_count) +{ + std::chrono::high_resolution_clock::time_point track_start_time = std::chrono::high_resolution_clock::now(); + + std::shared_ptr tracker0 = std::make_shared(); + if (tracker0 == nullptr) + { + LogError << "init tracker0 failed, nullptr"; + } + std::shared_ptr tracker1 = std::make_shared(); + if (tracker1 == nullptr) + { + LogError << "init tracker1 failed, nullptr"; + } + // 用于计算帧率 + size_t old_count = 0; + std::chrono::high_resolution_clock::time_point count_time; + std::chrono::high_resolution_clock::time_point old_count_time = std::chrono::high_resolution_clock::now(); + size_t one_step = 2; + size_t track_count = 0; + while (track_count < target_count) + { + // 计算帧率 + // 如果count_time-old_count_time的值大于one_step,就计算一下这个step里面的帧数,然后除以step的值 + count_time = std::chrono::high_resolution_clock::now(); + if (std::chrono::duration_cast(count_time - old_count_time).count() / videoInfo::MS_PPE_SECOND > one_step) + { + old_count_time = count_time; + LogInfo << "rate: " << (track_count - old_count) / one_step * 1.0; + old_count = track_count; + } + // 下面是业务循环 + if (track_count >= videoInfo::postprocessOutputVector.size()) + { + continue; + } + // 从后处理结果的队列中取结果用于跟踪去重 + std::vector> objectInfos = videoInfo::postprocessOutputVector[track_count]; + FrameImage frame = videoInfo::realImageVector[track_count]; + MxBase::Size originalSize = frame.image.GetOriginalSize(); + + // 根据channelId的不同使用不同的tracker + std::vector objInfos_ = {}; + if (frame.channelId == 0) + { + tracker0->ProcessSort(objectInfos, frame.frameId); + APP_ERROR ret = tracker0->GettrackResult(objInfos_); + if (ret != APP_ERR_OK) + { + LogError << "No tracker0"; + } + } + else + { + tracker1->ProcessSort(objectInfos, frame.frameId); + APP_ERROR ret = tracker1->GettrackResult(objInfos_); + if (ret != APP_ERR_OK) + { + LogError << "No tracker1"; + } + } + + uint32_t video_height = originalSize.height; + uint32_t video_width = originalSize.width; + // 初始化OpenCV图像信息矩阵 + cv::Mat imgYuv = cv::Mat(video_height * videoInfo::YUV_BYTE_NU / videoInfo::YUV_BYTE_DE, video_width, CV_8UC1, frame.image.GetData().get()); + cv::Mat imgBgr = cv::Mat(video_height, video_width, CV_8UC3); + // 颜色空间转换 + cv::cvtColor(imgYuv, imgBgr, cv::COLOR_YUV420sp2RGB); + std::vector info; + bool headFlag = false; + for (uint32_t i = 0; i < objInfos_.size(); i++) + { + if (objInfos_[i].className == videoInfo::TARGET_CLASS_NAME) + { + headFlag = true; + LogWarn << "Warning:Not wearing a helmet, channelId:" << frame.channelId << ", frameId:" << frame.frameId; + // (blue, green, red) + const cv::Scalar color = cv::Scalar(0, 0, 255); + // width for rectangle + const uint32_t thickness = 2; + // draw the rectangle + cv::rectangle(imgBgr, + cv::Rect(objInfos_[i].x0, objInfos_[i].y0, objInfos_[i].x1 - objInfos_[i].x0, objInfos_[i].y1 - objInfos_[i].y0), + color, thickness); + } + } + // 如果检测结果中有head标签,就保存为图片 + if (headFlag) + { + // 把Mat类型的图像矩阵保存为图像到指定位置。 + std::string outPath = frame.channelId == 0 ? "one" : "two"; + std::string fileName = "./result/" + outPath + "/result" + std::to_string(frame.frameId) + ".jpg"; + cv::imwrite(fileName, imgBgr); + } + + // 计数 + track_count++; + } +} + +#endif From d85c63ca24b18832eb12398e7669f488774c3341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 08:30:30 +0000 Subject: [PATCH 33/58] update HelmetIdentification_V2/src/main.cpp. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/src/main.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/HelmetIdentification_V2/src/main.cpp b/HelmetIdentification_V2/src/main.cpp index 83a03a8..1eba4d9 100644 --- a/HelmetIdentification_V2/src/main.cpp +++ b/HelmetIdentification_V2/src/main.cpp @@ -273,10 +273,10 @@ bool checkArg(int argc, char *argv[]) LogWarn << "Please input video path, such as './video_sample test.264'."; return false; } - else if(argc == arg_one_video) + else if (argc == arg_one_video) { std::string videoPath0 = argv[arg1]; - if(!checkFile(videoPath0)) + if (!checkFile(videoPath0)) { return false; } @@ -287,15 +287,15 @@ bool checkArg(int argc, char *argv[]) std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0, src_width0, src_height0); threadVdec0.join(); } - else if(argc == arg_two_video) + else if (argc == arg_two_video) { std::string videoPath0 = argv[arg1]; std::string videoPath1 = argv[arg4]; - if(!checkFile(videoPath0)) + if (!checkFile(videoPath0)) { return false; } - if(!checkFile(videoPath1)) + if (!checkFile(videoPath1)) { return false; } @@ -330,7 +330,7 @@ APP_ERROR main(int argc, char *argv[]) { LogError << "MxInit failed, ret=" << ret << "."; } - if(!checkArg(argc, argv)) + if (!checkArg(argc, argv)) { return APP_ERR_OK; } From 6e71e58e654148c7fbf84710838da1fefe183d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 11:00:18 +0000 Subject: [PATCH 34/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/readme.md | 285 ------------------------------ 1 file changed, 285 deletions(-) delete mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md deleted file mode 100644 index 7136a72..0000000 --- a/HelmetIdentification_V2/readme.md +++ /dev/null @@ -1,285 +0,0 @@ -# 安全帽识别 - -## 1 介绍 -安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 - -本项目支持2路视频实时分析,其主要流程为: - -​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 - -### 1.1 支持的产品 - -本项目以昇腾Atlas 200DK为主要的硬件平台。 - -### 1.2 支持的版本 - -本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 - -MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 - -### 1.3 软件方案介绍 - -请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 - -表1.1 系统方案各子系统功能描述: - -| 序号 | 子系统 | 功能描述 | -| ---- | ---------- | ------------------------------------------------------------ | -| 1 | 视频解码 | 从264视频流中解码图像帧 | -| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | -| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | -| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | -| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | -| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | - -### 1.4 代码目录结构与说明 - -本工程名称为HelmetIdentification_V2,工程目录如下图所示: - -``` -. -├── model - ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 - ├── YOLOv5_s.om // 模型文件(需自行下载或转换) - └── imgclass.names // label文件 -├── src - ├── main.cpp // 安全帽检测的main函数入口 - ├── main-image.cpp // 读取图片测试精度 - ├── utils.h // 实现多线程的工具类 - ├── cropResizePaste.hpp // 自定义的等比例缩放 - ├── MOTConnection.h // 跟踪匹配方法的头文件 - ├── MOTConnection.cpp // 跟踪匹配方法的实现 - ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 - ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 - ├── Hungar ian.h // 匈牙利算法的头文件 - ├── Hungarian.cpp // 匈牙利算法的实现 - ├── DataType.h // 自定义的数据结构 - └── CMakeLists.txt // CMake文件 -├── result - ├── one // 文件夹,保存第一路输入的结果(需要新建) - └── two // 文件夹,保存第二路输入的结果(需要新建) -└── CMakeLists.txt // CMake文件 -``` - -### 1.5 技术实现流程图 - -![](./images/flow.jpg) - -### 1.6 特性及适用场景 - -本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 - - - -## 2 环境依赖 - -### 2.1 软件依赖 - -环境依赖软件和版本如下表: - -| 软件 | 版本 | 说明 | 获取方式 | -| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | -| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | -| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | -| Ubuntu | 18.04 | | | - -### 2.2 环境变量 - -在编译运行项目前,需要设置环境变量: - -MindX SDK环境变量: - -. ${SDK-path}/set_env.sh - -CANN环境变量: - -. ${ascend-toolkit-path}/set_env.sh - -环境变量介绍 - -SDK-path:mxVision SDK安装路径 - -ascend-toolkit-path:CANN安装路径 - - - -## 3 模型转换 - -模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html - -本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 - -具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 - -运行命令如下: - -``` -sh atc-env.sh -``` - -脚本中包含atc命令: - -``` ---model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" -``` - -其参数如下表所示: - -| 参数名 | 参数描述 | -| ---------------- | ------------------------------------------------------------ | -| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | -| --model | 原始模型文件路径与文件名 | -| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | -| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | -| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | -| --input_shape | 模型输入数据的 shape。 | -| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | - -其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 - -注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 - -## 4 编译与运行 - -项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 - -### 4.1 测试性能 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 - -2. 进入src文件夹,修改其中的utils.h文件 - - * 配置文件路径,根据服务器路径修改: - - **以下路径需要设置为绝对路径,设置为相对路径编译能够通过但是执行时会报错。** - - 第68行**labelPath**、第69行**configPath**、第70行**modelPath**。 - -3. 进入src文件夹,修改其中的CMakeLists.txt - - * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - - * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - - -**步骤2** 选择对应的main文件 - -测试性能使用main.cpp -需要删除main-image.cpp -可以使用如下命令进行: - -```shell -cd src -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹 - -如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): - -``` -mkdir result -cd result -mkdir one -mkdir two -``` - -退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 - -``` -./main ${video0Path} ${video1Path} -``` - -video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 - -如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 - -![](./images/warn.png) - -图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 - -图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 - -图片示例: - -![](./images/result0.jpg) - -在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: - -![](./images/rate.jpg) - -### 4.2 测试精度 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 -2. 进入src文件夹,修改其中的CMakeLists.txt - 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试精度使用main-image.cpp -需要删除main.cpp,然后将main-image.cpp重命名为main.cpp -可以使用如下命令进行: - -```sh -cd src -rm ./main.cpp -cp ./main-image.cpp ./main.cpp -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) - -从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: - -``` -./main ${imagePath} -``` - -imagePath是图片路径(例如 ./main 000023.jpg) - -正确执行会输出检测到的目标框的信息,如下所示: - -![](./images/objInfo.png) - From 3a0a4877e2ca7b50b4e5823be4d4f7f4cc2b4f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 11:00:28 +0000 Subject: [PATCH 35/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/readme.md | 272 ++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md new file mode 100644 index 0000000..b4ba8a5 --- /dev/null +++ b/HelmetIdentification_V2/readme.md @@ -0,0 +1,272 @@ +# 安全帽识别 + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── YOLOv5_s.om // 模型文件(需自行下载或转换) + └── imgclass.names // label文件 +├── src + ├── main.cpp // 安全帽检测的main函数入口 + ├── main-image.cpp // 读取图片测试精度 + ├── utils.h // 实现多线程的工具类 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── Hungar ian.h // 匈牙利算法的头文件 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── DataType.h // 自定义的数据结构 + └── CMakeLists.txt // CMake文件 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + └── two // 文件夹,保存第二路输入的结果(需要新建) +└── CMakeLists.txt // CMake文件 +``` + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + +### 1.6 特性及适用场景 + +本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video0width} ${video0height} ${video1Path} ${video1width} ${video1height} +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imagePath} +``` + +imagePath是图片路径(例如 ./main 000023.jpg) + +正确执行会输出检测到的目标框的信息,如下所示: + +![](./images/objInfo.png) + From f1752ac14fe243efdb65bdd0f3dc9de479c3c719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 11:01:14 +0000 Subject: [PATCH 36/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/readme.md | 272 ------------------------------ 1 file changed, 272 deletions(-) delete mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md deleted file mode 100644 index b4ba8a5..0000000 --- a/HelmetIdentification_V2/readme.md +++ /dev/null @@ -1,272 +0,0 @@ -# 安全帽识别 - -## 1 介绍 -安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 - -本项目支持2路视频实时分析,其主要流程为: - -​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 - -### 1.1 支持的产品 - -本项目以昇腾Atlas 200DK为主要的硬件平台。 - -### 1.2 支持的版本 - -本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 - -MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 - -### 1.3 软件方案介绍 - -请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 - -表1.1 系统方案各子系统功能描述: - -| 序号 | 子系统 | 功能描述 | -| ---- | ---------- | ------------------------------------------------------------ | -| 1 | 视频解码 | 从264视频流中解码图像帧 | -| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | -| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | -| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | -| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | -| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | - -### 1.4 代码目录结构与说明 - -本工程名称为HelmetIdentification_V2,工程目录如下图所示: - -``` -. -├── model - ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 - ├── YOLOv5_s.om // 模型文件(需自行下载或转换) - └── imgclass.names // label文件 -├── src - ├── main.cpp // 安全帽检测的main函数入口 - ├── main-image.cpp // 读取图片测试精度 - ├── utils.h // 实现多线程的工具类 - ├── cropResizePaste.hpp // 自定义的等比例缩放 - ├── MOTConnection.h // 跟踪匹配方法的头文件 - ├── MOTConnection.cpp // 跟踪匹配方法的实现 - ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 - ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 - ├── Hungar ian.h // 匈牙利算法的头文件 - ├── Hungarian.cpp // 匈牙利算法的实现 - ├── DataType.h // 自定义的数据结构 - └── CMakeLists.txt // CMake文件 -├── result - ├── one // 文件夹,保存第一路输入的结果(需要新建) - └── two // 文件夹,保存第二路输入的结果(需要新建) -└── CMakeLists.txt // CMake文件 -``` - -### 1.5 技术实现流程图 - -![](./images/flow.jpg) - -### 1.6 特性及适用场景 - -本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 - - - -## 2 环境依赖 - -### 2.1 软件依赖 - -环境依赖软件和版本如下表: - -| 软件 | 版本 | 说明 | 获取方式 | -| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | -| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | -| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | -| Ubuntu | 18.04 | | | - -### 2.2 环境变量 - -在编译运行项目前,需要设置环境变量: - -MindX SDK环境变量: - -. ${SDK-path}/set_env.sh - -CANN环境变量: - -. ${ascend-toolkit-path}/set_env.sh - -环境变量介绍 - -SDK-path:mxVision SDK安装路径 - -ascend-toolkit-path:CANN安装路径 - - - -## 3 模型转换 - -模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html - -本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 - -具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 - -运行命令如下: - -``` -sh atc-env.sh -``` - -脚本中包含atc命令: - -``` ---model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" -``` - -其参数如下表所示: - -| 参数名 | 参数描述 | -| ---------------- | ------------------------------------------------------------ | -| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | -| --model | 原始模型文件路径与文件名 | -| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | -| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | -| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | -| --input_shape | 模型输入数据的 shape。 | -| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | - -其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 - -注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 - -## 4 编译与运行 - -项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 - -### 4.1 测试性能 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 - -3. 进入src文件夹,修改其中的CMakeLists.txt - - * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - - * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试性能使用main.cpp -需要删除main-image.cpp -可以使用如下命令进行: - -```shell -cd src -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹 - -如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): - -``` -mkdir result -cd result -mkdir one -mkdir two -``` - -退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 - -``` -./main ${video0Path} ${video0width} ${video0height} ${video1Path} ${video1width} ${video1height} -``` - -video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 - -如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 - -![](./images/warn.png) - -图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 - -图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 - -在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: - -![](./images/rate.jpg) - -### 4.2 测试精度 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 -2. 进入src文件夹,修改其中的CMakeLists.txt - 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试精度使用main-image.cpp -需要删除main.cpp,然后将main-image.cpp重命名为main.cpp -可以使用如下命令进行: - -```sh -cd src -rm ./main.cpp -cp ./main-image.cpp ./main.cpp -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) - -从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: - -``` -./main ${imagePath} -``` - -imagePath是图片路径(例如 ./main 000023.jpg) - -正确执行会输出检测到的目标框的信息,如下所示: - -![](./images/objInfo.png) - From 3107a964d517e81a4d7a924d8e9516a10bef81e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Fri, 2 Dec 2022 11:01:23 +0000 Subject: [PATCH 37/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/readme.md | 272 ++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md new file mode 100644 index 0000000..eb4ae96 --- /dev/null +++ b/HelmetIdentification_V2/readme.md @@ -0,0 +1,272 @@ +# 安全帽识别 + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── YOLOv5_s.om // 模型文件(需自行下载或转换) + └── imgclass.names // label文件 +├── src + ├── main.cpp // 安全帽检测的main函数入口 + ├── main-image.cpp // 读取图片测试精度 + ├── utils.h // 实现多线程的工具类 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── Hungar ian.h // 匈牙利算法的头文件 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── DataType.h // 自定义的数据结构 + └── CMakeLists.txt // CMake文件 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + └── two // 文件夹,保存第二路输入的结果(需要新建) +└── CMakeLists.txt // CMake文件 +``` + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + +### 1.6 特性及适用场景 + +本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video0width} ${video0height} [${video1Path} ${video1width} ${video1height}] +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imagePath} +``` + +imagePath是图片路径(例如 ./main 000023.jpg) + +正确执行会输出检测到的目标框的信息,如下所示: + +![](./images/objInfo.png) + From 73a2914182cda4be289b01f9150ec3b7539df995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 6 Dec 2022 09:14:33 +0000 Subject: [PATCH 38/58] =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=E4=B8=AD=E7=94=A8=E5=88=B0=E7=9A=84=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- .../model/aipp_YOLOv5.config | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 HelmetIdentification_V2/model/aipp_YOLOv5.config diff --git a/HelmetIdentification_V2/model/aipp_YOLOv5.config b/HelmetIdentification_V2/model/aipp_YOLOv5.config new file mode 100644 index 0000000..f4196df --- /dev/null +++ b/HelmetIdentification_V2/model/aipp_YOLOv5.config @@ -0,0 +1,37 @@ +aipp_op{ + aipp_mode:static + input_format : YUV420SP_U8 + + src_image_size_w : 640 + src_image_size_h : 640 + + crop: false + load_start_pos_h : 0 + load_start_pos_w : 0 + crop_size_w : 640 + crop_size_h: 640 + + csc_switch : true + rbuv_swap_switch : false + + # 色域转换 + matrix_r0c0: 256 + matrix_r0c1: 0 + matrix_r0c2: 359 + matrix_r1c0: 256 + matrix_r1c1: -88 + matrix_r1c2: -183 + matrix_r2c0: 256 + matrix_r2c1: 454 + matrix_r2c2: 0 + input_bias_0: 0 + input_bias_1: 128 + input_bias_2: 128 + + # 均值归一化 + min_chn_0 : 0 + min_chn_1 : 0 + min_chn_2 : 0 + var_reci_chn_0: 0.003921568627451 + var_reci_chn_1: 0.003921568627451 + var_reci_chn_2: 0.003921568627451} \ No newline at end of file From 68af7e043584c1f1ca60d7822d7bc936fc9ed38f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 6 Dec 2022 09:14:41 +0000 Subject: [PATCH 39/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/readme.md | 272 ------------------------------ 1 file changed, 272 deletions(-) delete mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md deleted file mode 100644 index eb4ae96..0000000 --- a/HelmetIdentification_V2/readme.md +++ /dev/null @@ -1,272 +0,0 @@ -# 安全帽识别 - -## 1 介绍 -安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 - -本项目支持2路视频实时分析,其主要流程为: - -​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 - -### 1.1 支持的产品 - -本项目以昇腾Atlas 200DK为主要的硬件平台。 - -### 1.2 支持的版本 - -本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 - -MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 - -### 1.3 软件方案介绍 - -请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 - -表1.1 系统方案各子系统功能描述: - -| 序号 | 子系统 | 功能描述 | -| ---- | ---------- | ------------------------------------------------------------ | -| 1 | 视频解码 | 从264视频流中解码图像帧 | -| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | -| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | -| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | -| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | -| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | - -### 1.4 代码目录结构与说明 - -本工程名称为HelmetIdentification_V2,工程目录如下图所示: - -``` -. -├── model - ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 - ├── YOLOv5_s.om // 模型文件(需自行下载或转换) - └── imgclass.names // label文件 -├── src - ├── main.cpp // 安全帽检测的main函数入口 - ├── main-image.cpp // 读取图片测试精度 - ├── utils.h // 实现多线程的工具类 - ├── cropResizePaste.hpp // 自定义的等比例缩放 - ├── MOTConnection.h // 跟踪匹配方法的头文件 - ├── MOTConnection.cpp // 跟踪匹配方法的实现 - ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 - ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 - ├── Hungar ian.h // 匈牙利算法的头文件 - ├── Hungarian.cpp // 匈牙利算法的实现 - ├── DataType.h // 自定义的数据结构 - └── CMakeLists.txt // CMake文件 -├── result - ├── one // 文件夹,保存第一路输入的结果(需要新建) - └── two // 文件夹,保存第二路输入的结果(需要新建) -└── CMakeLists.txt // CMake文件 -``` - -### 1.5 技术实现流程图 - -![](./images/flow.jpg) - -### 1.6 特性及适用场景 - -本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 - - - -## 2 环境依赖 - -### 2.1 软件依赖 - -环境依赖软件和版本如下表: - -| 软件 | 版本 | 说明 | 获取方式 | -| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | -| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | -| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | -| Ubuntu | 18.04 | | | - -### 2.2 环境变量 - -在编译运行项目前,需要设置环境变量: - -MindX SDK环境变量: - -. ${SDK-path}/set_env.sh - -CANN环境变量: - -. ${ascend-toolkit-path}/set_env.sh - -环境变量介绍 - -SDK-path:mxVision SDK安装路径 - -ascend-toolkit-path:CANN安装路径 - - - -## 3 模型转换 - -模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html - -本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 - -具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 - -运行命令如下: - -``` -sh atc-env.sh -``` - -脚本中包含atc命令: - -``` ---model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" -``` - -其参数如下表所示: - -| 参数名 | 参数描述 | -| ---------------- | ------------------------------------------------------------ | -| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | -| --model | 原始模型文件路径与文件名 | -| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | -| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | -| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | -| --input_shape | 模型输入数据的 shape。 | -| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | - -其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 - -注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 - -## 4 编译与运行 - -项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 - -### 4.1 测试性能 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 - -3. 进入src文件夹,修改其中的CMakeLists.txt - - * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - - * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试性能使用main.cpp -需要删除main-image.cpp -可以使用如下命令进行: - -```shell -cd src -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹 - -如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): - -``` -mkdir result -cd result -mkdir one -mkdir two -``` - -退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 - -``` -./main ${video0Path} ${video0width} ${video0height} [${video1Path} ${video1width} ${video1height}] -``` - -video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 - -如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 - -![](./images/warn.png) - -图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 - -图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 - -在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: - -![](./images/rate.jpg) - -### 4.2 测试精度 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 -2. 进入src文件夹,修改其中的CMakeLists.txt - 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试精度使用main-image.cpp -需要删除main.cpp,然后将main-image.cpp重命名为main.cpp -可以使用如下命令进行: - -```sh -cd src -rm ./main.cpp -cp ./main-image.cpp ./main.cpp -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) - -从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: - -``` -./main ${imagePath} -``` - -imagePath是图片路径(例如 ./main 000023.jpg) - -正确执行会输出检测到的目标框的信息,如下所示: - -![](./images/objInfo.png) - From 938f053ad853235095dffc68614e6c7de32e434f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 6 Dec 2022 09:14:57 +0000 Subject: [PATCH 40/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/readme.md | 273 ++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md new file mode 100644 index 0000000..c04e67f --- /dev/null +++ b/HelmetIdentification_V2/readme.md @@ -0,0 +1,273 @@ +# 安全帽识别 + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── YOLOv5_s.om // 模型文件(需自行下载或转换) + ├── aipp_YOLOv5.config // 模型转换中用到的配置文件 + └── imgclass.names // label文件 +├── src + ├── main.cpp // 安全帽检测的main函数入口 + ├── main-image.cpp // 读取图片测试精度 + ├── utils.h // 实现多线程的工具类 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── Hungar ian.h // 匈牙利算法的头文件 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── DataType.h // 自定义的数据结构 + └── CMakeLists.txt // CMake文件 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + └── two // 文件夹,保存第二路输入的结果(需要新建) +└── CMakeLists.txt // CMake文件 +``` + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + +### 1.6 特性及适用场景 + +本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video0width} ${video0height} [${video1Path} ${video1width} ${video1height}] +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imagePath} +``` + +imagePath是图片路径(例如 ./main 000023.jpg) + +正确执行会输出检测到的目标框的信息,如下所示: + +![](./images/objInfo.png) + From 4ad5113d44f3430c21cc71bc28694a1c6c242534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 6 Dec 2022 09:20:49 +0000 Subject: [PATCH 41/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/src/.keep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/src/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 HelmetIdentification_V2/src/.keep diff --git a/HelmetIdentification_V2/src/.keep b/HelmetIdentification_V2/src/.keep deleted file mode 100644 index e69de29..0000000 From d98520310528307fc1823910ebc6b326bea81b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 6 Dec 2022 09:32:43 +0000 Subject: [PATCH 42/58] =?UTF-8?q?update=20HelmetIdentification=5FV2/src/ma?= =?UTF-8?q?in.cpp.=20=E4=B9=8B=E5=89=8D=E5=8A=A0=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E5=80=99=E5=BF=98=E8=AE=B0=E5=8A=A0=E4=B8=8A?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/src/main.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/HelmetIdentification_V2/src/main.cpp b/HelmetIdentification_V2/src/main.cpp index 1eba4d9..803c2f3 100644 --- a/HelmetIdentification_V2/src/main.cpp +++ b/HelmetIdentification_V2/src/main.cpp @@ -62,7 +62,6 @@ namespace frameConfig int arg4 = 4; int arg5 = 5; int arg6 = 6; - int arg7 = 7; // channel0对应文件的指针 AVFormatContext *pFormatCtx0 = nullptr; @@ -273,24 +272,24 @@ bool checkArg(int argc, char *argv[]) LogWarn << "Please input video path, such as './video_sample test.264'."; return false; } - else if (argc == arg_one_video) + else if (argc == frameConfig::arg_one_video) { - std::string videoPath0 = argv[arg1]; + std::string videoPath0 = argv[frameConfig::arg1]; if (!checkFile(videoPath0)) { return false; } - uint32_t src_width0 = (uint32_t)stoul(argv[arg2]); - uint32_t src_height0 = (uint32_t)stoul(argv[arg3]); + uint32_t src_width0 = (uint32_t)stoul(argv[frameConfig::arg2]); + uint32_t src_height0 = (uint32_t)stoul(argv[frameConfig::arg3]); LogWarn << "videoPath0: " << videoPath0 << ", src_width0: " << src_width0 << ", src_height0: " << src_height0; PullStream0(videoPath0); std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0, src_width0, src_height0); threadVdec0.join(); } - else if (argc == arg_two_video) + else if (argc == frameConfig::arg_two_video) { - std::string videoPath0 = argv[arg1]; - std::string videoPath1 = argv[arg4]; + std::string videoPath0 = argv[frameConfig::arg1]; + std::string videoPath1 = argv[frameConfig::arg4]; if (!checkFile(videoPath0)) { return false; @@ -300,15 +299,15 @@ bool checkArg(int argc, char *argv[]) return false; } - uint32_t src_width0 = (uint32_t)stoul(argv[arg2]); - uint32_t src_height0 = (uint32_t)stoul(argv[arg3]); + uint32_t src_width0 = (uint32_t)stoul(argv[frameConfig::arg2]); + uint32_t src_height0 = (uint32_t)stoul(argv[frameConfig::arg3]); LogWarn << "videoPath0: " << videoPath0 << ", src_width0: " << src_width0 << ", src_height0: " << src_height0; PullStream0(videoPath0); std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0, src_width0, src_height0); threadVdec0.join(); - uint32_t src_width1 = (uint32_t)stoul(argv[arg5]); - uint32_t src_height1 = (uint32_t)stoul(argv[arg6]); + uint32_t src_width1 = (uint32_t)stoul(argv[frameConfig::arg5]); + uint32_t src_height1 = (uint32_t)stoul(argv[frameConfig::arg6]); LogWarn << "videoPath1: " << videoPath1 << ", src_width1: " << src_width1 << ", src_height1: " << src_height1; PullStream1(videoPath1); std::thread threadVdec1(VdecThread1, frameConfig::frameCountChannel1, frameConfig::skipIntervalChannel1, frameConfig::channelId1, src_width1, src_height1); From 12e0c03c030077616114a193cebb5d1f37c89be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Mon, 12 Dec 2022 07:15:39 +0000 Subject: [PATCH 43/58] add HelmetIdentification_V2/model. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/model/act-env.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 HelmetIdentification_V2/model/act-env.sh diff --git a/HelmetIdentification_V2/model/act-env.sh b/HelmetIdentification_V2/model/act-env.sh new file mode 100644 index 0000000..e69de29 From 405af95e70aece96bf32f64b3876effa27f3bb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Mon, 12 Dec 2022 07:15:55 +0000 Subject: [PATCH 44/58] update HelmetIdentification_V2/model/act-env.sh. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/model/act-env.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/HelmetIdentification_V2/model/act-env.sh b/HelmetIdentification_V2/model/act-env.sh index e69de29..2afbfa4 100644 --- a/HelmetIdentification_V2/model/act-env.sh +++ b/HelmetIdentification_V2/model/act-env.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# This is used to convert onnx model file to .om model file. +export install_path=/usr/local/Ascend/ascend-toolkit/latest +export PATH=/usr/local/python3.9.2/bin:${install_path}/arm64-linux/atc/ccec_compiler/bin:${install_path}/arm64-linux/atc/bin:$PATH +export PYTHONPATH=${install_path}/arm64-linux/atc/python/site-packages:${install_path}/arm64-linux/atc/python/site-packages/auto_tune.egg/auto_tune:${install_path}/arm64-linux/atc/python/site-packages/schedule_search.egg +export LD_LIBRARY_PATH=${install_path}/arm64-linux/atc/lib64:$LD_LIBRARY_PATH +export ASCEND_OPP_PATH=${install_path}/opp +export Home="./path/" +# Home is set to the path where the model is located + +# Execute, transform YOLOv5 model. +atc --model="${Home}"/YOLOv5_s.onnx --framework=5 --output="${Home}"/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +# --model is the path where onnx is located. --output is the path where the output of the converted model is located \ No newline at end of file From 21c80fa9362bde3189a8f9f91d44ae77959307af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Mon, 12 Dec 2022 07:17:22 +0000 Subject: [PATCH 45/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/readme.md | 273 ------------------------------ 1 file changed, 273 deletions(-) delete mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md deleted file mode 100644 index c04e67f..0000000 --- a/HelmetIdentification_V2/readme.md +++ /dev/null @@ -1,273 +0,0 @@ -# 安全帽识别 - -## 1 介绍 -安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 - -本项目支持2路视频实时分析,其主要流程为: - -​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 - -### 1.1 支持的产品 - -本项目以昇腾Atlas 200DK为主要的硬件平台。 - -### 1.2 支持的版本 - -本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 - -MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 - -### 1.3 软件方案介绍 - -请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 - -表1.1 系统方案各子系统功能描述: - -| 序号 | 子系统 | 功能描述 | -| ---- | ---------- | ------------------------------------------------------------ | -| 1 | 视频解码 | 从264视频流中解码图像帧 | -| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | -| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | -| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | -| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | -| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | - -### 1.4 代码目录结构与说明 - -本工程名称为HelmetIdentification_V2,工程目录如下图所示: - -``` -. -├── model - ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 - ├── YOLOv5_s.om // 模型文件(需自行下载或转换) - ├── aipp_YOLOv5.config // 模型转换中用到的配置文件 - └── imgclass.names // label文件 -├── src - ├── main.cpp // 安全帽检测的main函数入口 - ├── main-image.cpp // 读取图片测试精度 - ├── utils.h // 实现多线程的工具类 - ├── cropResizePaste.hpp // 自定义的等比例缩放 - ├── MOTConnection.h // 跟踪匹配方法的头文件 - ├── MOTConnection.cpp // 跟踪匹配方法的实现 - ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 - ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 - ├── Hungar ian.h // 匈牙利算法的头文件 - ├── Hungarian.cpp // 匈牙利算法的实现 - ├── DataType.h // 自定义的数据结构 - └── CMakeLists.txt // CMake文件 -├── result - ├── one // 文件夹,保存第一路输入的结果(需要新建) - └── two // 文件夹,保存第二路输入的结果(需要新建) -└── CMakeLists.txt // CMake文件 -``` - -### 1.5 技术实现流程图 - -![](./images/flow.jpg) - -### 1.6 特性及适用场景 - -本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 - - - -## 2 环境依赖 - -### 2.1 软件依赖 - -环境依赖软件和版本如下表: - -| 软件 | 版本 | 说明 | 获取方式 | -| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | -| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | -| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | -| Ubuntu | 18.04 | | | - -### 2.2 环境变量 - -在编译运行项目前,需要设置环境变量: - -MindX SDK环境变量: - -. ${SDK-path}/set_env.sh - -CANN环境变量: - -. ${ascend-toolkit-path}/set_env.sh - -环境变量介绍 - -SDK-path:mxVision SDK安装路径 - -ascend-toolkit-path:CANN安装路径 - - - -## 3 模型转换 - -模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html - -本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 - -具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 - -运行命令如下: - -``` -sh atc-env.sh -``` - -脚本中包含atc命令: - -``` ---model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" -``` - -其参数如下表所示: - -| 参数名 | 参数描述 | -| ---------------- | ------------------------------------------------------------ | -| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | -| --model | 原始模型文件路径与文件名 | -| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | -| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | -| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | -| --input_shape | 模型输入数据的 shape。 | -| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | - -其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 - -注:1. [aipp配置文件教程链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0015.html) 2.atc-env.sh脚本内 Home 为onnx文件所在路径。 - -## 4 编译与运行 - -项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 - -### 4.1 测试性能 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 - -3. 进入src文件夹,修改其中的CMakeLists.txt - - * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - - * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试性能使用main.cpp -需要删除main-image.cpp -可以使用如下命令进行: - -```shell -cd src -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹 - -如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): - -``` -mkdir result -cd result -mkdir one -mkdir two -``` - -退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 - -``` -./main ${video0Path} ${video0width} ${video0height} [${video1Path} ${video1width} ${video1height}] -``` - -video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 - -如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 - -![](./images/warn.png) - -图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 - -图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 - -在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: - -![](./images/rate.jpg) - -### 4.2 测试精度 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 -2. 进入src文件夹,修改其中的CMakeLists.txt - 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试精度使用main-image.cpp -需要删除main.cpp,然后将main-image.cpp重命名为main.cpp -可以使用如下命令进行: - -```sh -cd src -rm ./main.cpp -cp ./main-image.cpp ./main.cpp -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) - -从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: - -``` -./main ${imagePath} -``` - -imagePath是图片路径(例如 ./main 000023.jpg) - -正确执行会输出检测到的目标框的信息,如下所示: - -![](./images/objInfo.png) - From 7eac9b3c9955f5ec5b6a626085a13a2e9f68feff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Mon, 12 Dec 2022 07:17:33 +0000 Subject: [PATCH 46/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/readme.md | 271 ++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md new file mode 100644 index 0000000..3961cbb --- /dev/null +++ b/HelmetIdentification_V2/readme.md @@ -0,0 +1,271 @@ +# 安全帽识别 + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── YOLOv5_s.om // 模型文件(需自行下载或转换) + ├── aipp_YOLOv5.config // 模型转换中用到的配置文件 + └── imgclass.names // label文件 +├── src + ├── main.cpp // 安全帽检测的main函数入口 + ├── main-image.cpp // 读取图片测试精度 + ├── utils.h // 实现多线程的工具类 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── Hungar ian.h // 匈牙利算法的头文件 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── DataType.h // 自定义的数据结构 + └── CMakeLists.txt // CMake文件 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + └── two // 文件夹,保存第二路输入的结果(需要新建) +└── CMakeLists.txt // CMake文件 +``` + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + +### 1.6 特性及适用场景 + +本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video0width} ${video0height} [${video1Path} ${video1width} ${video1height}] +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imagePath} +``` + +imagePath是图片路径(例如 ./main 000023.jpg) + +正确执行会输出检测到的目标框的信息,如下所示: + +![](./images/objInfo.png) + From a6dd7b139627cd78e7efbb1cd4be07fba02bcefa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Mon, 12 Dec 2022 07:21:25 +0000 Subject: [PATCH 47/58] update HelmetIdentification_V2/src/main.cpp. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/src/main.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/HelmetIdentification_V2/src/main.cpp b/HelmetIdentification_V2/src/main.cpp index 803c2f3..b755a26 100644 --- a/HelmetIdentification_V2/src/main.cpp +++ b/HelmetIdentification_V2/src/main.cpp @@ -304,13 +304,15 @@ bool checkArg(int argc, char *argv[]) LogWarn << "videoPath0: " << videoPath0 << ", src_width0: " << src_width0 << ", src_height0: " << src_height0; PullStream0(videoPath0); std::thread threadVdec0(VdecThread0, frameConfig::frameCountChannel0, frameConfig::skipIntervalChannel0, frameConfig::channelId0, src_width0, src_height0); - threadVdec0.join(); - + uint32_t src_width1 = (uint32_t)stoul(argv[frameConfig::arg5]); uint32_t src_height1 = (uint32_t)stoul(argv[frameConfig::arg6]); LogWarn << "videoPath1: " << videoPath1 << ", src_width1: " << src_width1 << ", src_height1: " << src_height1; PullStream1(videoPath1); std::thread threadVdec1(VdecThread1, frameConfig::frameCountChannel1, frameConfig::skipIntervalChannel1, frameConfig::channelId1, src_width1, src_height1); + + // threadVdec0新建后直接join会导致channel0的图片全部排在channel1的图片之前 + threadVdec0.join(); threadVdec1.join(); } else From 2d3a3bcaaf79b67f12c32f3e86c1f7902ef29095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Mon, 12 Dec 2022 07:27:48 +0000 Subject: [PATCH 48/58] update HelmetIdentification_V2/src/main-image.cpp. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/src/main-image.cpp | 217 ++++++++++++++++++++- 1 file changed, 212 insertions(+), 5 deletions(-) diff --git a/HelmetIdentification_V2/src/main-image.cpp b/HelmetIdentification_V2/src/main-image.cpp index a764a67..eea3d0e 100644 --- a/HelmetIdentification_V2/src/main-image.cpp +++ b/HelmetIdentification_V2/src/main-image.cpp @@ -24,7 +24,7 @@ using namespace std; // 如果在200DK上运行就改为 USE_200DK #define USE_DVPP -APP_ERROR readImage(std::string imgPath, MxBase::Image& image, MxBase::ImageProcessor& imageProcessor) +APP_ERROR readImage(std::string imgPath, MxBase::Image &image, MxBase::ImageProcessor &imageProcessor) { APP_ERROR ret; #ifdef USE_DVPP @@ -72,7 +72,8 @@ APP_ERROR readImage(std::string imgPath, MxBase::Image& image, MxBase::ImageProc } } -void postProcess(std::vector modelOutputs, std::shared_ptr postProcessorDptr, MxBase::Size originalSize, MxBase::Size resizeSize) +void postProcess(std::vector modelOutputs, std::shared_ptr postProcessorDptr, + MxBase::Size originalSize, MxBase::Size resizeSize, std::string outFileName) { MxBase::ResizedImageInfo imgInfo; auto shape = modelOutputs[0].GetShape(); @@ -96,6 +97,7 @@ void postProcess(std::vector modelOutputs, std::shared_ptr> objectInfos; postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); std::cout << "===---> Size of objectInfos is " << objectInfos.size() << std::endl; + std::ofstream out(outFileName); for (size_t i = 0; i < objectInfos.size(); i++) { std::cout << "objectInfo-" << i << " , Size:" << objectInfos[i].size() << std::endl; @@ -110,8 +112,23 @@ void postProcess(std::vector modelOutputs, std::shared_ptr("labelPath", videoInfo::labelPath)); std::shared_ptr postProcessorDptr = std::make_shared(); postProcessorDptr->Init(postConfig); - - std::string imgPath = argv[1]; + + std::string imgPath = "dataSet/TestImages/" + num + ".jpg"; // 读取图片 MxBase::Image image; readImage(imgPath, image, imageProcessor); @@ -164,7 +182,196 @@ APP_ERROR main(int argc, char *argv[]) } // 后处理 - postProcess(modelOutputs, postProcessorDptr, originalSize, resizeSize); + std::string outFileName = "dataSet/V2txt/" + num + ".txt"; + postProcess(modelOutputs, postProcessorDptr, originalSize, resizeSize, outFileName); + + return APP_ERR_OK; +}/* + * Copyright(C) 2021. Huawei Technologies Co.,Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils.h" + +#include +#include +#include +using namespace std; + +// 如果在200DK上运行就改为 USE_200DK +#define USE_DVPP + +APP_ERROR readImage(std::string imgPath, MxBase::Image &image, MxBase::ImageProcessor &imageProcessor) +{ + APP_ERROR ret; +#ifdef USE_DVPP + // if USE DVPP + ret = imageProcessor.Decode(imgPath, image); +#endif +#ifdef USE_200DK + std::shared_ptr dataPtr; + uint32_t dataSize; + // Get image data to memory, this method can be substituted or designed by yourself! + std::ifstream file; + file.open(imgPath.c_str(), std::ios::binary); + if (!file) + { + LogInfo << "Invalid file."; + return APP_ERR_COMM_INVALID_PARAM; + } + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); + + char *p = (char *)malloc(content.size()); + memcpy(p, content.data(), content.size()); + auto deleter = [](void *p) -> void + { + free(p); + p = nullptr; + }; + + dataPtr.reset(static_cast((void *)(p)), deleter); + dataSize = content.size(); + file.close(); + if (ret != APP_ERR_OK) + { + LogError << "Getimage failed, ret=" << ret; + return ret; + } + ret = imageProcessor.Decode(dataPtr, dataSize, image, MxBase::ImageFormat::YUV_SP_420); + // endif +#endif + if (ret != APP_ERR_OK) + { + LogError << "Decode failed, ret=" << ret; + return ret; + } +} + +void postProcess(std::vector modelOutputs, std::shared_ptr postProcessorDptr, + MxBase::Size originalSize, MxBase::Size resizeSize, std::string outFileName) +{ + MxBase::ResizedImageInfo imgInfo; + auto shape = modelOutputs[0].GetShape(); + imgInfo.widthOriginal = originalSize.width; + imgInfo.heightOriginal = originalSize.height; + imgInfo.widthResize = resizeSize.width; + imgInfo.heightResize = resizeSize.height; + imgInfo.resizeType = MxBase::RESIZER_MS_KEEP_ASPECT_RATIO; + float resizeRate = originalSize.width > originalSize.height ? (originalSize.width * 1.0 / videoInfo::YOLOV5_RESIZE) : (originalSize.height * 1.0 / videoInfo::YOLOV5_RESIZE); + imgInfo.keepAspectRatioScaling = 1 / resizeRate; + std::vector imageInfoVec = {}; + imageInfoVec.push_back(imgInfo); + // make postProcess inputs + std::vector tensors; + for (size_t i = 0; i < modelOutputs.size(); i++) + { + MxBase::MemoryData memoryData(modelOutputs[i].GetData(), modelOutputs[i].GetByteSize()); + MxBase::TensorBase tensorBase(memoryData, true, modelOutputs[i].GetShape(), MxBase::TENSOR_DTYPE_INT32); + tensors.push_back(tensorBase); + } + std::vector> objectInfos; + postProcessorDptr->Process(tensors, objectInfos, imageInfoVec); + std::cout << "===---> Size of objectInfos is " << objectInfos.size() << std::endl; + std::ofstream out(outFileName); + for (size_t i = 0; i < objectInfos.size(); i++) + { + std::cout << "objectInfo-" << i << " , Size:" << objectInfos[i].size() << std::endl; + for (size_t j = 0; j < objectInfos[i].size(); j++) + { + std::cout << std::endl + << "*****objectInfo-" << i << ":" << j << std::endl; + std::cout << "x0 is " << objectInfos[i][j].x0 << std::endl; + std::cout << "y0 is " << objectInfos[i][j].y0 << std::endl; + std::cout << "x1 is " << objectInfos[i][j].x1 << std::endl; + std::cout << "y1 is " << objectInfos[i][j].y1 << std::endl; + std::cout << "confidence is " << objectInfos[i][j].confidence << std::endl; + std::cout << "classId is " << objectInfos[i][j].classId << std::endl; + std::cout << "className is " << objectInfos[i][j].className << std::endl; + + // 这里的out是输出流,不是std::cout + out << objectInfos[i][j].className; + out << " "; + out << objectInfos[i][j].confidence; + out << " "; + out << objectInfos[i][j].x0; + out << " "; + out << objectInfos[i][j].y0; + out << " "; + out << objectInfos[i][j].x1; + out << " "; + out << objectInfos[i][j].y1; + out << "\n"; + } + } + out.close(); +} + +APP_ERROR main(int argc, char *argv[]) +{ + APP_ERROR ret; + + // global init + ret = MxBase::MxInit(); + if (ret != APP_ERR_OK) + { + LogError << "MxInit failed, ret=" << ret << "."; + } + // 检测是否输入了文件路径 + if (argc <= 1) + { + LogWarn << "Please input image path, such as 'test.jpg'."; + return APP_ERR_OK; + } + std::string num = argv[1]; + + // imageProcess init + MxBase::ImageProcessor imageProcessor(videoInfo::DEVICE_ID); + // model init + MxBase::Model yoloModel(videoInfo::modelPath, videoInfo::DEVICE_ID); + // postprocessor init + std::map postConfig; + postConfig.insert(std::pair("postProcessConfigPath", videoInfo::configPath)); + postConfig.insert(std::pair("labelPath", videoInfo::labelPath)); + std::shared_ptr postProcessorDptr = std::make_shared(); + postProcessorDptr->Init(postConfig); + + std::string imgPath = "dataSet/TestImages/" + num + ".jpg"; + // 读取图片 + MxBase::Image image; + readImage(imgPath, image, imageProcessor); + + // 缩放图片 + MxBase::Size originalSize = image.GetOriginalSize(); + MxBase::Size resizeSize = MxBase::Size(videoInfo::YOLOV5_RESIZE, videoInfo::YOLOV5_RESIZE); + MxBase::Image resizedImage = resizeKeepAspectRatioFit(originalSize.width, originalSize.height, resizeSize.width, resizeSize.height, image, imageProcessor); + + // 模型推理 + MxBase::Tensor tensorImg = resizedImage.ConvertToTensor(); + tensorImg.ToDevice(videoInfo::DEVICE_ID); + std::vector inputs; + inputs.push_back(tensorImg); + std::vector modelOutputs = yoloModel.Infer(inputs); + for (auto output : modelOutputs) + { + output.ToHost(); + } + + // 后处理 + std::string outFileName = "dataSet/V2txt/" + num + ".txt"; + postProcess(modelOutputs, postProcessorDptr, originalSize, resizeSize, outFileName); return APP_ERR_OK; } \ No newline at end of file From 5934cb2997124270220d1f499846a285a8890a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Mon, 12 Dec 2022 07:33:51 +0000 Subject: [PATCH 49/58] update HelmetIdentification_V2/src/cropResizePaste.hpp. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/src/cropResizePaste.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/HelmetIdentification_V2/src/cropResizePaste.hpp b/HelmetIdentification_V2/src/cropResizePaste.hpp index b3648ec..ed4fa12 100644 --- a/HelmetIdentification_V2/src/cropResizePaste.hpp +++ b/HelmetIdentification_V2/src/cropResizePaste.hpp @@ -32,6 +32,14 @@ MxBase::Rect GetPasteRect(uint32_t inputWidth, uint32_t inputHeight, uint32_t outputWidth, uint32_t outputHeight) { bool widthRatioLarger = true; + if(outputWidth == 0) + { + LogError << "outputWidth equals to 0."; + } + if(outputHeight == 0) + { + LogError << "outputHeight equals to 0."; + } float resizeRatio = static_cast(inputWidth) / outputWidth; if (resizeRatio < (static_cast(inputHeight) / outputHeight)) { From 29eaf19db3437693212bf8ee2541477928d6ef07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Mon, 12 Dec 2022 07:49:55 +0000 Subject: [PATCH 50/58] update HelmetIdentification_V2/src/cropResizePaste.hpp. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/src/cropResizePaste.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HelmetIdentification_V2/src/cropResizePaste.hpp b/HelmetIdentification_V2/src/cropResizePaste.hpp index ed4fa12..a1501da 100644 --- a/HelmetIdentification_V2/src/cropResizePaste.hpp +++ b/HelmetIdentification_V2/src/cropResizePaste.hpp @@ -32,11 +32,11 @@ MxBase::Rect GetPasteRect(uint32_t inputWidth, uint32_t inputHeight, uint32_t outputWidth, uint32_t outputHeight) { bool widthRatioLarger = true; - if(outputWidth == 0) + if (outputWidth == 0) { LogError << "outputWidth equals to 0."; } - if(outputHeight == 0) + if (outputHeight == 0) { LogError << "outputHeight equals to 0."; } From 26f31af3d12f73a92f572f3a01dba4e3fa37424c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 13 Dec 2022 07:28:00 +0000 Subject: [PATCH 51/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/images/objInfo.png?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/images/objInfo.png | Bin 9455 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 HelmetIdentification_V2/images/objInfo.png diff --git a/HelmetIdentification_V2/images/objInfo.png b/HelmetIdentification_V2/images/objInfo.png deleted file mode 100644 index e642d774a1931ec2a0eb423cb2b7d200461f6c76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9455 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 Date: Tue, 13 Dec 2022 07:28:11 +0000 Subject: [PATCH 52/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/images/map_calculate.png | Bin 0 -> 5607 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 HelmetIdentification_V2/images/map_calculate.png diff --git a/HelmetIdentification_V2/images/map_calculate.png b/HelmetIdentification_V2/images/map_calculate.png new file mode 100644 index 0000000000000000000000000000000000000000..95f67d9c53cc0dc33a858d9e6b84cdb50c7ae631 GIT binary patch literal 5607 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 Date: Tue, 13 Dec 2022 07:28:19 +0000 Subject: [PATCH 53/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/readme.md | 271 ------------------------------ 1 file changed, 271 deletions(-) delete mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md deleted file mode 100644 index 3961cbb..0000000 --- a/HelmetIdentification_V2/readme.md +++ /dev/null @@ -1,271 +0,0 @@ -# 安全帽识别 - -## 1 介绍 -安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 - -本项目支持2路视频实时分析,其主要流程为: - -​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 - -### 1.1 支持的产品 - -本项目以昇腾Atlas 200DK为主要的硬件平台。 - -### 1.2 支持的版本 - -本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 - -MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 - -### 1.3 软件方案介绍 - -请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 - -表1.1 系统方案各子系统功能描述: - -| 序号 | 子系统 | 功能描述 | -| ---- | ---------- | ------------------------------------------------------------ | -| 1 | 视频解码 | 从264视频流中解码图像帧 | -| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | -| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | -| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | -| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | -| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | - -### 1.4 代码目录结构与说明 - -本工程名称为HelmetIdentification_V2,工程目录如下图所示: - -``` -. -├── model - ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 - ├── YOLOv5_s.om // 模型文件(需自行下载或转换) - ├── aipp_YOLOv5.config // 模型转换中用到的配置文件 - └── imgclass.names // label文件 -├── src - ├── main.cpp // 安全帽检测的main函数入口 - ├── main-image.cpp // 读取图片测试精度 - ├── utils.h // 实现多线程的工具类 - ├── cropResizePaste.hpp // 自定义的等比例缩放 - ├── MOTConnection.h // 跟踪匹配方法的头文件 - ├── MOTConnection.cpp // 跟踪匹配方法的实现 - ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 - ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 - ├── Hungar ian.h // 匈牙利算法的头文件 - ├── Hungarian.cpp // 匈牙利算法的实现 - ├── DataType.h // 自定义的数据结构 - └── CMakeLists.txt // CMake文件 -├── result - ├── one // 文件夹,保存第一路输入的结果(需要新建) - └── two // 文件夹,保存第二路输入的结果(需要新建) -└── CMakeLists.txt // CMake文件 -``` - -### 1.5 技术实现流程图 - -![](./images/flow.jpg) - -### 1.6 特性及适用场景 - -本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 - - - -## 2 环境依赖 - -### 2.1 软件依赖 - -环境依赖软件和版本如下表: - -| 软件 | 版本 | 说明 | 获取方式 | -| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | -| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | -| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | -| Ubuntu | 18.04 | | | - -### 2.2 环境变量 - -在编译运行项目前,需要设置环境变量: - -MindX SDK环境变量: - -. ${SDK-path}/set_env.sh - -CANN环境变量: - -. ${ascend-toolkit-path}/set_env.sh - -环境变量介绍 - -SDK-path:mxVision SDK安装路径 - -ascend-toolkit-path:CANN安装路径 - - - -## 3 模型转换 - -模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html - -本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 - -具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 - -运行命令如下: - -``` -sh atc-env.sh -``` - -脚本中包含atc命令: - -``` ---model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" -``` - -其参数如下表所示: - -| 参数名 | 参数描述 | -| ---------------- | ------------------------------------------------------------ | -| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | -| --model | 原始模型文件路径与文件名 | -| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | -| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | -| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | -| --input_shape | 模型输入数据的 shape。 | -| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | - -其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 - -## 4 编译与运行 - -项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 - -### 4.1 测试性能 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 - -3. 进入src文件夹,修改其中的CMakeLists.txt - - * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - - * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试性能使用main.cpp -需要删除main-image.cpp -可以使用如下命令进行: - -```shell -cd src -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹 - -如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): - -``` -mkdir result -cd result -mkdir one -mkdir two -``` - -退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 - -``` -./main ${video0Path} ${video0width} ${video0height} [${video1Path} ${video1width} ${video1height}] -``` - -video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 - -如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 - -![](./images/warn.png) - -图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 - -图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 - -在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: - -![](./images/rate.jpg) - -### 4.2 测试精度 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 -2. 进入src文件夹,修改其中的CMakeLists.txt - 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试精度使用main-image.cpp -需要删除main.cpp,然后将main-image.cpp重命名为main.cpp -可以使用如下命令进行: - -```sh -cd src -rm ./main.cpp -cp ./main-image.cpp ./main.cpp -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) - -从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: - -``` -./main ${imagePath} -``` - -imagePath是图片路径(例如 ./main 000023.jpg) - -正确执行会输出检测到的目标框的信息,如下所示: - -![](./images/objInfo.png) - From f4d525c993da9c1fa48b93e9a71a326a89b49367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Tue, 13 Dec 2022 07:28:34 +0000 Subject: [PATCH 54/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/readme.md | 282 ++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md new file mode 100644 index 0000000..eb43d9a --- /dev/null +++ b/HelmetIdentification_V2/readme.md @@ -0,0 +1,282 @@ +# 安全帽识别 + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── YOLOv5_s.om // 模型文件(需自行下载或转换) + ├── aipp_YOLOv5.config // 模型转换中用到的配置文件 + └── imgclass.names // label文件 +├── src + ├── main.cpp // 安全帽检测的main函数入口 + ├── main-image.cpp // 读取图片测试精度 + ├── utils.h // 实现多线程的工具类 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── Hungar ian.h // 匈牙利算法的头文件 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── DataType.h // 自定义的数据结构 + └── CMakeLists.txt // CMake文件 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + └── two // 文件夹,保存第二路输入的结果(需要新建) +└── CMakeLists.txt // CMake文件 +``` + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + +### 1.6 特性及适用场景 + +本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video0width} ${video0height} [${video1Path} ${video1width} ${video1height}] +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imageName} +``` + +imageName是图片名称(不包含后缀名) + +例如 `./main 000023`会**读取000023.jpg,然后生成000023.txt并向其中写入检测结果(目标框的坐标)**。 + +每次只能读取一张图片进行检测,检测完所有待检测的图片后,使用V1版本中的`map_calculate.py`计算精度,会打印具体的指标值(如下所示)并将precision、recall和map记录在**output/output.txt**文件中。 + +![](./images/map_calculate.png) + +具体命令如下: + +``` +python3 map_calculate.py --label_path ${label_path} --npu_txt_path ${txt_path} -na -np +``` + +* label_path是数据集中的标签,具体生成方式见[V1版本](https://gitee.com/ascend/mindxsdk-referenceapps/tree/master/contrib/HelmetIdentification#32-%E7%B2%BE%E5%BA%A6%E6%B5%8B%E8%AF%95)。 +* txt_path是上面提到的生成的结果文件,将所有的txt文件都放到一个文件中,map_calculate.py会自动检测文件名,将其与label_path中对应的文件进行比较。 + From 044a9691d695214e4586cbd7820ac021ac9e2a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Wed, 14 Dec 2022 09:38:00 +0000 Subject: [PATCH 55/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/readme.md | 282 ------------------------------ 1 file changed, 282 deletions(-) delete mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md deleted file mode 100644 index eb43d9a..0000000 --- a/HelmetIdentification_V2/readme.md +++ /dev/null @@ -1,282 +0,0 @@ -# 安全帽识别 - -## 1 介绍 -安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 - -本项目支持2路视频实时分析,其主要流程为: - -​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 - -### 1.1 支持的产品 - -本项目以昇腾Atlas 200DK为主要的硬件平台。 - -### 1.2 支持的版本 - -本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 - -MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 - -### 1.3 软件方案介绍 - -请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 - -表1.1 系统方案各子系统功能描述: - -| 序号 | 子系统 | 功能描述 | -| ---- | ---------- | ------------------------------------------------------------ | -| 1 | 视频解码 | 从264视频流中解码图像帧 | -| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | -| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | -| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | -| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | -| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | - -### 1.4 代码目录结构与说明 - -本工程名称为HelmetIdentification_V2,工程目录如下图所示: - -``` -. -├── model - ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 - ├── YOLOv5_s.om // 模型文件(需自行下载或转换) - ├── aipp_YOLOv5.config // 模型转换中用到的配置文件 - └── imgclass.names // label文件 -├── src - ├── main.cpp // 安全帽检测的main函数入口 - ├── main-image.cpp // 读取图片测试精度 - ├── utils.h // 实现多线程的工具类 - ├── cropResizePaste.hpp // 自定义的等比例缩放 - ├── MOTConnection.h // 跟踪匹配方法的头文件 - ├── MOTConnection.cpp // 跟踪匹配方法的实现 - ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 - ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 - ├── Hungar ian.h // 匈牙利算法的头文件 - ├── Hungarian.cpp // 匈牙利算法的实现 - ├── DataType.h // 自定义的数据结构 - └── CMakeLists.txt // CMake文件 -├── result - ├── one // 文件夹,保存第一路输入的结果(需要新建) - └── two // 文件夹,保存第二路输入的结果(需要新建) -└── CMakeLists.txt // CMake文件 -``` - -### 1.5 技术实现流程图 - -![](./images/flow.jpg) - -### 1.6 特性及适用场景 - -本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 - - - -## 2 环境依赖 - -### 2.1 软件依赖 - -环境依赖软件和版本如下表: - -| 软件 | 版本 | 说明 | 获取方式 | -| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | -| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | -| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | -| Ubuntu | 18.04 | | | - -### 2.2 环境变量 - -在编译运行项目前,需要设置环境变量: - -MindX SDK环境变量: - -. ${SDK-path}/set_env.sh - -CANN环境变量: - -. ${ascend-toolkit-path}/set_env.sh - -环境变量介绍 - -SDK-path:mxVision SDK安装路径 - -ascend-toolkit-path:CANN安装路径 - - - -## 3 模型转换 - -模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html - -本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 - -具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 - -运行命令如下: - -``` -sh atc-env.sh -``` - -脚本中包含atc命令: - -``` ---model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" -``` - -其参数如下表所示: - -| 参数名 | 参数描述 | -| ---------------- | ------------------------------------------------------------ | -| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | -| --model | 原始模型文件路径与文件名 | -| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | -| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | -| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | -| --input_shape | 模型输入数据的 shape。 | -| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | - -其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 - -## 4 编译与运行 - -项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 - -### 4.1 测试性能 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 - -3. 进入src文件夹,修改其中的CMakeLists.txt - - * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - - * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试性能使用main.cpp -需要删除main-image.cpp -可以使用如下命令进行: - -```shell -cd src -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹 - -如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): - -``` -mkdir result -cd result -mkdir one -mkdir two -``` - -退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 - -``` -./main ${video0Path} ${video0width} ${video0height} [${video1Path} ${video1width} ${video1height}] -``` - -video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 - -如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 - -![](./images/warn.png) - -图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 - -图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 - -在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: - -![](./images/rate.jpg) - -### 4.2 测试精度 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 -2. 进入src文件夹,修改其中的CMakeLists.txt - 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试精度使用main-image.cpp -需要删除main.cpp,然后将main-image.cpp重命名为main.cpp -可以使用如下命令进行: - -```sh -cd src -rm ./main.cpp -cp ./main-image.cpp ./main.cpp -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) - -从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: - -``` -./main ${imageName} -``` - -imageName是图片名称(不包含后缀名) - -例如 `./main 000023`会**读取000023.jpg,然后生成000023.txt并向其中写入检测结果(目标框的坐标)**。 - -每次只能读取一张图片进行检测,检测完所有待检测的图片后,使用V1版本中的`map_calculate.py`计算精度,会打印具体的指标值(如下所示)并将precision、recall和map记录在**output/output.txt**文件中。 - -![](./images/map_calculate.png) - -具体命令如下: - -``` -python3 map_calculate.py --label_path ${label_path} --npu_txt_path ${txt_path} -na -np -``` - -* label_path是数据集中的标签,具体生成方式见[V1版本](https://gitee.com/ascend/mindxsdk-referenceapps/tree/master/contrib/HelmetIdentification#32-%E7%B2%BE%E5%BA%A6%E6%B5%8B%E8%AF%95)。 -* txt_path是上面提到的生成的结果文件,将所有的txt文件都放到一个文件中,map_calculate.py会自动检测文件名,将其与label_path中对应的文件进行比较。 - From ac574ce1a86e7ef2199c59c7b47df6e73578ee93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Wed, 14 Dec 2022 09:38:20 +0000 Subject: [PATCH 56/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/readme.md | 284 ++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md new file mode 100644 index 0000000..8a5a1c6 --- /dev/null +++ b/HelmetIdentification_V2/readme.md @@ -0,0 +1,284 @@ +# 安全帽识别 + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── act-env.sh // 设置环境变量的脚本 + ├── aipp_YOLOv5.config // 模型转换中用到的配置文件 + ├── imgclass.names // label文件 + └── YOLOv5_s.om // 模型文件(需自行下载或转换) +├── src + ├── CMakeLists.txt // CMake文件 + ├── DataType.h // 自定义的数据结构 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── Hungarian.h // 匈牙利算法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── main-image.cpp // 读取图片测试精度 + ├── main.cpp // 安全帽检测的main函数入口 + └── utils.h // 实现多线程的工具类 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + └── two // 文件夹,保存第二路输入的结果(需要新建) +├── CMakeLists.txt // CMake文件 +└── readme.md // Readdme +``` + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + +### 1.6 特性及适用场景 + +本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video0width} ${video0height} [${video1Path} ${video1width} ${video1height}] +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imageName} +``` + +imageName是图片名称(不包含后缀名) + +例如 `./main 000023`会**读取000023.jpg,然后生成000023.txt并向其中写入检测结果(目标框的坐标)**。 + +每次只能读取一张图片进行检测,检测完所有待检测的图片后,使用V1版本中的`map_calculate.py`计算精度,会打印具体的指标值(如下所示)并将precision、recall和map记录在**output/output.txt**文件中。 + +![](./images/map_calculate.png) + +具体命令如下: + +``` +python3 map_calculate.py --label_path ${label_path} --npu_txt_path ${txt_path} -na -np +``` + +* label_path是数据集中的标签,具体生成方式见[V1版本](https://gitee.com/ascend/mindxsdk-referenceapps/tree/master/contrib/HelmetIdentification#32-%E7%B2%BE%E5%BA%A6%E6%B5%8B%E8%AF%95)。 +* txt_path是上面提到的生成的结果文件,将所有的txt文件都放到一个文件中,map_calculate.py会自动检测文件名,将其与label_path中对应的文件进行比较。 + From c72a2367de5b13144cc5e4e4629293f063749960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Wed, 14 Dec 2022 09:38:54 +0000 Subject: [PATCH 57/58] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20He?= =?UTF-8?q?lmetIdentification=5FV2/readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HelmetIdentification_V2/readme.md | 284 ------------------------------ 1 file changed, 284 deletions(-) delete mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md deleted file mode 100644 index 8a5a1c6..0000000 --- a/HelmetIdentification_V2/readme.md +++ /dev/null @@ -1,284 +0,0 @@ -# 安全帽识别 - -## 1 介绍 -安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 - -本项目支持2路视频实时分析,其主要流程为: - -​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 - -### 1.1 支持的产品 - -本项目以昇腾Atlas 200DK为主要的硬件平台。 - -### 1.2 支持的版本 - -本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 - -MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 - -### 1.3 软件方案介绍 - -请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 - -表1.1 系统方案各子系统功能描述: - -| 序号 | 子系统 | 功能描述 | -| ---- | ---------- | ------------------------------------------------------------ | -| 1 | 视频解码 | 从264视频流中解码图像帧 | -| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | -| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | -| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | -| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | -| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | - -### 1.4 代码目录结构与说明 - -本工程名称为HelmetIdentification_V2,工程目录如下图所示: - -``` -. -├── model - ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 - ├── act-env.sh // 设置环境变量的脚本 - ├── aipp_YOLOv5.config // 模型转换中用到的配置文件 - ├── imgclass.names // label文件 - └── YOLOv5_s.om // 模型文件(需自行下载或转换) -├── src - ├── CMakeLists.txt // CMake文件 - ├── DataType.h // 自定义的数据结构 - ├── Hungarian.cpp // 匈牙利算法的实现 - ├── Hungarian.h // 匈牙利算法的头文件 - ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 - ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 - ├── MOTConnection.cpp // 跟踪匹配方法的实现 - ├── MOTConnection.h // 跟踪匹配方法的头文件 - ├── cropResizePaste.hpp // 自定义的等比例缩放 - ├── main-image.cpp // 读取图片测试精度 - ├── main.cpp // 安全帽检测的main函数入口 - └── utils.h // 实现多线程的工具类 -├── result - ├── one // 文件夹,保存第一路输入的结果(需要新建) - └── two // 文件夹,保存第二路输入的结果(需要新建) -├── CMakeLists.txt // CMake文件 -└── readme.md // Readdme -``` - -### 1.5 技术实现流程图 - -![](./images/flow.jpg) - -### 1.6 特性及适用场景 - -本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 - - - -## 2 环境依赖 - -### 2.1 软件依赖 - -环境依赖软件和版本如下表: - -| 软件 | 版本 | 说明 | 获取方式 | -| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | -| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | -| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | -| Ubuntu | 18.04 | | | - -### 2.2 环境变量 - -在编译运行项目前,需要设置环境变量: - -MindX SDK环境变量: - -. ${SDK-path}/set_env.sh - -CANN环境变量: - -. ${ascend-toolkit-path}/set_env.sh - -环境变量介绍 - -SDK-path:mxVision SDK安装路径 - -ascend-toolkit-path:CANN安装路径 - - - -## 3 模型转换 - -模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html - -本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 - -具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 - -运行命令如下: - -``` -sh atc-env.sh -``` - -脚本中包含atc命令: - -``` ---model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" -``` - -其参数如下表所示: - -| 参数名 | 参数描述 | -| ---------------- | ------------------------------------------------------------ | -| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | -| --model | 原始模型文件路径与文件名 | -| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | -| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | -| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | -| --input_shape | 模型输入数据的 shape。 | -| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | - -其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 - -## 4 编译与运行 - -项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 - -### 4.1 测试性能 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 - -3. 进入src文件夹,修改其中的CMakeLists.txt - - * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - - * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试性能使用main.cpp -需要删除main-image.cpp -可以使用如下命令进行: - -```shell -cd src -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -从build文件夹退回到HelmetIdentification_V2文件夹 - -如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): - -``` -mkdir result -cd result -mkdir one -mkdir two -``` - -退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 - -``` -./main ${video0Path} ${video0width} ${video0height} [${video1Path} ${video1width} ${video1height}] -``` - -video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 - -如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 - -![](./images/warn.png) - -图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 - -图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 - -在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: - -![](./images/rate.jpg) - -### 4.2 测试精度 - -**步骤1** 修改参数 - -1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 -2. 进入src文件夹,修改其中的CMakeLists.txt - 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 - 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 - 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 - -**步骤2** 选择对应的main文件 - -测试精度使用main-image.cpp -需要删除main.cpp,然后将main-image.cpp重命名为main.cpp -可以使用如下命令进行: - -```sh -cd src -rm ./main.cpp -cp ./main-image.cpp ./main.cpp -rm ./main-image.cpp -``` - -**步骤3** 编译 - -进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 - -```shell -mkdir build -cd build -cmake .. -make -``` - -出现如下图所示的结果即表示编译成功 - -![](./images/make.png) - -**步骤4** 运行及输出结果 - -**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) - -从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: - -``` -./main ${imageName} -``` - -imageName是图片名称(不包含后缀名) - -例如 `./main 000023`会**读取000023.jpg,然后生成000023.txt并向其中写入检测结果(目标框的坐标)**。 - -每次只能读取一张图片进行检测,检测完所有待检测的图片后,使用V1版本中的`map_calculate.py`计算精度,会打印具体的指标值(如下所示)并将precision、recall和map记录在**output/output.txt**文件中。 - -![](./images/map_calculate.png) - -具体命令如下: - -``` -python3 map_calculate.py --label_path ${label_path} --npu_txt_path ${txt_path} -na -np -``` - -* label_path是数据集中的标签,具体生成方式见[V1版本](https://gitee.com/ascend/mindxsdk-referenceapps/tree/master/contrib/HelmetIdentification#32-%E7%B2%BE%E5%BA%A6%E6%B5%8B%E8%AF%95)。 -* txt_path是上面提到的生成的结果文件,将所有的txt文件都放到一个文件中,map_calculate.py会自动检测文件名,将其与label_path中对应的文件进行比较。 - From 44bf661da6c68d4b72e113ef0a8ab62b77946f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E7=94=9F?= <2930043128@qq.com> Date: Wed, 14 Dec 2022 09:39:19 +0000 Subject: [PATCH 58/58] readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 叶生 <2930043128@qq.com> --- HelmetIdentification_V2/readme.md | 283 ++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 HelmetIdentification_V2/readme.md diff --git a/HelmetIdentification_V2/readme.md b/HelmetIdentification_V2/readme.md new file mode 100644 index 0000000..9281a55 --- /dev/null +++ b/HelmetIdentification_V2/readme.md @@ -0,0 +1,283 @@ +# 安全帽识别 + +## 1 介绍 +安全帽作为工作中一样重要的安全防护用品,主要保护头部,防高空物体坠落,防物体打击、碰撞。通过识别每个人是否戴上安全帽,可以对没戴安全帽的人做出告警。 + +本项目支持2路视频实时分析,其主要流程为: + +​ 分两路接收外部输入视频,通过视频解码将264格式视频解码为YUV格式图片,然后对这些图片串行化进行缩放、推理、后处理、跟踪去重等处理。模型推理使用YOLOv5进行安全帽识别,对重复检测出的没戴安全帽的对象进行去重,最后将识别结果输出为两路,并对没佩戴安全帽的情况告警。 + +### 1.1 支持的产品 + +本项目以昇腾Atlas 200DK为主要的硬件平台。 + +### 1.2 支持的版本 + +本项目配套的CANN版本为 [6.0.RC1](https://www.hiascend.com/software/cann/commercial) ,MindX SDK版本为 [3.0.RC2](https://www.hiascend.com/software/Mindx-sdk) 。 + +MindX SDK安装前准备可参考《用户指南》中的[安装教程](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/quickStart/1-1安装SDK开发套件.md)。 + +### 1.3 软件方案介绍 + +请先总体介绍项目的方案架构。如果项目设计方案中涉及子系统,请详细描述各子系统功能。如果设计了不同的功能模块,则请详细描述各模块功能。 + +表1.1 系统方案各子系统功能描述: + +| 序号 | 子系统 | 功能描述 | +| ---- | ---------- | ------------------------------------------------------------ | +| 1 | 视频解码 | 从264视频流中解码图像帧 | +| 2 | 图像帧缩放 | 将输入的图像帧使用等比例缩放方法缩放到YOLO模型要求的宽高 | +| 3 | 模型推理 | 将缩放后的图像输入模型获得结果(tensor格式) | +| 4 | 后处理 | 调用后处理接口根据模型推理结果计算检测出的目标框在原图中的位置 | +| 5 | 跟踪去重 | 使用匈牙利算法和卡尔曼滤波对同一路视频流不同图像中的目标框进行跟踪 | +| 6 | 结果输出 | 如果检测出了未佩戴头盔的情况且跟踪去重模块判断为未重复就输出告警信息并将检测结果按照输入分为两路保存 | + +### 1.4 代码目录结构与说明 + +本工程名称为HelmetIdentification_V2,工程目录如下图所示: + +``` +. +├── model + ├── Helmet_yolov5.cfg // 模型转换aipp配置文件 + ├── act-env.sh // 设置环境变量的脚本 + ├── aipp_YOLOv5.config // 模型转换中用到的配置文件 + ├── imgclass.names // label文件 + └── YOLOv5_s.om // 模型文件(需自行下载或转换) +├── src + ├── CMakeLists.txt // CMake文件 + ├── DataType.h // 自定义的数据结构 + ├── Hungarian.cpp // 匈牙利算法的实现 + ├── Hungarian.h // 匈牙利算法的头文件 + ├── KalmanTracker.cpp // 卡尔曼滤波方法的实现 + ├── KalmanTracker.h // 卡尔曼滤波方法的头文件 + ├── MOTConnection.cpp // 跟踪匹配方法的实现 + ├── MOTConnection.h // 跟踪匹配方法的头文件 + ├── cropResizePaste.hpp // 自定义的等比例缩放 + ├── main-image.cpp // 读取图片测试精度 + ├── main.cpp // 安全帽检测的main函数入口 + └── utils.h // 实现多线程的工具类 +├── result + ├── one // 文件夹,保存第一路输入的结果(需要新建) + └── two // 文件夹,保存第二路输入的结果(需要新建) +└── CMakeLists.txt // CMake文件 +``` + +### 1.5 技术实现流程图 + +![](./images/flow.jpg) + +### 1.6 特性及适用场景 + +本案例可以满足未佩戴安全帽的目标检测,要求输入的视频为264格式。 + + + +## 2 环境依赖 + +### 2.1 软件依赖 + +环境依赖软件和版本如下表: + +| 软件 | 版本 | 说明 | 获取方式 | +| ------------------- | ------- | ----------------------------- | --------------------------------------------------------- | +| mxVision | 3.0.RC2 | mxVision软件包 | [链接](https://www.hiascend.com/software/Mindx-sdk) | +| Ascend-CANN-toolkit | 6.0.RC1 | Ascend-cann-toolkit开发套件包 | [链接](https://www.hiascend.com/software/cann/commercial) | +| Ubuntu | 18.04 | | | + +### 2.2 环境变量 + +在编译运行项目前,需要设置环境变量: + +MindX SDK环境变量: + +. ${SDK-path}/set_env.sh + +CANN环境变量: + +. ${ascend-toolkit-path}/set_env.sh + +环境变量介绍 + +SDK-path:mxVision SDK安装路径 + +ascend-toolkit-path:CANN安装路径 + + + +## 3 模型转换 + +模型转换所需ATC工具环境搭建参考链接:https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha001/infacldevg/atctool/atlasatc_16_0004.html + +本项目使用YOLOV5_s模型用于完成目标检测任务,可点击[链接](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%2520%2Fcontrib%2FHelmetIdentification%2Fmodel.zip)下载onnx模型然后使用ATC工具转换。 + +具体步骤为:将onnx模型上传至服务器任意目录,运行atc-env脚本将onnx转为om模型。 + +运行命令如下: + +``` +sh atc-env.sh +``` + +脚本中包含atc命令: + +``` +--model=${Home}/YOLOv5_s.onnx --framework=5 --output=${Home}/YOLOv5_s --insert_op_conf=./aipp_YOLOv5.config --input_format=NCHW --log=info --soc_version=Ascend310 --input_shape="images:1,3,640,640" +``` + +其参数如下表所示: + +| 参数名 | 参数描述 | +| ---------------- | ------------------------------------------------------------ | +| -- framework | 原始框架类型。当取值为5时,即为ONNX网络模型,仅支持ai.onnx算子域中opset v11版本的算 子。用户也可以将其他opset版本的算子(比如opset v9),通过PyTorch转换成 opset v11版本的onnx算子 | +| --model | 原始模型文件路径与文件名 | +| --output | 如果是开源框架的网络模型,存放转换后的离线模型的路径以及文件名。 | +| --soc_version | 模型转换时指定芯片版本。昇腾AI处理器的版本,可从ATC工具安装路径的“/usr/local/Ascend/ascend-toolkit/latest/arm64-linux/atc/data/platform_config”目录下 查看。 ".ini"文件的文件名即为对应的${soc_version} | +| --insert_op_conf | 插入算子的配置文件路径与文件名, 例如aipp预处理算子。 | +| --input_shape | 模型输入数据的 shape。 | +| --out_nodes | 指定输出节点,如果不指定输出节点(算子名称),则模型的输出默认为最后一层的算子信息,如果 指定,则以指定的为准。 | + +其中--insert_op_conf参数为aipp预处理算子配置文件路径。该配置文件aipp_YOLOv5.config在输入图像进入模型前进行预处理。该配置文件保存在源码Models目录下。 + +## 4 编译与运行 + +项目要求实现性能和精度的测试验证,所以提供了两个main文件,根据需求保留其中一个就可以。下面按照性能和精度的测试分开写步骤 + +### 4.1 测试性能 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 + +3. 进入src文件夹,修改其中的CMakeLists.txt + + * 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + + * 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试性能使用main.cpp +需要删除main-image.cpp +可以使用如下命令进行: + +```shell +cd src +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +从build文件夹退回到HelmetIdentification_V2文件夹 + +如果是第一次运行,需要新建result文件夹以及内部的one、two两个文件夹用于存放结果,可以运行以下命令(确保当前在HelmetIdentification_V2层级): + +``` +mkdir result +cd result +mkdir one +mkdir two +``` + +退回到HelmetIdentification_V2文件夹,运行如下命令计算处理的帧率 + +``` +./main ${video0Path} ${video0width} ${video0height} [${video1Path} ${video1width} ${video1height}] +``` + +video0Path和video1Path分别是两路输入视频的路径,目前要求是一个264文件,宽高在第二步中设置(默认宽1920高1080)。 + +如果检测到某一帧图像中存在未佩戴头盔的情况,会打印告警信息并保存图像。 + +![](./images/warn.png) + +图片保存在HelmetIdentification_V2/result文件夹中,内部有两个文件夹,文件夹one保存第一路输入的结果,文件夹two保存第二路输入的结果。 + +图片的文件名命名规则为`result[frameId].jpg`,frameId表示是输入视频中的第几帧。 + +在运行时会输出跟踪去重线程的**帧率**(每秒处理的图片数量),示例如下: + +![](./images/rate.jpg) + +### 4.2 测试精度 + +**步骤1** 修改参数 + +1. 将HelmetIdentification_V2文件夹及其中代码上传至服务器任意目录,请不要修改文件夹结构 +2. 进入src文件夹,修改其中的CMakeLists.txt + 第24行需要修改为 对应环境中ascend-toolkit的路径中的include文件夹 + 第35行需要修改为 对应环境中ascend-toolkit的路径中的lib64文件夹 + 注:第34行、第36行应该每个服务器都是一样的,除非有报错不然不需要修改 + +**步骤2** 选择对应的main文件 + +测试精度使用main-image.cpp +需要删除main.cpp,然后将main-image.cpp重命名为main.cpp +可以使用如下命令进行: + +```sh +cd src +rm ./main.cpp +cp ./main-image.cpp ./main.cpp +rm ./main-image.cpp +``` + +**步骤3** 编译 + +进入HelmetIdentification_V2文件夹,执行以下命令以完成外部编译 + +```shell +mkdir build +cd build +cmake .. +make +``` + +出现如下图所示的结果即表示编译成功 + +![](./images/make.png) + +**步骤4** 运行及输出结果 + +**测试精度所用的图片数据集需要自行下载**:[Safety-Helmet-Wearing-Dataset](https://gitee.com/link?target=https%3A%2F%2Fmindx.sdk.obs.cn-north-4.myhuaweicloud.com%2Fmindxsdk-referenceapps%20%2Fcontrib%2FHelmetIdentification%2Fdata.zip) + +从build文件夹退回到HelmetIdentification_V2文件夹,运行如下命令读取图片进行检测: + +``` +./main ${imageName} +``` + +imageName是图片名称(不包含后缀名) + +例如 `./main 000023`会**读取000023.jpg,然后生成000023.txt并向其中写入检测结果(目标框的坐标)**。 + +每次只能读取一张图片进行检测,检测完所有待检测的图片后,使用V1版本中的`map_calculate.py`计算精度,会打印具体的指标值(如下所示)并将precision、recall和map记录在**output/output.txt**文件中。 + +![](./images/map_calculate.png) + +具体命令如下: + +``` +python3 map_calculate.py --label_path ${label_path} --npu_txt_path ${txt_path} -na -np +``` + +* label_path是数据集中的标签,具体生成方式见[V1版本](https://gitee.com/ascend/mindxsdk-referenceapps/tree/master/contrib/HelmetIdentification#32-%E7%B2%BE%E5%BA%A6%E6%B5%8B%E8%AF%95)。 +* txt_path是上面提到的生成的结果文件,将所有的txt文件都放到一个文件中,map_calculate.py会自动检测文件名,将其与label_path中对应的文件进行比较。 +