【问题标题】:how to capture images per second from video files by using opencv and c++?如何使用opencv和c++从视频文件中每秒捕获图像?
【发布时间】:2019-06-15 00:02:33
【问题描述】:

我在设计程序时遇到过,允许每秒从视频文件(avi、mp4 等)中捕获图像。

首先,我能够从视频文件中逐帧捕获图像。 其次,我能够同时分析同一文件夹中图像的像素颜色值,并将像素值保存在 txt 文件中。

这里我有一些问题。我现在正试图一次组合这两个代码,但我得到了奇怪的结果。我参考下面的代码。

int main(){
VideoCapture cap("D:\\data\\extra\\video200ul.avi"); 
if (!cap.isOpened())  
    return -1;

Ptr<BackgroundSubtractor> pMOG2 = createBackgroundSubtractorMOG2(20, 16, true);

Mat fg_mask;
Mat frame;
int count = 0;

String name, folder;

for (;;) {
    // Get frame
    cap >> frame; // get a new frame from video
    ++count;
    // Update counter

    // Background subtraction
    if (count % 2 == 0) {
        pMOG2->apply(frame, fg_mask, 0.001);

        cout << count << endl;

        if (!frame.empty()) {
            imshow("frame", frame);
            //      imshow("fg_mask", fg_mask);
        }
        // Save foreground mask
        name = "mask" + std::to_string(count) + ".png";
        //      string name = "mask_" + std::to_string(static_cast<long long>(count) + ".png";
        folder = imwrite("D:\\data\\extra\\" + name, frame);

    }
    anal(folder);
}   
waitKey(0);
return 0;

}

首先,我写的上面的代码是从视频文件中逐帧捕获图像。但是,如果我得到每帧的图像,我的文件夹中会有很多图片,所以我想每秒从视频文件中捕获一张图像。我尝试使用 CV_CAP_PROP_POS_MSEC 代替 cap

其次,当我将此代码合并到我在下面编写的另一个代码时,它显示了一些错误消息,例如“libpng 警告图像宽度、长度、数据在 ihdr 中为零。”

