Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14756339ee | ||
|
|
ad0fa4b047 |
@@ -1,10 +1,7 @@
|
||||
using Basler.Pylon;
|
||||
using Microsoft.Win32;
|
||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
@@ -17,6 +14,14 @@ namespace WpfApp1
|
||||
private ICameraInfo? _selectedCameraInfo;
|
||||
private bool _isCameraStarted = false;
|
||||
|
||||
// 复用同一个转换器和缓冲区,减少 GC 压力
|
||||
private readonly PixelDataConverter _converter = new PixelDataConverter
|
||||
{
|
||||
OutputPixelFormat = PixelType.BGRA8packed
|
||||
};
|
||||
|
||||
private byte[]? _buffer;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -26,38 +31,136 @@ namespace WpfApp1
|
||||
|
||||
ButtonPanel1.IsEnabled = false;
|
||||
StatusText.Text = "正在初始化相机...请稍后";
|
||||
Task.Run(() => InitializeCameraAsync());
|
||||
|
||||
// 在 Loaded 事件中做异步初始化,避免构造函数里 Task.Run 套娃
|
||||
Loaded += async (_, __) => await InitializeCameraAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步初始化:枚举一次相机,默认选第一台
|
||||
/// </summary>
|
||||
private async Task InitializeCameraAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var cameras = CameraFinder.Enumerate();
|
||||
// 相机枚举可能比较耗时,放到后台线程
|
||||
var cameras = await Task.Run(() => CameraFinder.Enumerate());
|
||||
|
||||
if (cameras.Count == 0)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ButtonPanel1.IsEnabled = true;
|
||||
StatusText.Text = "未检测到相机";
|
||||
});
|
||||
ButtonPanel1.IsEnabled = true;
|
||||
StatusText.Text = "未检测到相机";
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedCameraInfo = cameras[0];
|
||||
|
||||
await Task.CompletedTask;
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ButtonPanel1.IsEnabled = true;
|
||||
StatusText.Text = "相机初始化完成,请选择相机并随后‘开启推流’开始取流";
|
||||
StartCameraButton.Content = "开启推流";
|
||||
});
|
||||
ButtonPanel1.IsEnabled = true;
|
||||
StatusText.Text = "相机初始化完成,请选择相机并随后‘开启推流’开始取流";
|
||||
StartCameraButton.Content = "开启推流";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.Invoke(() => MessageBox.Show("初始化失败: " + ex.Message));
|
||||
ButtonPanel1.IsEnabled = true;
|
||||
MessageBox.Show("初始化失败: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保当前选择的相机已经打开(不含取流)
|
||||
/// </summary>
|
||||
private bool EnsureCameraOpen()
|
||||
{
|
||||
if (_camera != null && _camera.IsOpen)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_selectedCameraInfo == null)
|
||||
{
|
||||
StatusText.Text = "请先选择一个相机";
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseCamera();
|
||||
|
||||
try
|
||||
{
|
||||
_camera = new Camera(_selectedCameraInfo);
|
||||
_camera.Open();
|
||||
_camera.Parameters[PLCamera.AcquisitionMode].SetValue(PLCamera.AcquisitionMode.Continuous);
|
||||
|
||||
_isCameraStarted = false;
|
||||
|
||||
string serial = _selectedCameraInfo[CameraInfoKey.SerialNumber];
|
||||
StatusText.Text = $"已选择并打开相机:{serial}(未取流)。点击‘开启推流’开始/停止推流";
|
||||
StartCameraButton.Content = "开启推流";
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = "打开相机失败:" + ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止取流但不关闭相机
|
||||
/// </summary>
|
||||
private void StopGrabbing()
|
||||
{
|
||||
if (_camera == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_camera.StreamGrabber.IsGrabbing)
|
||||
{
|
||||
_camera.StreamGrabber.Stop();
|
||||
}
|
||||
|
||||
_camera.StreamGrabber.ImageGrabbed -= OnImageGrabbed;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 可在此记录日志
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isCameraStarted = false;
|
||||
StartCameraButton.Content = "开启推流";
|
||||
StatusText.Text = "推流已关闭";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭相机(内部会先停止取流)
|
||||
/// </summary>
|
||||
private void CloseCamera()
|
||||
{
|
||||
if (_camera == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopGrabbing();
|
||||
|
||||
try
|
||||
{
|
||||
if (_camera.IsOpen)
|
||||
{
|
||||
_camera.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = "关闭相机时出错:" + ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_camera = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,54 +168,26 @@ namespace WpfApp1
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保相机已打开
|
||||
if ((_camera == null || !_camera.IsOpen) && _selectedCameraInfo != null)
|
||||
// 保证相机已打开
|
||||
if (!EnsureCameraOpen())
|
||||
{
|
||||
if (_camera != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_camera.StreamGrabber.Stop();
|
||||
_camera.StreamGrabber.ImageGrabbed -= OnImageGrabbed;
|
||||
_camera.Close();
|
||||
}
|
||||
catch { }
|
||||
finally { _camera = null; }
|
||||
}
|
||||
|
||||
_camera = new Camera(_selectedCameraInfo);
|
||||
_camera.Open();
|
||||
_camera.Parameters[PLCamera.AcquisitionMode].SetValue(PLCamera.AcquisitionMode.Continuous);
|
||||
}
|
||||
|
||||
if (_camera == null || !_camera.IsOpen)
|
||||
{
|
||||
StatusText.Text = "请先选择一个相机";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isCameraStarted)
|
||||
{
|
||||
_camera.StreamGrabber.ImageGrabbed += OnImageGrabbed;
|
||||
_camera.StreamGrabber.Start(GrabStrategy.LatestImages, GrabLoop.ProvidedByStreamGrabber);
|
||||
_camera.StreamGrabber.Start(
|
||||
GrabStrategy.LatestImages,
|
||||
GrabLoop.ProvidedByStreamGrabber);
|
||||
|
||||
_isCameraStarted = true;
|
||||
StatusText.Text = "推流已开启";
|
||||
StartCameraButton.Content = "关闭推流";
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_camera.StreamGrabber.Stop();
|
||||
_camera.StreamGrabber.ImageGrabbed -= OnImageGrabbed;
|
||||
_isCameraStarted = false;
|
||||
StatusText.Text = "推流已关闭";
|
||||
StartCameraButton.Content = "开启推流";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = "关闭推流失败:" + ex.Message;
|
||||
}
|
||||
StopGrabbing();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -121,6 +196,9 @@ namespace WpfApp1
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连续取流回调:更新预览
|
||||
/// </summary>
|
||||
private void OnImageGrabbed(object sender, ImageGrabbedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -129,30 +207,48 @@ namespace WpfApp1
|
||||
{
|
||||
if (!result.GrabSucceeded) return;
|
||||
|
||||
var converter = new PixelDataConverter();
|
||||
converter.OutputPixelFormat = PixelType.BGRA8packed;
|
||||
int width = result.Width;
|
||||
int height = result.Height;
|
||||
int stride = width * 4;
|
||||
int bufferSize = stride * height;
|
||||
|
||||
byte[] buffer = new byte[result.Width * result.Height * 4];
|
||||
converter.Convert(buffer, result);
|
||||
// 每帧自己分配一块 buffer
|
||||
byte[] localBuffer = new byte[bufferSize];
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
_converter.Convert(localBuffer, result);
|
||||
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
CameraImage.Source = BitmapSource.Create(
|
||||
result.Width, result.Height, 96, 96,
|
||||
System.Windows.Media.PixelFormats.Bgra32,
|
||||
null, buffer, result.Width * 4);
|
||||
});
|
||||
try
|
||||
{
|
||||
CameraImage.Source = BitmapSource.Create(
|
||||
width,
|
||||
height,
|
||||
96,
|
||||
96,
|
||||
System.Windows.Media.PixelFormats.Bgra32,
|
||||
null,
|
||||
localBuffer,
|
||||
stride);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
// 这里可以打个日志看看具体参数,如果还出错
|
||||
System.Diagnostics.Debug.WriteLine("Create bitmap failed: " + ex);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 忽略异常
|
||||
System.Diagnostics.Debug.WriteLine("OnImageGrabbed error: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void CaptureButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (CameraImage.Source == null)
|
||||
if (CameraImage.Source is not BitmapSource bitmap)
|
||||
{
|
||||
StatusText.Text = "没有图像可保存";
|
||||
return;
|
||||
@@ -160,10 +256,16 @@ namespace WpfApp1
|
||||
|
||||
try
|
||||
{
|
||||
string filePath = Path.Combine(_saveFolder,
|
||||
if (!Directory.Exists(_saveFolder))
|
||||
{
|
||||
Directory.CreateDirectory(_saveFolder);
|
||||
}
|
||||
|
||||
string filePath = Path.Combine(
|
||||
_saveFolder,
|
||||
$"Capture_{DateTime.Now:yyyyMMdd_HHmmss}.png");
|
||||
|
||||
SaveImage(CameraImage.Source as BitmapSource, filePath);
|
||||
SaveImage(bitmap, filePath);
|
||||
|
||||
StatusText.Text = $"图像已保存到:{filePath}";
|
||||
}
|
||||
@@ -195,62 +297,35 @@ namespace WpfApp1
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
base.OnClosing(e);
|
||||
|
||||
if (_camera != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_camera.StreamGrabber.Stop();
|
||||
_camera.StreamGrabber.ImageGrabbed -= OnImageGrabbed;
|
||||
_camera.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = "关闭相机时出错:" + ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_camera = null;
|
||||
_isCameraStarted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectCameraButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new SelectCamera();
|
||||
dlg.Owner = this;
|
||||
var dlg = new SelectCamera
|
||||
{
|
||||
Owner = this
|
||||
};
|
||||
|
||||
if (dlg.ShowDialog() == true)
|
||||
{
|
||||
var selectedInfo = dlg.SelectedCameraInfo;
|
||||
_selectedCameraInfo = selectedInfo;
|
||||
_selectedCameraInfo = dlg.SelectedCameraInfo;
|
||||
|
||||
// 关闭旧相机,打开新相机,但不取流
|
||||
try
|
||||
{
|
||||
if (_camera != null)
|
||||
CloseCamera();
|
||||
|
||||
if (_selectedCameraInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_camera.StreamGrabber.Stop();
|
||||
_camera.StreamGrabber.ImageGrabbed -= OnImageGrabbed;
|
||||
_camera.Close();
|
||||
}
|
||||
catch { }
|
||||
finally { _camera = null; _isCameraStarted = false; }
|
||||
_camera = new Camera(_selectedCameraInfo);
|
||||
_camera.Open();
|
||||
_camera.Parameters[PLCamera.AcquisitionMode].SetValue(PLCamera.AcquisitionMode.Continuous);
|
||||
|
||||
_isCameraStarted = false;
|
||||
|
||||
string serial = _selectedCameraInfo[CameraInfoKey.SerialNumber];
|
||||
StatusText.Text =
|
||||
$"已选择并打开相机:{serial}(未取流)。点击‘开启推流’开始/停止推流";
|
||||
StartCameraButton.Content = "开启推流";
|
||||
}
|
||||
|
||||
_camera = new Camera(_selectedCameraInfo);
|
||||
_camera.Open();
|
||||
_camera.Parameters[PLCamera.AcquisitionMode].SetValue(PLCamera.AcquisitionMode.Continuous);
|
||||
|
||||
_isCameraStarted = false;
|
||||
StatusText.Text = $"已选择并打开相机:{selectedInfo[CameraInfoKey.SerialNumber]}(未取流)。点击‘开启推流’开始/停止推流";
|
||||
StartCameraButton.Content = "开启推流";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -266,15 +341,27 @@ namespace WpfApp1
|
||||
|
||||
private void TimedCaptureButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 定时拍摄只要求相机已打开,无需取流
|
||||
if (_camera == null || !_camera.IsOpen)
|
||||
// 定时拍摄只要求相机已打开,无需连续取流
|
||||
if (!EnsureCameraOpen())
|
||||
{
|
||||
MessageBox.Show("请先选择相机(会自动打开但不取流)");
|
||||
return;
|
||||
}
|
||||
var dlg = new TimedCapture(_camera);
|
||||
dlg.Owner = this;
|
||||
|
||||
// 避免和定时抓拍争用同一个 StreamGrabber
|
||||
StopGrabbing();
|
||||
|
||||
var dlg = new TimedCapture(_camera)
|
||||
{
|
||||
Owner = this
|
||||
};
|
||||
dlg.ShowDialog();
|
||||
}
|
||||
|
||||
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
base.OnClosing(e);
|
||||
CloseCamera();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,11 @@
|
||||
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="10">
|
||||
<Button x:Name="TimedCaptureStart" Content="开始拍摄" Width="100" Margin="0,0,20,0" Click="TimedCaptureStart_Click"/>
|
||||
<Button x:Name="TimedCaptureStop" Content="停止拍摄" Width="100" Click="TimedCaptureStop_Click"/>
|
||||
<Button x:Name="ToggleCaptureButton"
|
||||
Content="开始定时拍照"
|
||||
Width="140"
|
||||
Padding="10,4"
|
||||
Click="ToggleCaptureButton_Click"/>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
@@ -14,23 +14,73 @@ namespace WpfApp1
|
||||
{
|
||||
private readonly Camera _camera;
|
||||
private readonly DispatcherTimer _timer = new DispatcherTimer();
|
||||
private string _saveFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
|
||||
|
||||
private string _saveFolder =
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
|
||||
|
||||
private int _intervalMs = 1000;
|
||||
private bool _isCapturing = false;
|
||||
|
||||
// 复用一个转换器,避免每次 new
|
||||
private readonly PixelDataConverter _converter = new PixelDataConverter
|
||||
{
|
||||
OutputPixelFormat = PixelType.BGRA8packed
|
||||
};
|
||||
|
||||
public TimedCapture(Camera camera)
|
||||
{
|
||||
InitializeComponent();
|
||||
_camera = camera;
|
||||
|
||||
var name = _camera?.CameraInfo?[CameraInfoKey.FriendlyName];
|
||||
CameraInfoTextBlock.Text = string.IsNullOrWhiteSpace(name) ? "未知相机" : name;
|
||||
_camera = camera ?? throw new ArgumentNullException(nameof(camera));
|
||||
|
||||
var name = _camera.CameraInfo?[CameraInfoKey.FriendlyName];
|
||||
CameraInfoTextBlock.Text = string.IsNullOrWhiteSpace(name)
|
||||
? "未知相机"
|
||||
: name;
|
||||
|
||||
FolderTextBox.Text = _saveFolder;
|
||||
IntervalTextBox.Text = _intervalMs.ToString();
|
||||
|
||||
_timer.Tick += OnTimerTick;
|
||||
_timer.IsEnabled = false;
|
||||
|
||||
UpdateUiState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 _isCapturing 更新按钮文本和状态栏
|
||||
/// </summary>
|
||||
private void UpdateUiState(string? extraStatus = null)
|
||||
{
|
||||
if (_isCapturing)
|
||||
{
|
||||
ToggleCaptureButton.Content = "停止定时拍照";
|
||||
if (extraStatus == null)
|
||||
{
|
||||
StatusTextBlock.Text = "状态:拍摄中";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusTextBlock.Text = extraStatus;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleCaptureButton.Content = "开始定时拍照";
|
||||
if (extraStatus == null)
|
||||
{
|
||||
StatusTextBlock.Text = "状态:已停止";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusTextBlock.Text = extraStatus;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选择保存路径
|
||||
/// </summary>
|
||||
private void BrowseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selected = FolderHelper.SelectFolder("请选择保存目录");
|
||||
@@ -41,78 +91,136 @@ namespace WpfApp1
|
||||
}
|
||||
}
|
||||
|
||||
private void TimedCaptureStart_Click(object sender, RoutedEventArgs e)
|
||||
/// <summary>
|
||||
/// 单按钮:开始 / 停止 定时拍照
|
||||
/// </summary>
|
||||
private void ToggleCaptureButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!_isCapturing)
|
||||
{
|
||||
// 当前是“未拍照”状态,点击后尝试开始
|
||||
if (!StartCapture())
|
||||
{
|
||||
// 开始失败,不改变 _isCapturing
|
||||
return;
|
||||
}
|
||||
|
||||
_isCapturing = true;
|
||||
UpdateUiState();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 当前在拍照,点击后停止
|
||||
StopCapture();
|
||||
_isCapturing = false;
|
||||
UpdateUiState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试开始定时拍照(参数校验、目录检查等)
|
||||
/// 返回 true 表示成功启动
|
||||
/// </summary>
|
||||
private bool StartCapture()
|
||||
{
|
||||
if (_camera == null || !_camera.IsOpen)
|
||||
{
|
||||
MessageBox.Show("相机未打开");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 读取间隔
|
||||
if (!int.TryParse(IntervalTextBox.Text, out var ms) || ms <= 0)
|
||||
{
|
||||
MessageBox.Show("请输入有效的拍摄间隔(毫秒)");
|
||||
return;
|
||||
MessageBox.Show("请输入有效的拍摄间隔(毫秒,>0)");
|
||||
return false;
|
||||
}
|
||||
_intervalMs = ms;
|
||||
_timer.Interval = TimeSpan.FromMilliseconds(_intervalMs);
|
||||
|
||||
// 读取保存路径
|
||||
if (string.IsNullOrWhiteSpace(FolderTextBox.Text))
|
||||
{
|
||||
MessageBox.Show("请选择保存文件夹");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_saveFolder = FolderTextBox.Text.Trim();
|
||||
|
||||
if (!Directory.Exists(_saveFolder))
|
||||
{
|
||||
try { Directory.CreateDirectory(_saveFolder); } catch (Exception ex) { MessageBox.Show("创建文件夹失败:" + ex.Message); return; }
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(_saveFolder);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("创建文件夹失败:" + ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
StatusTextBlock.Text = "状态:拍摄中";
|
||||
TimedCaptureStart.IsEnabled = false;
|
||||
TimedCaptureStop.IsEnabled = true;
|
||||
_timer.Start();
|
||||
UpdateUiState("状态:拍摄中");
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TimedCaptureStop_Click(object sender, RoutedEventArgs e)
|
||||
/// <summary>
|
||||
/// 停止定时拍照
|
||||
/// </summary>
|
||||
private void StopCapture()
|
||||
{
|
||||
_timer.Stop();
|
||||
StatusTextBlock.Text = "状态:已停止";
|
||||
TimedCaptureStart.IsEnabled = true;
|
||||
TimedCaptureStop.IsEnabled = false;
|
||||
UpdateUiState("状态:已停止");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定时抓拍:每次 Tick 抓一帧并保存
|
||||
/// (现在 Tick 在 UI 线程执行,如果将来发现卡顿,可以再改成后台线程抓拍)
|
||||
/// </summary>
|
||||
private void OnTimerTick(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 单次抓拍
|
||||
using (IGrabResult grabResult = _camera.StreamGrabber.GrabOne(_intervalMs))
|
||||
{
|
||||
if (grabResult != null && grabResult.GrabSucceeded)
|
||||
if (grabResult == null || !grabResult.GrabSucceeded)
|
||||
{
|
||||
var converter = new PixelDataConverter { OutputPixelFormat = PixelType.BGRA8packed };
|
||||
byte[] buffer = new byte[grabResult.Width * grabResult.Height * 4];
|
||||
converter.Convert(buffer, grabResult);
|
||||
|
||||
var bitmap = BitmapSource.Create(
|
||||
grabResult.Width,
|
||||
grabResult.Height,
|
||||
96, 96,
|
||||
System.Windows.Media.PixelFormats.Bgra32,
|
||||
null,
|
||||
buffer,
|
||||
grabResult.Width * 4);
|
||||
|
||||
string file = Path.Combine(_saveFolder, $"Timed_{DateTime.Now:yyyyMMdd_HHmmss_fff}.png");
|
||||
SaveImage(bitmap, file);
|
||||
StatusTextBlock.Text = "状态:已保存 " + System.IO.Path.GetFileName(file);
|
||||
UpdateUiState("状态:抓拍失败(无效图像)");
|
||||
return;
|
||||
}
|
||||
|
||||
int width = grabResult.Width;
|
||||
int height = grabResult.Height;
|
||||
int stride = width * 4;
|
||||
int bufferSize = stride * height;
|
||||
|
||||
// 每次使用局部 buffer,避免多线程和尺寸问题
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
_converter.Convert(buffer, grabResult);
|
||||
|
||||
var bitmap = BitmapSource.Create(
|
||||
width,
|
||||
height,
|
||||
96,
|
||||
96,
|
||||
System.Windows.Media.PixelFormats.Bgra32,
|
||||
null,
|
||||
buffer,
|
||||
stride);
|
||||
|
||||
string filePath = Path.Combine(
|
||||
_saveFolder,
|
||||
$"Timed_{DateTime.Now:yyyyMMdd_HHmmss_fff}.png");
|
||||
|
||||
SaveImage(bitmap, filePath);
|
||||
|
||||
UpdateUiState("状态:已保存 " + System.IO.Path.GetFileName(filePath));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusTextBlock.Text = "状态:抓拍失败 - " + ex.Message;
|
||||
UpdateUiState("状态:抓拍失败 - " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +228,7 @@ namespace WpfApp1
|
||||
{
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(bitmap));
|
||||
|
||||
using var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
|
||||
encoder.Save(stream);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user