OpenCV 与 Webcam 结合

保存时间:2026/4/2 10:55:01


OpenCV 与 Webcam 结合
内容由豆包 AI 生成
我现在 Ubuntu 就是 Linux 下的话, OpenCV 和 Webcam 结合需要额外添加什么样驱动吗?还是说,就是说有一个所谓的 Linux 的一个动态库吧,忘掉名字了。但是可以直接去获取摄像头的照片或视频。
在 Ubuntu 下将 OpenCV 与 Webcam 结合,通常不需要额外安装特定的摄像头驱动,因为 Linux 系统一般自带了常见摄像头的驱动。
OpenCV 可以通过调用系统的视频捕捉接口来访问摄像头,它依赖于一些动态库,比如libv4l(Video for Linux 2)库。libv4l是 Linux 下用于视频设备访问的库,OpenCV 会借助它来与摄像头进行交互,从而获取摄像头的照片或视频。
要在 Ubuntu 上使用 OpenCV 与Webcam,需确保安装了 OpenCV 库以及相关的开发包,还可能需要安装libv4l-dev等相关依赖,以提供对视频设备的支持。安装完成后,就可以使用 OpenCV 的相关函数来打开摄像头、读取视频帧等操作。
对,我说的意思就是这个。我现在额外需要做什么?比如说你给一个最简单的,使用 Linux V4L 直接获取摄像头的一个照片,一个 snapshot 快照的这种 Hello World 程序吧。
以下是一个使用 Linux 的 Video4Linux2(V4L2)接口获取摄像头快照(照片)的简单 C 语言示例代码。在运行代码之前,确保你已经安装了libv4l-dev库(可以通过sudo apt-get install libv4l-dev命令安装)。
c
运行
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/videodev2.h>

#define WIDTH 640
#define HEIGHT 480
#define BUFFER_COUNT 4

struct buffer {
    void   *start;
    size_t length;
};

int main(int argc, char **argv)
{
    int fd;
    struct v4l2_format fmt;
    struct v4l2_requestbuffers req;
    struct buffer *buffers;
    int i;

    // 打开摄像头设备文件
    fd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
    if (fd < 0) {
        perror("无法打开摄像头设备");
        return -1;
    }

    // 设置视频格式为MJPEG
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = WIDTH;
    fmt.fmt.pix.height      = HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    fmt.fmt.pix.field       = V4L2_FIELD_NONE;
    if (-1 == ioctl(fd, VIDIOC_S_FMT, &fmt)) {
        perror("设置视频格式失败");
        close(fd);
        return -1;
    }

    // 请求分配缓冲区
    memset(&req, 0, sizeof(req));
    req.count  = BUFFER_COUNT;
    req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    if (-1 == ioctl(fd, VIDIOC_REQBUFS, &req)) {
        perror("请求缓冲区失败");
        close(fd);
        return -1;
    }

    // 映射缓冲区
    buffers = calloc(req.count, sizeof(*buffers));
    for (i = 0; i < req.count; ++i) {
        struct v4l2_buffer buf;

        memset(&buf, 0, sizeof(buf));
        buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index  = i;

        if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf)) {
            perror("查询缓冲区失败");
            free(buffers);
            close(fd);
            return -1;
        }

        buffers[i].length = buf.length;
        buffers[i].start = mmap(NULL, buf.length,
                                PROT_READ | PROT_WRITE, MAP_SHARED,
                                fd, buf.m.offset);
        if (MAP_FAILED == buffers[i].start) {
            perror("映射缓冲区失败");
            free(buffers);
            close(fd);
            return -1;
        }
    }

    // 将缓冲区放入队列
    for (i = 0; i < req.count; ++i) {
        struct v4l2_buffer buf;

        memset(&buf, 0, sizeof(buf));
        buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index  = i;

        if (-1 == ioctl(fd, VIDIOC_QBUF, &buf)) {
            perror("将缓冲区放入队列失败");
            for (i = 0; i < req.count; ++i)
                munmap(buffers[i].start, buffers[i].length);
            free(buffers);
            close(fd);
            return -1;
        }
    }

    // 开始捕获
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == ioctl(fd, VIDIOC_STREAMON, &type)) {
        perror("开始捕获失败");
        for (i = 0; i < req.count; ++i)
            munmap(buffers[i].start, buffers[i].length);
        free(buffers);
        close(fd);
        return -1;
    }

    // 从队列中取出一个缓冲区获取图像数据
    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof(buf));
    buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    if (-1 == ioctl(fd, VIDIOC_DQBUF, &buf)) {
        perror("取出缓冲区失败");
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        ioctl(fd, VIDIOC_STREAMOFF, &type);
        for (i = 0; i < req.count; ++i)
            munmap(buffers[i].start, buffers[i].length);
        free(buffers);
        close(fd);
        return -1;
    }

    // 将获取到的图像数据保存为文件(这里简单保存为jpeg格式)
    FILE *fp = fopen("snapshot.jpg", "wb");
    if (fp == NULL) {
        perror("无法打开文件保存图像");
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        ioctl(fd, VIDIOC_STREAMOFF, &type);
        for (i = 0; i < req.count; ++i)
            munmap(buffers[i].start, buffers[i].length);
        free(buffers);
        close(fd);
        return -1;
    }
    fwrite(buffers[buf.index].start, 1, buf.bytesused, fp);
    fclose(fp);

    // 将缓冲区重新放入队列
    if (-1 == ioctl(fd, VIDIOC_QBUF, &buf)) {
        perror("将缓冲区重新放入队列失败");
    }

    // 停止捕获
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == ioctl(fd, VIDIOC_STREAMOFF, &type)) {
        perror("停止捕获失败");
    }

    // 释放映射的缓冲区
    for (i = 0; i < req.count; ++i)
        munmap(buffers[i].start, buffers[i].length);
    free(buffers);

    // 关闭摄像头设备
    close(fd);

    return 0;
}
上述代码实现了以下功能:
  1. 打开摄像头设备。
  2. 设置视频格式为 MJPEG,分辨率为 640x480。
  3. 分配并映射视频缓冲区。
  4. 开始捕获,从队列中取出一个缓冲区获取图像数据。
  5. 将获取到的图像数据保存为"snapshot.jpg"文件。
  6. 停止捕获,释放缓冲区并关闭摄像头设备。
