OpenCV C++案例,蓝线特效及相册查看器

涛涛CV

共 8758字,需浏览 18分钟

 ·

2022-06-24 10:22

关注涛涛CV,设置星标,更新错过




作者Zero___Chen

来源blog.csdn.net/Zero___Chen

本文将使用OpenCV C++ 实现抖音上的特效“蓝线挑战”。


虽然看起来觉得很牛的样子,但如果了解其中的原理就非常简单了。本案例是我自己对于这个特效实现过程的理解,仅供参考。

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

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

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

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

接下来就具体来看看是如何实现的吧。


一、图像扫描

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


二、生成定格图像

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


      //从顶向下逐行扫描图像      if (h < height)      {            h++;            //将扫描到的图像像素进行重新绘制,生成新图像            for (int j = 0; j < width; j++)            {                  for (int c = 0; c < 3; c++)                  {                        temp.at(h, j)[c] = canvas.at(h, j)[c];                  }            }            //绘制扫描过程            line(canvas, Point(0, h), Point(width, h), Scalar(255, 255, 0), 2);      }


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


三、图像混合

//将两幅图像进行线性混合bool Linear_Blend(Mat src1, Mat src2, Mat& dst){      /*      参数说明:      src1:生成的定格图像。由于生成的定格图像是从顶往下逐行扫描,故在扫描线下的像素为0      src2:原视频帧图像      dst:新生成的定格图像。      算法原理:经扫描到的像素用src1进行填充,未扫描到的像素用src2进行填充,这样就可以得到我们所要的效果了。      */

for (int i = 0; i < src1.rows; i++) { for (int j = 0; j < src1.cols; j++) { for (int c = 0; c < 3; c++) { if (src1.at(i, j)[0] != 0) { dst.at(i, j)[c] = src1.at(i, j)[c]; } else { dst.at(i, j)[c] = src2.at(i, j)[c]; } } }      } return true;}


四、效果显示

如上图所示,至此我们已经完成了案例所想要的效果。请参考源码,注释也比较详细了。


五、源码

#include<iostream>#include<opencv2/opencv.hpp>using namespace std;using namespace cv;
/*抖音特效:蓝线挑战算法原理: 1、将视频(图像)从(顶->底)或(左->右)逐行(列)扫描图像。 2、将扫描完成的行(列)像素重新生成定格图像 3、使用原帧图像像素填充未扫描到的像素*/
//将两幅图像进行线性混合bool Linear_Blend(Mat src1, Mat src2, Mat& dst){ /* 参数说明: src1:生成的定格图像。由于生成的定格图像是从顶往下逐行扫描,故在扫描线下的像素为0 src2:原视频帧图像 dst:新生成的定格图像。 算法原理:经扫描到的像素用src1进行填充,未扫描到的像素用src2进行填充,这样就可以得到我们所要的效果了。 */
for (int i = 0; i < src1.rows; i++) { for (int j = 0; j < src1.cols; j++) { for (int c = 0; c < 3; c++) { if (src1.at<Vec3b>(i, j)[0] != 0) { dst.at<Vec3b>(i, j)[c] = src1.at<Vec3b>(i, j)[c]; } else { dst.at<Vec3b>(i, j)[c] = src2.at<Vec3b>(i, j)[c]; } } } }
return true;}
int main(){ VideoCapture capture; capture.open("test.avi"); if (!capture.isOpened()) { cout << "can not open the camera!" << endl; system("pause"); return -1; }
int width = capture.get(CAP_PROP_FRAME_WIDTH);//视频帧宽 int height = capture.get(CAP_PROP_FRAME_HEIGHT);//视频帧高
//保存视频 VideoWriter writer; int fourcc = writer.fourcc('m', 'p', '4', 'v'); //视频编码 Size size(capture.get(CAP_PROP_FRAME_WIDTH), capture.get(CAP_PROP_FRAME_HEIGHT)); writer.open("result.avi", fourcc, 30, size, true);
int h = 0;//定义变量,代表当前扫描高度
//用于生成定格照 Mat temp = Mat::zeros(Size(width, height), CV_8UC3); Mat frame; while (capture.read(frame)) { //将图像拷贝一份,用于每帧更新 Mat canvas = frame.clone();
//从顶向下逐行扫描图像 if (h < height) { h++; //将扫描到的图像像素进行重新绘制,生成新图像 for (int j = 0; j < width; j++) { for (int c = 0; c < 3; c++) { temp.at<Vec3b>(h, j)[c] = canvas.at<Vec3b>(h, j)[c]; } } //绘制扫描过程 line(canvas, Point(0, h), Point(width, h), Scalar(255, 255, 0), 2); }
Mat result = Mat::zeros(frame.size(), frame.type());//蓝线挑战最终定格图 Linear_Blend(temp, canvas, result); //将两张图像进行像素叠加
//writer.write(temp);//进行视频保存
imshow("定格图像", temp); imshow("原视频帧", canvas); imshow("蓝线挑战", result);
char key = waitKey(10); if (key == 27) break; }
capture.release(); system("pause"); return 0;}

