qaz 发表于 2025-2-7 03:13:14

OpenCV开发笔记(八十二):两图拼接使用渐进色蒙版场景过渡缝隙

前言


  对于图像拼接,前面探讨了通过基于Stitcher进行拼接过渡和基于特征点进行拼接过渡,这2个过渡的方式是摄像头拍摄角度和方向不应差距太大。
  对于特定的场景,本身摄像头拍摄角度差距较大,拉伸变换后也难做到完美的缝隙拼接,这个时候使用渐近过渡反倒是最好的。

 
Demo


  单独蒙版
   

  

  

  

  蒙版过渡,这里只是根据图来,其实可对每个像素对于第一张图为系数k,而第二张为255-k,实现渐近过渡。
  

  

  

  

  直接使用第一张蒙版优化
  

  

  

 
准本蒙版


  蒙版可以混合,也可以分开,为了让读者更好的深入理解原理,这里都使用:
  找个工具,造单色渐进色,红色蒙版,只是r通道,bga都为0
  

  (注意:使用rgba四通道)
  

  (上面这张图,加了边框,导致了“入坑二”打印像素值不对)
  

  由于工具渐进色无法叠层,这个工具无法实现rgba不同向渐进色再一张图(横向、纵向、斜向),更改了方式,每个使用一张图:
  为了方便,不管a通道了,直接a为100%(255)。
  

  再弄另外一个通道的:
  

  在这里使用工具就只能单独一张了:
  

 
一个蒙版图的过渡实例


步骤一:打开图片和蒙版


  

   cv::Mat matLeft = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/29.jpg");    cv::Mat matRight = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/30.jpg");    cv::Mat matMask1 = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/37.png", cv::IMREAD_UNCHANGED);    cv::Mat matMask2 = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/38.png", cv::IMREAD_UNCHANGED);    cv::Mat matMask3 = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/39.png", cv::IMREAD_UNCHANGED);    cv::Mat matMask4 = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/40.png", cv::IMREAD_UNCHANGED);
步骤二:将蒙版变成和原图一样大小


  

    cv::resize(matLeft, matLeft, cv::Size(0, 0), 0.5, 0.5);    cv::resize(matRight, matRight, cv::Size(0, 0), 0.5, 0.5);    cv::resize(matMask1, matMask1, cv::Size(matLeft.cols, matLeft.rows));    cv::resize(matMask2, matMask2, cv::Size(matLeft.cols, matLeft.rows));    cv::resize(matMask3, matMask3, cv::Size(matLeft.cols, matLeft.rows));    cv::resize(matMask4, matMask4, cv::Size(matLeft.cols, matLeft.rows));
步骤三:底图


  由于两张图虽然是同样大小,但是其不是按照整体拼接后的大小,所以需要假设一个拼接后的大小的底图。
  

    // 底图,扩大500横向,方便移动    cv::Mat matResult = cv::Mat(matLeft.rows, matLeft.cols + 500, CV_8UC3);
步骤四:原图融合


  

      // 副本,每次都要重新清空来调整      cv::Mat matResult2 = matResult.clone();#if 1      // 第一张图,直接比例赋值,因为底图为0      for(int row = 0; row < matLeft.rows; row++)      {            for(int col = 0; col < matLeft.cols; col++)            {                double r = matMask1.at<cv::Vec4b>(row, col) / 255.0f;//                double r = matMask2.at<cv::Vec4b>(row, col) / 255.0f;//                double r = matMask3.at<cv::Vec4b>(row, col) / 255.0f;//                double r = matMask4.at<cv::Vec4b>(row, col) / 255.0f;                matResult2.at<cv::Vec3b>(row, col) = (matLeft.at<cv::Vec3b>(row, col) * r);                matResult2.at<cv::Vec3b>(row, col) = (matLeft.at<cv::Vec3b>(row, col) * r);                matResult2.at<cv::Vec3b>(row, col) = (uchar)(matLeft.at<cv::Vec3b>(row, col) * r);            }      }#endif
步骤五:另外一张图的融合


  

#if 1      // 第二张图,加法,因为底图为原图了      for(int row = 0; row < matRight.rows; row++)      {            for(int col = 0; col < matRight.cols; col++)            {                double g = matMask2.at<cv::Vec4b>(row, col) / 255.0f;                // 偏移了x坐标                matResult2.at<cv::Vec3b>(row, col + x) += matRight.at<cv::Vec3b>(row, col) * g;                matResult2.at<cv::Vec3b>(row, col + x) += matRight.at<cv::Vec3b>(row, col) * g;                matResult2.at<cv::Vec3b>(row, col + x) += matRight.at<cv::Vec3b>(row, col) * g;            }      }#endif
步骤六(与步骤五互斥):优化的融合


  

#if 1      // 第二张图,加法,因为底图为原图了(优化)      for(int row = 0; row < matRight.rows; row++)      {            for(int col = 0; col < matRight.cols; col++)            {                double r2;                if(x + col <= matLeft.cols)                {                  r2 = (255 - matMask1.at<cv::Vec4b>(row, col + x)) / 255.0f;                }else{                  r2 = 1.0f;                }                // 偏移了x坐标                matResult2.at<cv::Vec3b>(row, col + x) += matRight.at<cv::Vec3b>(row, col) * r2;                matResult2.at<cv::Vec3b>(row, col + x) += matRight.at<cv::Vec3b>(row, col) * r2;                matResult2.at<cv::Vec3b>(row, col + x) += matRight.at<cv::Vec3b>(row, col) * r2;            }      }#endif
 
函数原型


  手码的像素算法,没有什么高级函数。

 
Demo源码


void OpenCVManager::testMaskSplicing(){    cv::Mat matLeft = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/29.jpg");    cv::Mat matRight = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/30.jpg");    cv::Mat matMask1 = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/37.png", cv::IMREAD_UNCHANGED);    cv::Mat matMask2 = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/38.png", cv::IMREAD_UNCHANGED);    cv::Mat matMask3 = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/39.png", cv::IMREAD_UNCHANGED);    cv::Mat matMask4 = cv::imread("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/40.png", cv::IMREAD_UNCHANGED);#if 0    // 打印通道数和数据类型    // ..\openCVDemo\modules\openCVManager\OpenCVManager.cpp 9166 "2024-10-31 20:07:42:619" 4 24 24    LOG << matMask.channels() << matMask.type() << CV_8UC4; // 4 24    // 打印mask蒙版行像素,隔一定行数打一次    for(int row = 0; row < matMask.rows; row += 10)    {      for(int col = 100; col < matMask.cols; col++)      {            int r = matMask.at<cv::Vec4b>(row, col);            int g = matMask.at<cv::Vec4b>(row, col);            int b = matMask.at<cv::Vec4b>(row, col);            int a = matMask.at<cv::Vec4b>(row, col);            LOG << "row:" << row << ", col:" << col << "r(rgba):" << r << g << b << a;            break;      }    }#endif    // 图片较大,缩为原来的0.5倍    cv::resize(matLeft, matLeft, cv::Size(0, 0), 0.5, 0.5);    cv::resize(matRight, matRight, cv::Size(0, 0), 0.5, 0.5);    cv::resize(matMask1, matMask1, cv::Size(matLeft.cols, matLeft.rows));    cv::resize(matMask2, matMask2, cv::Size(matLeft.cols, matLeft.rows
页: [1]
查看完整版本: OpenCV开发笔记(八十二):两图拼接使用渐进色蒙版场景过渡缝隙