• OpenCV实现“蓝线挑战“特效


    原理

    算法原理可以分为三个流程:

    1、将视频(图像)从(顶->底)或(左->右)逐行(列)扫描图像。

    2、将扫描完成的行(列)像素重新生成定格图像。

    3、使用原帧图像像素填充未扫描到的像素。

    图像扫描

    首先第一步,拿到一个视频(很多帧图像)可以简单的看成图像处理。我们需要将图像从顶到底逐行进行像素扫描,当然也可以从左到右逐列扫描,这要看你想要实现什么样的效果。在这里,我实现的是从上到下逐行扫描。效果如图所示。

     生成定格图像

    所谓生成定格图像就是将我们每扫描到的行像素重新进行绘制。

    1. //从顶向下逐行扫描图像
    2. if (h < height)
    3. {
    4. h++;
    5. //将扫描到的图像像素进行重新绘制,生成新图像
    6. for (int j = 0; j < width; j++)
    7. {
    8. for (int c = 0; c < 3; c++)
    9. {
    10. temp.at<Vec3b>(h, j)[c] = canvas.at<Vec3b>(h, j)[c];
    11. }
    12. }
    13. //绘制扫描过程
    14. line(canvas, Point(0, h), Point(width, h), Scalar(255, 255, 0), 2);
    15. }

     

     如图所示,这是使用上面代码段实现的逐行扫描生成定格图像。从效果上看,已经得到了我们想要的大致效果了。不过现在的问题是,经扫描到的行有像素填充,未扫描到的行还是漆黑一片。所以接下来我们需要做的就是将未扫描到的行用原图进行填充。具体请看源码注释。

    图像混合

    1. //将两幅图像进行线性混合
    2. bool Linear_Blend(Mat src1, Mat src2, Mat& dst)
    3. {
    4.     /*
    5.     参数说明:
    6.     src1:生成的定格图像。由于生成的定格图像是从顶往下逐行扫描,故在扫描线下的像素为0
    7.     src2:原视频帧图像
    8.     dst:新生成的定格图像。
    9.     算法原理:经扫描到的像素用src1进行填充,未扫描到的像素用src2进行填充,这样就可以得到我们所要的效果了。
    10.     */
    11.     for (int i = 0; i < src1.rows; i++)
    12.     {
    13.         for (int j = 0; j < src1.cols; j++)
    14.         {
    15.             for (int c = 0; c < 3; c++)
    16.             {
    17.                 if (src1.at<Vec3b>(i, j)[0] != 0)
    18.                 {
    19.                     dst.at<Vec3b>(i, j)[c] = src1.at<Vec3b>(i, j)[c];
    20.                 }
    21.                 else
    22.                 {
    23.                     dst.at<Vec3b>(i, j)[c] = src2.at<Vec3b>(i, j)[c];
    24.                 }
    25.             }
    26.         }
    27.     }
    28.     return true;
    29. }

     效果

     源码

    1. #include<iostream>
    2. #include<opencv2/opencv.hpp>
    3. using namespace std;
    4. using namespace cv;
    5. /*
    6. 抖音特效:蓝线挑战
    7. 算法原理:
    8.     1、将视频(图像)从(顶->底)或(左->右)逐行(列)扫描图像。
    9.     2、将扫描完成的行(列)像素重新生成定格图像
    10.     3、使用原帧图像像素填充未扫描到的像素
    11. */
    12. //将两幅图像进行线性混合
    13. bool Linear_Blend(Mat src1, Mat src2, Mat& dst)
    14. {
    15.     /*
    16.     参数说明:
    17.     src1:生成的定格图像。由于生成的定格图像是从顶往下逐行扫描,故在扫描线下的像素为0
    18.     src2:原视频帧图像
    19.     dst:新生成的定格图像。
    20.     算法原理:经扫描到的像素用src1进行填充,未扫描到的像素用src2进行填充,这样就可以得到我们所要的效果了。
    21.     */
    22.     for (int i = 0; i < src1.rows; i++)
    23.     {
    24.         for (int j = 0; j < src1.cols; j++)
    25.         {
    26.             for (int c = 0; c < 3; c++)
    27.             {
    28.                 if (src1.at<Vec3b>(i, j)[0] != 0)
    29.                 {
    30.                     dst.at<Vec3b>(i, j)[c] = src1.at<Vec3b>(i, j)[c];
    31.                 }
    32.                 else
    33.                 {
    34.                     dst.at<Vec3b>(i, j)[c] = src2.at<Vec3b>(i, j)[c];
    35.                 }
    36.             }
    37.         }
    38.     }
    39.     return true;
    40. }
    41. int main()
    42. {
    43.     VideoCapture capture;
    44.     capture.open("test.avi");
    45.     if (!capture.isOpened())
    46.     {
    47.         cout << "can not open the camera!" << endl;
    48.         system("pause");
    49.         return -1;
    50.     }
    51.     int width = capture.get(CAP_PROP_FRAME_WIDTH);//视频帧宽
    52.     int height = capture.get(CAP_PROP_FRAME_HEIGHT);//视频帧高
    53.     //保存视频
    54.     VideoWriter writer;
    55.     int fourcc = writer.fourcc('m', 'p', '4', 'v'); //视频编码
    56.     Size size(capture.get(CAP_PROP_FRAME_WIDTH), capture.get(CAP_PROP_FRAME_HEIGHT));
    57.     writer.open("result.avi", fourcc, 30, size, true);
    58.     int h = 0;//定义变量,代表当前扫描高度
    59.     //用于生成定格照
    60.     Mat temp = Mat::zeros(Size(width, height), CV_8UC3);
    61.     
    62.     Mat frame;
    63.     while (capture.read(frame))
    64.     {
    65.         //将图像拷贝一份,用于每帧更新
    66.         Mat canvas = frame.clone();
    67.         //从顶向下逐行扫描图像
    68.         if (h < height)
    69.         {
    70.             h++;
    71.             //将扫描到的图像像素进行重新绘制,生成新图像
    72.             for (int j = 0; j < width; j++)
    73.             {
    74.                 for (int c = 0; c < 3; c++)
    75.                 {
    76.                     temp.at<Vec3b>(h, j)[c] = canvas.at<Vec3b>(h, j)[c];
    77.                 }
    78.             }
    79.             //绘制扫描过程
    80.             line(canvas, Point(0, h), Point(width, h), Scalar(255, 255, 0), 2);
    81.         }
    82.         Mat result = Mat::zeros(frame.size(), frame.type());//蓝线挑战最终定格图
    83.         Linear_Blend(temp, canvas, result); //将两张图像进行像素叠加
    84.         //writer.write(temp);//进行视频保存
    85.         imshow("定格图像", temp);
    86.         imshow("原视频帧", canvas);
    87.         imshow("蓝线挑战", result);
    88.         char key = waitKey(10);
    89.         if (key == 27) break;
    90.     }
    91.     capture.release();
    92.     system("pause");
    93.     return 0;
    94. }

  • 相关阅读:
    C++基础(二)
    DQL语句_查询表中指定的字段_以及指定的别名_以及字段去重
    Mac 下如何查看 Homebrew 安装的软件位置
    适用于 Windows 的 12 个最佳 PDF 编辑器
    数据结构之栈和队列
    MinIO Server配置NGINX代理官网文档翻译
    超好用的IDEA插件推荐
    Matplotlib:Python数据可视化的全面指南
    Servlet —— Tomcat, 初学 Servlet 程序
    GEE:线性回归
  • 原文地址:https://blog.csdn.net/hulinhulin/article/details/133101940