Avant le traitement des images, les algorithmes traditionnels ou l’apprentissage en profondeur collectent toujours d’abord les images, ce qu’on appelle le flux pull. Il existe deux façons de résoudre le problème de l'extraction de flux. L'une consiste à utiliser directement opencv pour obtenir le flux, et l'autre consiste à utiliser ffmpeg pour obtenir le flux. Les deux méthodes pour extraire le flux sont présentées ci-dessous.
1. Opencv prend le flux directement
La méthode d'acquisition de flux d'Opencv utilise principalement la classe VideoCapture pour le traitement. VideoCapture fournit un ensemble complet de solutions pour lire les informations du flux vidéo. Les principales fonctions sont les suivantes :
VideoCapture a trois constructeurs :
- Constructeur sans aucun paramètre
- Constructeur avec une adresse de flux vidéo
- La
fonction constructeur isOpened() avec un index vidéo est principalement utilisée pour déterminer si l'adresse du flux est ouverte avec succès.
read() lit les données vidéo.
La fonction release() est utilisée pour libérer l'objet de classe.
Adresse de référence spécifique : https : //docs.opencv.org/4.0.0/d8/dfe/classcv_1_1VideoCapture.html
1.1Diffusion Python
Le processus principal est divisé en les étapes suivantes :
- Instanciez la classe VideoCapture par l'adresse du flux
- Déterminer si l'adresse du flux est ouverte avec succès
- Parcourez chaque image de flux de données et traitez-la
- Libérer l'objet instancié
- versioncv
def vedio2Img(vedio_path, save_path):
cap = cv2.VideoCapture(vedio_path)
fps = int(cap.get(cv2.CAP_PROP_FPS))
total_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)
count = 0
img_idx = 0
if not cap.isOpened():
return
while True:
success, frame = cap.read()
if success:
try:
count += 1
if count % fps == 0:
img_idx += 1
name = save_path.split('\\')[-1]
save_path1 = os.path.join(save_path, '{}_vedio_{}.jpg'.format(name, str(img_idx)))
save_img(save_path1, frame)
print('finish number {} img save'.format(img_idx))
cv2.waitKey(1)
except:
print('encounter some wrong')
continue
cap.release()
cv2.destroyAllWindows()
1.2 Flux d'extraction opencv C++
La façon dont C++ utilise opencv pour extraire des flux est fondamentalement la même qu'opencv (ps : la couche inférieure de python doit être implémentée en C++), donc son format d'implémentation est le suivant :
std::string vedio_path = "rtsp://admin:[email protected]/Streaming/Channels/11000";
cv::VideoCapture cap;
cap.open(vedio_path);
if (!cap.isOpened()) {
std::cout << "error about cap" << std::endl;
}
VideoFrameDecode videoframe;
cv::Mat frame;
while (cap.read(frame))
{
if (frame.empty()) {
break;
}
int w = frame.size().width;
int h = frame.size().height;
printf("h=%i,w=%i", h, w);
unsigned char* buffer = frame.data;
size_t stride = frame.step;
cv::Mat img = cv::Mat(h, w, CV_8UC3, (void*)buffer, stride);
cv::namedWindow("demo", cv::WINDOW_NORMAL);
cv::imshow("demo", img);
cv::waitKey(0);
}
cap.release();
cv::destroyAllWindows();
2. streaming ffmpeg (implémentation C++)
-
Téléchargez l'
adresse de téléchargement du package ffmpeg du package ffmpeg.
La version 5.1.2 téléchargée par le blogueur
-
Utilisez la configuration vs2022.
Ajoutez le chemin d'inclusion du package ffmpeg nouvellement téléchargé dans C/C++> Répertoire d'inclusion supplémentaire.
Ajoutez le chemin du fichier lib du package ffmpeg dans Linker-> Répertoire de bibliothèque supplémentaire.
Dans Linker-> Entrée-> Dépendances supplémentaires. Ajoutez le répertoire de bibliothèque lib requis et organisez-le comme suit :avcodec.lib avdevice.lib avfilter.lib avformat.lib avutil.lib swresample.lib swscale.lib
Si vous ne souhaitez pas configurer le répertoire du fichier bin dans ffmpeg dans la variable d'environnement, vous pouvez utiliser la méthode suivante pour le configurer temporairement : Utilisez
Path=D:\ffmpeg\bin;%PATH dans Debug->Environment pour utiliser temporairement.
Le blogueur a encapsulé la méthode de streaming dans une classe, et le code principal est le suivant :
Le fichier ffmpeg.h est le suivant :
#ifndef __FFMPEG_DECODE_H__
#define __FFMPEG_DECODE_H__
// Opencv
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
extern "C"
{
#include<libavutil/avutil.h>
#include<libavutil/imgutils.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include<libavdevice/avdevice.h>
};
struct VideoFrameDecode {
void* buffer; //֡帧的buffer指针(仅支持RGB格式)
int pitch; //图像一行的宽度
};
class ReadFfmpeg
{
public:
ReadFfmpeg(char* rtsppath);
~ReadFfmpeg();
void processOneFrame(cv::Mat &img);
private:
AVFormatContext* formatContext = nullptr;
int ret = -1;
int videoStreamIndex = -1;
AVCodecParameters* codecParameters = nullptr;
const AVCodec* codec = nullptr;
AVCodecContext* codecContext = nullptr;
AVPacket packet;
AVFrame* pFrameRGB;
uint8_t* buffer;
SwsContext* sws_ctx;
};
#endif
Le fichier ffmpeg.cpp spécifique implémenté est le suivant
#include "ReadFfmpeg.h"
#include <iostream>
#include<chrono>
#include<thread>
using namespace std;
ReadFfmpeg::ReadFfmpeg(char* rtsppath)
{
avformat_network_init();
AVDictionary* formatOptions = nullptr;
av_dict_set_int(&formatOptions, "buffer_size", 2 << 20, 0);
av_dict_set(&formatOptions, "rtsp_transport", "tcp", 0); //默认使用udp协议进行传输,会出现max delay reached. need to consume packet
av_dict_set_int(&formatOptions, "timeout", 5000000, 0);
formatContext = avformat_alloc_context();
ret = avformat_open_input(&formatContext, rtsppath, nullptr, &formatOptions);
if (ret != 0) {
std::cerr << "Failed to open RTSP stream." << std::endl;
}
ret = avformat_find_stream_info(formatContext, nullptr);
if (ret < 0) {
std::cerr << "Failed to find stream info." << std::endl;
}
for (unsigned int i = 0; i < formatContext->nb_streams; ++i) {
if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
break;
}
}
if (videoStreamIndex == -1) {
std::cerr << "Failed to find video stream." << std::endl;
}
codecParameters = formatContext->streams[videoStreamIndex]->codecpar;
codec = avcodec_find_decoder(codecParameters->codec_id);
if (codec == nullptr) {
std::cerr << "Failed to find video decoder." << std::endl;
}
codecContext = avcodec_alloc_context3(codec);
if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) {
std::cerr << "Failed to allocate codec context." << std::endl;
}
ret = avcodec_open2(codecContext, codec, nullptr);
if (ret < 0) {
std::cerr << "Failed to open codec." << std::endl;
}
pFrameRGB = av_frame_alloc();
buffer = (uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 1));
av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer, AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 1);
sws_ctx = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
codecContext->width, codecContext->height, AV_PIX_FMT_RGB24,
SWS_BILINEAR, nullptr, nullptr, nullptr);
ret = av_read_frame(formatContext, &packet);
if (ret < 0) {
std::cerr << "Failed to open packet." << std::endl;
}
}
ReadFfmpeg::~ReadFfmpeg()
{
avformat_network_deinit();
avcodec_free_context(&codecContext);
sws_freeContext(sws_ctx);
av_free(pFrameRGB);
av_free(buffer);
av_free(codecParameters);
avformat_close_input(&formatContext);
}
void ReadFfmpeg::processOneFrame(cv::Mat& img)
{
if (img.empty())
{
img = cv::Mat(codecContext->height, codecContext->width, CV_8UC3);
}
int ret = av_read_frame(formatContext, &packet);
if (ret >= 0) {
if (packet.stream_index == videoStreamIndex) {
avcodec_send_packet(codecContext, &packet);
AVFrame* avFrame = av_frame_alloc();
int res = avcodec_receive_frame(codecContext, avFrame);
if (res == 0) {
// Convert frame to RGB
sws_scale(sws_ctx, avFrame->data, avFrame->linesize, 0, codecContext->height, pFrameRGB->data, pFrameRGB->linesize);
img.data = pFrameRGB->data[0];
}
av_frame_free(&avFrame);
}
}
av_packet_unref(&packet);
}
void test() {
char* filename = (char*)"rtsp://admin:[email protected]:10000/Streaming/Channels/10000";
ReadFfmpeg* fmpeg = new ReadFfmpeg(filename);
cv::Mat img;
int nFrame = 0;
auto start = std::chrono::system_clock::now();
for (;;)
{
nFrame++;
fmpeg->processOneFrame(img);
if (nFrame % 100==0) {
nFrame = 0;
auto end = std::chrono::system_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "the fps is: " << static_cast<float>(100 / (duration.count() / 1000.0)) << std::endl;
start = end;
}
// Display frame
cv::namedWindow("RTSP Stream", cv::WINDOW_NORMAL);
cv::imshow("RTSP Stream", img);
cv::waitKey(1);
}
delete fmpeg;
}
Ce qui précède est une méthode très simple pour extraire des flux. Elle ne peut être utilisée que comme démonstration pour implémenter la lecture de flux. Si vous souhaitez obtenir un streaming et un traitement en temps réel, vous devez utiliser le multithread pour implémenter un thread pour lire le flux. données.Mettez les données dans la file d'attente et implémentez en même temps un thread pour lire les données du flux, lire les données de la file d'attente et exécuter en même temps.
annexe
En fait, opencv peut également utiliser ffmpeg pour extraire des flux, mais vous devez spécifier la version de ffmpeg lors de la compilation d'opencv.