目录
3. 创建WriteableBitmapHelper.cs类
1. 环境
Visual Studio 2019 + .NET Framework 4.8.1
2. NuGet导入依赖
OpenCvSharp4
OpenCvSharp4.Extensions
OpenCvSharp4.runtime.win
System.Management
3. 创建WriteableBitmapHelper.cs类
using System.IO;
using System.Windows;
using System.Windows.Media.Imaging;
namespace FaceDetectionDemo
{
public class WriteableBitmapHelper
{
//将Bitmap 转换成WriteableBitmap
public static WriteableBitmap BitmapToWriteableBitmap(System.Drawing.Bitmap src)
{
var wb = CreateCompatibleWriteableBitmap(src);
System.Drawing.Imaging.PixelFormat format = src.PixelFormat;
if (wb == null)
{
wb = new WriteableBitmap(src.Width, src.Height, 0, 0, System.Windows.Media.PixelFormats.Bgra32, null);
format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
}
BitmapCopyToWriteableBitmap(src, wb, new System.Drawing.Rectangle(0, 0, src.Width, src.Height), 0, 0, format);
return wb;
}
//创建尺寸和格式与Bitmap兼容的WriteableBitmap
public static WriteableBitmap CreateCompatibleWriteableBitmap(System.Drawing.Bitmap src)
{
System.Windows.Media.PixelFormat format;
switch (src.PixelFormat)
{
case System.Drawing.Imaging.PixelFormat.Format16bppRgb555:
format = System.Windows.Media.PixelFormats.Bgr555;
break;
case System.Drawing.Imaging.PixelFormat.Format16bppRgb565:
format = System.Windows.Media.PixelFormats.Bgr565;
break;
case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
format = System.Windows.Media.PixelFormats.Bgr24;
break;
case System.Drawing.Imaging.PixelFormat.Format32bppRgb:
format = System.Windows.Media.PixelFormats.Bgr32;
break;
case System.Drawing.Imaging.PixelFormat.Format32bppPArgb:
format = System.Windows.Media.PixelFormats.Pbgra32;
break;
case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
format = System.Windows.Media.PixelFormats.Bgra32;
break;
default:
return null;
}
return new WriteableBitmap(src.Width, src.Height, 0, 0, format, null);
}
//将Bitmap数据写入WriteableBitmap中
public static void BitmapCopyToWriteableBitmap(System.Drawing.Bitmap src, WriteableBitmap dst, System.Drawing.Rectangle srcRect, int destinationX, int destinationY, System.Drawing.Imaging.PixelFormat srcPixelFormat)
{
var data = src.LockBits(new System.Drawing.Rectangle(new System.Drawing.Point(0, 0), src.Size), System.Drawing.Imaging.ImageLockMode.ReadOnly, srcPixelFormat);
dst.WritePixels(new Int32Rect(srcRect.X, srcRect.Y, srcRect.Width, srcRect.Height), data.Scan0, data.Height * data.Stride, data.Stride, destinationX, destinationY);
src.UnlockBits(data);
}
public static BitmapImage ConvertWriteableBitmapToBitmapImage(WriteableBitmap wbm)
{
BitmapImage bmImage = new BitmapImage();
using (MemoryStream stream = new MemoryStream())
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(wbm));
encoder.Save(stream);
bmImage.BeginInit();
bmImage.CacheOption = BitmapCacheOption.OnLoad;
bmImage.StreamSource = stream;
bmImage.EndInit();
bmImage.Freeze();
}
return bmImage;
}
}
}
4. 编辑MainWindow.xaml.cs
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Management;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
namespace FaceDetectionDemo
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow
{
private VideoCapture capCamera;
private VideoWriter videoWriter;
private Mat matImage = new Mat();
private Thread cameraThread;
private Thread writerThread;
private CascadeClassifier haarCascade;
private WriteableBitmap writeableBitmap;
private Rectangle rectangle;
private Mat gray;
private Mat result;
private OpenCvSharp.Rect[] faces;
public List<string> CameraArray
{
get { return (List<string>)GetValue(CameraArrayProperty); }
set { SetValue(CameraArrayProperty, value); }
}
public static readonly DependencyProperty CameraArrayProperty =
DependencyProperty.Register("CameraArray", typeof(List<string>), typeof(MainWindow), new PropertyMetadata(null));
public int CameraIndex
{
get { return (int)GetValue(CameraIndexProperty); }
set { SetValue(CameraIndexProperty, value); }
}
public static readonly DependencyProperty CameraIndexProperty =
DependencyProperty.Register("CameraIndex", typeof(int), typeof(MainWindow), new PropertyMetadata(0));
public bool IsSave
{
get { return (bool)GetValue(IsSaveProperty); }
set { SetValue(IsSaveProperty, value); }
}
public static readonly DependencyProperty IsSaveProperty =
DependencyProperty.Register("IsSave", typeof(bool), typeof(MainWindow), new UIPropertyMetadata(IsSaveChanged));
private static void IsSaveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var mainWindow = d as MainWindow;
if (e.NewValue != null)
{
var save = (bool)e.NewValue;
if (save)
mainWindow.StartRecording();
else
mainWindow.StopRecording();
}
}
public bool IsFace
{
get { return (bool)GetValue(IsFaceProperty); }
set { SetValue(IsFaceProperty, value); }
}
public static readonly DependencyProperty IsFaceProperty =
DependencyProperty.Register("IsFace", typeof(bool), typeof(MainWindow), new UIPropertyMetadata(IsFaceChanged));
private static void IsFaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var mainWindow = d as MainWindow;
if (e.NewValue != null)
{
var save = (bool)e.NewValue;
if (save)
mainWindow.CreateFace();
else
mainWindow.CloseFace();
}
}
public MainWindow()
{
InitializeComponent();
Width = SystemParameters.WorkArea.Width / 1.5;
Height = SystemParameters.WorkArea.Height / 1.5;
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
InitializeCamera();
}
private void ComboBoxCamera_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (CameraArray.Count - 1 < CameraIndex)
return;
if (capCamera != null && cameraThread != null)
{
cameraThread.Abort();
StopDispose();
}
CreateCamera();
writeableBitmap = new WriteableBitmap(capCamera.FrameWidth, capCamera.FrameHeight, 0, 0, System.Windows.Media.PixelFormats.Bgra32, null);
imgViewport.Source = writeableBitmap;
}
private void btStop_Click(object sender, RoutedEventArgs e)
{
StopDispose();
btStop.IsEnabled = false;
}
protected override void OnClosing(CancelEventArgs e)
{
if (MessageBox.Show("是否关闭系统?", "询问", MessageBoxButton.OKCancel, MessageBoxImage.Question) != MessageBoxResult.OK)
{
e.Cancel = true;
return;
}
}
protected override void OnClosed(EventArgs e)
{
StopDispose();
}
private void btPlay_Click(object sender, RoutedEventArgs e)
{
btPlay.IsEnabled = false;
btStop.IsEnabled = true;
CreateCamera();
}
#region 方法
void CloseFace()
{
if (haarCascade != null)
{
haarCascade.Dispose();
haarCascade = null;
gray.Dispose();
gray = null;
result.Dispose();
result = null;
faces = null;
}
}
void CreateFace()
{
var facePath = System.IO.Path.Combine(System.Environment.CurrentDirectory, "Data/haarcascade_frontalface_default.xml");
if (!System.IO.File.Exists(facePath))
{
MessageBox.Show("缺少人脸检测文件。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
haarCascade = new CascadeClassifier(facePath);
}
private void InitializeCamera()
{
CameraArray = GetAllConnectedCameras();
}
List<string> GetAllConnectedCameras()
{
var cameraNames = new List<string>();
using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity WHERE (PNPClass = 'Image' OR PNPClass = 'Camera')"))
{
foreach (var device in searcher.Get())
{
cameraNames.Add(device["Caption"].ToString());
}
}
return cameraNames;
}
void CreateCamera()
{
capCamera = new VideoCapture(CameraIndex);
capCamera.Fps = 30;
cameraThread = new Thread(PlayCamera);
cameraThread.Start();
}
private void PlayCamera()
{
while (capCamera != null && !capCamera.IsDisposed)
{
capCamera.Read(matImage);
if (matImage.Empty()) break;
Dispatcher.Invoke(new Action(() =>
{
if (IsFace)
{
result = matImage.Clone();
gray = new Mat();
Cv2.CvtColor(result, gray, ColorConversionCodes.BGR2GRAY);
faces = haarCascade.DetectMultiScale(gray, 1.3);
if (faces.Length > 0)
{
Cv2.Rectangle(matImage, faces[0], Scalar.Green, 2);
}
result.Dispose();
}
}));
using (var img = BitmapConverter.ToBitmap(matImage))
{
var now = DateTime.Now;
var g = Graphics.FromImage(img);
var brush = new SolidBrush(System.Drawing.Color.Red);
System.Globalization.CultureInfo cultureInfo = new CultureInfo("zh-CN");
var week = cultureInfo.DateTimeFormat.GetAbbreviatedDayName(now.DayOfWeek);
g.DrawString($"{week} { now.ToString("yyyy年MM月dd日 HH:mm:ss ")} ", new System.Drawing.Font(System.Drawing.SystemFonts.DefaultFont.Name, System.Drawing.SystemFonts.DefaultFont.Size), brush, new PointF(0, matImage.Rows - 20));
brush.Dispose();
g.Dispose();
rectangle = new Rectangle(0, 0, img.Width, img.Height);
Dispatcher.Invoke(new Action(() =>
{
WriteableBitmapHelper.BitmapCopyToWriteableBitmap(img, writeableBitmap, rectangle, 0, 0, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
}));
img.Dispose();
};
Thread.Sleep(100);
}
}
private void StartRecording()
{
if (capCamera == null)
{
MessageBox.Show("未开启摄像机", "提示", MessageBoxButton.OKCancel, MessageBoxImage.Error);
return;
}
var videoFile = System.IO.Path.Combine(System.Environment.CurrentDirectory, "Video");
if (!System.IO.Directory.Exists(videoFile))
System.IO.Directory.CreateDirectory(videoFile);
var currentTime = System.IO.Path.Combine(videoFile, $"{DateTime.Now.ToString("yyyyMMddHHmmsshh")}.avi");
videoWriter = new VideoWriter(currentTime, FourCC.XVID, capCamera.Fps, new OpenCvSharp.Size(capCamera.FrameWidth, capCamera.FrameHeight));
writerThread = new Thread(AddCameraFrameToRecording);
writerThread.Start();
}
private void StopRecording()
{
if (videoWriter != null && !videoWriter.IsDisposed)
{
videoWriter.Release();
videoWriter.Dispose();
videoWriter = null;
}
}
private void AddCameraFrameToRecording()
{
var waitTimeBetweenFrames = 1_000 / capCamera.Fps;
var lastWrite = DateTime.Now;
while (!videoWriter.IsDisposed)
{
if (DateTime.Now.Subtract(lastWrite).TotalMilliseconds < waitTimeBetweenFrames)
continue;
lastWrite = DateTime.Now;
videoWriter.Write(matImage);
}
}
void StopDispose()
{
if (capCamera != null && capCamera.IsOpened())
{
capCamera.Dispose();
capCamera = null;
}
if (videoWriter != null && !videoWriter.IsDisposed)
{
videoWriter.Release();
videoWriter.Dispose();
videoWriter = null;
}
CloseFace();
btPlay.IsEnabled = true;
GC.Collect();
}
void CreateRecord()
{
cameraThread = new Thread(PlayCamera);
cameraThread.Start();
}
#endregion
}
}
5. 编辑MainWindow.xaml
<Window x:Class="FaceDetectionDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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"
xmlns:local="clr-namespace:FaceDetectionDemo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="500" Width="8*"/>
<ColumnDefinition Width="2*" MinWidth="200"/>
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Name="imgViewport"/>
<GridSplitter Grid.Column="0" HorizontalAlignment="Right" Width="2"/>
<GroupBox Header="Operation" Grid.Column="1" Margin="0,0,4,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" HorizontalAlignment="Left">
<CheckBox IsChecked="{Binding IsSave,RelativeSource={RelativeSource AncestorType=local:MainWindow}}"
VerticalAlignment="Center" Content="Save" Margin="0,4"/>
<CheckBox IsChecked="{Binding IsFace,RelativeSource={RelativeSource AncestorType=local:MainWindow}}"
VerticalAlignment="Center" Content="Face" Margin="0,4"/>
<ComboBox Name="ComboBoxCamera" ItemsSource="{Binding CameraArray,RelativeSource={RelativeSource AncestorType=local:MainWindow}}"
SelectedIndex="{Binding CameraIndex,RelativeSource={RelativeSource AncestorType=local:MainWindow}}"
SelectionChanged="ComboBoxCamera_SelectionChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Center">
<Button Name="btPlay" Content="Play" Click="btPlay_Click" IsEnabled="False"/>
<Button Name="btStop" Click="btStop_Click" Content="Stop" Margin="4,0"/>
</StackPanel>
</Grid>
</GroupBox>
</Grid>
</Window>
6. OpenCV人脸识别文件
将人脸识别文件haarcascade-frontalface-default.xml添加到项目bin/debug/Data路径下
源码:https://github.com/liugang198409/WpfDemo/tree/master/FaceDetectionDemo
视频:WPF编程--人脸识别学习Demo_哔哩哔哩_bilibili
OpenCV人脸识别文件haarcascade-frontalface-default.xml下载链接:https://download.csdn.net/download/liugang590/87423430