时间:2025-04-13 20:13
人气:
作者:admin
<PackageReference Include="OpenCvSharp4" Version="4.10.0.20241108" />
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.10.0.20241108" />
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.10.0.20241108" />
public partial class Camera : UserControl
{
private VideoCapture? _capture;//视频捕捉器
private CancellationTokenSource? _cancellationTokenSource;//线程令牌
private bool _isRunning;//视频状态
public static readonly StyledProperty<bool> IsOpenCameraProperty =
AvaloniaProperty.Register<Camera, bool>(
nameof(IsOpenCamera), defaultValue: false);
public static readonly StyledProperty<WriteableBitmap> CurrentBitmapProperty =
AvaloniaProperty.Register<Camera, WriteableBitmap>(
nameof(CurrentBitmap));
public event EventHandler<string>? CameraErrorOccurred;
public Camera()
{
InitializeComponent();
this.GetObservable(IsOpenCameraProperty).Subscribe(OnIsOpenCameraChanged);
}
public bool IsOpenCamera
{
get => GetValue(IsOpenCameraProperty);
set => SetValue(IsOpenCameraProperty, value);
}
public WriteableBitmap CurrentBitmap
{
get => GetValue(CurrentBitmapProperty);
set => SetValue(CurrentBitmapProperty, value);
}
private void OnIsOpenCameraChanged(bool isOpen)
{
if (isOpen)
StartCamera();
else
StopCamera();
}
/// <summary>
/// 开启摄像头
/// </summary>
private void StartCamera()
{
if (_isRunning) return;
_capture = new VideoCapture(0);
if (!_capture.IsOpened())
{
_capture.Dispose();
_capture = null;
CameraErrorOccurred?.Invoke(this, "未找到可用的摄像头或设备已被占用。");
return;
}
_cancellationTokenSource = new CancellationTokenSource();
_isRunning = true;
Task.Run(() => CaptureLoop(_cancellationTokenSource.Token));
}
/// <summary>
/// 关闭摄像头
/// </summary>
private void StopCamera()
{
if (!_isRunning) return;
_cancellationTokenSource?.Cancel();
_capture?.Release();
_capture?.Dispose();
_isRunning = false;
}
/// <summary>
/// 捕获帧画面更新到Image控件上
/// </summary>
/// <param name="token"></param>
private void CaptureLoop(CancellationToken token)
{
using var mat = new Mat();
while (!token.IsCancellationRequested && _capture!.IsOpened())
{
_capture.Read(mat);
if (mat.Empty())
continue;
var bitmap = ConvertMatToBitmap(mat);
Dispatcher.UIThread.InvokeAsync(() =>
{
CurrentBitmap = bitmap;
VideoImage.Source = bitmap;
});
Thread.Sleep(30); // 控制帧率
}
}
/// <summary>
/// 用户控件销毁时释放资源
/// </summary>
/// <param name="e"></param>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
StopCamera();
}
/// <summary>
/// 将帧画面转换为Bitmap
/// </summary>
/// <param name="mat"></param>
/// <returns></returns>
private static WriteableBitmap ConvertMatToBitmap(Mat mat)
{
using var ms = mat.ToMemoryStream();
ms.Seek(0, SeekOrigin.Begin);
return WriteableBitmap.Decode(ms);
}
}
IsOpenCamera来控制是否开启摄像头,CurrentBitmap为当前帧画面。IsOpenCamera的状态来控制视频的捕捉,在构造函数中有这么一句代码public Camera()
{
InitializeComponent();
this.GetObservable(IsOpenCameraProperty).Subscribe(OnIsOpenCameraChanged);
}
StartCamera中我们使用了线程来循环执行视频捕捉事件,通过捕捉每一帧的画面,更新到Image控件上,实现视频的实时预览。<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:local="using:GeneralPurposeProgram.Controls"
xmlns:vm="using:GeneralPurposeProgram.ViewModels.UserViewModels"
x:DataType="vm:HomeViewModel"
x:Class="GeneralPurposeProgram.Views.UserViews.HomeView">
<Design.DataContext>
<vm:HomeViewModel></vm:HomeViewModel>
</Design.DataContext>
<Grid ColumnDefinitions="*,300">
<Grid Grid.Column="0" RowDefinitions="50,300,*">
<StackPanel Spacing="20" Grid.Row="0" Orientation="Horizontal">
<Button Content="开始摄像头" HotKey="F5" Command="{Binding StartCameraCommand}" Margin="0,0,0,10" Width="150" />
<Button Content="关闭摄像头" HotKey="F6" Command="{Binding StopCameraCommand}" Margin="0,0,0,10" Width="150" />
<Button Content="采集图像" HotKey="F10" Command="{Binding CaptureFrameCommand}" Margin="0,0,0,10" Width="150" />
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<local:Camera x:Name="CameraVideo"
IsOpenCamera="{Binding IsOpenCamera,Mode=TwoWay}"
CurrentBitmap="{Binding PreviewImage,Mode=TwoWay}" />
</StackPanel>
</Grid>
<Grid Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ListBox ItemsSource="{Binding Images}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Height="260" Stretch="Uniform" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid>
</Grid>
</UserControl>
public class HomeViewModel : ViewModelBase
{
private WriteableBitmap? _previewImage;
public WriteableBitmap? PreviewImage
{
get => _previewImage;
set => this.RaiseAndSetIfChanged(ref _previewImage, value);
}
private ObservableCollection<WriteableBitmap> _images = [];
public ObservableCollection<WriteableBitmap> Images
{
get => _images;
set => this.RaiseAndSetIfChanged(ref _images, value);
}
public ReactiveCommand<Unit, Unit> StartCameraCommand { get; }
public ReactiveCommand<Unit, Unit> StopCameraCommand { get; }
public ReactiveCommand<Unit, Unit> CaptureFrameCommand { get; }
private bool _isOpenVideo = false;
public bool IsOpenVideo
{
get => _isOpenVideo;
set => this.RaiseAndSetIfChanged(ref _isOpenVideo, value);
}
public HomeViewModel()
{
StartCameraCommand = ReactiveCommand.Create(StartCamera);
StopCameraCommand = ReactiveCommand.Create(StopCamera);
CaptureFrameCommand = ReactiveCommand.Create(CaptureFrame);
Images = [];
}
private void StartCamera()
{
IsOpenVideo = true;
}
private void CaptureFrame()
{
if (PreviewImage != null && IsOpenVideo)
{
Images.Add(PreviewImage);
}
}
private void StopCamera()
{
IsOpenVideo = false;
}
}
IsOpenCamera和CurrentBitmap直接暴露给了父控件,通过事件修改IsOpenCamera的值就能实现视频的开启和关闭。采集图像则只需要将CurrentBitmap当前帧画面保存起来,存入Images集合中给ListBox显示出来即完成了采图功能。HotKey="Key"即可
HotKey="Ctrl+F5"。
Microsoft Agent Framework Skills 执行 Scripts(实
EF Core 原生 SQL 实战:FromSql、SqlQuery 与对