AForge.video摄像头深入研究
By
admin
at 10 天前 • 0人收藏 • 70人看过
AForge 官方提供了一个专门用于显示视频的控件:VideoSourcePlayer(位于 AForge.Controls 命名空间中)。
它内部已经处理好了所有的跨线程、重绘和双缓冲问题,直接将 VideoDevice 赋值给它的 VideoSource 属性即可自动播放,不需要手动去写 OnNewFrame 的克隆和释放逻辑,开发起来更加省心

using AForge;
using AForge.Video;
using AForge.Video.DirectShow;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
VideoCaptureDevice VideoDevice;
// 新增:记录镜像状态的变量
private bool isMirrorX = false; // 左右镜像
private bool isMirrorY = false; // 上下镜像
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
FilterInfoCollection VideoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
if (VideoDevices.Count > 0)
{
VideoDevice = new VideoCaptureDevice(VideoDevices[0].MonikerString);
// 确保摄像头确实返回了支持的分辨率列表
if (VideoDevice.VideoCapabilities.Length > 0)
{
// 自动寻找最高分辨率和最高帧率的配置
var bestResolution = VideoDevice.VideoCapabilities
.OrderByDescending(c => c.FrameSize.Width * c.FrameSize.Height) // 优先按面积(像素总数)降序排
.ThenByDescending(c => c.MaximumFrameRate) // 分辨率相同时,按最大帧率降序排
.First(); // 取出排在第一位的(也就是最好的)
// 赋值给摄像头
VideoDevice.VideoResolution = bestResolution;
// 打印出来确认一下
Console.WriteLine($"已自动选择最佳画质: {bestResolution.FrameSize.Width} x {bestResolution.FrameSize.Height} @ {bestResolution.MaximumFrameRate} fps");
}
// 假设你拖了一个 VideoSourcePlayer 控件到窗体上,命名为 videoSourcePlayer1
videoSourcePlayer1.VideoSource = VideoDevice;
// 订阅控件的 NewFrame 事件
videoSourcePlayer1.NewFrame += VideoSourcePlayer1_NewFrame;
// 启动播放器 (不需要调用 VideoDevice.Start())
videoSourcePlayer1.Start();
}
}
private void VideoSourcePlayer1_NewFrame(object sender, ref Bitmap image)
{
// 确定翻转类型
RotateFlipType flipType = RotateFlipType.RotateNoneFlipNone;
if (isMirrorX && isMirrorY)
flipType = RotateFlipType.RotateNoneFlipXY;
else if (isMirrorX)
flipType = RotateFlipType.RotateNoneFlipX;
else if (isMirrorY)
flipType = RotateFlipType.RotateNoneFlipY;
// 如果需要镜像,直接对 ref 传入的 image 进行翻转
if (flipType != RotateFlipType.RotateNoneFlipNone)
{
image.RotateFlip(flipType);
}
// 注意:不要在这里调用 image.Dispose()!
// 也不需要写任何 BeginInvoke 代码。
// 处理完后,控件会自动把处理后的图画到界面上,并自己负责释放内存。
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// 确保播放器正在运行才去停止它
if (videoSourcePlayer1 != null && videoSourcePlayer1.IsRunning)
{
// 1. 发送停止信号(非阻塞,告诉后台线程准备退出)
videoSourcePlayer1.SignalToStop();
// 2. 等待后台线程安全结束当前帧的处理并关闭硬件连接
videoSourcePlayer1.WaitForStop();
}
}
// 提取一个公共的停止方法
// 左右镜像复选框的事件
private void chkMirrorX_CheckedChanged(object sender, EventArgs e)
{
isMirrorX = chkMirrorX.Checked;
}
// 上下镜像复选框的事件
private void chkMirrorY_CheckedChanged(object sender, EventArgs e)
{
isMirrorY = chkMirrorY.Checked;
}
}
}
3 个回复 | 最后更新于 6 天前
登录后方可回帖
为了实现与界面的完美解耦、支持多摄像头独立配置以及统一生命周期管理,我让AI封装了一个独立的类库
用起来就很简单了
using AForge; using AForge.Video; using AForge.Video.DirectShow; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApp2 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { // 示例:打开第一个摄像头 (索引 0),水平左右镜像 (2),画面塞进 panel1 AForgeCameraManager.InitCamera(0, 3, panel1); // 示例:假设你有第二个摄像头,打开它 (索引 1),不镜像 (0),画面塞进 panel2 AForgeCameraManager.InitCamera(1, 1, panel2); } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { AForgeCameraManager.CloseAllCameras(); } } }AForgeCameraManager.CS 类库代码如下:
using AForge.Controls; using AForge.Video.DirectShow; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; namespace WindowsFormsApp2 { public class AForgeCameraManager { // 静态列表:用于记录当前打开的所有摄像头播放器实例,方便统一关闭 private static List<VideoSourcePlayer> activePlayers = new List<VideoSourcePlayer>(); /// <summary> /// 1. 初始化摄像头并在指定控件中显示 /// </summary> /// <param name="deviceIndex">摄像头端口号(索引,从0开始)</param> /// <param name="mirrorType">镜像方向:0=不镜像, 1=上下, 2=左右, 3=上下左右</param> /// <param name="placeholder">占位控件(例如 Panel,用于决定摄像头画面的位置和大小)</param> public static void InitCamera(int deviceIndex, int mirrorType, Control placeholder) { // 1. 获取所有视频输入设备 FilterInfoCollection videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice); if (videoDevices.Count == 0) { MessageBox.Show("未检测到任何摄像头设备!", "提示"); return; } if (deviceIndex < 0 || deviceIndex >= videoDevices.Count) { MessageBox.Show($"找不到索引为 {deviceIndex} 的摄像头!", "提示"); return; } // 2. 实例化对应摄像头 VideoCaptureDevice videoDevice = new VideoCaptureDevice(videoDevices[deviceIndex].MonikerString); // 3. 自动寻找最佳画质 if (videoDevice.VideoCapabilities.Length > 0) { var bestResolution = videoDevice.VideoCapabilities .OrderByDescending(c => c.FrameSize.Width * c.FrameSize.Height) .ThenByDescending(c => c.MaximumFrameRate) .First(); videoDevice.VideoResolution = bestResolution; Console.WriteLine($"摄像头[{deviceIndex}] 选择画质: {bestResolution.FrameSize.Width}x{bestResolution.FrameSize.Height} @ {bestResolution.MaximumFrameRate}fps"); } // 4. 动态创建视频播放控件,解耦UI VideoSourcePlayer player = new VideoSourcePlayer(); player.VideoSource = videoDevice; // 设置停靠方式为填满占位控件 player.Dock = DockStyle.Fill; // 5. 解析并应用镜像设置 RotateFlipType flipType = RotateFlipType.RotateNoneFlipNone; switch (mirrorType) { case 1: flipType = RotateFlipType.RotateNoneFlipY; break; // 上下 case 2: flipType = RotateFlipType.RotateNoneFlipX; break; // 左右 case 3: flipType = RotateFlipType.RotateNoneFlipXY; break; // 上下左右 } // 只有需要翻转时才订阅事件,节省性能 if (flipType != RotateFlipType.RotateNoneFlipNone) { // 使用匿名委托(Lambda)绑定事件,这样每个摄像头都能独立记住自己的翻转方式,互不干扰 player.NewFrame += (object sender, ref Bitmap image) => { image.RotateFlip(flipType); }; } // 6. 将控件加入占位符,并启动 // 清空占位控件中可能残留的其他内容 placeholder.Controls.Clear(); placeholder.Controls.Add(player); // 记录到静态列表中 activePlayers.Add(player); // 启动播放 player.Start(); } /// <summary> /// 2. 关闭全部已知打开的摄像头 /// </summary> public static void CloseAllCameras() { foreach (var player in activePlayers) { if (player != null && player.IsRunning) { // 发送停止信号 player.SignalToStop(); // 阻塞等待当前帧处理完毕并安全释放硬件连接 player.WaitForStop(); } // 从 UI 占位控件中移除并释放资源 if (player != null && !player.IsDisposed) { player.Dispose(); } } // 清空追踪列表 activePlayers.Clear(); Console.WriteLine("所有摄像头已安全关闭。"); } } }完整工程: