Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a95979a1a7 | ||
|
|
79931087a8 | ||
|
|
8269f21f55 | ||
|
|
b2bdc1d862 | ||
|
|
6160bf05d4 | ||
|
|
6d03eefe45 | ||
|
|
e06d77c1d2 | ||
|
|
e099144127 | ||
|
|
51abe75e07 | ||
|
|
45dc9de223 | ||
|
|
97fa55e60a | ||
|
|
1117223cff | ||
|
|
faad90645d | ||
|
|
f176893d1b | ||
|
|
28a6e8aeb3 | ||
|
|
4067ef6c34 |
@@ -16,14 +16,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.ACTIONS_PAT }}
|
||||
- name: Setup .NET 9
|
||||
uses: actions/setup-dotnet@v4
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 9.x
|
||||
dotnet-version: 10.x
|
||||
- name: Install versionize
|
||||
run: dotnet tool install --global Versionize
|
||||
- name: Setup git
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: Upload changelog
|
||||
if: steps.versionize.outcome == 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: change-log
|
||||
path: src/BGR.Console/CHANGELOG.md
|
||||
@@ -55,39 +55,41 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref }}
|
||||
token: ${{ secrets.ACTIONS_PAT }}
|
||||
- name: Setup .NET 9
|
||||
uses: actions/setup-dotnet@v4
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 9.x
|
||||
dotnet-version: 10.x
|
||||
- name: Build
|
||||
run: dotnet build src
|
||||
- name: Publish for mac-os
|
||||
run: dotnet publish src/BGR.Console/BGR.Console.csproj -c Release -r osx-x64 --self-contained -o dist/mac-os
|
||||
- name: Rename mac-os binary
|
||||
run: mv dist/mac-os/BGR.Console dist/bgr_osx_x64
|
||||
- name: Create zip for mac-os
|
||||
run: zip -j dist/bgr_osx_x64.zip dist/mac-os/*
|
||||
- name: Publish for linux-os
|
||||
run: dotnet publish src/BGR.Console/BGR.Console.csproj -c Release -r linux-x64 --self-contained -o dist/linux-os
|
||||
- name: Rename linux-os binary
|
||||
run: mv dist/linux-os/BGR.Console dist/bgr_linux_x64
|
||||
- name: Create zip for linux-os
|
||||
run: zip -j dist/bgr_linux_x64.zip dist/linux-os/*
|
||||
- name: Publish for windows-os
|
||||
run: dotnet publish src/BGR.Console/BGR.Console.csproj -c Release -r win-x64 --self-contained -o dist/windows-os
|
||||
- name: Rename windows-os binary
|
||||
run: mv dist/windows-os/BGR.Console.exe dist/bgr_windows_x64.exe
|
||||
- name: Create zip for windows-os
|
||||
run: zip -j dist/bgr_windows_x64.zip dist/windows-os/*
|
||||
- name: Get project version
|
||||
uses: kzrnm/get-net-sdk-project-versions-action@v1
|
||||
id: get-version
|
||||
with:
|
||||
proj-path: src/BGR.Console/BGR.Console.csproj
|
||||
- name: Download changlog
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: change-log
|
||||
path: src/BGR.Console
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_PAT }}
|
||||
name: bgr v${{ steps.get-version.outputs.version }}
|
||||
@@ -95,6 +97,6 @@ jobs:
|
||||
draft: false
|
||||
body_path: src/BGR.Console/CHANGELOG.md
|
||||
files: |
|
||||
dist/bgr_osx_x64
|
||||
dist/bgr_linux_x64
|
||||
dist/bgr_windows_x64.exe
|
||||
dist/bgr_osx_x64.zip
|
||||
dist/bgr_linux_x64.zip
|
||||
dist/bgr_windows_x64.zip
|
||||
|
||||
@@ -18,14 +18,14 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.ACTIONS_PAT }}
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 9.x.x
|
||||
dotnet-version: 10.x.x
|
||||
- name: Format project
|
||||
run: dotnet format src --verbosity normal
|
||||
- name: Commit Changes
|
||||
@@ -49,14 +49,14 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.ACTIONS_PAT }}
|
||||
- name: Setup .NET 9
|
||||
uses: actions/setup-dotnet@v4
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 9.x
|
||||
dotnet-version: 10.x
|
||||
- name: Install report generator
|
||||
run: dotnet tool install --global dotnet-reportgenerator-globaltool
|
||||
- name: Restore dependencies
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
- name: Rename test coverage report
|
||||
run: mv src/BGR.Console.Tests/TestResults/Coverage/coverage.cobertura.xml src/BGR.Console.Tests/TestResults/Coverage/${{ matrix.os }}-coverage.cobertura.xml
|
||||
- name: Upload test coverage report for ${{ matrix.os }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: test-coverage-${{ matrix.os }}
|
||||
path: src/BGR.Console.Tests/TestResults/Coverage/${{ matrix.os }}-coverage.cobertura.xml
|
||||
@@ -78,26 +78,26 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.ACTIONS_PAT }}
|
||||
- name: Download ubuntu-latest report
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: test-coverage-ubuntu-latest
|
||||
path: ./coverage
|
||||
- name: Download windows-latest report
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: test-coverage-windows-latest
|
||||
path: ./coverage
|
||||
- name: Download macos-latest report
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: test-coverage-macos-latest
|
||||
path: ./coverage
|
||||
- name: Upload test coverage reports
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v7
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
[](https://codecov.io/gh/StevanFreeborn/bgr)
|
||||
[](https://github.com/StevanFreeborn/bgr/actions/workflows/pull_request.yml)
|
||||
[](https://github.com/StevanFreeborn/bgr/actions/workflows/publish.yml)
|
||||

|
||||

|
||||
[](https://github.com/semantic-release/semantic-release)
|
||||

|
||||
|
||||
# BGR
|
||||
|
||||
This is a background removal app that uses machine learning models to remove the background from an image. The app is available for download as a single file executable [here](). The app can also be built from source using the [.NET SDK](https://dotnet.microsoft.com/download). If you'd like to read more about the idea and initial development behind the app you can read the blog post I wrote about it [here]().
|
||||
This is a background removal app that uses machine learning models to remove the background from an image. The app is available for download [here](https://github.com/StevanFreeborn/bgr/releases). You'll just need to unzip the files, place them in a directory, and add it to your PATH. You can rename the executable to your preferred name. The app can also be built from source using the [.NET SDK](https://dotnet.microsoft.com/download). If you'd like to read more about the idea and initial development behind the app you can read the blog post I wrote about it [here](https://blog.stevanfreeborn.com/building-a-background-removal-app-with-machine-learning-and-dotnet).
|
||||
|
||||
## Usage
|
||||
|
||||
The app is meant to be run from the command line. For example:
|
||||
|
||||
```pwsh
|
||||
bgr /path/to/image.jpg --output /path/to/output.jpg
|
||||
bgr /path/to/image.jpg --output /path/to/output.png
|
||||
```
|
||||
|
||||
You can find the full list of commands, arguments, and options using the `--help` option:
|
||||
@@ -26,6 +34,21 @@ bgr --help
|
||||
|
||||

|
||||
|
||||
### Feathering Options
|
||||
|
||||
By default, BGR applies feathering to create smooth transitions between the foreground and background, reducing harsh edges and halos. You can control the feathering range using two options:
|
||||
|
||||
- `--feather-min` - Mask values below this threshold become fully transparent. Default: `70`
|
||||
- `--feather-max` - Mask values above this threshold become fully opaque. Default: `117`
|
||||
|
||||
Values between `feather-min` and `feather-max` are linearly scaled to produce partial transparency, creating a smooth gradient at edges.
|
||||
|
||||
```pwsh
|
||||
bgr /path/to/image.jpg --feather-min 60 --feather-max 130
|
||||
```
|
||||
|
||||
A wider range (e.g., `50` to `150`) produces softer edges, while a narrower range (e.g., `90` to `100`) produces sharper edges.
|
||||
|
||||
## Issues
|
||||
|
||||
If you encounter any issues while using the app, please open an issue on the repository. If you have any suggestions or feature requests, feel free to open an issue as well.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
@@ -2,25 +2,26 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<NoWarn>1591;</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PackageReference Include="coverlet.collector" Version="10.0.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.msbuild" Version="6.0.4">
|
||||
<PackageReference Include="coverlet.msbuild" Version="10.0.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Spectre.Console.Testing" Version="0.49.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
|
||||
<PackageReference Include="Spectre.Console.Testing" Version="0.57.2" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -183,7 +183,10 @@ public class ImageSharpProcessorTests : IDisposable
|
||||
await mask.SaveAsPngAsync(maskStream);
|
||||
maskStream.Position = 0;
|
||||
|
||||
var result = await _sut.RemoveBackgroundAsync(imageStream, maskStream);
|
||||
const byte featherMin = 70;
|
||||
const byte featherMax = 117;
|
||||
|
||||
var result = await _sut.RemoveBackgroundAsync(imageStream, maskStream, featherMin, featherMax);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.Length.ShouldBeGreaterThan(0);
|
||||
@@ -203,6 +206,37 @@ public class ImageSharpProcessorTests : IDisposable
|
||||
resultImage[1, 1].A.ShouldBe((byte)255);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RemoveBackgroundAsync_WithMidRangeMaskValue_ShouldApplyPartialAlpha()
|
||||
{
|
||||
using var imageStream = new MemoryStream();
|
||||
using var image = new Image<Rgba32>(1, 1);
|
||||
image[0, 0] = new Rgba32(100, 150, 200, 255);
|
||||
await image.SaveAsPngAsync(imageStream);
|
||||
imageStream.Position = 0;
|
||||
|
||||
using var maskStream = new MemoryStream();
|
||||
using var mask = new Image<Rgba32>(1, 1);
|
||||
mask[0, 0] = new Rgba32(100, 100, 100, 255);
|
||||
await mask.SaveAsPngAsync(maskStream);
|
||||
maskStream.Position = 0;
|
||||
|
||||
const byte featherMin = 70;
|
||||
const byte featherMax = 117;
|
||||
|
||||
var result = await _sut.RemoveBackgroundAsync(imageStream, maskStream, featherMin, featherMax);
|
||||
|
||||
result.Position = 0;
|
||||
using var resultImage = await Image.LoadAsync<Rgba32>(result);
|
||||
|
||||
resultImage[0, 0].R.ShouldBe((byte)100);
|
||||
resultImage[0, 0].G.ShouldBe((byte)150);
|
||||
resultImage[0, 0].B.ShouldBe((byte)200);
|
||||
|
||||
var expectedAlpha = (byte)((100 - 70) / (float)(117 - 70) * 255f);
|
||||
resultImage[0, 0].A.ShouldBe(expectedAlpha);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveImageAsync_WhenCalled_ItShouldSaveImageToDiskAtProvidedPath()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using BGR.Console.Removal.Onnx;
|
||||
|
||||
using Microsoft.ML.OnnxRuntime.Tensors;
|
||||
|
||||
namespace BGR.Console.Tests.Unit;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Spectre.Console.Cli;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace BGR.Console.Tests.Unit;
|
||||
|
||||
@@ -28,6 +26,7 @@ public class RemovalCommandTests : IDisposable
|
||||
[Theory]
|
||||
[InlineData("output.png", false)]
|
||||
[InlineData("", true)]
|
||||
[SuppressMessage("Reliability", "CA2025:Do not pass 'IDisposable' instances into unawaited tasks", Justification = "Matching invocations")]
|
||||
public async Task ExecuteAsync_WhenCalled_ItShouldProcessImage(string outputPath, bool includeMask)
|
||||
{
|
||||
var imagePath = $"{Guid.NewGuid()}.png";
|
||||
@@ -73,7 +72,7 @@ public class RemovalCommandTests : IDisposable
|
||||
.ReturnsAsync(maskStream);
|
||||
|
||||
_imageProcessorMock
|
||||
.Setup(p => p.RemoveBackgroundAsync(image.Data, maskStream))
|
||||
.Setup(p => p.RemoveBackgroundAsync(image.Data, maskStream, It.IsAny<byte>(), It.IsAny<byte>()))
|
||||
.ReturnsAsync(outputStream);
|
||||
|
||||
var commandContext = new CommandContext(
|
||||
@@ -92,7 +91,7 @@ public class RemovalCommandTests : IDisposable
|
||||
_imageProcessorMock.Verify(p => p.CreateTensorInputAsync(image.Data, model), Times.Once);
|
||||
_inferenceRunnerMock.Verify(r => r.Run(model.Bytes, inputTensor), Times.Once);
|
||||
_imageProcessorMock.Verify(p => p.GenerateMaskAsync(outputTensor, image.Width, image.Height), Times.Once);
|
||||
_imageProcessorMock.Verify(p => p.RemoveBackgroundAsync(image.Data, maskStream), Times.Once);
|
||||
_imageProcessorMock.Verify(p => p.RemoveBackgroundAsync(image.Data, maskStream, It.IsAny<byte>(), It.IsAny<byte>()), Times.Once);
|
||||
_imageProcessorMock.Verify(p => p.SaveImageAsync(outputStream, It.IsAny<string>()), Times.AtLeastOnce);
|
||||
|
||||
File.Delete(imagePath);
|
||||
|
||||
@@ -38,17 +38,17 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.20.1" />
|
||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.10" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.27.1" />
|
||||
<PackageReference Include="Serilog" Version="4.4.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.49.1" />
|
||||
<PackageReference Include="Spectre.Console.Cli" Version="0.49.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.57.2" />
|
||||
<PackageReference Include="Spectre.Console.Cli" Version="0.55.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [versionize](https://github.com/versionize/versionize) for commit guidelines.
|
||||
|
||||
<a name="0.0.0"></a>
|
||||
## [0.0.0](https://www.github.com/StevanFreeborn/bgr/releases/tag/v0.0.0) (2025-02-14)
|
||||
<a name="0.1.0"></a>
|
||||
## [0.1.0](https://www.github.com/StevanFreeborn/bgr/releases/tag/v0.1.0) (2026-07-15)
|
||||
|
||||
### Features
|
||||
|
||||
* initial working POC ([651869c](https://www.github.com/StevanFreeborn/bgr/commit/651869c27ff9ed390d78bf7ae6b81b3aa1acefb3))
|
||||
* refactor to proper app + write tests ([42435ad](https://www.github.com/StevanFreeborn/bgr/commit/42435ad9150db459ee97a399c53050a7ec58012c))
|
||||
* working on adding abstractions ([baf4035](https://www.github.com/StevanFreeborn/bgr/commit/baf40356164be64735ed53fa42966d664a41a3a9))
|
||||
* improve background removal feathering ([e099144](https://www.github.com/StevanFreeborn/bgr/commit/e099144127ae719f85f3375be8be72fe3b5914c6))
|
||||
* upgrade to .NET 10 ([45dc9de](https://www.github.com/StevanFreeborn/bgr/commit/45dc9de2232e64b234b8016e64e7b281b613e6f9))
|
||||
|
||||
<a name="0.0.0"></a>
|
||||
## [0.0.0](https://www.github.com/StevanFreeborn/bgr/releases/tag/v0.0.0) (2025-02-14)
|
||||
|
||||
@@ -8,7 +8,7 @@ internal abstract class ImageProcessor
|
||||
|
||||
public abstract Task<Stream> GenerateMaskAsync(ITensor<float> maskTensor, int width, int height);
|
||||
|
||||
public abstract Task<Stream> RemoveBackgroundAsync(Stream image, Stream mask);
|
||||
public abstract Task<Stream> RemoveBackgroundAsync(Stream image, Stream mask, byte featherMin, byte featherMax);
|
||||
|
||||
public abstract Task SaveImageAsync(Stream image, string path);
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ internal class ImageSharpProcessor : ImageProcessor
|
||||
return stream;
|
||||
}
|
||||
|
||||
public override async Task<Stream> RemoveBackgroundAsync(Stream image, Stream mask)
|
||||
public override async Task<Stream> RemoveBackgroundAsync(Stream image, Stream mask, byte featherMin, byte featherMax)
|
||||
{
|
||||
image.Position = 0;
|
||||
mask.Position = 0;
|
||||
@@ -76,19 +76,14 @@ internal class ImageSharpProcessor : ImageProcessor
|
||||
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;
|
||||
var alpha = AdjustAlpha(maskPixel.R, featherMin, featherMax);
|
||||
|
||||
imageWithBgRemoved[x, y] = alpha > alphaThreshold
|
||||
? new Rgba32(sourcePixel.R, sourcePixel.G, sourcePixel.B, sourcePixel.A)
|
||||
: transparentPixel;
|
||||
imageWithBgRemoved[x, y] = new Rgba32(sourcePixel.R, sourcePixel.G, sourcePixel.B, alpha);
|
||||
});
|
||||
|
||||
var result = new MemoryStream();
|
||||
@@ -107,12 +102,7 @@ internal class ImageSharpProcessor : ImageProcessor
|
||||
|
||||
private static float Normalize(float value)
|
||||
{
|
||||
const float binarizationThreshold = 0.5f;
|
||||
const float normalizationFactor = 2f;
|
||||
|
||||
return value > binarizationThreshold
|
||||
? (value - binarizationThreshold) * normalizationFactor
|
||||
: 0f;
|
||||
return value * value;
|
||||
}
|
||||
|
||||
private static byte ConvertToGreyscale(float value)
|
||||
@@ -129,4 +119,21 @@ internal class ImageSharpProcessor : ImageProcessor
|
||||
|
||||
return sigmoidScale / (sigmoidShift + MathF.Exp(sigmoidDivisor * x));
|
||||
}
|
||||
|
||||
private static byte AdjustAlpha(byte maskValue, byte minVal, byte maxVal)
|
||||
{
|
||||
if (maskValue <= minVal)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (maskValue >= maxVal)
|
||||
{
|
||||
return 255;
|
||||
}
|
||||
|
||||
var proportion = (maskValue - minVal) / (float)(maxVal - minVal);
|
||||
|
||||
return (byte)(proportion * 255f);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace BGR.Console.Removal.Onnx;
|
||||
|
||||
public class OnnxTensor : ITensor<float>
|
||||
internal class OnnxTensor : ITensor<float>
|
||||
{
|
||||
private readonly Tensor<float> _tensor;
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace BGR.Console.Removal;
|
||||
|
||||
internal class RemovalCommand(
|
||||
@@ -16,7 +14,19 @@ internal class RemovalCommand(
|
||||
private readonly IAnsiConsole _console = console;
|
||||
private readonly ILogger<RemovalCommand> _logger = logger;
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
|
||||
public async Task<int> ExecuteAsync(
|
||||
CommandContext context,
|
||||
Settings settings
|
||||
)
|
||||
{
|
||||
return await ExecuteAsync(context, settings, CancellationToken.None);
|
||||
}
|
||||
|
||||
protected override async Task<int> ExecuteAsync(
|
||||
CommandContext context,
|
||||
Settings settings,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
await _console.Status()
|
||||
.Spinner(Spinner.Known.Dots)
|
||||
@@ -56,7 +66,7 @@ internal class RemovalCommand(
|
||||
ctx.Status("Removing background...");
|
||||
var output = await _logger.TimeAndLogActionAsync(
|
||||
"Removing background",
|
||||
async () => await _imageProcessor.RemoveBackgroundAsync(image.Data, mask)
|
||||
async () => await _imageProcessor.RemoveBackgroundAsync(image.Data, mask, settings.FeatherMin, settings.FeatherMax)
|
||||
);
|
||||
|
||||
if (settings.IncludeMask)
|
||||
@@ -105,6 +115,14 @@ internal class RemovalCommand(
|
||||
[Description("Path to output image without background to. File extension will always be .png")]
|
||||
public string Output { get; init; } = string.Empty;
|
||||
|
||||
[CommandOption("--feather-min")]
|
||||
[Description("Minimum mask value below which pixels become fully transparent (default: 70)")]
|
||||
public byte FeatherMin { get; init; } = 70;
|
||||
|
||||
[CommandOption("--feather-max")]
|
||||
[Description("Maximum mask value above which pixels become fully opaque (default: 117)")]
|
||||
public byte FeatherMax { get; init; } = 117;
|
||||
|
||||
public string ResourceName => Models[Model];
|
||||
|
||||
public string MaskPath => GetOutputPath("_mask");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AnalysisLevel>latest</AnalysisLevel>
|
||||
@@ -8,6 +8,7 @@
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user