实时音视频通话(RTC)越来越注重安全审核,特别是在1v1娱乐社交场景中,对于视频反垃圾的需求也越来越大。随之而来的是客户对审核成本降低的诉求日益强烈。针对1v1场景,将两路视频拼接成一张图片进行审核相比于分别审核两路视频可以降低约50%的成本。然而,这种方法存在缺点:某些检测细节准确度会稍微降低一些,因为同一个特征在合成图里尺寸会变小。
cv::cvtColor(yuv1, bgr1, cv::COLOR_YUV2BGR_I420)
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
源文件:two_yuv_to_one_jpeg.cpp
编译:g++ two_yuv_to_one_jpeg.cpp `pkg-config --cflags --libs opencv
#include
#include
#include
#include
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;
}