Skip to content

Commit fb4b212

Browse files
committed
Video background/foreground masking/removal
1 parent 9bf504d commit fb4b212

File tree

6 files changed

+73
-30
lines changed

6 files changed

+73
-30
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>0.1.30</Version>
3+
<Version>0.1.31</Version>
44
<Company>TensorStack</Company>
55
<Copyright>TensorStack - 2025</Copyright>
66
<RepositoryUrl>https://github.com/TensorStack-AI/TensorStack</RepositoryUrl>

Examples/TensorStack.Example.Extractors/Services/ExtractorService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,8 @@ async Task<VideoFrame> FrameProcessor(VideoFrame frame)
287287
var processedFrame = await pipeline.RunAsync(new BackgroundImageOptions
288288
{
289289
Image = frame.Frame,
290-
Mode = options.Mode
290+
Mode = options.Mode,
291+
IsTransparentSupported = false,
291292
}, cancellationToken: cancellationToken);
292293

293294
progressCallback.Report(new RunProgress(frame.Index, frameCount));

Examples/TensorStack.Example.Extractors/Views/VideoExtractorView.xaml

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -105,22 +105,7 @@
105105
<UniformGrid Columns="2">
106106
<StackPanel>
107107
<TextBlock Text="Background Mode" Style="{StaticResource FieldTextBlockStyle}" />
108-
<ComboBox SelectedItem="{Binding SelectedBackgroundMode}" ItemsSource="{Binding Source={StaticResource BackgroundMode}}" >
109-
<ComboBox.ItemContainerStyle>
110-
<Style TargetType="ComboBoxItem">
111-
<Style.Triggers>
112-
<DataTrigger Binding="{Binding}" Value="RemoveBackground">
113-
<Setter Property="IsEnabled" Value="False"/>
114-
<Setter Property="Opacity" Value="0.5"/>
115-
</DataTrigger>
116-
<DataTrigger Binding="{Binding}" Value="RemoveForeground">
117-
<Setter Property="IsEnabled" Value="False"/>
118-
<Setter Property="Opacity" Value="0.5"/>
119-
</DataTrigger>
120-
</Style.Triggers>
121-
</Style>
122-
</ComboBox.ItemContainerStyle>
123-
</ComboBox>
108+
<ComboBox SelectedItem="{Binding SelectedBackgroundMode}" ItemsSource="{Binding Source={StaticResource BackgroundMode}}" />
124109
</StackPanel>
125110
</UniformGrid>
126111
</StackPanel>

TensorStack.Common/Tensor/ImageTensor.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,47 @@ public void UpdateAlphaChannel(ImageTensor tensor)
113113
}
114114

115115

116+
/// <summary>
117+
/// Flattens the alpha channel.
118+
/// </summary>
119+
public void FlattenAlphaChannel()
120+
{
121+
FlattenAlphaChannel(GetChannel(4));
122+
}
123+
124+
125+
/// <summary>
126+
/// Flattens the alpha channel.
127+
/// </summary>
128+
/// <param name="alphaChannel">The alpha channel.</param>
129+
public void FlattenAlphaChannel(ReadOnlySpan<float> alphaChannel)
130+
{
131+
var mask = 1f;
132+
var pixelCount = Height * Width;
133+
var inputSpan = Memory.Span;
134+
var outputSpan = Memory.Span;
135+
136+
var rSpan = inputSpan.Slice(0 * pixelCount, pixelCount);
137+
var gSpan = inputSpan.Slice(1 * pixelCount, pixelCount);
138+
var bSpan = inputSpan.Slice(2 * pixelCount, pixelCount);
139+
140+
var outR = outputSpan.Slice(0 * pixelCount, pixelCount);
141+
var outG = outputSpan.Slice(1 * pixelCount, pixelCount);
142+
var outB = outputSpan.Slice(2 * pixelCount, pixelCount);
143+
144+
for (int i = 0; i < pixelCount; i++)
145+
{
146+
float alpha = alphaChannel[i];
147+
float invAlpha = 1f - alpha;
148+
149+
outR[i] = rSpan[i] * alpha + mask * invAlpha;
150+
outG[i] = gSpan[i] * alpha + mask * invAlpha;
151+
outB[i] = bSpan[i] * alpha + mask * invAlpha;
152+
}
153+
OnTensorDataChanged();
154+
}
155+
156+
116157
/// <summary>
117158
/// Resizes the ImageTensor
118159
/// </summary>

