前言

  对于图像拼接,前面探讨了通过基于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)[2] / 255.0f;
// double r = matMask2.at<cv::Vec4b>(row, col)[1] / 255.0f;
// double r = matMask3.at<cv::Vec4b>(row, col)[0] / 255.0f;
// double r = matMask4.at<cv::Vec4b>(row, col)[0] / 255.0f;
matResult2.at<cv::Vec3b>(row, col)[0] = (matLeft.at<cv::Vec3b>(row, col)[0] * r);
matResult2.at<cv::Vec3b>(row, col)[1] = (matLeft.at<cv::Vec3b>(row, col)[1] * r);
matResult2.at<cv::Vec3b>(row, col)[2] = (uchar)(matLeft.at<cv::Vec3b>(row, col)[2] * 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)[1] / 255.0f;
// 偏移了x坐标
matResult2.at<cv::Vec3b>(row, col + x)[0] += matRight.at<cv::Vec3b>(row, col)[0] * g;
matResult2.at<cv::Vec3b>(row, col + x)[1] += matRight.at<cv::Vec3b>(row, col)[1] * g;
matResult2.at<cv::Vec3b>(row, col + x)[2] += matRight.at<cv::Vec3b>(row, col)[2] * 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)[2]) / 255.0f;
}else{
r2 = 1.0f;
}
// 偏移了x坐标
matResult2.at<cv::Vec3b>(row, col + x)[0] += matRight.at<cv::Vec3b>(row, col)[0] * r2;
matResult2.at<cv::Vec3b>(row, col + x)[1] += matRight.at<cv::Vec3b>(row, col)[1] * r2;
matResult2.at<cv::Vec3b>(row, col + x)[2] += matRight.at<cv::Vec3b>(row, col)[2] * 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)[2];
int g = matMask.at<cv::Vec4b>(row, col)[1];
int b = matMask.at<cv::Vec4b>(row, col)[0];
int a = matMask.at<cv::Vec4b>(row, col)[3];
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));
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); // 第一张图
int key = 0;
int x = 0;
while(true)
{
// 副本,每次都要重新清空来调整
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)[2] / 255.0f;
// double r = matMask2.at<cv::Vec4b>(row, col)[1] / 255.0f;
// double r = matMask3.at<cv::Vec4b>(row, col)[0] / 255.0f;
// double r = matMask4.at<cv::Vec4b>(row, col)[0] / 255.0f;
matResult2.at<cv::Vec3b>(row, col)[0] = (matLeft.at<cv::Vec3b>(row, col)[0] * r);
matResult2.at<cv::Vec3b>(row, col)[1] = (matLeft.at<cv::Vec3b>(row, col)[1] * r);
matResult2.at<cv::Vec3b>(row, col)[2] = (uchar)(matLeft.at<cv::Vec3b>(row, col)[2] * r);
}
}
#endif
#if 0
// 第二张图,加法,因为底图为原图了
for(int row = 0; row < matRight.rows; row++)
{
for(int col = 0; col < matRight.cols; col++)
{
double g = matMask2.at<cv::Vec4b>(row, col)[1] / 255.0f;
// 偏移了x坐标
matResult2.at<cv::Vec3b>(row, col + x)[0] += matRight.at<cv::Vec3b>(row, col)[0] * g;
matResult2.at<cv::Vec3b>(row, col + x)[1] += matRight.at<cv::Vec3b>(row, col)[1] * g;
matResult2.at<cv::Vec3b>(row, col + x)[2] += matRight.at<cv::Vec3b>(row, col)[2] * 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)[2]) / 255.0f;
}else{
r2 = 1.0f;
}
// 偏移了x坐标
matResult2.at<cv::Vec3b>(row, col + x)[0] += matRight.at<cv::Vec3b>(row, col)[0] * r2;
matResult2.at<cv::Vec3b>(row, col + x)[1] += matRight.at<cv::Vec3b>(row, col)[1] * r2;
matResult2.at<cv::Vec3b>(row, col + x)[2] += matRight.at<cv::Vec3b>(row, col)[2] * r2;
}
}
#endif // cv::imshow("matMask1", matMask1);
// cv::imshow("matLeft", matLeft);
cv::imshow("matResult2", matResult2);
key = cv::waitKey(0);
if(key == 'a')
{
x--;
if(x < 0)
{
x = 0;
}
}else if(key == 'd')
{
x++;
if(x + matRight.cols > matResult2.cols)
{
x = matResult2.cols - matRight.cols;
}
}else if(key == 'q')
{
break;
}
}
}
 

工程模板v1.72.0

  

 

入坑

入坑一:读取通道rgba失败

问题:读取通道rgba失败

  

原因

  是uchar,转换成byte,而不是int
  

解决

  

  

入坑二:读取通道一直是0,0,0,255

问题

  读取通道一直是0,0,0,255。
  

原因

  弄了张图,还是255,然后发现是为了截图更清楚,弄了个边框,而我们打印正好是打印了0位置。
  

  

解决

  最终是要去掉边框,没边框就是空看不出,如下图:
  

  

入坑三:过渡有黑线赋值不对

问题

  直接位置赋值,出现条纹
  

  

原因

  类型是vec4b
  

解决

  

  

入坑四:原图融合比例有黑线

问题

  

原因

  跟上面一样,mask蒙版是rgba的,需要vec4b
  

