139 lines
4.4 KiB
C#
139 lines
4.4 KiB
C#
using System.Diagnostics;
|
|||
|
|
|
||
|
|
namespace BGR.Console.Removal;
|
||
|
|
|
||
|
|
internal class RemovalCommand(
|
||
|
|
IModelFactory modelFactory,
|
||
|
|
ImageProcessor imageProcessor,
|
||
|
|
IInferenceRunner inferenceRunner,
|
||
|
|
IAnsiConsole console,
|
||
|
|
ILogger<RemovalCommand> logger
|
||
|
|
) : AsyncCommand<RemovalCommand.Settings>
|
||
|
|
{
|
||
|
|
private readonly IModelFactory _modelFactory = modelFactory;
|
||
|
|
private readonly ImageProcessor _imageProcessor = imageProcessor;
|
||
|
|
private readonly IInferenceRunner _inferenceRunner = inferenceRunner;
|
||
|
|
private readonly IAnsiConsole _console = console;
|
||
|
|
private readonly ILogger<RemovalCommand> _logger = logger;
|
||
|
|
|
||
|
|
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
|
||
|
|
{
|
||
|
|
await _console.Status()
|
||
|
|
.Spinner(Spinner.Known.Dots)
|
||
|
|
.SpinnerStyle(Style.Parse("green"))
|
||
|
|
.StartAsync("Removing background...", async ctx =>
|
||
|
|
{
|
||
|
|
ctx.Status("Loading model...");
|
||
|
|
var model = _logger.TimeAndLogAction(
|
||
|
|
"Loading model",
|
||
|
|
() => _modelFactory.Create(settings.ResourceName)
|
||
|
|
);
|
||
|
|
|
||
|
|
ctx.Status("Loading image...");
|
||
|
|
var image = await _logger.TimeAndLogActionAsync(
|
||
|
|
"Loading image",
|
||
|
|
async () => await _imageProcessor.LoadImageAsync(settings.Image)
|
||
|
|
);
|
||
|
|
|
||
|
|
ctx.Status("Creating tensor input...");
|
||
|
|
var inputTensor = await _logger.TimeAndLogActionAsync(
|
||
|
|
"Creating tensor input",
|
||
|
|
async () => await _imageProcessor.CreateTensorInputAsync(image.Data, model)
|
||
|
|
);
|
||
|
|
|
||
|
|
ctx.Status("Running inference...");
|
||
|
|
var outputTensor = _logger.TimeAndLogAction(
|
||
|
|
"Running inference",
|
||
|
|
() => _inferenceRunner.Run(model.Bytes, inputTensor)
|
||
|
|
);
|
||
|
|
|
||
|
|
ctx.Status("Generating mask...");
|
||
|
|
var mask = await _logger.TimeAndLogActionAsync(
|
||
|
|
"Generating mask",
|
||
|
|
async () => await _imageProcessor.GenerateMaskAsync(outputTensor, image.Width, image.Height)
|
||
|
|
);
|
||
|
|
|
||
|
|
ctx.Status("Removing background...");
|
||
|
|
var output = await _logger.TimeAndLogActionAsync(
|
||
|
|
"Removing background",
|
||
|
|
async () => await _imageProcessor.RemoveBackgroundAsync(image.Data, mask)
|
||
|
|
);
|
||
|
|
|
||
|
|
if (settings.IncludeMask)
|
||
|
|
{
|
||
|
|
await _logger.TimeAndLogActionAsync(
|
||
|
|
"Saving mask",
|
||
|
|
async () => await _imageProcessor.SaveImageAsync(mask, settings.MaskPath)
|
||
|
|
);
|
||
|
|
|
||
|
|
_console.MarkupLine($"[bold]Mask saved to:[/] [blue]{settings.OutputPath}[/]");
|
||
|
|
}
|
||
|
|
|
||
|
|
await _logger.TimeAndLogActionAsync(
|
||
|
|
"Saving output",
|
||
|
|
async () => await _imageProcessor.SaveImageAsync(output, settings.OutputPath)
|
||
|
|
);
|
||
|
|
|
||
|
|
_console.MarkupLine($"[bold]Output saved to:[/] [green]{settings.OutputPath}[/]");
|
||
|
|
});
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
internal class Settings : CommandSettings
|
||
|
|
{
|
||
|
|
private static readonly Dictionary<string, string> Models = new()
|
||
|
|
{
|
||
|
|
{ RmbgModel.Id, "rmbg.onnx" },
|
||
|
|
{ ModNetModel.Id, "modnet.onnx" },
|
||
|
|
{ U2NetModel.Id, "u2net.onnx" },
|
||
|
|
};
|
||
|
|
|
||
|
|
[CommandArgument(0, "<image>")]
|
||
|
|
[Description("Path to the image file whose background you want to remove")]
|
||
|
|
public string Image { get; init; } = string.Empty;
|
||
|
|
|
||
|
|
[CommandOption("--model|-m")]
|
||
|
|
[Description("The model to use for background removal")]
|
||
|
|
public string Model { get; init; } = "rmbg";
|
||
|
|
|
||
|
|
[CommandOption("--include-mask|-i")]
|
||
|
|
[Description("Generate and output the mask used for background removal")]
|
||
|
|
public bool IncludeMask { get; init; } = false;
|
||
|
|
|
||
|
|
[CommandOption("--output|-o")]
|
||
|
|
[Description("Path to output image without background to. File extension will always be .png")]
|
||
|
|
public string Output { get; init; } = string.Empty;
|
||
|
|
|
||
|
|
public string ResourceName => Models[Model];
|
||
|
|
|
||
|
|
public string MaskPath => GetOutputPath("_mask");
|
||
|
|
|
||
|
|
public string OutputPath => GetOutputPath("_no_bg");
|
||
|
|
|
||
|
|
public override ValidationResult Validate()
|
||
|
|
{
|
||
|
|
if (File.Exists(Image) is false)
|
||
|
|
{
|
||
|
|
return ValidationResult.Error($"The image file '{Image}' does not exist.");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Models.ContainsKey(Model) is false)
|
||
|
|
{
|
||
|
|
return ValidationResult.Error($"The model '{Model}' is not supported.");
|
||
|
|
}
|
||
|
|
|
||
|
|
return ValidationResult.Success();
|
||
|
|
}
|
||
|
|
|
||
|
|
private string GetOutputPath(string modifier)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(Output))
|
||
|
|
{
|
||
|
|
return Path.ChangeExtension(Image, null) + modifier + ".png";
|
||
|
|
}
|
||
|
|
|
||
|
|
return Path.ChangeExtension(Output, ".png");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|