You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Windows系统下用Python的ctypes捕获摄像头照片?

轻量摄像头捕获方案(C/C++实现 + Python ctypes调用)

Windows 平台实现

C/C++ 代码(编译为动态库)

以下代码使用Windows原生Video for Windows API实现摄像头捕获,仅依赖系统自带的avicap32.dll,编译后体积极小:

#include <windows.h>
#include <avicap.h>

// 回调函数:处理捕获的帧
LRESULT CALLBACK FrameCallback(HWND hWnd, LPVIDEOHDR lpVHdr) {
    // 将帧数据保存为BMP文件(仅支持24位RGB格式)
    FILE* fp = fopen("capture.bmp", "wb");
    if (fp) {
        BITMAPFILEHEADER bmfHeader;
        BITMAPINFOHEADER bmiHeader;

        // 填充BMP头信息
        bmfHeader.bfType = 0x4D42; // "BM"标识
        bmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + lpVHdr->dwBufferLength;
        bmfHeader.bfReserved1 = 0;
        bmfHeader.bfReserved2 = 0;
        bmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

        bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        bmiHeader.biWidth = 640; // 捕获分辨率,可按需调整
        bmiHeader.biHeight = -480; // 负数值表示从上到下的像素顺序
        bmiHeader.biPlanes = 1;
        bmiHeader.biBitCount = 24;
        bmiHeader.biCompression = BI_RGB;
        bmiHeader.biSizeImage = lpVHdr->dwBufferLength;
        bmiHeader.biXPelsPerMeter = 0;
        bmiHeader.biYPelsPerMeter = 0;
        bmiHeader.biClrUsed = 0;
        bmiHeader.biClrImportant = 0;

        fwrite(&bmfHeader, sizeof(BITMAPFILEHEADER), 1, fp);
        fwrite(&bmiHeader, sizeof(BITMAPINFOHEADER), 1, fp);
        fwrite(lpVHdr->lpData, lpVHdr->dwBufferLength, 1, fp);
        fclose(fp);
    }

    // 释放帧缓冲区并取消回调
    capSetCallbackOnFrame(hWnd, NULL);
    return 0;
}

// 导出函数:执行一次捕获
__declspec(dllexport) int CapturePhoto() {
    HWND hCapWnd = capCreateCaptureWindowA("Capture Window", WS_CHILD | WS_VISIBLE, 0, 0, 640, 480, NULL, 0);
    if (!hCapWnd) return -1;

    // 连接默认摄像头
    if (!capDriverConnect(hCapWnd, 0)) {
        DestroyWindow(hCapWnd);
        return -2;
    }

    // 设置捕获格式为640x480 24位RGB
    BITMAPINFO bmi = {0};
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = 640;
    bmi.bmiHeader.biHeight = 480;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 24;
    capSetVideoFormat(hCapWnd, &bmi, sizeof(BITMAPINFO));

    // 设置帧回调,捕获一帧后触发
    capSetCallbackOnFrame(hCapWnd, FrameCallback);

    // 开始捕获
    capCaptureSequenceNoFile(hCapWnd);

    // 等待捕获完成(简单延时,可优化为事件等待)
    Sleep(1000);

    // 断开连接并销毁窗口
    capDriverDisconnect(hCapWnd);
    DestroyWindow(hCapWnd);

    return 0;
}

编译命令(使用MinGW):

gcc -shared -o capture.dll capture.c -lavicap32

Python ctypes 调用示例

import ctypes

# 加载编译好的动态库
capture_lib = ctypes.CDLL("./capture.dll")

# 调用捕获函数
result = capture_lib.CapturePhoto()
if result == 0:
    print("照片已保存为 capture.bmp")
elif result == -1:
    print("创建捕获窗口失败")
elif result == -2:
    print("连接摄像头失败")

Linux 平台实现

C/C++ 代码(编译为共享库)

以下代码使用Linux原生V4L2接口实现摄像头捕获,仅依赖系统库,编译后体积小巧:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <sys/mman.h>

#define DEVICE_PATH "/dev/video0"
#define WIDTH 640
#define HEIGHT 480