int anal(String folder) {

folder = "D:\\data\\extra\\*.png"; 
vector<String> filenames;

glob(folder, filenames);

cv::Mat ori_image;

for (size_t i = 0; i < filenames.size(); ++i) {

    ori_image = imread(filenames[i], IMREAD_COLOR);

    if (ori_image.empty()) {
        cout << "Check your file again." << std::endl;
        return -1;
    }

    rectangle(ori_image, Point(215, 98), Point(245, 110), Scalar(0, 255, 255), 1); 

    imshow("Original Image", ori_image);

    cv::Scalar sums;
    sums = cv::sum(ori_image);

    double totalSum = sums[0] + sums[1] + sums[2];

    if (totalSum <= 0) {
        cout << "$$ RGB percentage $$" << " \n\n";
        cout << "R: " << 100.0 / 3 << " % \n";
        cout << "G: " << 100.0 / 3 << " % \n";
        cout << "B: " << 100.0 / 3 << " % \n\n";
    }
    else {
        cout << "$$ RGB percentage $$" << " \n\n"; // red value
        cout << "R: " << sums[2] / totalSum * 100 << " % \n"; // red value
        cout << "G: " << sums[1] / totalSum * 100 << " % \n"; // green value
        cout << "B: " << sums[0] / totalSum * 100 << " % \n\n"; // blue value
    }

}

当我准备上面的代码时,我尝试计算视频中所有捕获图像的红色、蓝色、绿色百分比。但是,当我将这两个代码分开并运行它们时,它们工作正常,但是如果我将它们合并在一起,它会显示错误消息。

我想结合这两个代码来分析每秒捕获的视频图像的颜色值。

请帮我解决这个问题。

提前谢谢你。

------------编辑部分------------

我使用了你的修改版本并应用到我更新的代码中,

void imageAnalysis(std::string folder, cv::Mat frame){
cv::Mat ori_image = frame.clone();
std::string path = folder;
cv::rectangle(ori_image, Point(215, 105), Point(245, 120), Scalar(0, 255, 255), 1);

cv::imshow("Original Image", ori_image);
cv::waitKey(1);


String folder = "D:\\data\\dfdf\\*.png";
vector<String> filenames;
cv::glob(path, filenames);

for (size_t t = 0; t < filenames.size(); t++) {
    ori_image = imread(filenames[t], IMREAD_COLOR); // ori_image

    if (ori_image.empty()) {    //ori_image
        cout << "Check your file again." << "\n";
        break;
        //return -1;
    }

    rectangle(ori_image, Point(215, 105), Point(245, 120), Scalar(0, 255, 255), 1); 
    imshow("Original Image", ori_image);
    cv::waitKey(1);

    Mat image_HSV;
    cvtColor(ori_image, image_HSV, CV_BGR2HSV);

    double h = 0.0;
    double s = 0.0;
    double v = 0.0;

    int col = image_HSV.cols; // 480
    int row = image_HSV.rows; // 272

    int corow = ((col - 235) - 215) * ((row - 152) - 108);  

    Mat mask;
    inRange(image_HSV, Scalar(100, 0, 0), Scalar(100, 255, 255), mask); // convert binary
    image_HSV.setTo(Scalar(0, 0, 0), mask);

    for (int i = 108; i < row - 152; i++) {     
        for (int j = 215; j < col - 235; j++) {
            Vec3b hsv = image_HSV.at<cv::Vec3b>(i, j);

            h += (int)(hsv.val[0]);
            s += (int)(hsv.val[1]);
            v += (int)(hsv.val[2]);

            if (hsv[0] != 100) {
                hsv[0] = 0;
                hsv[1] = 0;
                hsv[2] = 0;
            }
        }
    }

    cout << "$$ Hue(H), Saturation(S), Brightness(V) $$" << filenames[t] << " !! \n\n";
    cout << "H: " << h / corow * 360 / 180 << " % \n";  // 
    cout << "S: " << s / corow * 100 / 255 << " % \n";
    cout << "V: " << v / corow * 100 / 255 << " % \n\n";

    std::ofstream file("D:\\data\\dfdf\\result_4.txt", std::ios_base::app);
    file << v / corow * 100 / 255 << " \n"; // v value
    file.close();
}   

}

你可以看到 imageAnalysis() 函数,我添加了 std::string 文件夹作为从视频剪辑中提取图像的路径。但是,当我应用此代码时,我得到了如下所示的非常奇怪的结果..

enter image description here

我以为我应该从每 24 个图像中获取颜色值,但正如您在上面看到的结果,我从所有图像中以随机顺序获取颜色值。

提前谢谢你。

学习如何高效地编码真是太好了!

【问题讨论】:

  • 您应该使用堆栈跟踪编写完整的错误。很难阅读大量代码并预见可能的问题。在这种情况下,我猜您正在保存一些空图像?您应该在第一个代码中包含 if(frame.empty()) break; 之类的内容,以避免出现空图像问题。另外,CV_CAP_PROP_POS_MSEC 有什么不适用的?这不是cap &gt;&gt; frame 的替代品。还有一件事,真的需要图像并重新加载它们,将图像传递给其他函数不是更好吗?
  • 嗨 api55,非常感谢您的评论。我需要每秒保存图像,因为我需要呈现一些颜色退化的图像。所以,这就是我从视频文件中保存图像的原因。然后,我尝试计算每张图像的 RGB 百分比。所以,这就是为什么我需要每秒保存图像。其实我可以分别操作这两个代码,但是我想节省时间直接从视频文件中获取颜色数据。
  • 我很感激我完全忘记添加“if(frame.empty()) break;”如你所说。
  • 此外,当我将 CV_CAP_PROP_POS_MSEC 应用于我的代码时,我发现了一些错误消息,例如“CV_CAP_PROP_POS_MSEC 未定义”。所以,我试图在stackoverflow上用类似的问题来解决这个问题,但我找不到合适的答案。
  • 而我昨天处理了这段代码,我没有错误信息并且运行良好,但是我仍然有一些问题。因此,我使用“if (count % 24 == 0)”语句每秒节省 24 帧。换句话说,我能够从视频文件中每秒保存图像。所以,我解决了我的第一个问题。但是,当我使用函数“anal(folder);”传输这些数据时,函数 anal() 会读取所有帧的 RGB 颜色。在这种情况下,我想提取每 24 帧(24、48、96、......)的颜色值。这是我的问题。

标签: c++ visual-studio opencv video


【解决方案1】:

只是为了清除您在 cmets 中提到的关于 CV_CAP_PROP_POS_MSEC 的错误:

当我将 CV_CAP_PROP_POS_MSEC 应用于我的代码时,我发现了一些错误 诸如“CV_CAP_PROP_POS_MSEC 未定义”之类的消息。

很多常量值都在 OpenCV 的范围内。这意味着,CV_CAP_PROP_POS_MSEC 未定义,但 cv::CV_CAP_PROP_POS_MSEC 已定义。您也可以通过cv::CAP_PROP_FPS 获取FPS。

现在对于您的代码,我实际上会做一些不需要保存和加载图像的事情,而是传递要处理的图像,如下所示:

#include "opencv2/opencv.hpp"
#include <iostream>

int main(){
  cv::VideoCapture cap("D:\\data\\extra\\video200ul.avi"); 
  if (!cap.isOpened())  
  {
    std::cout << "Could not open video" << std::endl;
    return -1;
  }

  cv::Ptr<cv::BackgroundSubtractor> pMOG2 = cv::createBackgroundSubtractorMOG2(20, 16, true);

  cv::Mat fg_mask, frame;
  int count = 0;
  const int fps = 24; // you may set here the fps or get them from the video

  std::string name, folder;

  // with cap.read you can check already if the video ended
  while (cap.read(frame)) {
    // Background subtraction
    if (count % fps == 0) {
        pMOG2->apply(frame, fg_mask, 0.001);
        // Save foreground mask
        name = "mask" + std::to_string(count) + ".png";
        bool result = cv::imwrite("D:\\data\\extra\\" + name, frame);
        imageAnalysis(frame, count);
    }
    // at the end of the loop so that the first image is used
    ++count;
  }   
  cv::waitKey(0);
  return 0;
}

imageAnalysis函数定义为:

// You can pass cv::Mat as value, it is almost like a smart pointer
void imageAnalysis(cv::Mat frame, int count)
{
  cv::Mat ori_image = frame.clone();
  cv::rectangle(ori_image, Point(215, 98), Point(245, 110), Scalar(0, 255, 255), 1); 
  // each imshow needs a waitKey to update the window in which it is being shown
  cv::imshow("Original Image", ori_image);
  cv::waitKey(1);

  cv::Scalar sums;
  sums = cv::sum(ori_image);
  double totalSum = sums[0] + sums[1] + sums[2];

  std::ofstream output("D:\\data\\extra\\mask" + std::to_string(count) + ".txt");
  if (totalSum <= 0) 
  {
    std::cout << "$$ RGB percentage $$" << std::endl << std::endl;
    std::cout << "R: " << 100.0 / 3 << std::endl;
    std::cout << "G: " << 100.0 / 3 << std::endl;
    std::cout << "B: " << 100.0 / 3 << std::endl << std::endl;
    output << "$$ RGB percentage $$" << std::endl << std::endl;
    output << "R: " << 100.0 / 3 << std::endl;
    output << "G: " << 100.0 / 3 << std::endl;
    output << "B: " << 100.0 / 3 << std::endl << std::endl;
  }
else {
    std::cout << "$$ RGB percentage $$" << std::endl << std::endl;
    std::cout << "R: " << sums[2] / totalSum * 100 << std::endl; // red value
    std::cout << "G: " << sums[1] / totalSum * 100 << std::endl; // green value
    std::cout << "B: " << sums[0] / totalSum * 100 << std::endl << std::endl; // blue value
    output  << "$$ RGB percentage $$" << std::endl << std::endl;
    output  << "R: " << sums[2] / totalSum * 100 << std::endl; // red value
    output  << "G: " << sums[1] / totalSum * 100 << std::endl; // green value
    output  << "B: " << sums[0] / totalSum * 100 << std::endl << std::endl; // blue value
  }
}

上面代码的一些cmets,我把cap &gt;&gt; frame换成了cap.read(frame)。它是相同的功能,但是如果它无法抓取图像,则后者会给出一个错误的布尔结果,例如视频结束时。我在最后更改计数添加,你得到帧 0,23,... 这样第一个也将被使用。最后,您应该使用命名空间cv::std:: 等。这只是最佳实践,它可以避免某些库可能出现的歧义和问题。

如果您不需要磁盘中的图像,而只需要分析,则删除保存部分并将每一帧传递给 imageAnalysis 函数,这样您可能会有更多数据用于统计。另外,考虑在函数中返回总和的 cv:Scalar,然后您可以对整秒或整个视频进行一些统计。

如果您有任何问题,请随时在 cmets 中提问。

【讨论】:

  • 非常感谢您的评论。它作为一个魅力!但是,我在这个程序中声明“保存和加载”代码的原因是我试图将 RGB 数据保存到 txt 文件中。因此,我更新了将数据保存在 txt 文件中的代码。而且,如果我不保存文件并加载,我无法在 txt 文件中写入颜色数据。因此,当我将数据保存在 txt 文件中时,所有图像的颜色数据都以随机顺序保存。这就是我想要解决的问题。因此,我尝试使用“保存和加载”功能来保存从视频中提取的图像。
  • @DavidS 您的意思是与图像同名但扩展名不同吗?这很简单,将 count 变量传递给 imageAnalysis 函数并在那里创建 txt 文件。它不应该是随机顺序,因为它是逐步进行的并且没有并行线程。我将更新答案以保存数据而不仅仅是打印它
  • @DavidS 顺便说一句,您的代码中的这一行:folder = imwrite("D:\\data\\extra\\" + name, frame); 是错误的。 imwrite 给出一个布尔值,因此文件夹将是布尔值到字符串的隐式转换。
  • 从您更新的代码中,我在这一行 std::ofstream output("D:\\data\\extra\\mask" + std::to_string(count) + ". txt", frame"); 它说“这个结构没有实例。”
  • @DavidS 这是一个错误,框架不去那里
猜你喜欢
  • 2020-03-10
  • 1970-01-01
  • 1970-01-01
  • 2023-01-27
  • 2014-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多