背景
实时音视频通话(RTC)越来越注重安全审核,特别是在1v1娱乐社交场景中,对于视频反垃圾的需求也越来越大。随之而来的是客户对审核成本降低的诉求日益强烈。针对1v1场景,将两路视频拼接成一张图片进行审核相比于分别审核两路视频可以降低约50%的成本。然而,这种方法存在缺点:某些检测细节准确度会稍微降低一些,因为同一个特征在合成图里尺寸会变小。
前置条件
- ubuntu 18.04
- 安装opencv
sudo apt install libopencv-dev
处理流程
- 读取两路YUV数据
- 将两路YUV数据进行拼接
- 将拼接后的数据保存为一张大图
实现细节
- 彩色图像转换:使用cv::cvtColor函数将yuv转换为rgb,示例如下:
cv::cvtColor(yuv1, bgr1, cv::COLOR_YUV2BGR_I420)
- 图像缩放模式:使用cv::resize函数实现图片缩放,示例如下:
cv::resize(bgr1, scaledImage, scaledSize, 0, 0, cv::INTER_LINEAR);
cv::resize(bgr1, outputImage1, dst_half_size, 0, 0,cv ::INTER_LINEAR);
使用双线性插值算法(cv::INTER_LINEAR)进行处理。
-
Fit模式:图片尺寸等比缩放。优先保证图片内容全部显示。若图片尺寸与显示视窗尺寸不一致,则未被填满的区域填充背景色(黑色)。
-
Full Fill模式:图片尺寸非等比缩放。保证图片内容全部显示,并且填满视窗。
- 图像拼接:将两个经过缩放处理的图像分别拷贝到输出图像的左半部分和右半部分,实现图像拼接。示例如下:
outputImage1.copyTo(dst(cv ::Rect(cv ::Point(0 ,0), dst_half_size)));
outputImage2.copyTo(dst(cv ::Rect(cv ::Point(out_w /2 ,0), dst_half_size)));
测试结果(可执行程序由下面的源码编译)
- 运行指令
./a.out [yuv1 yuv1_width yuv1_height] [yuv2 yuv2_width yuv2_height] [output.jpeg output_width output_height] [mode]
mode:
0: Fit
1: Full Fill - 输入yuv
- 1280x720.yuv
- 1920x1080.yuv
- 输出jpeg
举例1: 输出1920x1080, Fit
举例2: 输出1920x1080, Full Fill
举例3: 输出500x500, Fit
举例4: 输出500x500, Full Fill
源码
源文件:two_yuv_to_one_jpeg.cpp
编译:g++ two_yuv_to_one_jpeg.cpp `pkg-config --cflags --libs opencv
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <string>
int main(int argc, char** argv)
{
if (argc != 11) {
std::cout << "Usage: " << argv[0] << " yuv_file1 width height yuv_file2 width height output_jpeg_file width height mode" << std::endl;
return -1;
}
// 打开第一个YUV文件
FILE* fp1 = fopen(argv[1], "rb");
if (!fp1) {
std::cout << "Failed to open file1 " << argv[1] << std::endl;
return -1;
}
// 打开第二个YUV文件
FILE* fp2 = fopen(argv[4], "rb");
if (!fp2) {
std::cout << "Failed to open file2 " << argv[4] << std::endl;
return -1;
}
int w1 = std::stoi(argv[2]);
int h1 = std::stoi(argv[3]);
int w2 = std::stoi(argv[5]);
int h2 = std::stoi(argv[6]);
int out_w = std::stoi(argv[8]);
int out_h = std::stoi(argv[9]);
int mode = std::stoi(argv[10]);
// 创建输出图像
cv::Mat dst(out_h, out_w, CV_8UC3);
// 两个yuv图像各占输出的图像的一半(左右排列),单个图像大小
cv::Size dst_half_size(out_w / 2, out_h);
// 图像1处理
cv::Mat yuv1(h1 + h1 / 2 , w1, CV_8UC1);
for (int i = 0; i < h1 + h1 / 2; i++) {
fread(yuv1.ptr(i), 1, w1, fp1);
}
// I420转RGB
cv::Mat bgr1;
cv::cvtColor(yuv1, bgr1, cv::COLOR_YUV2BGR_I420);
// 创建一个临时输出图像,大小为dst_half_size,颜色为黑色
cv::Mat outputImage1(dst_half_size, CV_8UC3, cv::Scalar(0, 0, 0));
if (mode == 0) { // fit
// 计算缩放比例
double scale = std::min((double) dst_half_size.width / bgr1.cols, (double) dst_half_size.height / bgr1.rows);
// 图像缩放
cv::Mat scaledImage;
cv::Size scaledSize(cvRound(bgr1.cols * scale), cvRound(bgr1.rows * scale));
cv::resize(bgr1, scaledImage, scaledSize, 0, 0, cv::INTER_LINEAR);
// 将缩放后的图像复制到临时输出图像中指定的位置
scaledImage.copyTo(outputImage1(cv::Rect((dst_half_size.width - scaledSize.width) / 2, (dst_half_size.height - scaledSize.height) / 2, scaledSize.width, scaledSize.height)));
} else { // full fill
cv::resize(bgr1, outputImage1, dst_half_size, 0, 0, cv::INTER_LINEAR);
}
// 将图像拷贝到输出图像的左半部分
outputImage1.copyTo(dst(cv::Rect(cv::Point(0, 0), dst_half_size)));
// 图像2处理
cv::Mat yuv2(h2 + h2 / 2 , w2, CV_8UC1);
for (int i = 0; i < (h2 + h2 / 2); i++) {
fread(yuv2.ptr(i), 1, w2, fp2);
}
// I420转RGB
cv::Mat bgr2;
cv::cvtColor(yuv2, bgr2, cv::COLOR_YUV2BGR_I420);
// 创建一个临时输出图像,大小为dst_half_size,颜色为黑色
cv::Mat outputImage2(dst_half_size, CV_8UC3, cv::Scalar(0, 0, 0));
if (mode == 0) { // fit
// 计算缩放比例
double scale = std::min((double) dst_half_size.width / bgr2.cols, (double) dst_half_size.height / bgr2.rows);
// 图像缩放
cv::Mat scaledImage;
cv::Size scaledSize(cvRound(bgr2.cols * scale), cvRound(bgr2.rows * scale));
cv::resize(bgr2, scaledImage, scaledSize, 0, 0, cv::INTER_LINEAR);
// 将缩放后的图像复制到临时输出图像中指定的位置
scaledImage.copyTo(outputImage2(cv::Rect((dst_half_size.width - scaledSize.width) / 2, (dst_half_size.height - scaledSize.height) / 2, scaledSize.width, scaledSize.height)));
} else {
cv::resize(bgr2, outputImage2, dst_half_size, 0, 0, cv::INTER_LINEAR);
}
// 将图像拷贝到输出图像的左半部分
outputImage2.copyTo(dst(cv::Rect(cv::Point(out_w / 2, 0), dst_half_size)));
// 关闭文件
fclose(fp1);
fclose(fp2);
// 保存输出图像
imwrite(argv[7], dst);
// 显示图像
imshow("YUV to Mat", dst);
cv::waitKey(0);
return 0;
}