FastestDet:轻量级目标检测 实时Anchor-free目标检测算法,虽然精度稍逊,但FastestDet在速度和参数量上有显著优势,适用于资源有限的设备。
FastDet项目链接:https://github.com/dog-qiuqiu/FastestDet
第一次写文章,不知道该写些什么,直接上代码吧,不足之处还望海涵
onnx模型直接使用作者给的模型 FastestDet-main/example/onnx-runtime/FastestDet.onnx,模型大小不到1MB大小,不得不说,这是真的轻量👍
模型信息
Inputs
-------------------------
name:input.1
tensor:Float[1, 3, 352, 352]
---------------------------------------------------------------
Outputs
-------------------------
name:758
tensor:Float[1, 85, 22, 22]
---------------------------------------------------------------
代码参考 FastestDet-main/example/ncnn/FastestDet.cpp
经多次测试,单次平均推理速度在15ms左右,速度是真的块
其中NMS(非极大值抑制)用的是作者给的函数,也可以使用OpenCV自带的CvDnn.NMSBoxes函数
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.Dnn;
using Point = OpenCvSharp.Point;
using Size = OpenCvSharp.Size;
namespace OPENCV
{
public partial class FastDetect : Form
{
public FastDetect()
{
InitializeComponent();
}
string fileFilter = "*.*|*.bmp;*.jpg;*.jpeg;*.tiff;*.tiff;*.png";
string image_path = "";
string[] class_names;
// 阈值
float thresh = 0.65f;
// 模型输入宽高
int input_width = 352;
int input_height = 352;
Net net;
float Sigmoid(float x)
{
return (float)(1.0f / (1.0f + Math.Exp(-x)));
}
float Tanh(float x)
{
return (float)(2.0f / (1.0f + Math.Exp(-2 * x)) - 1);
}
private float IntersectionArea(TargetBox a, TargetBox b)
{
if (a.x1 > b.x2 || a.x2 < b.x1 || a.y1 > b.y2 || a.y2 < b.y1)
{
// no intersection
return 0.0f;
}
float inter_width = Math.Min(a.x2, b.x2) - Math.Max(a.x1, b.x1);
float inter_height = Math.Min(a.y2, b.y2) - Math.Min(a.y1, b.y1);
return inter_width * inter_height;
}
int scoreSort(TargetBox a, TargetBox b)
{
if (a.score > b.score)
return 1;
else
return 0;
}
int nmsHandle(List<TargetBox> src_boxes, List<TargetBox> dst_boxes)
{
List<int> picked = new List<int>();
src_boxes.Sort((x,y)=> scoreSort(x, y));
for (int i = 0; i < src_boxes.Count; i++)
{
int keep = 1;
for (int j = 0; j < picked.Count; j++)
{
//交集
float inter_area = IntersectionArea(src_boxes[i], src_boxes[picked[j]]);
//并集
float union_area = src_boxes[i].area() + src_boxes[picked[j]].area() - inter_area;
float IoU = inter_area / union_area;
if (IoU > 0.45 && src_boxes[i].category == src_boxes[picked[j]].category)
{
keep = 0;
break;
}
}
if (keep > 0)
{
picked.Add(i);
}
}
for (int i = 0; i < picked.Count; i++)
{
dst_boxes.Add(src_boxes[picked[i]]);
}
return 0;
}
private void FastDetect_Load(object sender, EventArgs e)
{
net = CvDnn.ReadNetFromOnnx("model/FastestDet.onnx");
net.SetPreferableBackend(Backend.OPENCV);
net.SetPreferableTarget(Target.CPU);
// 类别标签
class_names = File.ReadAllLines("model/coco.names");
}
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = fileFilter;
if (ofd.ShowDialog() != DialogResult.OK) return;
pictureBox1.Image = null;
image_path = ofd.FileName;
pictureBox1.Image = new Bitmap(image_path);
}
private void button2_Click(object sender, EventArgs e)
{
// 类别数量
int class_num = class_names.Length;
Mat img = Cv2.ImRead(image_path);
//输入图像的原始宽高
int img_width = img.Cols;
int img_height = img.Rows;
var blob = CvDnn.BlobFromImage(img, 1 / 255.0, new Size(input_width, input_height), 0, true, false);
DateTime start = DateTime.Now;
net.SetInput(blob);
Mat pred = net.Forward();
List<TargetBox> target_boxes = new List<TargetBox>();
float[] output = new float[22 * 22 * 85];
Marshal.Copy(pred.Data,output,0,output.Length);
for (int h = 0; h < 22; h++)
{
for (int w = 0; w < 22; w++)
{
// 前景概率
int obj_score_index = (0 * 22 * 22) + (h * 22) + w;
float obj_score = output[obj_score_index];
// 解析类别
int category = 0;
float max_score = 0.0f;
for (int i = 0; i < class_num; i++)
{
obj_score_index = ((5 + i) * 22 * 22) + (h * 22) + w;
float cls_score = output[obj_score_index];
if (cls_score > max_score)
{
max_score = cls_score;
category = i;
}
}
float score = (float)(Math.Pow(max_score, 0.4) * Math.Pow(obj_score, 0.6));
// 阈值筛选
if (score > thresh)
{
// 解析坐标
int x_offset_index = (1 * 22 * 22) + (h * 22) + w;
int y_offset_index = (2 * 22 * 22) + (h * 22) + w;
int box_width_index = (3 * 22 * 22) + (h * 22) + w;
int box_height_index = (4 * 22 * 22) + (h * 22) + w;
float x_offset = Tanh(output[x_offset_index]);
float y_offset = Tanh(output[y_offset_index]);
float box_width = Sigmoid(output[box_width_index]);
float box_height = Sigmoid(output[box_height_index]);
float cx = (w + x_offset) / 22;
float cy = (h + y_offset) / 22;
int x1 = (int)((cx - box_width * 0.5) * img_width);
int y1 = (int)((cy - box_height * 0.5) * img_height);
int x2 = (int)((cx + box_width * 0.5) * img_width);
int y2 = (int)((cy + box_height * 0.5) * img_height);
target_boxes.Add(new TargetBox(x1, y1, x2, y2, category, score));
}
}
}
// NMS处理
List<TargetBox> nms_boxes = new List<TargetBox>();
nmsHandle(target_boxes, nms_boxes);
// 打印耗时
DateTime end = DateTime.Now;
double time = (end - start).TotalMilliseconds;
textBox1.AppendText(string.Format("Time:{0} ms\r\n", time));
// draw result
for (int i = 0; i < nms_boxes.Count; i++)
{
TargetBox box = nms_boxes[i];
string s_score = box.score.ToString("P0");
Cv2.Rectangle(img, new Point(box.x1, box.y1), new Point(box.x2, box.y2), new Scalar(0, 0, 255), 2);
Cv2.PutText(img, class_names[box.category]+":"+s_score, new Point(box.x1, box.y1), HersheyFonts.HersheySimplex, 0.65, new Scalar(0, 255, 0), 1);
}
pictureBox2.Image?.Dispose();
pictureBox2.Image = Bitmap.FromStream(img.ToMemoryStream());
blob.Dispose();
pred.Dispose();
img.Dispose();
}
}
}