手把手教你实现条纹结构光三维重建(2)——条纹解码

在第一讲中,我们讲到了条纹的生成,这一讲,我们将实现条纹的解码。我们这里的解码技术很简单,即高低频倍数解码,详细的论文可以参考:《Temporal phase unwrapping algorithms for fringe projection profilometry a comparative review》。

简单介绍如下:

        1. 根据相位结算公式,计算每个像素相位值:

        2. 根据相位倍数,对当前包裹相位进行展开:

        3. 利用展开后的相位,继续对后续包裹相位进行展开。

对着上面的公式,我们编辑代码如下(对着代码看,再加上些调试,很容易就懂了):

#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>#define PROJECTOR_WIDTH_720P    1280          //这里定义的是720P投影仪的分辨率
#define PROJECTOR_HEIGHT_720P   720#define PROJECTOR_WIDTH_480P    854          //这里定义的是480P投影仪的分辨率
#define PROJECTOR_HEIGHT_480P   480
#define PI 3.1415926// Absolute phase from 4 frames
cv::Mat get_phase4(const cv::Mat I1, const cv::Mat I2, const cv::Mat I3, const cv::Mat I4) {cv::Mat_<float> I1_(I1);cv::Mat_<float> I2_(I2);cv::Mat_<float> I3_(I3);cv::Mat_<float> I4_(I4);//获取wrap相位int m_nHeight = I1.rows;int m_nWidth = I1.cols;cv::Mat phase = cv::Mat::zeros(m_nHeight, m_nWidth, CV_32FC1);//#pragma omp parallel forfor (int i = 0; i < m_nHeight; i++){for (int j = 0; j < m_nWidth; j++){int a1 = I1_.at<float>(i, j);int a2 = I2_.at<float>(i, j);int a3 = I3_.at<float>(i, j);int a4 = I4_.at<float>(i, j);phase.at<float>(i, j) = (float)atan2((a2 - a4), (a1 - a3));if (phase.at<float>(i, j) < 0)phase.at<float>(i, j) += (2 * PI);}}return phase;
}
cv::Mat unwrap_with_cue(const cv::Mat up, const cv::Mat upCue, float nPhase)
{// Determine number of jumpscv::Mat P = ((upCue)*nPhase - up) / (2 * PI);cv::Mat tmp(P.rows, P.cols, CV_32FC1);for (int i = 0; i < up.rows; i++) {for (int j = 0; j < up.cols; j++) {tmp.at<float>(i, j) = round(P.at<float>(i, j));}}// Add to phasecv::Mat upUnwrapped = up + tmp * 2 * PI;// Scale to range [0; 2pi]upUnwrapped *= 1.0 / nPhase;return upUnwrapped;
}cv::Mat decode_pattern(const std::vector<cv::Mat>& encode_images, const std::vector<float>& phases, const int projector_lens)
{//前面四组图案最低频率的编码(频率为1),所以不需要进行相位展开std::vector<cv::Mat> frames_low_freq(encode_images.begin(), encode_images.begin() + 4);    cv::Mat upCue = get_phase4(frames_low_freq[0], frames_low_freq[1], frames_low_freq[2], frames_low_freq[3]);for (int index = 1; index < phases.size(); index++)  //两两求解双频{std::vector<cv::Mat> frames_high_freq(encode_images.begin() + 4 * (index), encode_images.begin() + 4 * (index + 1));cv::Mat unPhase = get_phase4(frames_high_freq[0], frames_high_freq[1], frames_high_freq[2], frames_high_freq[3]);upCue = unwrap_with_cue(unPhase, upCue, phases[index]);}cv::Mat decode_phase_img = projector_lens * ((upCue) / (2 * PI));return decode_phase_img;
}void generate_freqs(std::vector <float>& freq_array, int length, int min_T)
{freq_array[4] = (double)length / min_T;     //我们需要生成五个频率,第五个频率为[投影宽度/周期]double x = sqrtf(sqrtf(freq_array[4]));    //第二个频率定义为第五个频率的开四次根号freq_array[3] = x * x * x; //第四个频率  freq_array[2] = x * x;     //第三个频率freq_array[1] = x;         //第二个频率freq_array[0] = 1;         //第一个频率
}bool read_images(std::string img_prefix, std::string img_suffix, std::vector<cv::Mat>& encode_images)
{for (int index = 0; index < 20; index++)   //获取竖条纹(图像水平方向)图像{std::string fileNameBmp = img_prefix + std::to_string(index+1) + img_suffix;cv::Mat encode_image = cv::imread(fileNameBmp, 0);if (encode_image.empty()){std::cout << "img file is error, please check the filename: " << fileNameBmp << std::endl;return 0;}encode_images.push_back(encode_image);}return 1;
}int main()
{//1. 解码的频率就是之前编码的频率,一定要一一对应std::vector<float> h_freq_array, v_freq_array;v_freq_array.resize(5);generate_freqs(v_freq_array, PROJECTOR_HEIGHT_720P, 10);        //图像垂直方向——横条纹频率h_freq_array.resize(5);generate_freqs(h_freq_array, PROJECTOR_WIDTH_720P, 10);         //图像水平方向——竖条纹频率//2. 读取本地图像std::vector<cv::Mat> encode_images_H, encode_images_V;std::string img_prefix = "..//class1//pattern_H//imagecode_H";std::string img_suffix = ".bmp";read_images(img_prefix, img_suffix,encode_images_H);img_prefix = "..//class1//pattern_V//imagecode_V";read_images(img_prefix, img_suffix, encode_images_V);//3. 对编码图像进行解码cv::Mat decode_img_H = decode_pattern(encode_images_H, h_freq_array, PROJECTOR_WIDTH_720P);cv::Mat decode_img_V = decode_pattern(encode_images_V, v_freq_array, PROJECTOR_HEIGHT_720P);//4. 测试一组采集的标定图像,我这里使用480P的投影仪采集的,所以需要更换分辨率并重新计算编码频率generate_freqs(v_freq_array, PROJECTOR_HEIGHT_480P, 10);generate_freqs(h_freq_array, PROJECTOR_WIDTH_480P, 10);std::vector<cv::Mat> calib_images_H, calib_images_V;img_prefix = ".//test_data//calib_images_H//calibImg_";read_images(img_prefix, img_suffix, calib_images_H);img_prefix = ".//test_data//calib_images_V//calibImg_";read_images(img_prefix, img_suffix, calib_images_V);decode_img_H = decode_pattern(calib_images_H, h_freq_array, PROJECTOR_WIDTH_480P);decode_img_V = decode_pattern(calib_images_V, v_freq_array, PROJECTOR_WIDTH_480P);return 0;
}

