feat: working on adding abstractions
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
namespace BGR.Console.Removal;
|
||||
|
||||
internal interface IImage
|
||||
{
|
||||
int Width { get; }
|
||||
int Height { get; }
|
||||
void Resize(int width, int height);
|
||||
IPixel GetPixel(int x, int y);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BGR.Console.Removal;
|
||||
|
||||
internal interface IPixel
|
||||
{
|
||||
float R { get; }
|
||||
float G { get; }
|
||||
float B { get; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BGR.Console.Removal;
|
||||
|
||||
internal interface ITensor<T>
|
||||
{
|
||||
int Height { get; }
|
||||
int Width { get; }
|
||||
void SetValue(int batch, int channel, int y, int x, T value);
|
||||
float GetValue(int batch, int channel, int y, int x);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace BGR.Console.Removal;
|
||||
|
||||
internal abstract class ImageProcessor
|
||||
{
|
||||
public abstract Task<ITensor<float>> CreateTensorInputAsync(Stream image, Model model);
|
||||
|
||||
public abstract Task<Stream> GenerateMaskAsync(OnnxTensor maskTensor, int width, int height);
|
||||
|
||||
public abstract Task<Stream> RemoveBackgroundAsync(Stream image, Stream mask);
|
||||
|
||||
protected static void WalkImage(int height, int width, Action<int, int> action)
|
||||
{
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
for (var x = 0; x < width; x++)
|
||||
{
|
||||
action(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
namespace BGR.Console.Removal.ImageSharp;
|
||||
|
||||
internal class ImageSharpProcessor : ImageProcessor
|
||||
{
|
||||
public override async Task<ITensor<float>> CreateTensorInputAsync(Stream image, Model model)
|
||||
{
|
||||
using var resized = await Image.LoadAsync<Rgba32>(image);
|
||||
resized.Mutate(x => x.Resize(model.InputWidth, model.InputHeight));
|
||||
|
||||
const int batchSize = 1;
|
||||
const int channels = 3;
|
||||
var tensor = new OnnxTensor(batchSize, channels, model.InputHeight, model.InputWidth);
|
||||
|
||||
WalkImage(resized.Height, resized.Width, (x, y) =>
|
||||
{
|
||||
var pixel = resized[x, y];
|
||||
tensor.SetValue(0, 0, y, x, model.NormalizeRed(pixel.R));
|
||||
tensor.SetValue(0, 1, y, x, model.NormalizeGreen(pixel.G));
|
||||
tensor.SetValue(0, 2, y, x, model.NormalizeBlue(pixel.B));
|
||||
});
|
||||
|
||||
return tensor;
|
||||
}
|
||||
|
||||
public override async Task<Stream> GenerateMaskAsync(OnnxTensor maskTensor, int width, int height)
|
||||
{
|
||||
using var mask = new Image<Rgba32>(width, height);
|
||||
|
||||
using Image<Rgba32> tempMask = new(maskTensor.Width, maskTensor.Height);
|
||||
|
||||
const byte opaqueAlpha = 255;
|
||||
|
||||
WalkImage(maskTensor.Height, maskTensor.Width, (x, y) =>
|
||||
{
|
||||
var sigmoidValue = CalculateSigmoid(maskTensor.GetValue(0, 0, y, x));
|
||||
var normalizedValue = Normalize(sigmoidValue);
|
||||
var intensity = ConvertToGreyscale(normalizedValue);
|
||||
|
||||
tempMask[x, y] = new Rgba32(intensity, intensity, intensity, opaqueAlpha);
|
||||
});
|
||||
|
||||
tempMask.Mutate(x => x.Resize(width, height));
|
||||
|
||||
WalkImage(height, width, (x, y) => mask[x, y] = tempMask[x, y]);
|
||||
|
||||
var stream = new MemoryStream();
|
||||
await mask.SaveAsync(stream, new PngEncoder());
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public override async Task<Stream> RemoveBackgroundAsync(Stream image, Stream mask)
|
||||
{
|
||||
var imageWithBg = await Image.LoadAsync<Rgba32>(image);
|
||||
var maskImage = await Image.LoadAsync<Rgba32>(mask);
|
||||
using var imageWithBgRemoved = new Image<Rgba32>(imageWithBg.Width, imageWithBg.Height);
|
||||
|
||||
const byte alphaThreshold = 20;
|
||||
var transparentPixel = new Rgba32(0, 0, 0, 0);
|
||||
|
||||
WalkImage(imageWithBg.Height, imageWithBg.Width, (x, y) =>
|
||||
{
|
||||
var sourcePixel = imageWithBg[x, y];
|
||||
var maskPixel = maskImage[x, y];
|
||||
|
||||
var alpha = maskPixel.R;
|
||||
|
||||
imageWithBgRemoved[x, y] = alpha > alphaThreshold
|
||||
? new Rgba32(sourcePixel.R, sourcePixel.G, sourcePixel.B, sourcePixel.A)
|
||||
: transparentPixel;
|
||||
});
|
||||
|
||||
var result = new MemoryStream();
|
||||
await imageWithBgRemoved.SaveAsync(result, new PngEncoder());
|
||||
return result;
|
||||
}
|
||||
|
||||
private static float Normalize(float value)
|
||||
{
|
||||
const float binarizationThreshold = 0.5f;
|
||||
const float normalizationFactor = 2f;
|
||||
|
||||
return value > binarizationThreshold
|
||||
? (value - binarizationThreshold) * normalizationFactor
|
||||
: 0f;
|
||||
}
|
||||
|
||||
private static byte ConvertToGreyscale(float value)
|
||||
{
|
||||
const float maxIntensity = 255f;
|
||||
return (byte)(value * maxIntensity);
|
||||
}
|
||||
|
||||
private static float CalculateSigmoid(float x)
|
||||
{
|
||||
const float sigmoidScale = 1f;
|
||||
const float sigmoidShift = 1f;
|
||||
const float sigmoidDivisor = -1f;
|
||||
|
||||
return sigmoidScale / (sigmoidShift + MathF.Exp(sigmoidDivisor * x));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace BGR.Console.Removal.ImageSharp;
|
||||
|
||||
internal class SharpImage(Image<Rgba32> image) : IImage
|
||||
{
|
||||
private readonly Image<Rgba32> _image = image;
|
||||
|
||||
public int Width => _image.Width;
|
||||
public int Height => _image.Height;
|
||||
|
||||
public void Resize(int width, int height)
|
||||
{
|
||||
_image.Mutate(x => x.Resize(width, height));
|
||||
}
|
||||
|
||||
public IPixel GetPixel(int x, int y)
|
||||
{
|
||||
return new SharpPixel(_image[x, y]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace BGR.Console.Removal.ImageSharp;
|
||||
|
||||
internal class SharpPixel(Rgba32 pixel) : IPixel
|
||||
{
|
||||
private readonly Rgba32 _pixel = pixel;
|
||||
|
||||
public float R => _pixel.R;
|
||||
public float G => _pixel.G;
|
||||
public float B => _pixel.B;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace BGR.Console.Removal.Models;
|
||||
|
||||
internal class ModNetModel : Model
|
||||
{
|
||||
public override int InputWidth => 512;
|
||||
public override int InputHeight => 512;
|
||||
public override float RedNormalizationMean => 0.485f;
|
||||
public override float GreenNormalizationMean => 0.456f;
|
||||
public override float BlueNormalizationMean => 0.406f;
|
||||
public override float RedNormalizationStd => 0.229f;
|
||||
public override float GreenNormalizationStd => 0.224f;
|
||||
public override float BlueNormalizationStd => 0.225f;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace BGR.Console.Removal.Models;
|
||||
|
||||
internal abstract class Model
|
||||
{
|
||||
private const float PixelMax = 255f;
|
||||
public abstract int InputWidth { get; }
|
||||
public abstract int InputHeight { get; }
|
||||
public abstract float RedNormalizationMean { get; }
|
||||
public abstract float GreenNormalizationMean { get; }
|
||||
public abstract float BlueNormalizationMean { get; }
|
||||
public abstract float RedNormalizationStd { get; }
|
||||
public abstract float GreenNormalizationStd { get; }
|
||||
public abstract float BlueNormalizationStd { get; }
|
||||
|
||||
public float NormalizeRed(float value)
|
||||
{
|
||||
return Normalize(value, RedNormalizationMean, RedNormalizationStd);
|
||||
}
|
||||
|
||||
public float NormalizeGreen(float value)
|
||||
{
|
||||
return Normalize(value, GreenNormalizationMean, GreenNormalizationStd);
|
||||
}
|
||||
|
||||
public float NormalizeBlue(float value)
|
||||
{
|
||||
return Normalize(value, BlueNormalizationMean, BlueNormalizationStd);
|
||||
}
|
||||
|
||||
private static float Normalize(float value, float mean, float std)
|
||||
{
|
||||
return ((value / PixelMax) - mean) / std;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
namespace BGR.Console.Removal.Models;
|
||||
|
||||
internal class RmbgModel : Model
|
||||
{
|
||||
public override int InputWidth => 1024;
|
||||
public override int InputHeight => 1024;
|
||||
public override float RedNormalizationMean => 0.485f;
|
||||
public override float GreenNormalizationMean => 0.456f;
|
||||
public override float BlueNormalizationMean => 0.406f;
|
||||
public override float RedNormalizationStd => 0.229f;
|
||||
public override float GreenNormalizationStd => 0.224f;
|
||||
public override float BlueNormalizationStd => 0.225f;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace BGR.Console.Removal.Models;
|
||||
|
||||
internal class U2NetModel : Model
|
||||
{
|
||||
public override int InputWidth => 320;
|
||||
public override int InputHeight => 320;
|
||||
public override float RedNormalizationMean => 0.485f;
|
||||
public override float GreenNormalizationMean => 0.456f;
|
||||
public override float BlueNormalizationMean => 0.406f;
|
||||
public override float RedNormalizationStd => 0.229f;
|
||||
public override float GreenNormalizationStd => 0.224f;
|
||||
public override float BlueNormalizationStd => 0.225f;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace BGR.Console.Removal.Onnx;
|
||||
|
||||
public class OnnxTensor(
|
||||
int batchSize,
|
||||
int channels,
|
||||
int height,
|
||||
int width
|
||||
) : ITensor<float>
|
||||
{
|
||||
private readonly DenseTensor<float> _tensor =
|
||||
new([batchSize, channels, height, width]);
|
||||
|
||||
public int Height => _tensor.Dimensions[2];
|
||||
|
||||
public int Width => _tensor.Dimensions[3];
|
||||
|
||||
public void SetValue(
|
||||
int batch,
|
||||
int channel,
|
||||
int y,
|
||||
int x,
|
||||
float value
|
||||
)
|
||||
{
|
||||
_tensor[batch, channel, y, x] = value;
|
||||
}
|
||||
|
||||
public float GetValue(
|
||||
int batch,
|
||||
int channel,
|
||||
int y,
|
||||
int x
|
||||
)
|
||||
{
|
||||
return _tensor[batch, channel, y, x];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user