当前位置:   article > 正文

探索OpenCV的光流算法视频超分与源码实现

视频超分

在OpenCV4.0以后,视频图像超分模块已经迁移到opencv_contrib独立仓库。在视频超分有两种形式:结合光流算法实现超分、使用CNN卷积神经网络实现超分。在这里主要探索光流算法实现视频超分,然后进一步分析源码实现。

一、视频超分示例

1、光流算法选择

OpenCV提供多种光流算法:farneback、tvl1、brox、pyrlk。同时支持GPU加速,示例代码如下:

  1. static Ptr<cv::superres::DenseOpticalFlowExt> createOptFlow(const string& name, bool useGpu)
  2. {
  3. if (name == "farneback")
  4. {
  5. if (useGpu) // 开启GPU加速,使用CUDA实现
  6. return cv::superres::createOptFlow_Farneback_CUDA();
  7. else
  8. return cv::superres::createOptFlow_Farneback();
  9. }
  10. else if (name == "tvl1")
  11. {
  12. if (useGpu)
  13. return cv::superres::createOptFlow_DualTVL1_CUDA();
  14. else
  15. return cv::superres::createOptFlow_DualTVL1();
  16. }
  17. else if (name == "brox")
  18. return cv::superres::createOptFlow_Brox_CUDA();
  19. else if (name == "pyrlk")
  20. return cv::superres::createOptFlow_PyrLK_CUDA();
  21. else
  22. cerr << "Incorrect Optical Flow algorithm - " << name << endl;
  23. return Ptr<cv::superres::DenseOpticalFlowExt>();
  24. }

2、创建超分实例

根据是否使用CUDA进行GPU加速,来创建视频超分实例:

  1. Ptr<SuperResolution> createSuperResolution(bool useCuda)
  2. {
  3. if (useCuda)
  4. return createSuperResolution_BTVL1_CUDA();
  5. else
  6. return createSuperResolution_BTVL1();
  7. }

3、执行视频超分

在创建光流算法、视频超分实例后,设置相关参数,执行视频超分操作。示例代码如下:

  1. #include <iostream>
  2. #include <string>
  3. #include <ctype.h>
  4. #include "opencv2/core.hpp"
  5. #include "opencv2/highgui.hpp"
  6. #include "opencv2/imgproc.hpp"
  7. #include "opencv2/superres.hpp"
  8. #include "opencv2/superres/optical_flow.hpp"
  9. #include "opencv2/opencv_modules.hpp"
  10. using namespace std;
  11. using namespace cv;
  12. using namespace cv::superres;
  13. int main(int argc, const char* argv[])
  14. {
  15. CommandLineParser cmd(argc, argv,
  16. "{ v video | | Input video (mandatory)}"
  17. "{ o output | | Output video }"
  18. "{ s scale | 4 | Scale factor }"
  19. "{ i iterations | 180 | Iteration count }"
  20. "{ t temporal | 4 | Radius of the temporal search area }"
  21. "{ f flow | farneback | Optical flow algorithm (farneback, tvl1, brox, pyrlk) }"
  22. "{ g gpu | false | CPU as default device, cuda for CUDA }"
  23. );
  24. // 解析参数
  25. const string inputVideoName = cmd.get<string>("video");
  26. const string outputVideoName = cmd.get<string>("output");
  27. const int scale = cmd.get<int>("scale");
  28. const int iterations = cmd.get<int>("iterations");
  29. const int temporalAreaRadius = cmd.get<int>("temporal");
  30. const string optFlow = cmd.get<string>("flow");
  31. string gpuOption = cmd.get<string>("gpu");
  32. bool useCuda = gpuOption.compare("cuda") == 0;
  33. // 创建视频超分实例
  34. Ptr<SuperResolution> superRes = createSuperResolution(useCuda);
  35. // 创建光流算法
  36. Ptr<cv::superres::DenseOpticalFlowExt> of = createOptFlow(optFlow, useCuda);
  37. // 设置光流算法、超分倍数、迭代次数、半径系数
  38. superRes->setOpticalFlow(of);
  39. superRes->setScale(scale);
  40. superRes->setIterations(iterations);
  41. superRes->setTemporalAreaRadius(temporalAreaRadius);
  42. // 读取视频帧
  43. Ptr<FrameSource> frameSource;
  44. if (useCuda)
  45. {
  46. try
  47. {
  48. frameSource = createFrameSource_Video_CUDA(inputVideoName);
  49. Mat frame;
  50. frameSource->nextFrame(frame);
  51. }
  52. catch (const cv::Exception&)
  53. {
  54. frameSource.release();
  55. }
  56. }
  57. if (!frameSource)
  58. frameSource = createFrameSource_Video(inputVideoName);
  59. // 跳过第一帧
  60. Mat frame;
  61. frameSource->nextFrame(frame);
  62. // 设置输入源
  63. superRes->setInput(frameSource);
  64. VideoWriter writer;
  65. for (int i = 0;; ++i)
  66. {
  67. Mat result;
  68. // 执行视频图像超分
  69. superRes->nextFrame(result);
  70. if (result.empty())
  71. break;
  72. imshow("Super Resolution", result);
  73. if (waitKey(1000) > 0)
  74. break;
  75. // 视频帧超分结果写到输出文件
  76. if (!outputVideoName.empty())
  77. {
  78. if (!writer.isOpened())
  79. writer.open(outputVideoName, VideoWriter::fourcc('X', 'V', 'I', 'D'), 25.0, result.size());
  80. writer << result;
  81. }
  82. }
  83. return 0;
  84. }

