Goal
Build a .NET MAUI app that:
✅ Captures an image using the camera
✅ Runs OCR using Tesseract OCR
✅ Displays extracted text on screen
✅ Prerequisites
-
Android/iOS device or emulator
-
Tesseract
tessdata
files (eng.traineddata
etc.)
π§© Key Libraries
dotnet add package Tesseract.Net.SDK dotnet add package SkiaSharp
SkiaSharp is used to handle and convert images into a format Tesseract can understand.
Step-by-Step Guide
1. Create the MAUI Project
dotnet new maui -n MauiCameraOCR
cd MauiCameraOCR
2. Add Permissions
✅ Android: Platforms/Android/AndroidManifest.xml
Add inside <manifest>
→ <application>
:
<uses-permission android:name="android.permission.CAMERA" />
And enable camera in capabilities:
<uses-feature android:name="android.hardware.camera" android:required="true" />
✅ iOS: Platforms/iOS/Info.plist
Add:
<key>NSCameraUsageDescription</key>
<string>This app uses the camera for OCR scanning</string>
3. Add NuGet Packages
dotnet add package Tesseract.Net.SDK dotnet add package SkiaSharp
4. Download Tesseract Language Files
Download from: https://github.com/tesseract-ocr/tessdata
-
Download
eng.traineddata
-
Create a folder
tessdata
in your project root -
Place
eng.traineddata
inside -
Set properties:
-
Build Action:
Content
-
Copy to Output Directory:
Copy always
-
5. Main UI (MainPage.xaml)
<VerticalStackLayout Padding="20" Spacing="15">
<Label Text="Camera OCR App" FontSize="24" HorizontalOptions="Center"/>
<Button Text="Capture Image" Clicked="OnCaptureImage"/>
<Image x:Name="CapturedImage" HeightRequest="200"/>
<ScrollView HeightRequest="300">
<Label x:Name="ResultLabel" FontSize="16"/>
</ScrollView>
</VerticalStackLayout>
6. Main Logic (MainPage.xaml.cs)
using Microsoft.Maui.Media;
using Tesseract;
using SkiaSharp;
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
async void OnCaptureImage(object sender, EventArgs e)
{
try
{
var photo = await MediaPicker.CapturePhotoAsync();
if (photo == null)
return;
var stream = await photo.OpenReadAsync();
CapturedImage.Source = ImageSource.FromStream(() => stream);
stream.Seek(0, SeekOrigin.Begin);
var text = await ExtractTextFromImage(stream);
ResultLabel.Text = text;
}
catch (Exception ex)
{
await DisplayAlert("Error", ex.Message, "OK");
}
}
async Task<string> ExtractTextFromImage(Stream stream)
{
using var engine = new TesseractEngine("./tessdata", "eng", EngineMode.Default);
// Convert stream to byte[]
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var bytes = ms.ToArray();
using var pix = Pix.LoadFromMemory(bytes);
using var page = engine.Process(pix);
return page.GetText();
}
}
π± Platform Notes
Android
-
MediaPicker will use the device camera.
-
Ensure emulator supports camera or test on device.
iOS
-
MediaPicker uses the system camera picker.
-
Permissions must be correctly set in
Info.plist
.
✅ Build and Run
dotnet build dotnet maui run -f:net8.0-android
Or:
dotnet maui run -f:net8.0-ios
live camera preview in a .NET MAUI app requires platform-specific code because .NET MAUI does not yet provide built-in camera preview controls as of .NET 8.
Here’s how you can implement live camera preview in Android and iOS using Handlers and native views, and integrate it into a .NET MAUI project.
π― Goal
Show live camera preview (not just capture)
Platform-specific implementations:
π± Android: Use CameraX
or CameraPreview
π iOS: Use AVCaptureSession
and AVCaptureVideoPreviewLayer
✅ Approach Overview
Create a CameraPreview
control in .NET MAUI.
Add native handlers for Android and iOS.
Embed native preview views.
Add a "Capture" button to trigger OCR.
π¦ Step-by-Step Guide
π§ Step 1: Create the MAUI CameraPreview Control
π Controls/CameraPreview.cs
namespace MauiCameraOCR.Controls;
public class CameraPreview : View
{
public event EventHandler<byte[]> PhotoCaptured;
public void OnPhotoCaptured(byte[] imageData) =>
PhotoCaptured?.Invoke(this, imageData);
public void CapturePhoto()
{
// This will be handled by the native handler
}
}
π¨ Step 2: Use It in XAML
π MainPage.xaml
<VerticalStackLayout Padding="20">
<Label Text="Live Camera OCR" FontSize="24" HorizontalOptions="Center" />
<ContentView HeightRequest="300">
<controls:CameraPreview x:Name="Camera" />
</ContentView>
<Button Text="Capture and OCR" Clicked="OnCaptureClicked" />
<ScrollView HeightRequest="300">
<Label x:Name="ResultLabel" FontSize="16" />
</ScrollView>
</VerticalStackLayout>
In code-behind:
private void OnCaptureClicked(object sender, EventArgs e)
{
Camera.CapturePhoto();
}
And subscribe to the PhotoCaptured
event in OnAppearing
.
π€ Step 3: Android Handler
π Platforms/Android/Handlers/CameraPreviewHandler.cs
using Android.Content;
using Android.Views;
using AndroidX.Camera.Core;
using AndroidX.Camera.Lifecycle;
using AndroidX.Camera.View;
using AndroidX.Lifecycle;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using MauiCameraOCR.Controls;
[assembly: ExportHandler(typeof(CameraPreview), typeof(CameraPreviewHandler))]
namespace MauiCameraOCR.Platforms.Android.Handlers;
public class CameraPreviewHandler : ViewHandler<CameraPreview, PreviewView>
{
PreviewView _previewView;
ImageCapture _imageCapture;
protected override PreviewView CreatePlatformView()
{
_previewView = new PreviewView(Context);
var cameraProviderFuture = ProcessCameraProvider.GetInstance(Context);
cameraProviderFuture.AddListener(() =>
{
var cameraProvider = (ProcessCameraProvider)cameraProviderFuture.Get();
var preview = new Preview.Builder().Build();
_imageCapture = new ImageCapture.Builder().Build();
preview.SetSurfaceProvider(_previewView.SurfaceProvider);
var cameraSelector = CameraSelector.DefaultBackCamera;
cameraProvider.UnbindAll();
cameraProvider.BindToLifecycle((ILifecycleOwner)Context, cameraSelector, preview, _imageCapture);
}, Context.MainExecutor);
return _previewView;
}
public override void ConnectHandler(PreviewView platformView)
{
base.ConnectHandler(platformView);
VirtualView.CapturePhoto = async () =>
{
var file = Java.IO.File.CreateTempFile("ocr_image", ".jpg");
var outputOptions = new ImageCapture.OutputFileOptions.Builder(file).Build();
_imageCapture.TakePicture(outputOptions, Context.MainExecutor, new ImageCaptureOnImageSavedCallback((bytes) =>
{
(VirtualView as CameraPreview)?.OnPhotoCaptured(bytes);
}));
};
}
class ImageCaptureOnImageSavedCallback : Java.Lang.Object, ImageCapture.IOnImageSavedCallback
{
readonly Action<byte[]> _onSaved;
public ImageCaptureOnImageSavedCallback(Action<byte[]> onSaved)
{
_onSaved = onSaved;
}
public void OnImageSaved(ImageCapture.OutputFileResults output)
{
var path = output.SavedUri?.Path;
var bytes = File.ReadAllBytes(path);
_onSaved(bytes);
}
public void OnError(ImageCaptureException exc)
{
Console.WriteLine("Error capturing image: " + exc.Message);
}
}
}
π Step 4: iOS Handler
π Platforms/iOS/Handlers/CameraPreviewHandler.cs
using AVFoundation;
using CoreGraphics;
using Foundation;
using Microsoft.Maui.Handlers;
using UIKit;
using MauiCameraOCR.Controls;
[assembly: ExportHandler(typeof(CameraPreview), typeof(CameraPreviewHandler))]
namespace MauiCameraOCR.Platforms.iOS.Handlers;
public class CameraPreviewHandler : ViewHandler<CameraPreview, UIView>
{
AVCaptureSession _session;
AVCapturePhotoOutput _photoOutput;
UIView _nativeView;
protected override UIView CreatePlatformView()
{
_session = new AVCaptureSession();
_photoOutput = new AVCapturePhotoOutput();
_session.AddOutput(_photoOutput);
var device = AVCaptureDevice.GetDefaultDevice(AVMediaTypes.Video);
var input = AVCaptureDeviceInput.FromDevice(device, out var error);
if (input != null) _session.AddInput(input);
var previewLayer = new AVCaptureVideoPreviewLayer(_session)
{
Frame = new CGRect(0, 0, 300, 300),
VideoGravity = AVLayerVideoGravity.ResizeAspectFill
};
_nativeView = new UIView();
_nativeView.Layer.AddSublayer(previewLayer);
_session.StartRunning();
VirtualView.CapturePhoto = () =>
{
var settings = AVCapturePhotoSettings.Create();
_photoOutput.CapturePhoto(settings, new PhotoCaptureDelegate((bytes) =>
{
VirtualView.OnPhotoCaptured(bytes);
}));
};
return _nativeView;
}
class PhotoCaptureDelegate : AVCapturePhotoCaptureDelegate
{
private readonly Action<byte[]> _callback;
public PhotoCaptureDelegate(Action<byte[]> callback) => _callback = callback;
public override void DidFinishProcessingPhoto(AVCapturePhotoOutput output, AVCapturePhoto photo, NSError error)
{
if (photo.FileDataRepresentation != null)
{
_callback(photo.FileDataRepresentation.ToArray());
}
}
}
}
π§ OCR Integration (Shared Code)
In your MainPage.xaml.cs
, handle:
protected override void OnAppearing()
{
base.OnAppearing();
Camera.PhotoCaptured += async (s, imgBytes) =>
{
using var engine = new TesseractEngine("./tessdata", "eng", EngineMode.Default);
using var pix = Pix.LoadFromMemory(imgBytes);
using var page = engine.Process(pix);
ResultLabel.Text = page.GetText();
};
}
✅ Final Touches
π‘ Permissions
Ensure you request camera permissions at runtime (especially on Android 13+).
var status = await Permissions.RequestAsync<Permissions.Camera>();
π¦ Summary
Platform Camera Live Preview Capture OCR Android ✅ ✅ (CameraX) ✅ ✅ iOS ✅ ✅ (AVFoundation) ✅ ✅
No comments:
Post a Comment