我们看下每个频率的展开相位,我喜欢用vs的image_watch插件,看图像很方便,效果如下:

另外几个频率展开的包裹相位分别如下:

最终的展开相位,我们可以利用image watch放大图像,看到其像素值

代码后面,我们对一组真实的标定图像进行了解码,由于采集图像时用的是480P的投影仪,所以需要重新计算解码频率,效果如下:

 

需要测试图像的朋友可以私信我。

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

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

相关文章

基于 Transformer 的大语言模型

语言建模作为语言模型&#xff08;LMs&#xff09;的基本功能&#xff0c;涉及对单词序列的建模以及预测后续单词的分布。 近年来&#xff0c;研究人员发现&#xff0c;扩大语言模型的规模不仅增强了它们的语言建模能力&#xff0c;而且还产生了处理传统NLP任务之外更复杂任务…

RAC11G修改监听默认端口

大多数现场集群都采用的是默认端口&#xff0c;现XX现场客户领导要求不使用默认端口&#xff0c;以下是修改监听端口的步骤&#xff0c;配置过程中若有疑问可添加文末公众号发私信一对一答疑解惑。 一.修改SCAN listener port 1.1 修改SCAN listener port 1.1.1.crs配置中修…

C++11中的类型推演工具decltype

decltype是根据表达式的实际类型推演出定义变量时所用的类型 auto和decltype的区别 auto类型用编译器计算变量的初始值来推断其类型, decltype虽然也让编译器分析表达式并得到它的类型&#xff0c;但是不实际计算表达式的值。 推演表达式类型作为变量的定义类型 int main()…

4-字符串-11-反转字符串-LeetCode344

4-字符串-11-反转字符串-LeetCode344 LeetCode: 题目序号344 更多内容欢迎关注我&#xff08;持续更新中&#xff0c;欢迎Star✨&#xff09; Github&#xff1a;CodeZeng1998/Java-Developer-Work-Note 技术公众号&#xff1a;CodeZeng1998&#xff08;纯纯技术文&#xff0…

认识一些分布函数-Frechet分布及其应用