二、视频超分源码

1、构造函数

视频图像超分的源码在opencv_contrib/modules/superres中,创建超分实例的函数位于btv_l1.cpp,其实是创建BTVL1()构造函数的智能指针:

  1. Ptr<cv::superres::SuperResolution> cv::superres::createSuperResolution_BTVL1()
  2. {
  3. return makePtr<BTVL1>();
  4. }

2、超分入口代码

而nextFrame()是执行超分的函数,位于super_resolution.cpp:

  1. void cv::superres::SuperResolution::nextFrame(OutputArray frame)
  2. {
  3. isUmat_ = frame.isUMat() && cv::ocl::useOpenCL();
  4. if (firstCall_)
  5. {
  6. initImpl(frameSource_);
  7. firstCall_ = false;
  8. }
  9. processImpl(frameSource_, frame);
  10. }

可以看到内部调用processImpl()函数来执行超分:

  1. void BTVL1::processImpl(Ptr<FrameSource>& frameSource, OutputArray _output)
  2. {
  3. if (outPos_ >= storePos_)
  4. {
  5. _output.release();
  6. return;
  7. }
  8. // 读取下一个视频帧
  9. readNextFrame(frameSource);
  10. // 处理视频帧
  11. if (procPos_ < storePos_)
  12. {
  13. ++procPos_;
  14. processFrame(procPos_);
  15. }
  16. ++outPos_;
  17. // 调用ocl_processImpl函数执行超分
  18. CV_OCL_RUN(isUmat_,
  19. ocl_processImpl(frameSource, _output))
  20. const Mat& curOutput = at(outPos_, outputs_);
  21. if (_output.kind() < _InputArray::OPENGL_BUFFER || _output.isUMat())
  22. curOutput.convertTo(_output, CV_8U);
  23. else
  24. {
  25. curOutput.convertTo(finalOutput_, CV_8U);
  26. arrCopy(finalOutput_, _output);
  27. }
  28. }

3、光流检测运动矢量

接着看readNextFrame()的实现,调用光流检测算法来计算运动矢量:

  1. void BTVL1::readNextFrame(Ptr<FrameSource>& frameSource)
  2. {
  3. frameSource->nextFrame(curFrame_);
  4. if (curFrame_.empty())
  5. return;
  6. ++storePos_;
  7. CV_OCL_RUN(isUmat_,
  8. ocl_readNextFrame(frameSource))
  9. curFrame_.convertTo(at(storePos_, frames_), CV_32F);
  10. // 结合上一帧和当前帧计算运动矢量
  11. if (storePos_ > 0)
  12. {
  13. opticalFlow_->calc(prevFrame_, curFrame_, at(storePos_ - 1, forwardMotions_));
  14. opticalFlow_->calc(curFrame_, prevFrame_, at(storePos_, backwardMotions_));
  15. }
  16. curFrame_.copyTo(prevFrame_);
  17. }

4、处理超分