总结

本文使用OpenCV C++ 实现抖音特效“蓝线挑战”,关键步骤有以下几点。

1、将图像进行逐行扫描

2、将扫描到的像素逐行生成定格图像

3、将定格图像与原图像进行像素叠加。




本文将使用OpenCV C++ 制作电子相册查看器。

类似于win10系统的“照片”功能。接下来就具体来看看是如何一步步的实现吧。


一、图片读取

我们想要一张张的查看文件夹下的图片,第一步就得读取将该文件夹下的所有图片。

如上图所示,为我创建的文件夹,该文件夹下有14张图片。接下来我们就编写代码读取该文件夹下的所有图片。将读取到的图片存储在images容器。



      //读取文件夹下所有图片      string filename = "images";      vectorimageList;      glob(filename, imageList);      vectorimages;      for (int i = 0; i < imageList.size(); i++)      {            Mat img = imread(imageList[i]);            images.push_back(img);      }


现在我们已经有了images容器,其实再使用一个for循环就能够一张张读取容器里的图片了。不过这样只能一张张往下读取,直到读取完最后一张图片程序结束。本案例的需求是使用键盘按键“->”向后读取,“<-”向前读取。


二、图片展示

我们需要一张白色的画布用来放置图片。为了将所有图片都居中在画布中显示,令画布中心为(cx,cy),当前图片宽width,高height。则该图片相对于画布起点为(x,y)。如下图所示。


      //将每一张照片放置画布中心      int x = cx - (width / 2);      int y = cy - (height / 2);      //将照片抠图到画布上,此时照片位于画布中心位置      images[index].copyTo(bg(Rect(x, y, width, height)));


在这里,使用一个判断语句,判断当前图片尺寸是否大于画布尺寸。如果当前图片尺寸大于画布尺寸,则将图片自适应剪切。否则的话,会造成内存溢出。



      //如果图片过大,则对其进行裁剪      if (width > canvas.cols || height > canvas.rows)      {            //进行自适应剪切,每次只在原基础上剪切百分之八十            while (true)            {                  resize(images[index], images[index], Size(0, 0), 0.8, 0.8, INTER_LINEAR);                  if (images[index].cols < canvas.cols&&images[index].rows < canvas.rows)                  {                        break;                  }            }                  width = images[index].cols;            height = images[index].rows;      }


三、键盘控制

根据上述代码我们已经可以将图片显示在画布中心了,接下来就需要使用键盘响应事件控制图片查看。

