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

news/2024/11/14 12:53:09/文章来源:https://www.cnblogs.com/qq21497936/p/18545756

前言

  对于图像拼接,前面探讨了通过基于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// 第一张图,直接比例赋值,因为底图为0for(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 24LOG << 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// 第一张图,直接比例赋值,因为底图为0for(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
  在这里插入图片描述

解决

  在这里插入图片描述

  在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/833373.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SageMath 9.3软件下载与安装教程】

1、安装包 SageMath 9.3: 链接:https://pan.quark.cn/s/e747450b98e1 提取码:vaMJ 2、安装教程(建议关闭杀毒软件) 1) 双击解压安装包安装,弹窗安装对话框2) 点击下一步3) 选择I accept,点击下一步4) 默认,点击下一步5) 默认文件夹,点…

【日记】感觉身体像僵尸(438 字)

正文不让人动真的是一种酷刑。今天实在受不了,在地上试了一下 Kick Out,结果发现还是老样子。可能稍微好了一点?说实话我不太能感受到。这已经休息了整整两周了,折磨得我死去活来。什么时候才能跳舞啊…… 我这膝盖还能不能好了……昨天应酬吃的东西感觉有问题。今天跑肚子…

第 4 篇 Scrum 冲刺博客

团队作业4——第 4 篇 Scrum 冲刺博客 作业要求这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/CSGrade22-34这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13234这个作业的目标 团队集体协作完成项目开发队名 雄狮般的男人站立式…

鸿蒙Navigation知识点详解

Navigation是路由导航的根视图容器,一般作为页面(@Entry)的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。Navigation组件适用于模块内和跨模块的路由切换,通过组件级路由能力实现更加自然流畅的转场体验,并提供多种标题栏样式来呈现更好的标题…

weblogic历史漏洞

weblogic历史漏洞 是什么?weblogic是一个web服务器应用(中间件),和jboss一样都是javaee中间件,只能识别java语言,绝大部分漏洞都是T3反序列化漏洞常见的中间件还有:Apache,nginx,IIS,tomact,weblogic,jboss等 默认端口:7001 Web界面:Error 404 -- Not Found 控制…

Z-Library 入口官方国内最新可用网址(2024持续更新)

Z-Library(简称 Z-Lib,前身为 BookFinder )是全球最大的电子图书馆之一,拥有1046万本书和8484万篇文章。Z-Library从2009年开始提供免费的电子书,至今遭遇了多次封锁,从2024年5月份也停止了国内的任何宣传渠道。所以便出现了很多Z-Library虚假域名(钓鱼网站),本文持续…

OVB

Homura 还在熟睡。 Madoka \(\text{[数据删除]}\)。“Homura 酱,快起床了。” “你看你看,这个是什么。” Madoka 指了指 Homura 的脖子下面。 Homura 醒了。 Madoka 指着的地方有一些黑黑的东西。 “看看这里写的什么,我 是 b a k a。” “啊啊啊啊啊,Madoka,你在干什么啊…

代码随想录算法训练营 | 200.岛屿的数量

岛屿的数量题目链接:https://leetcode.cn/problems/number-of-islands/此题目要点:dfs和bfs都可以解决此题,但是使用dfs代码会更加的简洁首先对grid进行遍历,每一个节点都进行检查,判断是否是1(陆地) 如果是,则进行dfs深搜,并将所有搜到的岛屿节点全置为0,表示此块岛…

5倍提升工作效率,智能合同审查开源系统助力律师行业智能化

一、系统概述 在法律行业,律师和法务人员每天需要处理大量的法律文件和复杂的案件信息。然而,手工分析和关联文档信息既耗时,又容易导致遗漏和错误。为此,我们推出了一款专为法律行业打造的开源免费软件,利用关系抽取算法和深度学习技术,支持自动识别底稿文档中的当事人、…

神经网络架构参考:2-2 卷积篇

densenet 结构层名称 类型 输入大小 (H x W x C) 输出大小 (H x W x C) 核尺寸 步长 参数数量Initial Conv Conv2D 224 x 224 x 3 112 x 112 x 64 7 x 7 2 9,408Max Pooling MaxPool2D 112 x 112 x 64 56 x 56 x 64 3 x 3 2 0Dense Block 1 Composite 56 x 56 x 64 56 x 56 x 2…

数据库字段设置非空, phalcon创建数据验证不通过

在使用phalcon的insert和update功能时,因为数据库所有的字段设置的都是NOT NULL,而phalcon的model在插入或更新之前会自动判断字段是否需要必填,因此导致有空字段时无法存入。 开始遇到这问题时,想到两种解决方法: 一、改数据库字段,把NOT NULL改为可以为空。但该数据库还…

2024年11月中国数据库排行榜:OB连续四月居榜首,腾讯云TDSQL升第九

11月墨天轮排行榜解读新鲜出炉!榜单前五稳中求进,OceanBase连续四月居榜首,第六至十位竞争加剧,此外亦有部分产品焕发活力,一起来看更多排名情况与解读!11月墨天轮社区的中国数据库流行度排行榜已更新,本期共有226个数据库产品参与,相较于年初的292个数据库,产品阵容已…