Face blur automation: protect privacy with AWS & .NET
In today's digital world, protecting personal privacy is more important than ever. In this post, we will build a .NET application that leverages AWS Rekognition to detect faces in images, and then uses an image processing library to automatically blur those areas. Detection can miss faces, and blurring does not guarantee anonymity or compliance with privacy regulations. Review outputs before sharing them, and only send images to AWS when you have permission to do so.
Set up your .NET project
Use .NET 8 or later. First, create a new .NET console application. Open your terminal and run:
dotnet new console -n FaceBlurAutomation
cd FaceBlurAutomation
Next, add the required NuGet packages. We will use the AWS SDK (Amazon.Rekognition) for face detection and SixLabors.ImageSharp for image processing:
dotnet add package AWSSDK.Rekognition --version 4.0.100.13
dotnet add package SixLabors.ImageSharp --version 2.1.13
This example stays on ImageSharp's 2.1 patch line; use a patched release rather than 2.1.3.
Configure credentials through the SDK's
default credential provider chain,
such as a local AWS profile or an IAM role. The identity needs rekognition:DetectFaces permission.
Set the region in your profile or with AWS_REGION, for example us-east-1. The client below uses
that configuration without embedding credentials. Rekognition requests incur AWS charges.
Understand AWS Rekognition
AWS Rekognition offers powerful tools for image analysis. Its DetectFaces API returns detailed
information about facial features, including bounding boxes that are relative to the image
dimensions. We can use these details to locate faces and apply post-processing effects like
blurring. DetectFaces returns up to the 100 largest faces, so it cannot establish that every face
in an image has been found.
Implementing face detection and blurring
Below is a complete C# example that loads an image, detects faces, and blurs each detected region
using ImageSharp. Replace Program.cs with this code and run dotnet run -- input.jpg output.png,
choosing an image at least 80 pixels wide and high and an output file that does not already exist:
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Amazon.Rekognition;
using Amazon.Rekognition.Model;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
namespace FaceBlurAutomation;
public class Program
{
public static async Task Main(string[] args)
{
try
{
if (args.Length < 2)
{
throw new ArgumentException("Usage: FaceBlurAutomation <inputImagePath> <outputImagePath>");
}
string inputImagePath = args[0];
string outputImagePath = args[1];
if (!File.Exists(inputImagePath))
{
throw new FileNotFoundException("Input image file not found", inputImagePath);
}
using var rekognitionClient = new AmazonRekognitionClient();
await ProcessImageAsync(inputImagePath, outputImagePath, rekognitionClient);
Console.WriteLine($"Processed image saved to {outputImagePath}");
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error: {ex.Message}");
Environment.Exit(1);
}
}
private static async Task ProcessImageAsync(
string inputPath, string outputPath, IAmazonRekognition rekognitionClient)
{
if (!string.Equals(Path.GetExtension(outputPath), ".png", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Choose a .png output path", nameof(outputPath));
}
if (File.Exists(outputPath))
{
throw new IOException("Choose an output file that does not already exist");
}
using var image = await SixLabors.ImageSharp.Image.LoadAsync(inputPath);
// Send exactly the oriented pixels that we will blur, without EXIF orientation metadata.
image.Mutate(ctx => ctx.AutoOrient());
image.Metadata.ExifProfile = null;
using var imageStream = new MemoryStream();
await image.SaveAsPngAsync(imageStream);
if (imageStream.Length > 5 * 1024 * 1024)
{
throw new InvalidDataException("The normalized PNG exceeds Rekognition's 5 MB Bytes limit");
}
imageStream.Position = 0;
var detectRequest = new DetectFacesRequest
{
Image = new Amazon.Rekognition.Model.Image
{
Bytes = imageStream
},
Attributes = new List<string> { "DEFAULT" }
};
var detectResponse = await rekognitionClient.DetectFacesAsync(detectRequest);
var faces = detectResponse.FaceDetails ?? new List<FaceDetail>();
if (faces.Count == 0)
{
Console.WriteLine("No faces detected; the output is unblurred and needs manual review");
}
foreach (var faceDetail in faces)
{
var box = faceDetail.BoundingBox;
if (box?.Left is not float left || box.Top is not float top
|| box.Width is not float width || box.Height is not float height
|| !float.IsFinite(left) || !float.IsFinite(top)
|| !float.IsFinite(width) || !float.IsFinite(height)
|| width <= 0 || height <= 0)
{
throw new InvalidDataException("Rekognition returned an invalid face bounding box");
}
// Edge faces can extend outside the image. Round outward, then clamp to its bounds.
int x1 = (int)Math.Floor(Math.Clamp((double)left * image.Width, 0, image.Width));
int y1 = (int)Math.Floor(Math.Clamp((double)top * image.Height, 0, image.Height));
int x2 = (int)Math.Ceiling(Math.Clamp(((double)left + width) * image.Width, 0, image.Width));
int y2 = (int)Math.Ceiling(Math.Clamp(((double)top + height) * image.Height, 0, image.Height));
if (x2 <= x1 || y2 <= y1)
{
throw new InvalidDataException("A face bounding box does not intersect the image");
}
var faceRegion = new Rectangle(x1, y1, x2 - x1, y2 - y1);
image.Mutate(ctx => ctx.GaussianBlur(10, faceRegion));
}
using var output = new FileStream(outputPath, FileMode.CreateNew, FileAccess.Write);
await image.SaveAsPngAsync(output);
}
}
How it works
- We load and orient the image, encode it as PNG, and pass it to
DetectFacesAsync. The normalized PNG must fit the 5 MB direct Bytes limit. The separate 15 MB S3-object limit does not apply to this example. - Rekognition returns a list of face details, each containing a normalized bounding box for the detected face region.
- We convert these coordinates to pixels in the same oriented image. Bounding boxes can extend beyond an edge, so we clamp them and reject invalid or non-intersecting boxes.
- For each detected face, we apply a Gaussian blur. The fixed blur radius may need adjustment after inspecting the result.
- Finally, we save a new PNG file without overwriting an existing file.
Error handling and testing
The example includes error handling for common scenarios:
- Missing or invalid command-line arguments
- Non-existent input files
- AWS service errors
- Image processing failures
- Cases where no faces are detected
Service and image-processing failures propagate to Main, which exits unsuccessfully. No output is
written when face detection fails or returns an invalid bounding box. In a server application,
return a generic error to callers and keep redacted diagnostics in server logs instead of exposing
exception messages. Apply input-size and decoded-pixel limits before accepting untrusted uploads.
Extend your application
This basic example can be extended in various ways:
- Integrate with a web API to process images uploaded by users.
- Use a storage service such as Amazon S3 for input and output images.
- Combine this technique with other image analysis tools to create a comprehensive privacy protection suite.
Summary
You have now built a .NET application that leverages AWS Rekognition to detect faces and applies a blur effect to reduce visible facial detail. This approach demonstrates how to integrate cloud-based image analysis with modern .NET libraries to address real-world challenges.
By the way, Transloadit also leverages similar AWS image analysis techniques in its /image/describe robot to enhance image processing workflows.
