无需OpenCV,在C语言中获取摄像头RGB帧数组的实现方案
可行的实现方案:C语言无OpenCV获取摄像头RGB帧并存入自定义结构体
当然可以做到!不用OpenCV的话,核心思路是调用目标平台的原生视频捕获API——因为C语言本身没有跨平台的摄像头访问标准库,所以得针对Windows和Linux分别处理。下面我给你详细的实现思路和可运行的代码片段:
Windows平台:使用Media Foundation(推荐,Windows Vista及以上)
Media Foundation是微软官方推荐的新一代多媒体API,比老旧的DirectShow更稳定、兼容性更好。
步骤1:确认结构体定义
首先补充宽高字段方便后续处理,确保结构体满足需求:
typedef struct CameraFeed { int size; // 总字节数 = width * height * 3(RGB24格式) int width; int height; unsigned char* rgbValuesArray; } CameraFeed;
步骤2:核心捕获逻辑(简化示例)
这里只展示关键流程,完整实现需要处理Media Foundation的初始化、设备枚举和回调:
#include <mfapi.h> #include <mfidl.h> #include <mfreadwrite.h> // 辅助宏:释放COM对象 #define SafeRelease(p) { if(p) { (p)->Release(); (p) = NULL; } } // 回调类:用于接收视频帧数据 class FrameCallback : public IMFSourceReaderCallback { private: CameraFeed* m_feed; LONG m_refCount; public: FrameCallback(CameraFeed* feed) : m_feed(feed), m_refCount(1) {} // 获取到帧时触发的核心方法 STDMETHODIMP OnReadSample( HRESULT hrStatus, DWORD dwStreamIndex, DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample* pSample ) { if (SUCCEEDED(hrStatus) && pSample) { IMFMediaBuffer* pBuffer = NULL; BYTE* pFrameData = NULL; DWORD dataLength = 0; // 获取帧的连续缓冲区 pSample->ConvertToContiguousBuffer(&pBuffer); pBuffer->Lock(&pFrameData, NULL, &dataLength); // 首次捕获时分配内存,后续直接覆盖数据 if (m_feed->rgbValuesArray == NULL) { m_feed->size = dataLength; m_feed->rgbValuesArray = (unsigned char*)malloc(dataLength); } memcpy(m_feed->rgbValuesArray, pFrameData, dataLength); // 释放资源 pBuffer->Unlock(); SafeRelease(&pBuffer); SafeRelease(&pSample); } return S_OK; } // 以下是必须实现的接口空方法 STDMETHODIMP OnEvent(DWORD, IMFMediaEvent*) { return S_OK; } STDMETHODIMP OnFlush(DWORD) { return S_OK; } STDMETHODIMP QueryInterface(REFIID riid, void** ppv) { if (riid == IID_IUnknown || riid == IID_IMFSourceReaderCallback) { *ppv = static_cast<IMFSourceReaderCallback*>(this); AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_refCount); } STDMETHODIMP_(ULONG) Release() { ULONG count = InterlockedDecrement(&m_refCount); if (count == 0) delete this; return count; } }; // 初始化Media Foundation并启动捕获 HRESULT StartCameraCapture(CameraFeed* feed) { HRESULT hr = S_OK; IMFSourceReader* pReader = NULL; IMFAttributes* pAttrs = NULL; FrameCallback* pCallback = new FrameCallback(feed); // 初始化Media Foundation环境 hr = MFStartup(MF_VERSION); if (FAILED(hr)) goto cleanup; // 创建源阅读器属性,绑定回调函数 hr = MFCreateAttributes(&pAttrs, 1); hr = pAttrs->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, pCallback); // 枚举摄像头并创建源阅读器(简化为第一个可用设备) hr = MFCreateSourceReaderFromMediaSource(NULL, pAttrs, &pReader); // 设置输出格式为RGB24 GUID targetSubtype = MFVideoFormat_RGB24; hr = pReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, NULL); // 此处可补充设置分辨率、帧率等参数,省略细节 // 启动异步捕获 hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, NULL, NULL, NULL); cleanup: SafeRelease(&pAttrs); SafeRelease(&pReader); pCallback->Release(); return hr; }
Linux平台:使用V4L2(Video for Linux 2)
V4L2是Linux系统原生的视频捕获API,几乎所有Linux发行版都支持,是嵌入式Linux场景的首选方案。
步骤1:结构体定义(和Windows一致)
typedef struct CameraFeed { int size; // width * height * 3 int width; int height; unsigned char* rgbValuesArray; } CameraFeed;
步骤2:核心捕获逻辑(含YUV转RGB)
大多数摄像头默认输出YUV格式(比如YUV420),所以需要自行实现格式转换:
#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> // YUV420转RGB24的转换函数(简化版,追求精度可替换专业公式) void yuv420_to_rgb24(unsigned char* yuv_buf, unsigned char* rgb_buf, int width, int height) { int y_size = width * height; unsigned char* y = yuv_buf; unsigned char* u = yuv_buf + y_size; unsigned char* v = yuv_buf + y_size + y_size / 4; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int y_val = y[i * width + j]; int u_val = u[(i/2)*(width/2) + (j/2)]; int v_val = v[(i/2)*(width/2) + (j/2)]; // RGB转换公式 int r = y_val + 1.370705 * (v_val - 128); int g = y_val - 0.698001 * (v_val - 128) - 0.337633 * (u_val - 128); int b = y_val + 1.732446 * (u_val - 128); // 限制值在0-255范围内 r = r < 0 ? 0 : (r > 255 ? 255 : r); g = g < 0 ? 0 : (g > 255 ? 255 : g); b = b < 0 ? 0 : (b > 255 ? 255 : b); // 写入RGB缓冲区 rgb_buf[(i*width + j)*3] = r; rgb_buf[(i*width + j)*3 + 1] = g; rgb_buf[(i*width + j)*3 + 2] = b; } } } // 捕获一帧摄像头数据并存入结构体 int CaptureSingleFrame(CameraFeed* feed, const char* device_path) { int fd = open(device_path, O_RDWR); if (fd == -1) { perror("Failed to open camera device"); return -1; } // 设置视频格式为YUV420 struct v4l2_format fmt = {0}; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = 640; fmt.fmt.pix.height = 480; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; fmt.fmt.pix.field = V4L2_FIELD_NONE; if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { perror("Failed to set video format"); close(fd); return -1; } // 初始化结构体参数 feed->width = fmt.fmt.pix.width; feed->height = fmt.fmt.pix.height; feed->size = feed->width * feed->height * 3; feed->rgbValuesArray = (unsigned char*)malloc(feed->size); if (!feed->rgbValuesArray) { perror("Failed to allocate RGB buffer"); close(fd); return -1; } // 请求内存映射缓冲区 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) { perror("Failed to request buffers"); free(feed->rgbValuesArray); close(fd); return -1; } // 查询并映射缓冲区 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) { perror("Failed to query buffer"); free(feed->rgbValuesArray); close(fd); return -1; } unsigned char* yuv_buf = (unsigned char*)mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (yuv_buf == MAP_FAILED) { perror("Failed to mmap buffer"); free(feed->rgbValuesArray); close(fd); return -1; } // 启动视频流 enum v4l2_buf_type stream_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_STREAMON, &stream_type) == -1) { perror("Failed to start stream"); munmap(yuv_buf, buf.length); free(feed->rgbValuesArray); close(fd); return -1; } // 入队并取出缓冲区(获取帧数据) ioctl(fd, VIDIOC_QBUF, &buf); ioctl(fd, VIDIOC_DQBUF, &buf); // 转换YUV到RGB并存入结构体 yuv420_to_rgb24(yuv_buf, feed->rgbValuesArray, feed->width, feed->height); // 清理资源 ioctl(fd, VIDIOC_STREAMOFF, &stream_type); munmap(yuv_buf, buf.length); close(fd); return 0; }
关键注意事项
- 内存管理:使用完
CameraFeed结构体后,必须调用free(feed->rgbValuesArray)释放内存,避免内存泄漏。 - 格式兼容性:如果摄像头不支持直接输出RGB格式,一定要确保YUV转RGB的逻辑正确(上面的转换函数是简化版,可根据需求替换更精准的公式)。
- 错误处理:实际项目中要完善每个API调用的错误检查,避免程序崩溃或异常行为。
- 跨平台适配:如果需要跨平台运行,可以用条件编译(
#ifdef _WIN32/#ifdef __linux__)分别处理不同平台的代码。
内容的提问来源于stack exchange,提问作者Yuval Friedman
相关产品推荐
相关产品推荐

