如何在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
相关产品推荐
相关产品推荐

