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

无需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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 04:03:43