Add functionality to save inference images for the debugging purposes.

Save bmp images of inference results to /tmp as bmp files. BMP was
chosen to reduce encoding time. Saving is fully threaded. It can be
enable with qmake CONFIG+=save_images option

Also:
  - use antialised fonts in RKNN inference
  - moved class strings to inference base class
  - fixed silly segfault in ONNX inference
  - prevent writing results if class if exceeds valid values

Issue: https://denyspopov.atlassian.net/browse/AZ-38
Type: Improvement
This commit is contained in:
Tuomas Järvinen
2024-08-19 12:15:42 +03:00
parent 022e4a1200
commit be59a02f9b
9 changed files with 182 additions and 104 deletions
+8
View File
@@ -2,6 +2,7 @@
#include <opencv2/highgui.hpp> #include <opencv2/highgui.hpp>
#include "aiengine.h" #include "aiengine.h"
#include "aiengineinference.h" #include "aiengineinference.h"
#include "aiengineimagesaver.h"
#if defined(OPI5_BUILD) #if defined(OPI5_BUILD)
#include "src-opi5/aiengineinferenceopi5.h" #include "src-opi5/aiengineinferenceopi5.h"
@@ -82,6 +83,13 @@ void AiEngine::inferenceResultsReceivedSlot(AiEngineInferenceResult result)
} }
cv::imshow("Received Frame", result.frame); cv::imshow("Received Frame", result.frame);
#ifdef SAVE_IMAGES
static int imageCounter = 0;
AiEngineImageSaver *saver = new AiEngineImageSaver(result.frame, ++imageCounter);
saver->start();
connect(saver, &AiEngineImageSaver::finished, saver, &QObject::deleteLater);
#endif
} }
@@ -0,0 +1,31 @@
#include <QFile>
#include <QDataStream>
#include <QDebug>
#include "aiengineimagesaver.h"
AiEngineImageSaver::AiEngineImageSaver(const cv::Mat &image, int imageNumber, QObject *parent)
: QThread(parent), image(image.clone()), imageNumber(imageNumber)
{
}
AiEngineImageSaver::~AiEngineImageSaver()
{
wait();
}
void AiEngineImageSaver::run()
{
if (image.empty() || image.channels() == 0) {
qWarning() << "AiEngineImageSaver. Empty image or no channels, nothing to save.";
return;
}
// Calculate the size of the upper half
int halfHeight = image.rows / 2;
cv::Mat upperHalf = image(cv::Rect(0, 0, image.cols, halfHeight));
// Use bpm to reduce encoding time.
QString filePath = QString("/tmp/image-%1.bmp").arg(imageNumber, 5, 10, QChar('0'));
cv::imwrite(filePath.toStdString(), upperHalf);
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <QThread>
#include <QString>
#include <opencv2/opencv.hpp>
class AiEngineImageSaver : public QThread
{
Q_OBJECT
public:
AiEngineImageSaver(const cv::Mat &image, int imageNumber, QObject *parent = nullptr);
~AiEngineImageSaver();
protected:
void run() override;
private:
cv::Mat image;
int imageNumber;
};
+99 -1
View File
@@ -5,7 +5,105 @@ AiEngineInference::AiEngineInference(QString modelPath, QObject *parent)
: QObject{parent}, : QObject{parent},
mModelPath(modelPath), mModelPath(modelPath),
mActive(false) mActive(false)
{} {
#ifdef YOLO_ONNX
mClassNames = {
"person",
"bicycle",
"car",
"motorcycle",
"airplane",
"bus",
"train",
"truck",
"boat",
"traffic light",
"fire hydrant",
"stop sign",
"parking meter",
"bench",
"bird",
"cat",
"dog",
"horse",
"sheep",
"cow",
"elephant",
"bear",
"zebra",
"giraffe",
"backpack",
"umbrella",
"handbag",
"tie",
"suitcase",
"frisbee",
"skis",
"snowboard",
"sports ball",
"kite",
"baseball bat",
"baseball glove",
"skateboard",
"surfboard",
"tennis racket",
"bottle",
"wine glass",
"cup",
"fork",
"knife",
"spoon",
"bowl",
"banana",
"apple",
"sandwich",
"orange",
"broccoli",
"carrot",
"hot dog",
"pizza",
"donut",
"cake",
"chair",
"couch",
"potted plant",
"bed",
"dining table",
"toilet",
"tv",
"laptop",
"mouse",
"remote",
"keyboard",
"cell phone",
"microwave",
"oven",
"toaster",
"sink",
"refrigerator",
"book",
"clock",
"vase",
"scissors",
"teddy bear",
"hair drier",
"toothbrush"
};
#else
mClassNames = {
"Armoured vehicle",
"Truck",
"Vehicle",
"Artillery",
"Shadow artillery",
"Trenches",
"Military man",
"Tyre tracks",
"Additional protection tank",
"Smoke"
};
#endif
}
bool AiEngineInference::isActive(void) bool AiEngineInference::isActive(void)
+1
View File
@@ -40,6 +40,7 @@ protected:
QString mModelPath; QString mModelPath;
bool mActive; bool mActive;
int mNumber; int mNumber;
QVector<QString> mClassNames;
public slots: public slots:
virtual void performInferenceSlot(cv::Mat frame) = 0; virtual void performInferenceSlot(cv::Mat frame) = 0;
+6
View File
@@ -16,9 +16,15 @@ else {
} }
yolo_onnx { yolo_onnx {
message("Using YOLOv8 ONNX models for indoor testing.")
QMAKE_CXXFLAGS += -DYOLO_ONNX QMAKE_CXXFLAGS += -DYOLO_ONNX
} }
save_images {
message("Saving inference images to /tmp.")
QMAKE_CXXFLAGS += -DSAVE_IMAGES
}
opi5 { opi5 {
message("OPI5 build") message("OPI5 build")
CONFIG += link_pkgconfig CONFIG += link_pkgconfig
@@ -14,104 +14,7 @@ AiEngineInferencevOnnxRuntime::AiEngineInferencevOnnxRuntime(QString modelPath,
mPredictor(modelPath.toStdString(), confThreshold, iouThreshold, maskThreshold) mPredictor(modelPath.toStdString(), confThreshold, iouThreshold, maskThreshold)
{ {
qDebug() << "TUOMAS AiEngineInferencevOnnxRuntime() mModelPath=" << mModelPath; qDebug() << "TUOMAS AiEngineInferencevOnnxRuntime() mModelPath=" << mModelPath;
qDebug() << "AiEngineInferencevOnnxRuntime() mClassNames.size() =" << mClassNames.size();
#ifdef YOLO_ONNX
mClassNames = {
"person",
"bicycle",
"car",
"motorcycle",
"airplane",
"bus",
"train",
"truck",
"boat",
"traffic light",
"fire hydrant",
"stop sign",
"parking meter",
"bench",
"bird",
"cat",
"dog",
"horse",
"sheep",
"cow",
"elephant",
"bear",
"zebra",
"giraffe",
"backpack",
"umbrella",
"handbag",
"tie",
"suitcase",
"frisbee",
"skis",
"snowboard",
"sports ball",
"kite",
"baseball bat",
"baseball glove",
"skateboard",
"surfboard",
"tennis racket",
"bottle",
"wine glass",
"cup",
"fork",
"knife",
"spoon",
"bowl",
"banana",
"apple",
"sandwich",
"orange",
"broccoli",
"carrot",
"hot dog",
"pizza",
"donut",
"cake",
"chair",
"couch",
"potted plant",
"bed",
"dining table",
"toilet",
"tv",
"laptop",
"mouse",
"remote",
"keyboard",
"cell phone",
"microwave",
"oven",
"toaster",
"sink",
"refrigerator",
"book",
"clock",
"vase",
"scissors",
"teddy bear",
"hair drier",
"toothbrush"
};
#else
mClassNames = {
"Armoured vehicle",
"Truck",
"Vehicle",
"Artillery",
"Shadow artillery",
"Trenches",
"Military man",
"Tyre tracks",
"Additional protection tank",
"Smoke"
};
#endif
} }
@@ -183,6 +86,11 @@ void AiEngineInferencevOnnxRuntime::performInferenceSlot(cv::Mat frame)
for (uint i = 0; i < detections.size(); i++) { for (uint i = 0; i < detections.size(); i++) {
const Yolov8Result &detection = detections[i]; const Yolov8Result &detection = detections[i];
if (detection.classId >= mClassNames.size()) {
qDebug() << "performInferenceSlot() invalid classId =" << detection.classId;
continue;
}
// Add detected objects to the results // Add detected objects to the results
AiEngineObject object; AiEngineObject object;
object.classId = detection.classId; object.classId = detection.classId;
@@ -17,5 +17,4 @@ public slots:
private: private:
cv::Mat drawLabels(const cv::Mat &image, const std::vector<Yolov8Result> &detections); cv::Mat drawLabels(const cv::Mat &image, const std::vector<Yolov8Result> &detections);
YOLOPredictor mPredictor; YOLOPredictor mPredictor;
QVector<QString> mClassNames;
}; };
@@ -90,6 +90,12 @@ void AiEngineInferenceOpi5::drawObjects(cv::Mat& image, const object_detect_resu
for (int i = 0; i < result_list.count; i++) { for (int i = 0; i < result_list.count; i++) {
const object_detect_result& result = result_list.results[i]; const object_detect_result& result = result_list.results[i];
if (result.cls_id >= mClassNames.size()) {
continue;
}
fprintf(stderr, "TUOMAS [%d] prop = %f\n", i, result.prop);
int left = result.box.left; int left = result.box.left;
int top = result.box.top; int top = result.box.top;
int right = result.box.right; int right = result.box.right;
@@ -99,9 +105,10 @@ void AiEngineInferenceOpi5::drawObjects(cv::Mat& image, const object_detect_resu
// Text // Text
char c_text[256]; char c_text[256];
sprintf(c_text, "%s %.1f%%", coco_cls_to_name(result.cls_id), result.prop * 100); //sprintf(c_text, "%s %d%%", coco_cls_to_name(result.cls_id), (int)(round(result.prop * 100)));
sprintf(c_text, "%s %d%%", mClassNames[result.cls_id].toStdString().c_str(), (int)(round(result.prop * 100)));
cv::Point textOrg(left, top - 5); cv::Point textOrg(left, top - 5);
cv::putText(image, std::string(c_text), textOrg, cv::FONT_HERSHEY_SIMPLEX, result.prop, cv::Scalar(0, 0, 255), 1); cv::putText(image, std::string(c_text), textOrg, cv::FONT_HERSHEY_COMPLEX, result.prop, cv::Scalar(0, 0, 255), 1, cv::LINE_AA);
} }
} }
@@ -117,6 +124,7 @@ void AiEngineInferenceOpi5::performInferenceSlot(cv::Mat frame)
object_detect_result_list od_results; object_detect_result_list od_results;
int ret = inference_yolov8_model(&mRrknnAppCtx0, &imgBuffer, &od_results, mNumber); int ret = inference_yolov8_model(&mRrknnAppCtx0, &imgBuffer, &od_results, mNumber);
freeImageBuffer(imgBuffer);
if (ret != 0) { if (ret != 0) {
qDebug() << "AiEngineInferenceOpi5::performInferenceSlot() failure! ret: " << ret; qDebug() << "AiEngineInferenceOpi5::performInferenceSlot() failure! ret: " << ret;
mActive = false; mActive = false;
@@ -138,8 +146,6 @@ void AiEngineInferenceOpi5::performInferenceSlot(cv::Mat frame)
} }
drawObjects(scaledFrame, od_results); drawObjects(scaledFrame, od_results);
freeImageBuffer(imgBuffer);
result.frame = scaledFrame.clone(); result.frame = scaledFrame.clone();
emit resultsReady(result); emit resultsReady(result);