processFrame()函数负责处理视频帧:

  1. void BTVL1::processFrame(int idx)
  2. {
  3. CV_OCL_RUN(isUmat_,
  4. ocl_processFrame(idx))
  5. const int startIdx = std::max(idx - temporalAreaRadius_, 0);
  6. const int procIdx = idx;
  7. const int endIdx = std::min(startIdx + 2 * temporalAreaRadius_, storePos_);
  8. const int count = endIdx - startIdx + 1;
  9. srcFrames_.resize(count);
  10. srcForwardMotions_.resize(count);
  11. srcBackwardMotions_.resize(count);
  12. int baseIdx = -1;
  13. for (int i = startIdx, k = 0; i <= endIdx; ++i, ++k)
  14. {
  15. if (i == procIdx)
  16. baseIdx = k;
  17. srcFrames_[k] = at(i, frames_);
  18. if (i < endIdx)
  19. srcForwardMotions_[k] = at(i, forwardMotions_);
  20. if (i > startIdx)
  21. srcBackwardMotions_[k] = at(i, backwardMotions_);
  22. }
  23. // 根据前后方向的运动矢量来处理视频帧
  24. process(srcFrames_, at(idx, outputs_), srcForwardMotions_, srcBackwardMotions_, baseIdx);
  25. }
  26. }

在计算得到前后方向的运动矢量后,调用BTVL1_Base基类的process()函数来处理:

  1. void BTVL1_Base::process(InputArrayOfArrays _src, OutputArray _dst, InputArrayOfArrays _forwardMotions,
  2. InputArrayOfArrays _backwardMotions, int baseIdx)
  3. {
  4. CV_OCL_RUN(_src.isUMatVector() && _dst.isUMat() && _forwardMotions.isUMatVector() &&
  5. _backwardMotions.isUMatVector(),
  6. ocl_process(_src, _dst, _forwardMotions, _backwardMotions, baseIdx))
  7. std::vector<Mat> & src = *(std::vector<Mat> *)_src.getObj(),
  8. & forwardMotions = *(std::vector<Mat> *)_forwardMotions.getObj(),
  9. & backwardMotions = *(std::vector<Mat> *)_backwardMotions.getObj();
  10. // 更新运动模糊(高斯滤波)、 btv权重
  11. if (blurKernelSize_ != curBlurKernelSize_ || blurSigma_ != curBlurSigma_ || src[0].type() != curSrcType_)
  12. {
  13. //filter_ = createGaussianFilter(src[0].type(), Size(blurKernelSize_, blurKernelSize_), blurSigma_);
  14. curBlurKernelSize_ = blurKernelSize_;
  15. curBlurSigma_ = blurSigma_;
  16. curSrcType_ = src[0].type();
  17. }
  18. if (btvWeights_.empty() || btvKernelSize_ != curBtvKernelSize_ || alpha_ != curAlpha_)
  19. {
  20. calcBtvWeights(btvKernelSize_, alpha_, btvWeights_);
  21. curBtvKernelSize_ = btvKernelSize_;
  22. curAlpha_ = alpha_;
  23. }
  24. // 计算相对运动
  25. calcRelativeMotions(forwardMotions, backwardMotions, lowResForwardMotions_, lowResBackwardMotions_, baseIdx, src[0].size());
  26. // 对运动矢量进行放大
  27. upscaleMotions(lowResForwardMotions_, highResForwardMotions_, scale_);
  28. upscaleMotions(lowResBackwardMotions_, highResBackwardMotions_, scale_);
  29. forwardMaps_.resize(highResForwardMotions_.size());
  30. backwardMaps_.resize(highResForwardMotions_.size());
  31. for (size_t i = 0; i < highResForwardMotions_.size(); ++i)
  32. buildMotionMaps(highResForwardMotions_[i], highResBackwardMotions_[i], forwardMaps_[i], backwardMaps_[i]);
  33. const Size lowResSize = src[0].size();
  34. const Size highResSize(lowResSize.width * scale_, lowResSize.height * scale_);
  35. resize(src[baseIdx], highRes_, highResSize, 0, 0, INTER_CUBIC);
  36. diffTerm_.create(highResSize, highRes_.type());
  37. a_.create(highResSize, highRes_.type());
  38. b_.create(highResSize, highRes_.type());
  39. c_.create(lowResSize, highRes_.type());
  40. for (int i = 0; i < iterations_; ++i)
  41. {
  42. diffTerm_.setTo(Scalar::all(0));
  43. for (size_t k = 0; k < src.size(); ++k)
  44. {
  45. // a = M * Ih
  46. remap(highRes_, a_, backwardMaps_[k], noArray(), INTER_NEAREST);
  47. // 高斯模糊 b = HM * Ih
  48. GaussianBlur(a_, b_, Size(blurKernelSize_, blurKernelSize_), blurSigma_);
  49. // c = DHM * Ih
  50. resize(b_, c_, lowResSize, 0, 0, INTER_NEAREST);
  51. diffSign(src[k], c_, c_);
  52. // 超分运算 a = Dt * diff
  53. upscale(c_, a_, scale_);
  54. // b = HtDt * diff
  55. GaussianBlur(a_, b_, Size(blurKernelSize_, blurKernelSize_), blurSigma_);
  56. // a = MtHtDt * diff
  57. remap(b_, a_, forwardMaps_[k], noArray(), INTER_NEAREST);
  58. add(diffTerm_, a_, diffTerm_);
  59. }
  60. if (lambda_ > 0)
  61. {
  62. calcBtvRegularization(highRes_, regTerm_, btvKernelSize_, btvWeights_, ubtvWeights_);
  63. addWeighted(diffTerm_, 1.0, regTerm_, -lambda_, 0.0, diffTerm_);
  64. }
  65. addWeighted(highRes_, 1.0, diffTerm_, tau_, 0.0, highRes_);
  66. }
  67. Rect inner(btvKernelSize_, btvKernelSize_, highRes_.cols - 2 * btvKernelSize_, highRes_.rows - 2 * btvKernelSize_);
  68. highRes_(inner).copyTo(_dst);
  69. }