你可以使用gcc编译该代码,例如:gcc v4l2_snapshot.c -o v4l2_snapshot -lv4l2,然后运行生成的可执行文件v4l2_snapshot来获取摄像头的快照。
那么 OpenCV 能够跟这个紧密结合,意思是说能够把一个内存中的图片的内存地址指针传递给 OpenCV 的打开图片的这种接口吗?
在 OpenCV 中,可以将内存中的图片数据通过内存地址指针传递给 OpenCV 的相关接口进行处理。
OpenCV 提供了cv::Mat类来表示图像数据。可以使用cv::Mat的构造函数,根据已有的图像数据内存地址、图像宽度、高度、数据类型等信息来创建一个cv::Mat对象,从而让 OpenCV 能够对该图像进行处理。以下是一个简单示例:
cpp
运行
#include <opencv2/opencv.hpp>

int main() {
    // 假设已经有了图像数据的内存地址、宽度、高度和数据类型
    unsigned char* imageData = nullptr;  // 这里替换为实际的图像数据内存地址
    int width = 640;
    int height = 480;
    int channels = 3;  // 例如RGB图像有3个通道

    // 创建cv::Mat对象
    cv::Mat image(height, width, CV_8UC3, imageData);

    // 现在可以使用OpenCV的函数对image进行处理,例如显示图像
    cv::imshow("Image", image);
    cv::waitKey(0);

    return 0;
}
在实际应用中,你需要将imageData替换为从 V4L2 获取图像数据后的内存地址,并且根据实际情况设置正确的图像宽度、高度和数据类型。这样就可以将通过 V4L2 获取到的图像数据传递给 OpenCV 进行进一步的处理和分析。
问题就是这个 Linux V4L 这个库,它获取的快照,存取的格式是什么?一定是先转成文件 JPG 吗?还是说可以有一个内存的 R RPG 格式呢?
Linux V4L(Video for Linux)库获取的快照可以有多种格式,不一定先转成JPG文件
V4L支持多种图像格式,包括但不限于YUV、RGB等。通常,它可以直接在内存中以这些格式存储图像数据,而无需先转换为JPG等文件格式。例如,常见的RGB格式可以直接在内存中表示为一系列的像素值,每个像素由红、绿、蓝三个分量组成。具体使用哪种格式取决于摄像头的支持以及应用程序的设置。
比如说在刚才那个 Linux V4L2 里面,这个示例代码中,你现在是设置为 MJPEG 格式,那如果我要获得 YUV 格式,要怎么做呢?再把它转成 RGB 呢?
以下是在 Linux V4L2 中获取 YUV 格式图像数据并将其转换为 RGB 格式的示例代码:
cpp
运行
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <opencv2/opencv.hpp>

