以目标检测为例分析mediapip流水线处理机制
算子 它是在MediaPipe框架中用于创建插件/算子 机制的基础
在MediaPipe中,插件是一种可扩展的计算模块,可以用于实现各种不同的计算功能。calculator_base.h 文件定义了一个基类,所有插件都需要继承这个基类,并实现其中的函数或方法。通过使用这个基类,MediaPipe可以统一管理插件的接口和功能,使得在创建复杂的多媒体处理程序时更加灵活和可扩展。插件可以像拼积木一样组合和排列,以实现不同的功能和效果。
calculator_base.h 文件通常会包含一些基本的函数和属性,例如插件的初始化、更新、清理等操作,以及插件之间的通信和数据交换等。这个基类为插件的实现提供了一个统一的框架和规范,使得开发者可以根据自己的需求和创意来创建自定义的插件,并将其集成到MediaPipe的多媒体处理程序中。
它 计算图里的每个node都是calculator,是计算图的逻辑计算的载体,一个calculator可以接受0或多个stream或side packet, 输出0或多个stream或side packet. Calculator需要继承相同的基类并实现所需要的接口,并且要在framework中进行注册,以便可以通过配置文件进行构建。
calculator_base.h 头文件,它定义了MediaPipe框架中用于创建插件/算子 机制的基础类。
// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_FRAMEWORK_COLLECTION_H_
#define MEDIAPIPE_FRAMEWORK_COLLECTION_H_
#include
#include
#include
下面将根据Graph数据流走向深入源码部分 需要一定mediapie基础知识储备
mediapipe源码中大量使用boost absel库等apii及智能指针提升性能或者增强鲁棒性
根据宏定义 MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER 决定调用 opecv cpu处理还是Gpu处理
GPU 渲染管线运行在opengl上下文GLContext openglThread中
absl::Status GpuBufferToImageFrameCalculator::Process(CalculatorContext* cc) {
if (cc->Inputs().Index(0).Value().ValidateAsType().ok()) {
cc->Outputs().Index(0).AddPacket(cc->Inputs().Index(0).Value());
return absl::OkStatus();
}
#ifdef HAVE_GPU_BUFFER
if (cc->Inputs().Index(0).Value().ValidateAsType().ok()) {
const auto& input = cc->Inputs().Index(0).Get();
#if MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER
std::unique_ptr frame =
CreateImageFrameForCVPixelBuffer(GetCVPixelBufferRef(input));
cc->Outputs().Index(0).Add(frame.release(), cc->InputTimestamp());
#else
helper_.RunInGlContext([this, &input, &cc]() {
auto src = helper_.CreateSourceTexture(input);
std::unique_ptr frame = absl::make_unique(
ImageFormatForGpuBufferFormat(input.format()), src.width(),
src.height(), ImageFrame::kGlDefaultAlignmentBoundary);
helper_.BindFramebuffer(src);
const auto info = GlTextureInfoForGpuBufferFormat(input.format(), 0,
helper_.GetGlVersion());
glReadPixels(0, 0, src.width(), src.height(), info.gl_format,
info.gl_type, frame->MutablePixelData());
glFlush();
cc->Outputs().Index(0).Add(frame.release(), cc->InputTimestamp());
src.Release();
});
#endif // MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER
return absl::OkStatus();
}
#endif // defined(HAVE_GPU_BUFFER)
return absl::Status(absl::StatusCode::kInvalidArgument,
"Input packets must be ImageFrame or GpuBuffer.");
}
…
节流图像流向下游流量控制。它穿过第一个传入的图像不变,并等待 tflitetensorstodetectioncalculator 下游图完成在它通过另一个之前生成相应的检测形象。所有在等待期间进入的图像都将被删除,从而限制了图像的数量在这个计算器和。之间的飞行图像的数目 tflitetensorstodetectioncalculator到1。这防止了中间的节点从传入的图像和数据排队过多,这导致增加延迟和内存使用,在实时移动应用程序中不需要。它还消除不必要的计算,例如,ImageTransformationCalculator可能会被拖到下游,如果后续的tfliteeconvertercalculator或tfliteinterencecalculator仍在忙处理之前的输入。
flow_limiter_calculator.cc
FlowLimiterCalculator类是MediaPipe框架中用于控制数据流的一个组件。它主要负责限制输入数据的流速,以避免模型过载或运算资源不足的情况。
在源代码中,FlowLimiterCalculator类通常会定义一个数据结构来存储和管理输入数据的流速限制信息。它包含以下内容:
数据缓冲区:用于存储输入数据,以便在下一帧图像可用之前进行运算。
计时器:用于计算输入数据到达的时间间隔,并根据设定的流速限制来决定是否将下一帧图像送入模型。
流速限制参数:这些参数可以设定输入数据的最大流速,例如每秒处理的帧数。
状态变量:用于记录当前处理的帧数和已处理的帧数,以便在达到流速限制时停止处理新的帧。
FlowLimiterCalculator类的核心功能如下:接收输入数据:每当有新的帧图像可用时,FlowLimiterCalculator会接收并存储在数据缓冲区中。
计算时间间隔:计时器会记录当前帧与上一帧之间的时间间隔。
判断是否达到流速限制:根据计时器记录的时间间隔和设定的流速限制参数,FlowLimiterCalculator会判断是否达到最大流速。如果达到流速限制,将停止处理新的帧,直到当前处理的帧数达到已处理的帧数为止。
处理帧:如果当前帧没有被丢弃(即未达到流速限制),FlowLimiterCalculator会将该帧送入模型进行运算处理。
更新状态变量:每次处理完一帧后,FlowLimiterCalculator会更新已处理的帧数状态变量,以便在达到流速限制时正确停止处理新的帧。
// Releases input packets allowed by the max_in_flight constraint.
absl::Status Process(CalculatorContext* cc) final {
options_ = tool::RetrieveOptions(options_, cc->Inputs());
// Process the FINISHED input stream.
Packet finished_packet = cc->Inputs().Tag(kFinishedTag).Value();
if (finished_packet.Timestamp() == cc->InputTimestamp()) {
while (!frames_in_flight_.empty() &&
frames_in_flight_.front() <= finished_packet.Timestamp()) {
frames_in_flight_.pop_front();
}
}
// Process the frame input streams.
for (int i = 0; i < cc->Inputs().NumEntries(""); ++i) {
Packet packet = cc->Inputs().Get("", i).Value();
if (!packet.IsEmpty()) {
input_queues_[i].push_back(packet);
}
}
// Abandon expired frames in flight. Note that old frames are abandoned
// when much newer frame timestamps arrive regardless of elapsed time.
TimestampDiff timeout = options_.in_flight_timeout();
Timestamp latest_ts = cc->Inputs().Get("", 0).Value().Timestamp();
if (timeout > 0 && latest_ts == cc->InputTimestamp() &&
latest_ts < Timestamp::Max()) {
while (!frames_in_flight_.empty() &&
(latest_ts - frames_in_flight_.front()) > timeout) {
frames_in_flight_.pop_front();
}
}
// Release allowed frames from the main input queue.
auto& input_queue = input_queues_[0];
while (ProcessingAllowed() && !input_queue.empty()) {
Packet packet = input_queue.front();
input_queue.pop_front();
cc->Outputs().Get("", 0).AddPacket(packet);
SendAllow(true, packet.Timestamp(), cc);
frames_in_flight_.push_back(packet.Timestamp());
}
// Limit the number of queued frames.
// Note that frames can be dropped after frames are released because
// frame-packets and FINISH-packets never arrive in the same Process call.
while (input_queue.size() > options_.max_in_queue()) {
Packet packet = input_queue.front();
input_queue.pop_front();
SendAllow(false, packet.Timestamp(), cc);
}
// Propagate the input timestamp bound.
if (!input_queue.empty()) {
Timestamp bound = input_queue.front().Timestamp();
SetNextTimestampBound(bound, &cc->Outputs().Get("", 0));
} else {
Timestamp bound =
cc->Inputs().Get("", 0).Value().Timestamp().NextAllowedInStream();
SetNextTimestampBound(bound, &cc->Outputs().Get("", 0));
if (cc->Outputs().HasTag(kAllowTag)) {
SetNextTimestampBound(bound, &cc->Outputs().Tag(kAllowTag));
}
}
ProcessAuxiliaryInputs(cc);
return absl::OkStatus();
}
code fragment
just flag tag
// Outputs a packet indicating whether a frame was sent or dropped.
void SendAllow(bool allow, Timestamp ts, CalculatorContext* cc) {
if (cc->Outputs().HasTag(kAllowTag)) {
cc->Outputs().Tag(kAllowTag).AddPacket(MakePacket(allow).At(ts));
}
}
调度器如何处理呢
image_transformation_calculator.cc
ImageTransformationCalculator 类是一个用于图像处理的类,它主要负责应用各种图像变换。这些变换可以包括旋转、缩放、剪切、扭曲等。此类通常用于图像增强、图像恢复以及计算机视觉任务。
图像操作功能:ImageTransformationCalculator 类应该具有能够读取、写入和处理图像的功能。这可能包括对图像进行解码和编码,处理图像文件,以及在内存中操作图像数据。
变换计算功能:此类应该具有能够计算和应用各种图像变换的功能。这可能包括旋转、缩放、剪切、扭曲、平移等变换。这些变换的计算可能需要使用一些数学和计算机视觉库,如OpenCV。
可配置性:此类可能具有一些配置选项,允许用户指定变换的类型、参数以及其他选项。这使得用户可以根据自己的需求定制变换的计算和应用方式。
线程安全性:此类可能需要支持多线程操作。这可能涉及到在多个线程之间共享图像数据和变换状态,以及同步访问共享资源的问题。
错误处理和异常处理:此类可能需要具有一些错误处理和异常处理的机制,以处理例如无法读取图像文件、无法应用某些变换等情况。
性能优化:由于图像处理可能是一个计算密集型的任务,因此此类可能需要使用一些性能优化技术来提高计算效率,例如使用并行计算、缓存等技术。
absl::Status ImageTransformationCalculator::Process(CalculatorContext* cc) {
// First update the video header if it is given, based on the rotation and
// dimensions specified as side packets or options. This will only be done
// once, so streaming transformation changes will not be reflected in
// the header.
if (cc->Inputs().HasTag(kVideoPrestreamTag) &&
!cc->Inputs().Tag(kVideoPrestreamTag).IsEmpty() &&
cc->Outputs().HasTag(kVideoPrestreamTag)) {
mediapipe::VideoHeader header =
cc->Inputs().Tag(kVideoPrestreamTag).Get();
// Update the header's width and height if needed.
ComputeOutputDimensions(header.width, header.height, &header.width,
&header.height);
cc->Outputs()
.Tag(kVideoPrestreamTag)
.AddPacket(mediapipe::MakePacket(header).At(
mediapipe::Timestamp::PreStream()));
}
// Override values if specified so.
if (cc->Inputs().HasTag("ROTATION_DEGREES") &&
!cc->Inputs().Tag("ROTATION_DEGREES").IsEmpty()) {
rotation_ =
DegreesToRotationMode(cc->Inputs().Tag("ROTATION_DEGREES").Get());
}
if (cc->Inputs().HasTag("FLIP_HORIZONTALLY") &&
!cc->Inputs().Tag("FLIP_HORIZONTALLY").IsEmpty()) {
flip_horizontally_ = cc->Inputs().Tag("FLIP_HORIZONTALLY").Get();
}
if (cc->Inputs().HasTag("FLIP_VERTICALLY") &&
!cc->Inputs().Tag("FLIP_VERTICALLY").IsEmpty()) {
flip_vertically_ = cc->Inputs().Tag("FLIP_VERTICALLY").Get();
}
if (cc->Inputs().HasTag("OUTPUT_DIMENSIONS")) {
if (cc->Inputs().Tag("OUTPUT_DIMENSIONS").IsEmpty()) {
return absl::OkStatus();
} else {
const auto& image_size =
cc->Inputs().Tag("OUTPUT_DIMENSIONS").Get>();
output_width_ = image_size.first;
output_height_ = image_size.second;
}
}
if (use_gpu_) {
#if !MEDIAPIPE_DISABLE_GPU
if (cc->Inputs().Tag(kGpuBufferTag).IsEmpty()) {
return absl::OkStatus();
}
return gpu_helper_.RunInGlContext(
[this, cc]() -> absl::Status { return RenderGpu(cc); });
#endif // !MEDIAPIPE_DISABLE_GPU
} else {
if (cc->Inputs().Tag(kImageFrameTag).IsEmpty()) {
return absl::OkStatus();
}
return RenderCpu(cc);
}
return absl::OkStatus();
}
这段代码定义了一个名为RunInGlContext的模板函数,它接受一个函数作为参数,并在一个lambda表达式中执行该函数,然后返回一个absl::OkStatus(),即使函数本身没有返回任何结果。
这段代码的主要目的是方便那些需要在OpenGL上下文中执行函数的情况,尤其是当这些函数没有返回结果(即返回类型为void)时。由于std::function
cpu porocess opencv api
absl::Status ImageTransformationCalculator::RenderCpu(CalculatorContext* cc) {
cv::Mat input_mat;
mediapipe::ImageFormat::Format format;
const auto& input = cc->Inputs().Tag(kImageFrameTag).Get();
input_mat = formats::MatView(&input);
format = input.Format();
const int input_width = input_mat.cols;
const int input_height = input_mat.rows;
int output_width;
int output_height;
ComputeOutputDimensions(input_width, input_height, &output_width,
&output_height);
if (output_width_ > 0 && output_height_ > 0) {
cv::Mat scaled_mat;
if (scale_mode_ == mediapipe::ScaleMode_Mode_STRETCH) {
int scale_flag =
input_mat.cols > output_width_ && input_mat.rows > output_height_
? cv::INTER_AREA
: cv::INTER_LINEAR;
cv::resize(input_mat, scaled_mat, cv::Size(output_width_, output_height_),
0, 0, scale_flag);
} else {
const float scale =
std::min(static_cast(output_width_) / input_width,
static_cast(output_height_) / input_height);
const int target_width = std::round(input_width * scale);
const int target_height = std::round(input_height * scale);
int scale_flag = scale < 1.0f ? cv::INTER_AREA : cv::INTER_LINEAR;
if (scale_mode_ == mediapipe::ScaleMode_Mode_FIT) {
cv::Mat intermediate_mat;
cv::resize(input_mat, intermediate_mat,
cv::Size(target_width, target_height), 0, 0, scale_flag);
const int top = (output_height_ - target_height) / 2;
const int bottom = output_height_ - target_height - top;
const int left = (output_width_ - target_width) / 2;
const int right = output_width_ - target_width - left;
cv::copyMakeBorder(intermediate_mat, scaled_mat, top, bottom, left,
right,
options_.constant_padding() ? cv::BORDER_CONSTANT
: cv::BORDER_REPLICATE);
} else {
cv::resize(input_mat, scaled_mat, cv::Size(target_width, target_height),
0, 0, scale_flag);
output_width = target_width;
output_height = target_height;
}
}
input_mat = scaled_mat;
}
该部分区分了android ios opengl 跨平台库,平台的opengl上下文已在开头 gpu_server内初始化完成,该部分构建render类 通过gpu处理二维数据 且以FBO方式 ,然后将GpuBuffer 设置到输出packet 送往下一节点
TEXTURE_EXTERNAL_OES
TEXTURE_EXTERNAL_OES 和普通纹理的主要区别在于它们的定义和用途。
普通纹理完全由 OpenGL ES 定义、分配和管理。它们是在 OpenGL ES 上下文中创建和使用的纹理。
TEXTURE_EXTERNAL_OES 是一种特殊类型的纹理,它在别处定义和分配,并以某种实现定义的方式导入 OpenGL ES。这种纹理主要用于导入 YUV 视频数据。系统中的一些外部实体定义了格式——它对应用程序不可见,颜色空间转换由驱动程序堆栈神奇地处理。具体支持哪些格式是实现定义的。这种纹理的主要优势是它们能够直接从 BufferQueue 数据进行渲染。例如,在 Android 平台上,BufferQueue 是连接图形数据生产方和消费方的队列,也就表示 OES 纹理能直接拿到某些生产方产生的图形数据进行渲染。
absl::Status ImageTransformationCalculator::RenderGpu(CalculatorContext* cc) {
#if !MEDIAPIPE_DISABLE_GPU
const auto& input = cc->Inputs().Tag(kGpuBufferTag).Get();
const int input_width = input.width();
const int input_height = input.height();
int output_width;
int output_height;
ComputeOutputDimensions(input_width, input_height, &output_width,
&output_height);
if (scale_mode_ == mediapipe::ScaleMode_Mode_FILL_AND_CROP) {
const float scale =
std::min(static_cast(output_width_) / input_width,
static_cast(output_height_) / input_height);
output_width = std::round(input_width * scale);
output_height = std::round(input_height * scale);
}
if (cc->Outputs().HasTag("LETTERBOX_PADDING")) {
auto padding = absl::make_unique>();
ComputeOutputLetterboxPadding(input_width, input_height, output_width,
output_height, padding.get());
cc->Outputs()
.Tag("LETTERBOX_PADDING")
.Add(padding.release(), cc->InputTimestamp());
}
QuadRenderer* renderer = nullptr;
GlTexture src1;
#if defined(MEDIAPIPE_IOS)
if (input.format() == GpuBufferFormat::kBiPlanar420YpCbCr8VideoRange ||
input.format() == GpuBufferFormat::kBiPlanar420YpCbCr8FullRange) {
if (!yuv_renderer_) {
yuv_renderer_ = absl::make_unique();
MP_RETURN_IF_ERROR(
yuv_renderer_->GlSetup(::mediapipe::kYUV2TexToRGBFragmentShader,
{"video_frame_y", "video_frame_uv"}));
}
renderer = yuv_renderer_.get();
src1 = gpu_helper_.CreateSourceTexture(input, 0);
} else // NOLINT(readability/braces)
#endif // iOS
{
src1 = gpu_helper_.CreateSourceTexture(input);
#if defined(TEXTURE_EXTERNAL_OES)
if (src1.target() == GL_TEXTURE_EXTERNAL_OES) {
if (!ext_rgb_renderer_) {
ext_rgb_renderer_ = absl::make_unique();
MP_RETURN_IF_ERROR(ext_rgb_renderer_->GlSetup(
::mediapipe::kBasicTexturedFragmentShaderOES, {"video_frame"}));
}
renderer = ext_rgb_renderer_.get();
} else // NOLINT(readability/braces)
#endif // TEXTURE_EXTERNAL_OES
{
if (!rgb_renderer_) {
rgb_renderer_ = absl::make_unique();
MP_RETURN_IF_ERROR(rgb_renderer_->GlSetup());
}
renderer = rgb_renderer_.get();
}
}
RET_CHECK(renderer) << "Unsupported input texture type";
mediapipe::FrameScaleMode scale_mode = mediapipe::FrameScaleModeFromProto(
scale_mode_, mediapipe::FrameScaleMode::kStretch);
mediapipe::FrameRotation rotation =
mediapipe::FrameRotationFromDegrees(RotationModeToDegrees(rotation_));
auto dst = gpu_helper_.CreateDestinationTexture(output_width, output_height,
input.format());
gpu_helper_.BindFramebuffer(dst);
glActiveTexture(GL_TEXTURE1);
glBindTexture(src1.target(), src1.name());
MP_RETURN_IF_ERROR(renderer->GlRender(
src1.width(), src1.height(), dst.width(), dst.height(), scale_mode,
rotation, flip_horizontally_, flip_vertically_,
/*flip_texture=*/false));
glActiveTexture(GL_TEXTURE1);
glBindTexture(src1.target(), 0);
// Execute GL commands, before getting result.
glFlush();
auto output = dst.template GetFrame();
cc->Outputs().Tag(kGpuBufferTag).Add(output.release(), cc->InputTimestamp());
#endif // !MEDIAPIPE_DISABLE_GPU
return absl::OkStatus();
}