在 C# 中编写摄像头拍照程序,主要依赖以下三类 API 技术栈:Windows API (avicap32.dll) 、DirectShow/Media Foundation 以及 第三方封装库 (如 AForge.NET/Emgu CV)。不同方案适用于不同的开发场景,从底层控制到快速应用开发各有优劣 。
核心 API 技术方案对比
| 技术方案 | 核心 API/库 | 实现原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|---|
| Windows API | avicap32.dll (P/Invoke) |
通过 P/Invoke 调用 capCreateCaptureWindow 等函数创建捕获窗口并发送消息控制 。 |
无需额外引用,系统原生支持,轻量级。 | 代码繁琐,仅支持基础功能,难以处理高清或复杂编码。 | 简单拍照、老旧系统兼容、无外部依赖需求。 |
| DirectShow / WMF | DirectShow.net, Windows.Media.Capture |
利用 COM 组件构建过滤器图(Filter Graph)或直接调用 UWP 接口访问媒体流 。 | 功能强大,支持视频流精细控制、多设备切换、高清采集。 | 学习曲线陡峭,DirectShow 需注册 COM 组件,WMF 主要限于 UWP/.NET Core。 | 专业视频监控、直播推流、需要录制视频的场景。 |
| 第三方库 | AForge.NET, Emgu CV, OpenCvSharp |
基于底层 API 封装的高级 .NET 类库,提供面向对象的操作接口 。 | 开发效率极高,内置图像处理和识别算法,代码简洁。 | 需引入外部 DLL 依赖,增加发布包体积。 | 快速原型开发、涉及图像处理(如二维码识别)的项目。 |
方案一:使用 Windows API (avicap32.dll) 实现
这是最轻量级的方法,直接通过 P/Invoke 调用系统动态链接库。适合仅需简单拍照功能的 WinForm 程序。
- API 声明与常量定义
首先定义所需的 API 函数和消息常量。
csharp
using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;
public class CameraAPI
{
// 定义 avicap32.dll 中的核心函数
[DllImport("avicap32.dll", EntryPoint = "capCreateCaptureWindowA", CharSet = CharSet.Ansi)]
public static extern IntPtr capCreateCaptureWindowA(
string lpszWindowName, int dwStyle, int x, int y, int nWidth, int nHeight,
IntPtr hwndParent, int nID);
[DllImport("user32.dll", EntryPoint = "SendMessageA", CharSet = CharSet.Ansi)]
public static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
public static extern bool SetWindowPos(IntPtr hwnd, int hWndInsertAfter, int x, int y, int cx, int cy, int wFlags);
// 定义控制消息常量
public const int WM_USER = 0x0400;
public const int WS_CHILD = 0x40000000;
public const int WS_VISIBLE = 0x10000000;
public const int SWP_NOMOVE = 0x2;
public const int SWP_NOZORDER = 0x4;
public const int WM_CAP_START = WM_USER;
public const int WM_CAP_SET_CALLBACK_VIDEOSTREAM = WM_CAP_START + 6;
public const int WM_CAP_SET_CALLBACK_FRAME = WM_CAP_START + 5;
public const int WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
public const int WM_CAP_SET_PREVIEW = WM_CAP_START + 50;
public const int WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52;
public const int WM_CAP_GRAB_FRAME = WM_CAP_START + 60;
public const int WM_CAP_COPY = WM_CAP_START + 54; // 复制到剪贴板
}
- 初始化与拍照逻辑
在 WinForm 中创建一个 Panel 作为容器,将摄像头窗口嵌入其中,并通过发送消息控制拍照。
csharp
public partial class MainForm : Form
{ private IntPtr _hWndCamera; // 摄像头窗口句柄
private const int CAMERA_INDEX = 0; // 摄像头索引,通常为 0
public MainForm()
{
InitializeComponent();
InitCamera();
}
private void InitCamera()
{
// 1. 创建捕获窗口,父窗口为 panel1
_hWndCamera = CameraAPI.capCreateCaptureWindowA(
"WebCam",
CameraAPI.WS_CHILD | CameraAPI.WS_VISIBLE,
0, 0, panel1.Width, panel1.Height,
panel1.Handle, 0);
if (_hWndCamera != IntPtr.Zero)
{
// 2. 连接驱动
CameraAPI.SendMessage(_hWndCamera, CameraAPI.WM_CAP_DRIVER_CONNECT, CAMERA_INDEX, 0);
// 3. 设置预览模式
CameraAPI.SendMessage(_hWndCamera, CameraAPI.WM_CAP_SET_PREVIEW, 1, 0);
// 4. 设置帧率 (66ms ≈ 15fps)
CameraAPI.SendMessage(_hWndCamera, CameraAPI.WM_CAP_SET_PREVIEWRATE, 66, 0);
}
else
{
MessageBox.Show("无法启动摄像头,请检查设备连接。");
}
}
private void btnTakePhoto_Click(object sender, EventArgs e)
{
if (_hWndCamera == IntPtr.Zero) return;
// 1. 抓取当前帧到剪贴板
CameraAPI.SendMessage(_hWndCamera, CameraAPI.WM_CAP_GRAB_FRAME, 0, 0);
CameraAPI.SendMessage(_hWndCamera, CameraAPI.WM_CAP_COPY, 0, 0);
// 2. 从剪贴板获取图像并保存
if (Clipboard.ContainsImage())
{
Image photo = Clipboard.GetImage();
string savePath = $"photo_{DateTime.Now:yyyyMMdd_HHmmss}.bmp";
photo.Save(savePath, System.Drawing.Imaging.ImageFormat.Bmp);
MessageBox.Show($"拍照成功!已保存至:{savePath}");
// 可选:在 PictureBox 中显示
pictureBox1.Image = photo;
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
// 释放资源:断开驱动连接
if (_hWndCamera != IntPtr.Zero)
{
CameraAPI.SendMessage(_hWndCamera, CameraAPI.WM_CAP_DRIVER_CONNECT, 0, 0);
_hWndCamera = IntPtr.Zero;
}
base.OnFormClosing(e);
}
}
方案二:使用 AForge.NET 库(推荐用于现代开发)
AForge.NET 是 C# 中经典的开源计算机视觉库,它封装了 DirectShow 的复杂性,提供了更简洁的 VideoCaptureDevice 类,且便于后续进行图像处理(如二维码识别)。
- 环境准备
需通过 NuGet 安装 AForge.Video 和 AForge.Video.DirectShow 包。
- 核心代码实现
此方案无需手动处理 API 消息,直接通过事件订阅获取视频帧。
csharp
using AForge.Video;
using AForge.Video.DirectShow;
using System;
using System.Drawing;
using System.Windows.Forms;
public class AForgeCameraExample : Form
{
private VideoCaptureDevice _videoSource;
private FilterInfoCollection _videoDevices;
private Bitmap _currentFrame; public AForgeCameraExample()
{
InitializeComponent();
LoadDevices();
}
// 加载可用摄像头列表
private void LoadDevices()
{
_videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
if (_videoDevices.Count == 0)
{
MessageBox.Show("未找到摄像头设备。");
return;
}
// 选择第一个摄像头
_videoSource = new VideoCaptureDevice(_videoDevices[0].MonikerString);
_videoSource.NewFrame += VideoSource_NewFrame; // 订阅新帧事件
}
// 启动摄像头
private void btnStart_Click(object sender, EventArgs e)
{
if (_videoSource != null && !_videoSource.IsRunning)
{
_videoSource.Start();
}
}
// 处理新帧事件(用于预览)
private void VideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
// 克隆图像以避免线程冲突
_currentFrame = (Bitmap)eventArgs.Frame.Clone();
// 更新 UI (需跨线程调用)
if (pictureBox1.InvokeRequired)
{
pictureBox1.Invoke(new Action(() => pictureBox1.Image = _currentFrame));
}
else
{
pictureBox1.Image = _currentFrame;
}
}
// 拍照逻辑
private void btnTakePhoto_Click(object sender, EventArgs e)
{
if (_currentFrame != null)
{
string path = $"capture_{DateTime.Now:yyyyMMdd_HHmmss}.jpg";
// 保存高质量 JPG
_currentFrame.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);
MessageBox.Show($"照片已保存:{path}");
// 此处可直接对 _currentFrame 进行 QR 码解析等处理
}
else
{
MessageBox.Show("暂无图像数据,请先启动摄像头。");
}
}
// 停止并释放
private void btnStop_Click(object sender, EventArgs e)
{
if (_videoSource != null && _videoSource.IsRunning)
{
_videoSource.SignalToStop();
_videoSource.WaitForStop();
}
}
}
关键技术点说明
- 权限管理:在 Windows 10/11 环境下,应用程序首次访问摄像头可能需要用户在系统设置中授予"隐私 - 相机"权限,否则 API 调用会失败或返回黑屏 。
- 线程安全 :视频流通常在独立线程中运行(如 AForge 的
NewFrame事件),在更新 WinForm 控件(如 PictureBox)时必须使用Invoke或BeginInvoke避免跨线程异常 。 - 资源释放 :无论是 API 句柄还是 DirectShow 对象,必须在程序退出或窗口关闭时显式断开连接(如
WM_CAP_DRIVER_CONNECT设为 0 或调用SignalToStop),否则摄像头指示灯可能常亮且无法被其他程序占用 。