我们再看下upscaleMotions()函数的实现:

  1. void upscaleMotions(InputArrayOfArrays _lowResMotions, OutputArrayOfArrays _highResMotions, int scale)
  2. {
  3. CV_OCL_RUN(_lowResMotions.isUMatVector() && _highResMotions.isUMatVector(),
  4. ocl_upscaleMotions(_lowResMotions, _highResMotions, scale))
  5. std::vector<Mat> & lowResMotions = *(std::vector<Mat> *)_lowResMotions.getObj(),
  6. & highResMotions = *(std::vector<Mat> *)_highResMotions.getObj();
  7. highResMotions.resize(lowResMotions.size());
  8. for (size_t i = 0; i < lowResMotions.size(); ++i)
  9. {
  10. // 三次样条差值
  11. resize(lowResMotions[i], highResMotions[i], Size(), scale, scale, INTER_CUBIC);
  12. // 运动矢量与缩放因子相乘
  13. multiply(highResMotions[i], Scalar::all(scale), highResMotions[i]);
  14. }
  15. }

接着是upscale()超分函数的实现:

  1. void upscale(InputArray _src, OutputArray _dst, int scale)
  2. {
  3. int cn = _src.channels();
  4. CV_Assert( cn == 1 || cn == 3 || cn == 4 );
  5. CV_OCL_RUN(_dst.isUMat(),
  6. ocl_upscale(_src, _dst, scale))
  7. typedef void (*func_t)(InputArray src, OutputArray dst, int scale);
  8. static const func_t funcs[] =
  9. {
  10. 0, upscaleImpl<float>, 0, upscaleImpl<Point3f>, upscaleImpl<Point4f>
  11. };
  12. const func_t func = funcs[cn];
  13. CV_Assert(func != 0);
  14. func(_src, _dst, scale);
  15. }

最终是调用upscaleImpl()进行超分运算,属于模板函数:

  1. template <typename T>
  2. void upscaleImpl(InputArray _src, OutputArray _dst, int scale)
  3. {
  4. Mat src = _src.getMat();
  5. _dst.create(src.rows * scale, src.cols * scale, src.type());
  6. _dst.setTo(Scalar::all(0));
  7. Mat dst = _dst.getMat();
  8. for (int y = 0, Y = 0; y < src.rows; ++y, Y += scale)
  9. {
  10. const T * const srcRow = src.ptr<T>(y);
  11. T * const dstRow = dst.ptr<T>(Y);
  12. for (int x = 0, X = 0; x < src.cols; ++x, X += scale)
  13. dstRow[X] = srcRow[x];
  14. }
  15. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/80127
推荐阅读
相关标签
  

闽ICP备14008679号