TensorStack.Extractors/Common/BackgroundOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public record BackgroundOptions : IRunOptions
1515
/// </summary>
1616
public BackgroundMode Mode { get; init; }
1717

18+
public bool IsTransparentSupported { get; init; } = true;
1819
}
1920

2021

TensorStack.Extractors/Pipelines/BackgroundPipeline.cs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public async Task UnloadAsync(CancellationToken cancellationToken = default)
5858
public async Task<ImageTensor> RunAsync(BackgroundImageOptions options, IProgress<RunProgress> progressCallback = null, CancellationToken cancellationToken = default)
5959
{
6060
var timestamp = RunProgress.GetTimestamp();
61-
var resultTensor = await ExtractBackgroundInternalAsync(options.Mode, options.Image, cancellationToken);
61+
var resultTensor = await ExtractBackgroundInternalAsync(options.Mode, options.IsTransparentSupported, options.Image, cancellationToken);
6262
progressCallback?.Report(new RunProgress(timestamp));
6363
return resultTensor;
6464
}
@@ -78,7 +78,7 @@ public void Dispose()
7878
/// </summary>
7979
/// <param name="imageInput">The image tensor.</param>
8080
/// <param name="cancellationToken">The cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
81-
private async Task<ImageTensor> ExtractBackgroundInternalAsync(BackgroundMode backgroundMode, ImageTensor imageInput, CancellationToken cancellationToken = default)
81+
private async Task<ImageTensor> ExtractBackgroundInternalAsync(BackgroundMode backgroundMode, bool isTranparentSupported, ImageTensor imageInput, CancellationToken cancellationToken = default)
8282
{
8383
var metadata = await _model.LoadAsync(cancellationToken: cancellationToken);
8484
cancellationToken.ThrowIfCancellationRequested();
@@ -104,16 +104,31 @@ private async Task<ImageTensor> ExtractBackgroundInternalAsync(BackgroundMode ba
104104

105105
// Normalize
106106
outputTensor.Normalize(_model.OutputNormalization);
107-
if (backgroundMode == BackgroundMode.MaskBackground || backgroundMode == BackgroundMode.RemoveForeground)
108-
outputTensor.Invert();
109107

110-
// Output Image
111-
var outputImage = backgroundMode == BackgroundMode.RemoveBackground || backgroundMode == BackgroundMode.RemoveForeground
112-
? inputTensor.CloneAs()
113-
: new ImageTensor(inputTensor.Height, inputTensor.Width, -1);
114-
115-
// Set Alpha
116-
outputImage.UpdateAlphaChannel(outputTensor.Span);
108+
// Process Image
109+
var outputImage = default(ImageTensor);
110+
if (backgroundMode == BackgroundMode.MaskForeground || backgroundMode == BackgroundMode.MaskBackground)
111+
{
112+
if (backgroundMode == BackgroundMode.MaskBackground)
113+
outputTensor.Invert();
114+
outputImage = new ImageTensor(inputTensor.Height, inputTensor.Width, -1);
115+
}
116+
else if (backgroundMode == BackgroundMode.RemoveBackground || backgroundMode == BackgroundMode.RemoveForeground)
117+
{
118+
if (backgroundMode == BackgroundMode.RemoveForeground)
119+
outputTensor.Invert();
120+
outputImage = inputTensor.CloneAs();
121+
}
122+
123+
// Set Alpha Channel
124+
if (isTranparentSupported)
125+
{
126+
outputImage.UpdateAlphaChannel(outputTensor.Span);
127+
}
128+
else
129+
{
130+
outputImage.FlattenAlphaChannel(outputTensor.Span);
131+
}
117132

118133
// Resize Output
119134
if (outputImage.Width != imageInput.Width || outputImage.Height != imageInput.Height)
@@ -132,7 +147,7 @@ private async Task<ImageTensor> ExtractBackgroundInternalAsync(BackgroundMode ba
132147
/// <returns>BackgroundPipeline.</returns>
133148
public static BackgroundPipeline Create(ExtractorConfig configuration)
134149
{
135-
return new BackgroundPipeline(ExtractorModel.Create(configuration));
150+
return new BackgroundPipeline(ExtractorModel.Create(configuration));
136151
}
137152
}
138153
}

0 commit comments

Comments
 (0)