#define VIDEO_DEVICE "/dev/video0"

int main() {
    // 打开视频设备
    int fd = open(VIDEO_DEVICE, O_RDWR);
    if (fd < 0) {
        std::cerr << "无法打开视频设备" << std::endl;
        return -1;
    }

    // 设置视频格式为YUV420P
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 640;
    fmt.fmt.pix.height = 480;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420P;
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
        std::cerr << "无法设置视频格式" << std::endl;
        close(fd);
        return -1;
    }

    // 分配缓冲区
    struct v4l2_requestbuffers req;
    memset(&req, 0, sizeof(req));
    req.count = 4;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
        std::cerr << "无法分配缓冲区" << std::endl;
        close(fd);
        return -1;
    }

    // 将缓冲区映射到用户空间
    unsigned char* buffer[4];
    size_t buffer_size[4];
    for (int i = 0; i < 4; ++i) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
            std::cerr << "无法查询缓冲区" << std::endl;
            close(fd);
            return -1;
        }
        buffer_size[i] = buf.length;
        buffer[i] = (unsigned char*)mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        if (buffer[i] == MAP_FAILED) {
            std::cerr << "无法映射缓冲区" << std::endl;
            close(fd);
            return -1;
        }
    }

    // 将缓冲区放入队列
    for (int i = 0; i < 4; ++i) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
            std::cerr << "无法将缓冲区放入队列" << std::endl;
            close(fd);
            return -1;
        }
    }

    // 开始捕获
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
        std::cerr << "无法开始捕获" << std::endl;
        close(fd);
        return -1;
    }

    // 从队列中取出缓冲区,获取图像数据
    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
        std::cerr << "无法取出缓冲区" << std::endl;
        close(fd);
        return -1;
    }

    // 将YUV数据转换为RGB
    cv::Mat yuvImage(480 + 480 / 2, 640, CV_8UC1, buffer[buf.index]);
    cv::Mat rgbImage;
    cv::cvtColor(yuvImage, rgbImage, cv::COLOR_YUV420P2RGB);

    // 显示图像
    cv::imshow("RGB Image", rgbImage);
    cv::waitKey(0);

    // 停止捕获
    if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) {
        std::cerr << "无法停止捕获" << std::endl;
        close(fd);
        return -1;
    }

    // 释放缓冲区
    for (int i = 0; i < 4; ++i) {
        munmap(buffer[i], buffer_size[i]);
    }

    // 关闭视频设备
    close(fd);

    return 0;
}
这段代码首先打开视频设备,将其设置为 YUV420P 格式,分配并映射缓冲区,然后开始捕获图像数据。从队列中取出包含 YUV 数据的缓冲区后,使用 OpenCV 的cvtColor函数将 YUV 数据转换为 RGB 格式,并显示图像。最后,停止捕获,释放缓冲区并关闭视频设备。
请注意,代码中的VIDEO_DEVICE宏定义了要打开的视频设备节点,你可能需要根据实际情况进行修改。同时,确保编译时链接了 OpenCV 库和必要的系统库。
反过来说, OpenCV 除了接受 RGB 格式,还有没有其他的图形格式呢? YUV 等等的,有没有?
OpenCV 除了接受 RGB 格式外,还支持多种其他图像格式,包括 YUV 格式。
OpenCV 可以处理常见的 YUV 格式,如 YUV420P、YUV422 等。在将 YUV 数据读入 OpenCV 的Mat对象时,需要根据具体的 YUV 格式进行正确的设置和处理。例如,对于 YUV420P 格式,可以像前面代码中那样,根据图像的宽、高和通道数来创建Mat对象,将 YUV 数据正确地映射到Mat中。
此外,OpenCV 还支持灰度图格式(单通道图像),以及其他一些较少见的图像格式,如 Bayer 格式等,具体取决于应用场景和需求。
用 OpenCV 能不能做一些简单的用户界面?比如说一个按钮,那个点击按钮激活一些事件,调用一些什么函数这个要怎么做?能不能给个 Hello World 的这样样板程序看一下?
OpenCV 本身主要用于计算机视觉相关任务,但它也提供了一些简单的创建用户界面元素(如窗口、轨迹条等)的功能,虽然不像专门的 GUI 库(如 Qt、GTK 等)那样强大和丰富,但可以满足一些基本需求。下面是一个使用 OpenCV 创建带有按钮(这里通过轨迹条模拟按钮效果)的简单示例,点击 “按钮”(即改变轨迹条的值)可以激活事件并调用函数:
cpp
运行
#include <opencv2/opencv.hpp>
#include <iostream>