我们使用方向键“->”控制向下查看,“<-”控制向上查看。具体请看源码注释。



      if (key == 2424832)      {            //如果按动键盘‘←’键,则向前查看相片            if (index > 0)//如果图片不是图库中第一张,则允许向前查看            {                  cout << "←" << endl;                  index--;            }      }      else if (key == 2555904)      {            //如果按动键盘‘→’键,则向后查看相片            if (index < size-1)//如果图片不是图库中最后一张,则允许向后查看            {                  cout << "→" << endl;                  index++;            }      }      //如果按动键盘‘ESC’键,则退出程序      else if (key == 27)      {            break;      }


四、效果显示

如上图所示,至此我们已经完成了案例所想要的效果。请参考源码,注释也比较详细了。


五、源码

#include<iostream>#include<opencv2/opencv.hpp>using namespace std;using namespace cv;
int main(){ //读取文件夹下所有图片 string filename = "images"; vector<string>imageList; glob(filename, imageList); vector<Mat>images; for (int i = 0; i < imageList.size(); i++) { Mat img = imread(imageList[i]); images.push_back(img); }
//创建画布,用于放置相片 Mat canvas = Mat(Size(1400, 900), CV_8UC3, Scalar::all(255)); //画布中心 int cx = canvas.cols / 2; int cy = canvas.rows / 2; int size = images.size();//图库中相片数量 int index = 0; //当前图库中相片索引 while (true) { //waitKey无法正常捕捉方向键(上下左右),故使用waitKeyEx int key = waitKeyEx(0);
if (key == 2424832) { //如果按动键盘‘←’键,则向前查看相片 if (index > 0)//如果图片不是图库中第一张,则允许向前查看 { cout << "←" << endl; index--; } } else if (key == 2555904) { //如果按动键盘‘→’键,则向后查看相片 if (index < size-1)//如果图片不是图库中最后一张,则允许向后查看 { cout << "→" << endl; index++; } } //如果按动键盘‘ESC’键,则退出程序 else if (key == 27) { break; }
//将画布拷贝一份,每经一次循环,更新一次图片。 Mat bg = canvas.clone();
//计算每一张图片的宽高 int width = images[index].cols; int height = images[index].rows;
//如果图片过大,则对其进行裁剪 if (width > canvas.cols || height > canvas.rows) { //进行自适应剪切,每次只在原基础上剪切百分之八十 while (true) { resize(images[index], images[index], Size(0, 0), 0.8, 0.8, INTER_LINEAR); if (images[index].cols < canvas.cols&&images[index].rows < canvas.rows) { break; } } width = images[index].cols; height = images[index].rows; }
//将每一张照片放置画布中心 int x = cx - (width / 2); int y = cy - (height / 2); //将照片抠图到画布上,此时照片位于画布中心位置 images[index].copyTo(bg(Rect(x, y, width, height)));
imshow("Demo", bg); }
destroyAllWindows(); system("pause"); return 0;}

总结

本文使用OpenCV C++ 制作电子相册查看器,类似win10系统下的“照片”功能,关键步骤有以下几点。

1、图片在画布上居中显示

2、使用键盘响应事件控制相片上下读取




总结:

10年机器视觉网站,5年人工智能网站

2019经历总结2018视觉总结

项目感悟赚钱思路项目视频

课程:

《机器视觉:应用讲解》一总体概述二相机篇三镜头篇四光源篇五光学系统选型六视觉开发软件七相机标定技术八项目案例解析九视觉公司分析十产业发展情况

笔记:

《智能革命》《人工智能》《AI•未来》《好好赚钱》《韭菜的自我修养》读书笔记

行业: 

服务机器人公司,机器视觉公司,自动驾驶公司,ADAS公司总结, 防疫机器人发展腾讯未来交通

SLAM:

Vslam方案+源码,语义SLAM与深度相机SLAM和导航避障视觉SLAM总结

秦学英《三维物体的识别与跟踪》章国锋《视觉SLAM》申抒含《基于图像的三维建模》姜翰青《RGB -D SLAM》记录笔记

视觉SLAM的建图课件3课件2课件1

机器视觉:

毫米波雷达雷达视觉融合2021视觉研讨会2020上海研讨会双目和激光的三维重建2021视觉市场研究太阳能行业应用

机器视觉基本概念笔记,记录五,记录四,记录三,记录二,记录一

图像处理:

图像处理基本概念笔记,记录八,记录七,记录六 ,记录五,记录四 ,记录三,记录二 ,记录二,记录一

欢迎支持,点击在看,分享

浏览 56
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报