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

news2024/11/6 5:28:54

若该文为原创文章,转载请注明原文出处
本文章博客地址: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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字IC后端实现之Innovus Place跑完density爆涨案例分析

下图所示为咱们社区a7core后端训练营学员的floorplan。 数字IC后端实现 | Innovus各个阶段常用命令汇总 该学员跑placement前density是59.467%&#xff0c;但跑完place后density飙升到87.68%。 仔细查看place过程中的log就可以发现Density一路飙升&#xff01; 数字IC后端物…

一文总结AI智能体与传统RPA机器人的16个关键区别

基于LLM的AI Agent&#xff08;智能体&#xff09;与**RPA&#xff08;机器人流程自动化&#xff0c;Robotic Process Automation&#xff09;**两种技术在自动化任务领域中扮演着至关重要的角色。AI智能体能够借助LLM拥有极高的灵活性&#xff0c;可以实时理解和响应环境的变化…

ES(2)(仅供自己参考)

Java代码的索引库&#xff1a; package cn.itcast.hotel;import lombok.AccessLevel; import org.apache.http.HttpHost; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.client.RequestOptions; import org.elasticsea…

【机器学习】24. 聚类-层次式 Hierarchical Clustering

1. 优势和缺点 优点&#xff1a; 无需提前指定集群的数量 通过对树状图进行不同层次的切割&#xff0c;可以得到所需数量的簇。树状图提供了一个有用的可视化-集群过程的可解释的描述树状图可能揭示一个有意义的分类 缺点&#xff1a; 计算复杂度较大, 限制了其在大规模数据…

Rust 力扣 - 2379. 得到 K 个黑块的最少涂色次数

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 本题可以转换为求长度为k的子数组中白色块的最少数量 我们遍历长度为k的窗口&#xff0c;我们只需要记录窗口内的白色块的数量即可&#xff0c;遍历过程中刷新白色块的数量的最小值 题解代码 impl Solution {…

Git创建和拉取项目分支的应用以及Gitlab太占内存,如何配置降低gitlab内存占用进行优化

一、Git创建和拉取项目分支的应用 1. 关于git创建分支&#xff0c; git创建分支&#xff0c;可以通过git管理平台可视化操作创建&#xff0c;也可以通过git bash命令行下创建&#xff1a; A. 是通过git管理平台创建&#xff1a; 进入gitlab管理平台具体的目标项目中&#xff…

ubuntu-开机黑屏问题快速解决方法

开机黑屏一般是由于显卡驱动出现问题导致。 快速解决方法&#xff1a; 通过ubuntu高级选项->recovery模式->resume->按esc即可进入recovery模式&#xff0c;进去后重装显卡驱动&#xff0c;重启即可解决。附加问题&#xff1a;ubuntu的默认显示管理器是gdm3,如果重…

《高频电子线路》 —— 反馈型振荡器

文章内容来源于【中国大学MOOC 华中科技大学通信&#xff08;高频&#xff09;电子线路精品公开课】&#xff0c;此篇文章仅作为笔记分享。 反馈型振荡器基本工作原理 振荡器分类 自激&#xff1a;没有信号输入他激&#xff1a;有信号输入RC振荡器主要产生低频的正弦波&#x…

unity发布webGL

1.安装WebGL板块 打开unity&#xff0c;进入该界面&#xff0c;然后选择圈中图标 选择添加模块 选择下载WebGL Build Support 2.配置项目设置 打开一个unity项目&#xff0c;如图进行选择 如图进行操作 根据自己的情况进行配置&#xff08;也可直接点击构建和运行&#xff09…

nodejs批量修改word文档目录样式

工作中遇到一个需求:写个nodejs脚本,对word文档(1000+个)的目录页面进行美化。实现过程遇到不少麻烦,在此分享下。 整体思路 众所周知,Docx格式的Word文档其实是个以xml文件为主的zip压缩包,所以,页面美化整体思路是:先将文档后缀名改为zip并解压到本地,然后将关键的…

c++仿函数--通俗易懂

1.仿函数是什么 仿函数也叫函数对象&#xff0c;是一种可以像函数一样被调用的对象。从编程实现的角度看&#xff0c;它是一个类&#xff0c;不过这个类重载了函数调用运算符() class Add { public:int operator()(int a, int b) {return a b;} }; 注意&#xff1a;使用的时…

玩转Docker | Docker基础入门与常用命令指南

玩转Docker | Docker基础入门与常用命令指南 引言基本概念help帮助信息常用命令管理镜像运行容器构建镜像其他Docker命令整理结语引言 Docker 是一种开源的应用容器引擎,它允许开发者将应用程序及其依赖打包进一个可移植的容器中,然后发布到任何流行的 Linux 机器上。这大大简…

【机器学习】22. 聚类cluster - K-means

聚类cluster - K-means 1. 定义2. 测量数据点之间的相似性3. Centroid and medoid4. Cluster之间距离的测量方式5. 聚类算法的类别6. K-mean7. 如何解决中心初始化带来的影响8. K-means问题&#xff1a;处理空集群9. 离群值的问题10. Bisecting K-means&#xff08;二分K-means…

wsl2.0(windows linux子系统)使用流程

1.什么是wsl wsl指的是windows的linux子系统&#xff0c;最初是wsl1.0&#xff0c;靠windows内核来模拟linux内核&#xff0c;并不运行真正的linux内核&#xff0c;所以有时会有兼容性的问题。 而wsl2.0是基于windows自带的虚拟机功能hyper-v的&#xff0c;它会把设备上的每个…

大数据新视界 -- 大数据大厂之数据质量管理全景洞察:从荆棘挑战到辉煌策略与前沿曙光

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

硅谷甄选(11)角色管理

角色管理模块 10.1 角色管理模块静态搭建 还是熟悉的组件&#xff1a;el-card、el-table 、el-pagination、el-form <template><el-card><el-form :inline"true" class"form"><el-form-item label"职位搜索"><el-…

这5个堪称每日必看的网站,你都有所了解吗?

身为一名出色的UI设计师&#xff0c;能够迅速捕捉灵感、始终保持敏锐的审美眼光以及时刻洞悉行业动态&#xff0c;无疑是必备的职业素养与技能。今儿个小编就特意为各位小伙伴精心梳理了一些UI设计师绝对不容错过的绝佳网站哦。通过浏览这些网站&#xff0c;大家可以第一时间掌…

安全成为大模型的核心;大模型安全的途径:大模型对齐

目录 安全成为大模型的核心 大模型安全的途径:大模型对齐 人类反馈强化学习(RLHF) 直接偏好优化(DPO) 安全成为大模型的核心 大模型安全的途径:大模型对齐 大模型对齐技术(Alignment Techniques for Large Language Models)是确保大规模语言模型(例如GPT-4)的输…

<项目代码>YOLOv8 煤矸石识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

练习LabVIEW第三十题

学习目标&#xff1a; 刚学了LabVIEW&#xff0c;在网上找了些题&#xff0c;练习一下LabVIEW&#xff0c;有不对不好不足的地方欢迎指正&#xff01; 第三十题&#xff1a; 用labview写一个获取当前系统时间的程序 开始编写&#xff1a; 前面板添加一个字符串显示控件&am…