// 回调函数,当轨迹条的值改变时被调用
void onTrackbarChange(int pos, void* userdata) {
    cv::Mat* img = static_cast<cv::Mat*>(userdata);
    std::cout << "按钮被点击,当前值: " << pos << std::endl;
    // 这里可以添加你想要执行的操作,比如处理图像等
    // 例如,简单地在图像上绘制一些东西
    cv::putText(*img, "Button Clicked", cv::Point(100, 100), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
    cv::imshow("UI Example", *img);
}

int main() {
    // 创建一个空白图像用于显示界面
    cv::Mat img = cv::Mat::zeros(400, 600, CV_8UC3);

    // 创建窗口
    cv::namedWindow("UI Example");

    // 创建轨迹条(模拟按钮)
    int buttonValue = 0;
    cv::createTrackbar("Button", "UI Example", &buttonValue, 1, onTrackbarChange, &img);

    // 显示图像
    cv::imshow("UI Example", img);

    // 等待按键输入,这里使用'q'键退出程序
    while (cv::waitKey(1) != 'q') {}

    cv::destroyAllWindows();
    return 0;
}
在这个示例中:
  1. 首先创建了一个空白图像img,用于显示用户界面。
  2. 使用cv::namedWindow创建一个名为 “UI Example” 的窗口。
  3. 通过cv::createTrackbar创建一个轨迹条,将其命名为 “Button”,初始值为 0,最大值为 1。当轨迹条的值改变时,会调用onTrackbarChange回调函数。
  4. onTrackbarChange函数中,简单地打印了一条消息,并在图像上绘制了一些文本,然后更新显示图像。
  5. 最后通过cv::waitKey等待用户按键输入,当按下'q'键时退出程序,并使用cv::destroyAllWindows关闭所有窗口。
这个示例模拟了一个简单的按钮点击事件,你可以根据自己的需求在回调函数中添加更复杂的操作。
能不能创建一个窗口,然后它捕捉用户的按键事件,就是说某一个键盘的事件,比如说用户按了那个字母 N 这个键,那么它的那个 event 这个响应事件,这个代码,给一个样本代码看看。
以下是使用 OpenCV 创建一个窗口并捕捉用户键盘按键事件的示例代码。当用户按下字母 N 键时,会在控制台输出相应的提示信息:
cpp
运行
#include <opencv2/opencv.hpp>
#include <iostream>

int main() {
    // 创建一个空白图像用于显示窗口
    cv::Mat img = cv::Mat::zeros(400, 400, CV_8UC3);

    // 创建窗口
    cv::namedWindow("Key Event Example");

    // 显示图像(窗口必须有内容显示才能正常捕捉事件)
    cv::imshow("Key Event Example", img);

    while (true) {
        // 等待按键事件,参数1表示等待时间(单位:毫秒),这里设为1毫秒
        int key = cv::waitKey(1);

        // 判断按下的键是否为字母 'N'(注意区分大小写,这里是大写 'N')
        if (key == 'N') {
            std::cout << "You pressed the 'N' key!" << std::endl;
            // 在这里可以添加按下 'N' 键后要执行的其他操作,比如处理图像等
        }
        // 判断按下的键是否为 'q',用于退出循环和关闭窗口
        else if (key == 'q') {
            break;
        }
    }

    // 销毁所有窗口
    cv::destroyAllWindows();

    return 0;
}
在上述代码中:
  1. 首先创建了一个空白的图像 img,并使用 cv::namedWindow 创建了一个名为 "Key Event Example" 的窗口,然后通过 cv::imshow 将图像显示在窗口中。
  2. 使用一个 while 循环,通过 cv::waitKey(1) 不断检测是否有按键事件发生,cv::waitKey 的返回值为按下键的 ASCII 码。
  3. 在循环中判断按下的键是否为 N,如果是则在控制台输出提示信息,同时你可以在相应的 if 语句块中添加更多自定义的操作。
  4. 当检测到按下的键为 q 时,跳出循环并使用 cv::destroyAllWindows 关闭所有窗口,结束程序。