1. 何为Frechet分布 Frechet分布也称为极值分布(EVD)类型II,用于对数据集中的最大值进行建模。它是四种常用极值分布之一。另外三种是古贝尔分布、威布尔分布和广义极值分布(Gumbel Distribution, the Weibull Distribution and the Generalized Extreme Value Distributi…

MyBatis-PageHelper 源码解说

归档 GitHub: MyBatis-PageHelper-源码解说 总说明 源码仓库&#xff1a; https://github.com/pagehelper/Mybatis-PageHelper克隆&#xff1a;git clone https://github.com/pagehelper/Mybatis-PageHelper.git切分支&#xff08;tag&#xff09;&#xff1a;git checkout m…

亿达中国武汉园区入选“武汉市科技金融工作站”及“武汉市线下首贷服务站”

近日&#xff0c;武汉市2024科技金融早春行活动在深交所湖北资本市场培育基地举行。会上&#xff0c;第四批武汉市科技金融工作站试点单位名单及第五批武汉地区金融系统线下首贷服务站名单正式公布&#xff0c;武汉软件新城成功入选上述两个名单。 为缓解科技型企业融资难题&a…

vue2+echarts实现简易的2d地图效果

背景 公司的一个可视化数据大屏里面&#xff0c;有一个使用echarts实现的2d地图。不是我开发的&#xff0c;在此记录一下实现过程以及一些扩展解构。应该是从哪个航空航线图改动了一下&#xff0c;效果看起来还是可以的。 效果预览 版本 vue版本使用的是"^2.6.12"…

TypeScript中的数组类型

数组类型 目录 数组类型 目录自动推断类型配置只读数组多维数组 自动推断 不定义类型&#xff0c;推断为never let array []; //never推断为字符串数组 const akun [akun.com, akun001] //const akun : string[] hd.push(100) //因为类型不允许&#xff0c;所以报错推断…

我要成为算法高手-双指针篇

目录 什么是双指针?问题1&#xff1a;移动零问题2&#xff1a;复写零问题3&#xff1a;快乐数问题4&#xff1a;盛最多水的容器问题5&#xff1a;有效三角形个数问题6&#xff1a;查找总价格和为目标值的两个商品(两数之和)问题7&#xff1a;三数之和问题8&#xff1a;四数之和…

Linux中“计划任务”设置以及补充

目录 一、关闭防火墙 &#xff08;1&#xff09;关闭防火墙 &#xff08;2&#xff09;关闭防火墙2 &#xff08;3&#xff09;关闭selinux 二、安装 php 第一步&#xff1a;yum源 第二步&#xff1a;下载php 第三步&#xff1a;启动php 第四步&#xff1a; 检查php是…

web前端开发前途:探索、挑战与无限可能

web前端开发前途&#xff1a;探索、挑战与无限可能 在数字化浪潮席卷全球的今天&#xff0c;Web前端开发作为连接技术与用户的桥梁&#xff0c;其前途无疑充满了探索、挑战与无限可能。本文将从四个方面、五个方面、六个方面和七个方面&#xff0c;深入剖析Web前端开发的前途&…

私域流量新利器:大模型+智慧客服+知识智能多管齐下,效果倍增!

前言 随着互联网环境的发展&#xff0c;线上市场竞争日益激烈&#xff0c;越来越多的企业开始关注私域流量的运营和管理——将品牌的用户数据在品牌官网、微信公众号、APP等自有平台上进行管理和运营&#xff0c;通过与用户建立深入的关系和互动&#xff0c;提升用户黏性、增强…

LNMP搭建:Linux+Nginx+MySQL+PHP

关闭防火墙和核心防护&#xff0c;使用一台机器Node1搭建LNMP systemctl stop firewalld; setenforce 0 所需源码包&#xff1a;可以去官网下载 编译Nginx 创建/data&#xff0c;在/data/下放源码包 [rootNode1 ~]#:mkdir /data;cd /data 安装依赖包 [rootNode1 data]#:yum …

利用STM32F103驱动舵机的指南(使用HAL库)

利用STM32F103驱动舵机的指南&#xff08;使用HAL库&#xff09; 舵机是一种常用的执行器&#xff0c;可以在机器人、遥控模型、自动化装置等项目中用来进行角度控制。本文将介绍如何利用STM32F103微控制器&#xff0c;通过HAL库来驱动舵机。 硬件准备 STM32F103开发板&…

第一个SpringBoot程序

第一个SpringBoot程序 目录介绍 当我们创建了一个SpringBoot项目之后&#xff0c;会出现如下的目录结构 SpringBoot项⽬有两个主要的⽬录&#xff1a; src/main/java: Java源代码 src/main/resources:为静态资源或配置⽂件&#xff1a; /static&#xff1a;静态资源⽂件夹,⽐…

Spring Cloud Netflix 之 Ribbon

前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;https://www.captainbed.cn/z ChatGPT体验地址 文章目录 前言前言1、负载均衡1.1、服务端负载均衡1.2、客户端负载均衡 2、Ribbon实现服务…

python 多线程条件竞争利用失败print不显示的原因

如下脚本&#xff0c;利用php的PHP_SESSION_UPLOAD_PROGRESS条件竞争漏洞执行一直着没反应&#xff1a; import requests import threading import syssession requests.session() sess zzx url1 "http://192.168.50.162/a.php" flag # file后为phpsession的路径 …

大模型企业落地:制造业可以选择的应用场景

前言 在当今制造业快速发展的背景下&#xff0c;设备稳定运行对于企业的发展至关重要。然而&#xff0c;传统的设备维修模式已无法满足现代企业的需求。为此&#xff0c;引入智能化、数字化的设备维修解决方案成为必然趋势。本文将探讨如何利用大模型技术&#xff0c;构建企业…

智慧工厂人员定位系统的影响与前景展望

随着科技的不断发展&#xff0c;智能制造正在迅速崛起&#xff0c;而智慧工厂人员定位系统作为其中的重要组成部分&#xff0c;正在改变传统制造业的面貌。这一系统通过利用物联网、室内定位等技术手段&#xff0c;能够实时准确地追踪和监控工厂内人员的位置。那么&#xff0c;…