Skip to content

Instantly share code, notes, and snippets.

@keesschollaart81
Created January 12, 2021 16:22
Show Gist options
  • Save keesschollaart81/83de609f0852670656290fe0180da318 to your computer and use it in GitHub Desktop.
Save keesschollaart81/83de609f0852670656290fe0180da318 to your computer and use it in GitHub Desktop.
Yolov5s TF Lite, outputs parsed in C#
using Microsoft.Extensions.Logging;
using Shared;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Diagnostics;
var outputTensors = new List<OutputTensor>
{
new OutputTensor(0.14855362474918365, 197, (1, 3, 1600, 8), new[] { (10, 13), (16, 30), (33, 23) }, "yolov5_output_tensor_0.bin"),
new OutputTensor(0.13808976113796234, 198, (1, 3, 400, 8), new[] { (30, 61), (62,45), (59, 119) }, "yolov5_output_tensor_1.bin"),
new OutputTensor(0.14308157563209534, 192, (1, 3, 100, 8), new[] { (116, 90), (156, 198), (373, 326) }, "yolov5_output_tensor_2.bin")
};
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
ILogger logger = loggerFactory.CreateLogger<BoundingBox>();
Console.WriteLine("Hello World!");
var classesCount = 3;
var modelInputRes = (320, 320);
var iouThres = 0.5f;
var postProcesssedResults = new List<float[]>();
foreach (var outputTensor in outputTensors)
{
logger.LogInformation("File: {file}", outputTensor.file);
var bytes = File.ReadAllBytes(outputTensor.file);
var pred = bytes.Select(b => (b - outputTensor.Zero) * outputTensor.Scale).ToArray();
var ny_nx = outputTensor.Shape.Item3; // 1600
var ratio = (float)modelInputRes.Item1 / modelInputRes.Item2;
var nx = (int)Math.Sqrt(ny_nx / ratio);
var ny = (int)(ratio * nx);
var stride = (int)Math.Floor((float)modelInputRes.Item1 / ny);
for (int boxY = 0; boxY < ny; boxY++)
{
for (int boxX = 0; boxX < nx; boxX++)
{
for (var a = 0; a < outputTensor.Anchors.Length; a++)
{
var anchorOffset = ny_nx * a * (classesCount + 5);
var yOffset = anchorOffset + (boxY * ny * (classesCount + 5));
var offset = yOffset + (boxX * (classesCount + 5));
var rv = pred[offset..(offset + classesCount + 5)];
var predConf = Sigmoid(rv[4]);
if (predConf < 0.25) continue;
var predBbox = rv.Select(Sigmoid).ToArray();
var predXywh = predBbox[0..4];
var predProb = predBbox.Skip(5).ToArray();
var rawDx = predXywh[0];
var rawDy = predXywh[1];
var rawDw = predXywh[2];
var rawDh = predXywh[3];
float predX = ((rawDx * 2f) - 0.5f + boxX) * stride;
float predY = ((rawDy * 2f) - 0.5f + boxY) * stride;
var pwr = (float)Math.Pow(rawDw * 2, 2);
float predW = pwr * outputTensor.Anchors[a].Item1;
float predH = (float)Math.Pow(rawDh * 2, 2) * outputTensor.Anchors[a].Item2;
// postprocess_boxes
// (1) (x, y, w, h) --> (xmin, ymin, xmax, ymax)
var box = Xywh2xyxy(new float[] { predX, predY, predW, predH });
float predX1 = box[0]; //predX - predW * 0.5f;
float predY1 = box[1]; //predY - predH * 0.5f;
float predX2 = box[2]; //predX + predW * 0.5f;
float predY2 = box[3]; //predY + predH * 0.5f;
// (2) (xmin, ymin, xmax, ymax) -> (xmin_org, ymin_org, xmax_org, ymax_org)
float org_h = modelInputRes.Item1;
float org_w = modelInputRes.Item2;
float inputSize = 320f;
float resizeRatio = Math.Min(inputSize / org_w, inputSize / org_h);
float dw = (inputSize - resizeRatio * org_w) / 2f;
float dh = (inputSize - resizeRatio * org_h) / 2f;
float orgX1 = 1f * (predX1 - dw) / resizeRatio; // left
float orgX2 = 1f * (predX2 - dw) / resizeRatio; // right
float orgY1 = 1f * (predY1 - dh) / resizeRatio; // top
float orgY2 = 1f * (predY2 - dh) / resizeRatio; // bottom
// (3) clip some boxes that are out of range
orgX1 = Math.Max(orgX1, 0);
orgY1 = Math.Max(orgY1, 0);
orgX2 = Math.Min(orgX2, org_w - 1);
orgY2 = Math.Min(orgY2, org_h - 1);
if (orgX1 > orgX2 || orgY1 > orgY2) continue; // invalid_mask
// (5) discard some boxes with low scores
var scores = predProb.Select(p => p * predConf).ToList();
float scoreMaxCat = scores.Max();
if (scoreMaxCat > 0.25)
{
logger.LogInformation("Class: {class} Score: {score} {orgX1:0.} {orgY1:0.} {orgX2:0.} {orgY2:0.}", scores.IndexOf(scoreMaxCat), scoreMaxCat, orgX1, orgY1, orgX2, orgY2);
postProcesssedResults.Add(new float[] { orgX1, orgY1, orgX2, orgY2, scoreMaxCat, scores.IndexOf(scoreMaxCat) });
}
}
}
}
}
postProcesssedResults = postProcesssedResults.OrderByDescending(x => x[4]).ToList(); // sort by confidence
int f = 0;
while (f < postProcesssedResults.Count)
{
var res = postProcesssedResults[f];
if (res == null)
{
f++;
continue;
}
var conf = res[4];
logger.LogInformation("End: Class: {class} Score: {score}", (int)res[5], conf);
postProcesssedResults[f] = null;
var iou = postProcesssedResults.Select(bbox => bbox == null ? float.NaN : BoxIoU(res, bbox)).ToList();
for (int j = 0; j < iou.Count; j++)
{
if (float.IsNaN(iou[j])) continue;
if (iou[j] > iouThres)
{
postProcesssedResults[j] = null; // deactivated for debugging
}
}
f++;
}
static float BoxIoU(float[] boxes1, float[] boxes2)
{
static float box_area(float[] box)
{
return (box[2] - box[0]) * (box[3] - box[1]);
}
var area1 = box_area(boxes1);
var area2 = box_area(boxes2);
Debug.Assert(area1 >= 0);
Debug.Assert(area2 >= 0);
var dx = Math.Max(0, Math.Min(boxes1[2], boxes2[2]) - Math.Max(boxes1[0], boxes2[0]));
var dy = Math.Max(0, Math.Min(boxes1[3], boxes2[3]) - Math.Max(boxes1[1], boxes2[1]));
var inter = dx * dy;
return inter / (area1 + area2 - inter);
}
static float[] Xywh2xyxy(float[] bbox)
{
var bboxAdj = new float[4];
bboxAdj[0] = bbox[0] - bbox[2] / 2f;
bboxAdj[1] = bbox[1] - bbox[3] / 2f;
bboxAdj[2] = bbox[0] + bbox[2] / 2f;
bboxAdj[3] = bbox[1] + bbox[3] / 2f;
return bboxAdj;
}
static float Sigmoid(double value)
{
return 1.0f / (1.0f + (float)Math.Exp(-value));
}
public record OutputTensor(double Scale, int Zero, (int, int, int, int) Shape, (int, int)[] Anchors, string file);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment