282 lines
8.5 KiB
C++
282 lines
8.5 KiB
C++
// 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.
|
|
|
|
#include "mediapipe/calculators/image/mask_overlay_calculator.pb.h"
|
|
#include "mediapipe/framework/calculator_framework.h"
|
|
#include "mediapipe/framework/port/ret_check.h"
|
|
#include "mediapipe/framework/port/status.h"
|
|
#include "mediapipe/gpu/gl_calculator_helper.h"
|
|
#include "mediapipe/gpu/gl_simple_shaders.h"
|
|
#include "mediapipe/gpu/shader_util.h"
|
|
|
|
enum { ATTRIB_VERTEX, ATTRIB_TEXTURE_POSITION, NUM_ATTRIBUTES };
|
|
|
|
namespace mediapipe {
|
|
|
|
using ::mediapipe::MaskOverlayCalculatorOptions_MaskChannel_ALPHA;
|
|
using ::mediapipe::MaskOverlayCalculatorOptions_MaskChannel_RED;
|
|
using ::mediapipe::MaskOverlayCalculatorOptions_MaskChannel_UNKNOWN;
|
|
|
|
// Mixes two frames using a third mask frame or constant value.
|
|
//
|
|
// Inputs:
|
|
// VIDEO:[0,1] (GpuBuffer):
|
|
// Two inputs should be provided.
|
|
// MASK (GpuBuffer):
|
|
// Optional.
|
|
// Where the mask is 0, VIDEO:0 will be used. Where it is 1, VIDEO:1.
|
|
// Intermediate values will blend.
|
|
// If not specified, CONST_MASK float must be present.
|
|
// CONST_MASK (float):
|
|
// Optional.
|
|
// If not specified, MASK GpuBuffer must be present.
|
|
// Similar to MASK GpuBuffer, but applied globally to every pixel.
|
|
//
|
|
// Outputs:
|
|
// OUTPUT (GpuBuffer):
|
|
// The mix.
|
|
|
|
class MaskOverlayCalculator : public CalculatorBase {
|
|
public:
|
|
MaskOverlayCalculator() {}
|
|
~MaskOverlayCalculator();
|
|
|
|
static absl::Status GetContract(CalculatorContract* cc);
|
|
|
|
absl::Status Open(CalculatorContext* cc) override;
|
|
absl::Status Process(CalculatorContext* cc) override;
|
|
|
|
absl::Status GlSetup(
|
|
const MaskOverlayCalculatorOptions::MaskChannel mask_channel);
|
|
absl::Status GlRender(const float mask_const);
|
|
|
|
private:
|
|
GlCalculatorHelper helper_;
|
|
bool initialized_ = false;
|
|
bool use_mask_tex_ = false; // Otherwise, use constant float value.
|
|
GLuint program_ = 0;
|
|
GLint unif_frame1_;
|
|
GLint unif_frame2_;
|
|
GLint unif_mask_;
|
|
};
|
|
REGISTER_CALCULATOR(MaskOverlayCalculator);
|
|
|
|
// static
|
|
absl::Status MaskOverlayCalculator::GetContract(CalculatorContract* cc) {
|
|
MP_RETURN_IF_ERROR(GlCalculatorHelper::UpdateContract(cc));
|
|
cc->Inputs().Get("VIDEO", 0).Set<GpuBuffer>();
|
|
cc->Inputs().Get("VIDEO", 1).Set<GpuBuffer>();
|
|
if (cc->Inputs().HasTag("MASK"))
|
|
cc->Inputs().Tag("MASK").Set<GpuBuffer>();
|
|
else if (cc->Inputs().HasTag("CONST_MASK"))
|
|
cc->Inputs().Tag("CONST_MASK").Set<float>();
|
|
else
|
|
return absl::Status(absl::StatusCode::kNotFound,
|
|
"At least one mask input stream must be present.");
|
|
cc->Outputs().Tag("OUTPUT").Set<GpuBuffer>();
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status MaskOverlayCalculator::Open(CalculatorContext* cc) {
|
|
cc->SetOffset(TimestampDiff(0));
|
|
if (cc->Inputs().HasTag("MASK")) {
|
|
use_mask_tex_ = true;
|
|
}
|
|
return helper_.Open(cc);
|
|
}
|
|
|
|
absl::Status MaskOverlayCalculator::Process(CalculatorContext* cc) {
|
|
return helper_.RunInGlContext([this, &cc]() -> absl::Status {
|
|
if (!initialized_) {
|
|
const auto& options = cc->Options<MaskOverlayCalculatorOptions>();
|
|
const auto mask_channel = options.mask_channel();
|
|
|
|
MP_RETURN_IF_ERROR(GlSetup(mask_channel));
|
|
initialized_ = true;
|
|
}
|
|
|
|
glDisable(GL_BLEND);
|
|
|
|
const Packet& input1_packet = cc->Inputs().Get("VIDEO", 1).Value();
|
|
const Packet& mask_packet = use_mask_tex_
|
|
? cc->Inputs().Tag("MASK").Value()
|
|
: cc->Inputs().Tag("CONST_MASK").Value();
|
|
|
|
if (mask_packet.IsEmpty()) {
|
|
cc->Outputs().Tag("OUTPUT").AddPacket(input1_packet);
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
const auto& input0_buffer = cc->Inputs().Get("VIDEO", 0).Get<GpuBuffer>();
|
|
const auto& input1_buffer = input1_packet.Get<GpuBuffer>();
|
|
|
|
auto src1 = helper_.CreateSourceTexture(input0_buffer);
|
|
auto src2 = helper_.CreateSourceTexture(input1_buffer);
|
|
|
|
GlTexture mask_tex;
|
|
if (use_mask_tex_) {
|
|
const auto& mask_buffer = mask_packet.Get<GpuBuffer>();
|
|
mask_tex = helper_.CreateSourceTexture(mask_buffer);
|
|
}
|
|
|
|
auto dst = helper_.CreateDestinationTexture(src1.width(), src1.height());
|
|
|
|
helper_.BindFramebuffer(dst);
|
|
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glBindTexture(src1.target(), src1.name());
|
|
|
|
glActiveTexture(GL_TEXTURE2);
|
|
glBindTexture(src2.target(), src2.name());
|
|
|
|
if (use_mask_tex_) {
|
|
const float mask_const = -1;
|
|
|
|
glActiveTexture(GL_TEXTURE3);
|
|
glBindTexture(mask_tex.target(), mask_tex.name());
|
|
|
|
MP_RETURN_IF_ERROR(GlRender(mask_const));
|
|
|
|
glActiveTexture(GL_TEXTURE3);
|
|
glBindTexture(mask_tex.target(), 0);
|
|
|
|
} else {
|
|
const float mask_const = mask_packet.Get<float>();
|
|
|
|
MP_RETURN_IF_ERROR(GlRender(mask_const));
|
|
}
|
|
|
|
glActiveTexture(GL_TEXTURE2);
|
|
glBindTexture(src2.target(), 0);
|
|
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glBindTexture(src1.target(), 0);
|
|
|
|
glFlush();
|
|
|
|
auto output = dst.GetFrame<GpuBuffer>();
|
|
src1.Release();
|
|
src2.Release();
|
|
if (use_mask_tex_) mask_tex.Release();
|
|
dst.Release();
|
|
|
|
cc->Outputs().Tag("OUTPUT").Add(output.release(), cc->InputTimestamp());
|
|
return absl::OkStatus();
|
|
});
|
|
}
|
|
|
|
absl::Status MaskOverlayCalculator::GlSetup(
|
|
const MaskOverlayCalculatorOptions::MaskChannel mask_channel) {
|
|
// Load vertex and fragment shaders
|
|
const GLint attr_location[NUM_ATTRIBUTES] = {
|
|
ATTRIB_VERTEX,
|
|
ATTRIB_TEXTURE_POSITION,
|
|
};
|
|
const GLchar* attr_name[NUM_ATTRIBUTES] = {
|
|
"position",
|
|
"texture_coordinate",
|
|
};
|
|
|
|
std::string mask_component;
|
|
switch (mask_channel) {
|
|
case MaskOverlayCalculatorOptions_MaskChannel_UNKNOWN:
|
|
case MaskOverlayCalculatorOptions_MaskChannel_RED:
|
|
mask_component = "r";
|
|
break;
|
|
case MaskOverlayCalculatorOptions_MaskChannel_ALPHA:
|
|
mask_component = "a";
|
|
break;
|
|
}
|
|
|
|
const std::string frag_src_tex =
|
|
std::string(kMediaPipeFragmentShaderPreamble) +
|
|
R"(
|
|
DEFAULT_PRECISION(highp, float)
|
|
|
|
in vec2 sample_coordinate;
|
|
uniform sampler2D frame1;
|
|
uniform sampler2D frame2;
|
|
uniform sampler2D mask;
|
|
|
|
void main() {
|
|
vec4 color1 = texture2D(frame1, sample_coordinate);
|
|
vec4 color2 = texture2D(frame2, sample_coordinate);
|
|
vec4 weight = texture2D(mask, sample_coordinate);
|
|
|
|
#define MASK_COMPONENT )" +
|
|
mask_component +
|
|
R"(
|
|
|
|
gl_FragColor = mix(color1, color2, weight.MASK_COMPONENT);
|
|
}
|
|
)";
|
|
|
|
const GLchar* frag_src_const = R"(
|
|
precision highp float;
|
|
|
|
varying vec2 sample_coordinate;
|
|
uniform sampler2D frame1;
|
|
uniform sampler2D frame2;
|
|
uniform float mask;
|
|
|
|
void main() {
|
|
vec4 color1 = texture2D(frame1, sample_coordinate);
|
|
vec4 color2 = texture2D(frame2, sample_coordinate);
|
|
float weight = mask;
|
|
|
|
gl_FragColor = mix(color1, color2, weight);
|
|
}
|
|
)";
|
|
|
|
// shader program
|
|
GlhCreateProgram(kBasicVertexShader,
|
|
use_mask_tex_ ? frag_src_tex.c_str() : frag_src_const,
|
|
NUM_ATTRIBUTES, &attr_name[0], attr_location, &program_);
|
|
RET_CHECK(program_) << "Problem initializing the program.";
|
|
unif_frame1_ = glGetUniformLocation(program_, "frame1");
|
|
unif_frame2_ = glGetUniformLocation(program_, "frame2");
|
|
unif_mask_ = glGetUniformLocation(program_, "mask");
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status MaskOverlayCalculator::GlRender(const float mask_const) {
|
|
glUseProgram(program_);
|
|
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, kBasicSquareVertices);
|
|
glEnableVertexAttribArray(ATTRIB_VERTEX);
|
|
glVertexAttribPointer(ATTRIB_TEXTURE_POSITION, 2, GL_FLOAT, 0, 0,
|
|
kBasicTextureVertices);
|
|
glEnableVertexAttribArray(ATTRIB_TEXTURE_POSITION);
|
|
|
|
glUniform1i(unif_frame1_, 1);
|
|
glUniform1i(unif_frame2_, 2);
|
|
if (use_mask_tex_)
|
|
glUniform1i(unif_mask_, 3);
|
|
else
|
|
glUniform1f(unif_mask_, mask_const);
|
|
|
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
MaskOverlayCalculator::~MaskOverlayCalculator() {
|
|
helper_.RunInGlContext([this] {
|
|
if (program_) {
|
|
glDeleteProgram(program_);
|
|
program_ = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
} // namespace mediapipe
|