// 保存BMP文件
int save_bmp(const char* filename, unsigned char* data, int width, int height) {
    FILE* fp = fopen(filename, "wb");
    if (!fp) return -1;

    int row_size = (width * 3 + 3) & ~3; // BMP行对齐到4字节
    int image_size = row_size * height;

    BITMAPFILEHEADER bmfHeader = {0};
    BITMAPINFOHEADER bmiHeader = {0};

    bmfHeader.bfType = 0x4D42;
    bmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + image_size;
    bmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmiHeader.biWidth = width;
    bmiHeader.biHeight = -height;
    bmiHeader.biPlanes = 1;
    bmiHeader.biBitCount = 24;
    bmiHeader.biCompression = BI_RGB;
    bmiHeader.biSizeImage = image_size;

    fwrite(&bmfHeader, sizeof(BITMAPFILEHEADER), 1, fp);
    fwrite(&bmiHeader, sizeof(BITMAPINFOHEADER), 1, fp);

    // YUYV转RGB并写入(简化计算,可按需优化色彩精度)
    unsigned char* rgb_data = malloc(image_size);
    if (rgb_data) {
        unsigned char* yuyv = data;
        unsigned char* rgb = rgb_data;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x += 2) {
                unsigned char y0 = yuyv[0], u = yuyv[1], y1 = yuyv[2], v = yuyv[3];
                rgb[0] = y0 + 1.370705 * (v - 128);
                rgb[1] = y0 - 0.698001 * (v - 128) - 0.337633 * (u - 128);
                rgb[2] = y0 + 1.732446 * (u - 128);
                rgb[3] = y1 + 1.370705 * (v - 128);
                rgb[4] = y1 - 0.698001 * (v - 128) - 0.337633 * (u - 128);
                rgb[5] = y1 + 1.732446 * (u - 128);
                yuyv += 4;
                rgb += 6;
            }
            // 补全行对齐空白字节
            int padding = row_size - width * 3;
            memset(rgb, 0, padding);
            rgb += padding;
        }
        fwrite(rgb_data, image_size, 1, fp);
        free(rgb_data);
    }

    fclose(fp);
    return 0;
}

// 导出函数:执行一次捕获
__attribute__((visibility("default"))) int CapturePhoto() {
    int fd = open(DEVICE_PATH, O_RDWR);
    if (fd == -1) return -1;

    // 设置视频格式
    struct v4l2_format fmt = {0};
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = WIDTH;
    fmt.fmt.pix.height = HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    fmt.fmt.pix.field = V4L2_FIELD_NONE;
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
        close(fd);
        return -2;
    }

    // 请求缓冲区
    struct v4l2_requestbuffers req = {0};
    req.count = 1;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
        close(fd);
        return -3;
    }

    // 映射缓冲区
    struct v4l2_buffer buf = {0};
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = 0;
    if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
        close(fd);
        return -4;
    }

    unsigned char* buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
    if (buffer == MAP_FAILED) {
        close(fd);
        return -5;
    }

    // 入队缓冲区
    if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
        munmap(buffer, buf.length);
        close(fd);
        return -6;
    }

    // 开始捕获
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
        munmap(buffer, buf.length);
        close(fd);
        return -7;
    }

    // 获取捕获帧
    if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
        ioctl(fd, VIDIOC_STREAMOFF, &type);
        munmap(buffer, buf.length);
        close(fd);
        return -8;
    }

    // 保存为BMP
    save_bmp("capture.bmp", buffer, WIDTH, HEIGHT);

    // 停止捕获并清理资源
    ioctl(fd, VIDIOC_STREAMOFF, &type);
    munmap(buffer, buf.length);
    close(fd);

    return 0;
}

编译命令:

gcc -shared -fPIC -o libcapture.so capture.c

Python ctypes 调用示例

import ctypes

# 加载共享库
capture_lib = ctypes.CDLL("./libcapture.so")

# 调用捕获函数
result = capture_lib.CapturePhoto()
if result == 0:
    print("照片已保存为 capture.bmp")
elif result == -1:
    print("打开摄像头设备失败")
elif result == -2:
    print("设置视频格式失败")
elif result == -3:
    print("请求缓冲区失败")
elif result == -4:
    print("查询缓冲区失败")
elif result == -5:
    print("映射缓冲区失败")
elif result == -6:
    print("入队缓冲区失败")
elif result == -7:
    print("启动捕获失败")
elif result == -8:
    print("获取帧失败")

内容的提问来源于stack exchange,提问作者LuckyCoder3607

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 07:02:04