解决

  

  

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

  1. 树莓派开发笔记(十二):入手研华ADVANTECH工控树莓派UNO-220套件(一):介绍和运行系统

    前言   树莓派也可以做商业应用,工业控制,其稳定性和可靠性已经得到了验证,故而工业控制,一些停车场等场景也有采用树莓派作为主控的,本片介绍了研华ADVANTECH的树莓派套件组UNO-220-P4N ...

  2. .net开发笔记(十二) 设计时与运行时的区别(续)

    上一篇博客详细讲到了设计时(DesignTime)和运行时(RunTime)的概念与区别,不过没有给出实际的Demo,今天整理了一下,做了一个例子,贴出来分享一下,巩固前一篇博客讲到的内容. 简单回顾 ...

  3. 安卓开发笔记(十二):SQLite数据库储存(上)

    SQLite数据库存储(上) 创建数据库 Android专门提供了一个 SQLiteOpenHelper帮助类对数据库进行创建和升级 SQLiteOpenHelper需要创建一个自己的帮助类去继承它并 ...

  4. Java开发笔记(十二)布尔变量论道与或非

    在编程语言的设计之初,它们除了可以进行数学计算,还常常用于逻辑推理和条件判断.为了实现逻辑判断的功能,Java引入了一种布尔类型boolean,用来表示“真”和“假”.该类型的变量只允许两个取值,即t ...

  5. PID控制器开发笔记之十二:模糊PID控制器的实现

    在现实控制中,被控系统并非是线性时不变的,往往需要动态调整PID的参数,而模糊控制正好能够满足这一需求,所以在接下来的这一节我们将讨论模糊PID控制器的相关问题.模糊PID控制器是将模糊算法与PID控 ...

  6. ESP32 开发笔记(十二)LittlevGL 添加自定义字体和物理按键

    LittlevGL 添加自定义字体获取字库 ttf 文件可以从一些网站上获取字库文件,比如请注意字体许可证 生成源文件使用 LittlevGL 提供的字库文件转换工具,将 ttf 字库文件转换为源文件 ...

  7. OpenCV开发笔记(七十二):红胖子8分钟带你使用opencv+dnn+tensorFlow识别物体

    前言   级联分类器的效果并不是很好,准确度相对深度学习较低,本章使用opencv通过tensorflow深度学习,检测已有模型的分类.   Demo       可以猜测,1其实是人,18序号类是狗 ...

  8. OpenCV开发笔记(六十五):红胖子8分钟带你深入了解ORB特征点(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  9. OpenCV开发笔记(六十九):红胖子8分钟带你使用传统方法识别已知物体(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  10. OpenCV开发笔记(五十五):红胖子8分钟带你深入了解Haar、LBP特征以及级联分类器识别过程(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

随机推荐

  1. linux下开发编辑器vim常用指令

    1.vim的3种模式:编辑模式.插入模式.命令行模式(最后一行模式) 编辑模式:对代码的修改(复制.粘贴.剪切 插入模式:用户编辑代码等等) 命令行模式:保存 退出 另存为等 vim3种模式的切换 最 ...

  2. Winform 子窗体调用父窗体方法

    子窗体部分 1.定义委托 /// <summary> /// 双击委托事件 /// </summary> /// <param name="path" ...

  3. zblog免费插件分享前端代码支持一键复制

    zblog默认的代码文件在网页前端是不支持一键复制的,这会让访客复制长代码的时候不太方便,甚至有可能会出错,影响体验,下面分享一个非常简单的免费插件,安装之后,前端代码就能一键复制了. 插件使用方法: ...

  4. SmartDeviceControl_FreeRTOS_Hal_F103

    系统框图 ## 显示子设备 框图 我们不想让文件互相调用 遇到的问题:OLED 无响应,I2C无法工作 解决:使用的是软件I2C,问题在于OLED的驱动中已经包含获取从机应答的函数 ### ==更新: ...

  5. 【YashanDB知识库】自关联外键插入数据时报错:YAS-02033 foreign key constraint violated parent key not found

    问题现象 使用如下的sql语句创建自关联外键表: drop table self_f_key; create table self_f_key(t1 number primary key not nu ...

  6. 【YashanDB知识库】outline固化执行计划

    [问题分类]性能优化,功能使用 [关键字]outline [问题描述]防止SQL执行计划突变,用outline固化执行计划 [问题原因分析]防止SQL执行计划突变,用outline固化执行计划 [解决 ...

  7. Spark Dataframe 转 Json

    import org.apache.spark.sql.DataFrame import org.apache.spark.sql.functions._ import org.apache.spar ...

  8. MYSQL数据库备份还原,并还原到最新状态(mysqldump)

    启用二进制日志文件 vim /etc/my.cnf 配置文件位置及文件名根据实际情况确定<br>sql_log_bin=on|off:是否记录二进制日志,默认为on 在需要的时候设置为of ...

  9. Falcon Mamba: 首个高效的无注意力机制 7B 模型

    Falcon Mamba 是由阿布扎比的 Technology Innovation Institute (TII) 开发并基于 TII Falcon Mamba 7B License 1.0 的开放 ...

  10. 15 Python模块

    本篇是 Python 系列教程第 15 篇,更多内容敬请访问我的 Python 合集 一个模块其实就是一个文件(以.py结尾).使用模块的好处是便于维护和重用代码. 要创建一个模块,只需编写一个新的文 ...