C#调用摄像头拍照的三种API方案

在 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 程序。

  1. 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; // 复制到剪贴板
}
  1. 初始化与拍照逻辑

在 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 类,且便于后续进行图像处理(如二维码识别)。

  1. 环境准备

需通过 NuGet 安装 AForge.Video 和 AForge.Video.DirectShow 包。

  1. 核心代码实现

此方案无需手动处理 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();
        }
    }
}

关键技术点说明

  1. 权限管理:在 Windows 10/11 环境下,应用程序首次访问摄像头可能需要用户在系统设置中授予"隐私 - 相机"权限,否则 API 调用会失败或返回黑屏 。
  2. 线程安全 :视频流通常在独立线程中运行(如 AForge 的 NewFrame 事件),在更新 WinForm 控件(如 PictureBox)时必须使用 Invoke 或 BeginInvoke 避免跨线程异常 。
  3. 资源释放 :无论是 API 句柄还是 DirectShow 对象,必须在程序退出或窗口关闭时显式断开连接(如 WM_CAP_DRIVER_CONNECT 设为 0 或调用 SignalToStop),否则摄像头指示灯可能常亮且无法被其他程序占用 。

参考来源

相关推荐
Msir_Modern6 小时前
Noita 元胞沙盒笔记
算法·unity·c#·游戏引擎·元胞自动机
星之擎8 小时前
Task 很优雅的取消方式是什么?C# 里面。
开发语言·c#
czhc11400756639 小时前
一周概念复盘:预热、漂移与门禁
c#
云中飞鸿10 小时前
.Net和C#有什么关系?(从C++视角观察)
开发语言·c#·.net
淡海水12 小时前
07-04-源码-.NET Core GC清扫与压缩
unity·架构·c#·.netcore·gc·1024程序员节
zwm26988881520 小时前
hd 3#负一西送风,能远程启不能远程停
c#
Ai-_Man20 小时前
Meta Muse还是不可以导出左侧所有对话,只能点进去单条内容导出,方法不对吗?
开发语言·人工智能·ai·小程序·c#·1024程序员节
2401_885885041 天前
国际语音C#接口怎么写?.NET环境下实现国际语音验证码批量下发
开发语言·c#·.net
淡海水1 天前
07-03-源码-.NET Core GC标记与扫描
unity·c#·.netcore·gc