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

若该文为原创文章,转载请注明原文出处
本文章博客地址:https://hpzwl.blog.csdn.net/article/details/143432922

长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…

OpenCV开发专栏(点击传送门)

上一篇:《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)[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开发笔记(八十一):通过棋盘格使用鱼眼方式标定相机内参矩阵矫正摄像头图像》
下一篇:持续补充中…


本文章博客地址:https://hpzwl.blog.csdn.net/article/details/143432922

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

「C/C++」C/C++标准库 之 #include<ctime> 时间日期库

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

安达发|零部件APS车间排程系统销售预测的优点

2024制造业面临着前所未有的挑战与机遇。为了保持竞争力&#xff0c;企业必须确保其生产系统能够高效、灵活地运作。在这方面&#xff0c;采用高级计划与排程系统&#xff0c;特别是零部件APS车间排程系统的预测方法&#xff0c;已成为提升生产效率和响应能力的关键策略。这种系…

【笔试刷题】笔记4

目录 1、过河卒 dfs bfs 动态规划 2、扑克牌顺子 排序 模拟 找规律 3、最长回文子串 中心拓展法 1、过河卒 5493. 过河卒 - AcWing题库 这道题我们很容易就能够想到dfs或bfs&#xff0c;但这两种算法都是会超时的 dfs #include <iostream> #include <v…

大模型中的token是什么;常见大语言模型的 token 情况

目录 大模型中的token是什么 常见大语言模型的 token 情况 大模型中的token是什么 定义 在大模型中,token 是文本处理的基本单位。它可以是一个字、一个词,或者是其他被模型定义的语言单元。简单来说,模型在理解和生成文本时,不是以完整的句子或段落为单位进行一次性处理…

kafka里的consumer 是推还是拉?

大家好&#xff0c;我是锋哥。今天分享关于【kafka里的consumer 是推还是拉&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; kafka里的consumer 是推还是拉&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Kafka中&#xff0c;消费者&…

少儿编程培训市场突破500亿元:教育新蓝海的崛起与未来展望

近年来&#xff0c;随着科技的迅速发展和家长对教育方式的重视&#xff0c;少儿编程市场成为一片新的蓝海。据最新市场调研报告显示&#xff0c;2024年中国少儿编程培训市场规模已突破500亿元&#xff0c;预计未来五年将持续增长。这一趋势反映了少儿编程教育的迅速崛起&#x…

【如何使用api接入星火大模型】(超详细,亲测有效!)

1 实现思路&#xff1a; 1.鉴权说明: 先在控制台创建应用&#xff0c;利用应用中提供的appid&#xff0c;APIKey&#xff0c; APISecret进行鉴权&#xff0c;生成最终请求的鉴权url。1.2 鉴权参数 host&#xff08;请求主机&#xff09;、date&#xff08;当前时间戳&#xff0…

国标GB28181软件EasyGBS国标GB28181网页直播平台在邮政快递场景中的应用

随着电子商务的迅猛发展&#xff0c;邮政快递行业迎来了前所未有的发展机遇&#xff0c;但同时也面临着诸多挑战。如何在保障货物安全、提高运输效率的同时&#xff0c;实现全面的监控和管理&#xff0c;成为邮政快递企业亟需解决的问题。国标GB28181网页直播平台EasyGBS作为一…

渗透测试练习题解析 7 (CTF web)

一、[红明谷CTF 2021]write_shell 1 考点&#xff1a; 1、PHP 短标签 2、 符号的使用 通过代码可知 check 是一个过滤函数&#xff0c;利用正则的方式过滤掉 空格、php、eval 等一些关键字或符号&#xff0c;$dir 是路径&#xff0c;这个值可以通过 actionpwd 获取到&#…

day-77 超级饮料的最大强化能量

思路 动态规划&#xff1a;因为每一步要么选A&#xff0c;要么选B&#xff0c;所以问题可以转换为求最后一步从A选或从B选中的较大值 解题过程 定义而二维数组dp,dp[i][0]表示最后一步从A取能获得的最大能量&#xff0c;dp[i][1]表示最后一步从B取能获得的最大能量状态转换方程…

(03)萨班斯-奥克斯利法案(SOX)--- 你和公司的违法成本有多大?

【前言】如果你的公司需要去美国上市&#xff1f;你知道如果违反了萨班斯-奥克斯利法案&#xff08;SOX&#xff09;&#xff0c;你和公司的风险和成本吗&#xff1f; 一、萨班斯-奥克斯利法案&#xff08;SOX&#xff09;合规性是指什么- 你必须知道&#xff0c;如果你的公司要…

C++ | Leetcode C++题解之第517题超级洗衣机

题目&#xff1a; 题解&#xff1a; class Solution { public:int findMinMoves(vector<int> &machines) {int tot accumulate(machines.begin(), machines.end(), 0);int n machines.size();if (tot % n) {return -1;}int avg tot / n;int ans 0, sum 0;for (…

【Linux】从内核角度理解 TCP 的 全连接队列(以及什么是 TCP 抓包)

文章目录 概念引入理解全连接队列内核方面理解Tcp抓包方法注意事项 概念引入 我们知道&#xff0c;TCP的三次握手是由TCP协议 自动处理的&#xff0c;建立连接的过程与用户是否进行accept无关&#xff0c;accept()的作用主要是为当前连接创建一个套接字&#xff0c;用于进行后…

适合视频搬运的素材网站推荐——短视频素材下载宝库

对于摄影爱好者和短视频创作者来说&#xff0c;找到适合搬运和创作的视频素材至关重要。无论是用于丰富画面、增加背景细节&#xff0c;还是提升作品的视觉吸引力&#xff0c;这些素材网站都能为你的创作提供极大帮助。今天&#xff0c;我将为大家推荐几个优质的素材网站&#…

百数功能更新——表单提交支持跳转到外部链接并支持传参

百数的表单外链功能允许用户将表单以链接的形式分享给外部用户&#xff0c;外部用户无需登录或加入团队即可访问并填写表单。 本次更新的表单提交后跳转指定链接的功能&#xff0c;在支持跳转内部链接的基础上&#xff0c;支持用户在完成表单填写并提交后&#xff0c;自动跳转…

初探Servlet

文章目录 1. Servlet概述1.1 定义1.2 作用 2. 主要知识点2.1 生命周期2.2 请求处理2.3 Servlet配置 3. 案例演示3.1 创建Web应用项目3.2 修改项目工件名3.3 重新部署Web项目3.4 创建WelcomeServlet3.5 编写doGet方法代码3.6 编写doPost方法代码3.7 访问WelcomeServlet 4. 小结 …

国标GB28181公网平台EasyGBS国标GB28181公网直播与国标协议的优质对接方案

随着信息技术的飞速发展&#xff0c;视频监控领域正经历从传统安防向智能化、网络化安防的深刻转变。在这一变革中&#xff0c;国标GB28181公网平台EasyGBS凭借其强大的功能和广泛的应用场景&#xff0c;成为视频监控平台与国标协议对接的优质方案。 应急管理部门正积极推进以信…

MySQL数据表导入到clickhouse数据库中

前言&#xff1a;研发需求&#xff0c;需要把MySQL数据导入到clickhouse中来测试计算性能是否提升。 从MySQL导入到clickhouse需要两个工具 NavicatDBeaver 导出MySQL数据 连接上MySQL>选择数据库>选择数据表 选择csv格式 导出数据 下面全部默认即可 开始导出 …

通俗易懂的餐厅例子来讲解JVM

餐厅版本 JVM&#xff08;Java虚拟机&#xff09;可以想象成一个虚拟的计算机&#xff0c;它能够运行Java程序。为了让你更容易理解&#xff0c;我们可以用一个餐厅的比喻来解释JVM&#xff1a; 菜单&#xff08;Java源代码&#xff09;&#xff1a; 想象一下&#xff0c;Java…

力扣——113. 路径总和

113. 路径总和 II 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [5,4,8,11,null,13,4,7,2,null,null,5,1], t…