代码:对鱼眼相机图像进行去畸变处理

news2024/11/13 14:48:56

图像投影模型:针孔[fx, fy, cx, cy]
图像畸变模型:切向径向畸变[k1, k2, p1, p2]
说明:用于备忘

  1. 第一部分是常规的去畸变操作,在已知内参的情况下对鱼眼相机进行去畸变,这里使用的是remap映射
  2. 在对图像去畸变后,对于图像边缘的像素全部造成了丢失,所以进行了一次没有意义的尝试,想尽可能的保留图像边缘像素,实现后发现边缘像素部分比较模糊而且还对中心位置的去畸变效果造成了影响

图像来源和参考链接:https://github.com/HLearning/fisheye

1.第一部分:常规去畸变操作

因为只是一个简单验证,所以将内参、图像加载路径和保存路径都写在了代码里面
代码片段:

    cv::Mat K = (cv::Mat_<double>(3, 3) << 652.8609862494474, 0.0, 1262.1021584894233,
         0.0, 653.1909758659955, 928.0871455436396,
         0.0, 0.0, 1.0);
    cv::Mat D = (cv::Mat_<double>(4, 1) << -0.024092199861108887, 0.002745976275100771, 0.002545415522352827, -0.0014366825722748522);
    cv::Mat raw_image = cv::imread("../data/pig.jpg");
    cout << raw_image.cols  << " " << raw_image.rows << endl;
    int width = raw_image.cols;
    int height = raw_image.rows;
    cv::Mat map1, map2;
    cv::Mat undistortImg;
    cv::Size imageSize(width, height);
    cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat(), K, imageSize, CV_16SC2, map1, map2);
    cv::remap(raw_image, undistortImg, map1, map2, cv::INTER_LINEAR, cv::BORDER_CONSTANT);
    cv::imwrite("../data/dst.png", undistortImg);

原图像和结果如下,因为remap函数要求输入与输出的图像大小一致,所以这个过程造成了信息丢失,边缘部分的小猪崽们都缺少了很多
在这里插入图片描述

2.第二部分:保留图像边界信息

思路很简单,既然像素丢失是因为在remap后像素坐标超出了边界,那直接对原图像进行扩边处理再去畸变就好了,图像的高和宽分别为height,width,对图像的上下边界各扩充 1 4 ⋅ h e i g h t \frac{1}{4} \cdot height 41height个像素,对图像左右边界各扩充 1 4 ⋅ w i d t h \frac{1}{4} \cdot width 41width个像素

图像大小改变后,投影内参也要对应的改变,主要指像素坐标远点的偏移距离cx, cy,上下左右各增加了高和宽的1/4,所以整体上高和宽增加了1/2,即变为原来的1.5倍

扩边函数使用cv::copyMakeBorder

代码片段如下:

    // 扩边处理
    cv::Mat K_ = (cv::Mat_<double>(3, 3) << 652.8609862494474, 0.0, 1.5 * 1262.1021584894233,
                  0.0, 653.1909758659955, 1.5 * 928.0871455436396,
                  0.0, 0.0, 1.0);
    cv::Mat D_ = (cv::Mat_<double>(4, 1) << -0.024092199861108887, 0.002745976275100771, 0.002545415522352827, -0.0014366825722748522);
    int width_ = raw_image.cols + raw_image.cols/2;
    int height_ = raw_image.rows + raw_image.rows/2;

    cv::Mat temp(height_, width_, raw_image.type());
    cv::copyMakeBorder(raw_image, temp, raw_image.rows/4, raw_image.rows/4, raw_image.cols/4, raw_image.cols/4, cv::BORDER_ISOLATED);
    cout << temp.cols  << " " << temp.rows << endl;
    cv::imwrite("../data/src2.png", temp);

    cv::Mat map3, map4;
    cv::Mat undistortImg_;
    cv::Size imageSize_(width_, height_);
    cv::fisheye::initUndistortRectifyMap(K_, D_, cv::Mat(), K_, imageSize_, CV_16SC2, map3, map4);
    cv::remap(temp, undistortImg_, map3, map4, cv::INTER_LINEAR, cv::BORDER_CONSTANT);
    cv::imwrite("../data/dst2.png", undistortImg_);

结果如下,可以看到图像边界部分的像素保留的更多了:
在这里插入图片描述

3.讨论

但是这种思路是错误的,因为在对图像扩边处理后,进行remap的输入图像发生了变化,remap映射要对扩边后的整个图像处理,这样会影响图像中心区域的去畸变效果,如果继续增加扩便的大小,比如将增加的边界由原来高和宽的1/4变为高和宽的1/2,会很明显的发现中心区域的去畸变效果受到影响(两条栏杆变的更弯了),这不是我们想要的。

正确的做法是在remap中做文章,在函数中新建一个大的空白图像,将去畸变后的像素映射到该图像上的对应位置,在remap结束后返回这张图像

4.完整代码

源代码文件fisheye.cpp

#include <iostream>
#include <string>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;
// using namespace cv;

int main(int argc, char **argv)
{
    cv::Mat K = (cv::Mat_<double>(3, 3) << 652.8609862494474, 0.0, 1262.1021584894233,
         0.0, 653.1909758659955, 928.0871455436396,
         0.0, 0.0, 1.0);
    cv::Mat D = (cv::Mat_<double>(4, 1) << -0.024092199861108887, 0.002745976275100771, 0.002545415522352827, -0.0014366825722748522);
    cv::Mat raw_image = cv::imread("../data/pig.jpg");
    cout << raw_image.cols  << " " << raw_image.rows << endl;
    int width = raw_image.cols;
    int height = raw_image.rows;
    cv::Mat map1, map2;
    cv::Mat undistortImg;
    cv::Size imageSize(width, height);
    cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat(), K, imageSize, CV_16SC2, map1, map2);
    cv::remap(raw_image, undistortImg, map1, map2, cv::INTER_LINEAR, cv::BORDER_CONSTANT);
    cv::imwrite("../data/dst.png", undistortImg);


    // 扩边处理
    cv::Mat K_ = (cv::Mat_<double>(3, 3) << 652.8609862494474, 0.0, 1.5 * 1262.1021584894233,
                  0.0, 653.1909758659955, 1.5 * 928.0871455436396,
                  0.0, 0.0, 1.0);
    cv::Mat D_ = (cv::Mat_<double>(4, 1) << -0.024092199861108887, 0.002745976275100771, 0.002545415522352827, -0.0014366825722748522);
    int width_ = raw_image.cols + raw_image.cols/2;
    int height_ = raw_image.rows + raw_image.rows/2;

    cv::Mat temp(height_, width_, raw_image.type());
    cv::copyMakeBorder(raw_image, temp, raw_image.rows/4, raw_image.rows/4, raw_image.cols/4, raw_image.cols/4, cv::BORDER_ISOLATED);
    cout << temp.cols  << " " << temp.rows << endl;
    cv::imwrite("../data/src2.png", temp);

    cv::Mat map3, map4;
    cv::Mat undistortImg_;
    cv::Size imageSize_(width_, height_);
    cv::fisheye::initUndistortRectifyMap(K_, D_, cv::Mat(), K_, imageSize_, CV_16SC2, map3, map4);
    cv::remap(temp, undistortImg_, map3, map4, cv::INTER_LINEAR, cv::BORDER_CONSTANT);
    cv::imwrite("../data/dst2.png", undistortImg_);

    return 0;
}

CMakeLists.txt文件

cmake_minimum_required(VERSION 2.8)

project(fisheye_cali)

find_package(OpenCV REQUIRED)

include_directories(${OpenCV_INCLUDE_DIRS})

add_executable(${PROJECT_NAME} fisheye.cpp)
target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS})

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

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

相关文章

毛玻璃 has 选择器卡片悬停效果

效果展示 页面结构 从上述的效果展示可以看到&#xff0c;页面是由多个卡片组成&#xff0c;并且鼠标悬停在卡片上时&#xff0c;会旋转用户图片并且韩式对应的用户信息框。 CSS3 知识点 :has 属性的运用 实现页面整体结构 <div class"container"><div…

复原akshare的股票代码缺失前面000代码

原始的akshare保存成文件&#xff0c;再读取时&#xff0c;代码会被自动转换为如下形式&#xff1a; 我们需要对每一个元素&#xff0c;补齐前面的0&#xff0c;将代码补充为6位 示例程序 import pandas as pdmy_df pd.read_csv("akshare下载文件.csv") my_df[代码…

【Linux基础】Linux发展史

&#x1f449;系列专栏&#xff1a;【Linux基础】 &#x1f648;个人主页&#xff1a;sunny-ll 一、前言 本篇主要介绍Linux的发展历史&#xff0c;这里并不需要我们掌握&#xff0c;但是作为一个合格的Linux学习者与操作者&#xff0c;这些东西是需要了解的&#xff0c;而且…

线程的概述

#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 功能&#xff1a;创建一个子线程 参数&#xff1a; -thread:传出参数&#xff0c;线程创建成功后&#xff0c;子线程的ID被写到…

SSRF+redis未授权漏洞复现

1.SSRF漏洞简介 SSRF&#xff08;Server-Side Request Forgery&#xff09;即服务器端请求伪造&#xff0c;是一种由攻击者构造攻击链传给服务器&#xff0c;服务器执行并发起请求造成安全问题的漏洞&#xff0c;一般用来在外网探测或攻击内网服务。当网站需要调用指定URL地址…

Inno Setup新手使用教程

1.编写脚本.iss文件 2.使用Inno Setup打开脚本 3.点击运行 4.打包好的文件在output文件夹下 注&#xff1a;运行不通过可能是文件不存在或者路径错误 推荐一个零声学院项目课&#xff0c;个人觉得老师讲得不错&#xff0c;分享给大家&#xff1a; 零声白金学习卡&#xff08;含…

重生奇迹MU刷装备注意事项

在打斗游戏里面装备的作用非常巨大&#xff0c;较好的武器装备可以为玩家带来不错的体验&#xff0c;很多玩家甚至不惜花重金打造属于好装备。那么游戏中刷装备需要注意什么。 一、对武器装备的认识 对于玩家来说需要对武器装备有一定的认识&#xff0c;连基本的武器装备分类都…

# Flink的状态

1.什么是时状态(state)&#xff1f; 有状态的计算是流处理框架要实现的重要功能&#xff0c;因为稍复杂的流处理场景都需要记录状态&#xff0c;然后在新流入数据的基础上不断更新状态。 例如以下状态都需要使用流处理的状态功能&#xff1a; 数据流中的数据有重复&#xff0c…

1.springcloudalibaba nacos2.2.3部署

前言 nacos是springcloudalibaba体系的注册中心&#xff0c;演示如何搭建最新稳定版本的linux搭建。 前置条件&#xff0c;安装好jdk1.8 一、二进制压缩包下载 1.1 下载压缩包 nacos下载 点击下载下载后得到二进制包如下 nacos-2.2.3.tar.gz二、安装步骤 2.1.解压二进制…

十、2023.10.4.计算机网络(one).10

文章目录 1、简述静态路由和动态路由&#xff1f;2、说说有哪些路由协议&#xff0c;都是如何更新的&#xff1f;3、简述域名解析过程&#xff0c;本机如何干预域名解析&#xff1f;4、简述 DNS 查询服务器的基本流程是什么&#xff1f;DNS 劫持是什么&#xff1f;5、简述网关的…

Lwip的接收邮箱大小的影响

LwIP&#xff08;Lightweight IP&#xff09;是一个用于嵌入式系统的轻量级的TCP/IP协议栈&#xff0c;它支持UDP和其他网络协议。 接收邮箱大小 在LwIP中&#xff0c;UDP接收邮箱的大小对系统性能和可靠性有一定影响。 首先&#xff0c;UDP接收邮箱的大小决定了可以同时接收…

如何去占用windows端口

一、问题: 测试的服务使用的端口号范围为6881~6888&#xff0c;一般使用6881&#xff0c;如果该端口被占用&#xff0c;应该去使用其他端口&#xff0c;验证是不是真的这样 二、占用windows端口号方法 1、修改注册表 修改window现有远程连接服务的端口号&#xff08;可能有其他…

计算机中的进制转换

在计算机软件中&#xff0c;经常需要进行进制转换&#xff0c;这包括二进制、八进制、十进制和十六进制之间的转换。以下是一些常见的转换方法&#xff1a; 二进制转十进制&#xff1a;这是最直接的转换&#xff0c;基本上不需要什么特别的算法。你只需要按照二进制的权值进行…

MacBook 录制电脑内部声音

MacBook 录制电脑内部声音 老妈喜欢跳广场舞&#xff0c;现在广场舞音频下载都收费了&#xff01;没办法&#xff0c;只能自己录歌了&#xff0c;外录有杂音大家也都知道&#xff0c;所以就只能采用内录的方式然后再用 Audition 调整一下音量大小。 一、&#xff08;前置条件&a…

【计算机网络】高级IO——select

文章目录 1. select函数介绍为什么要有select&#xff1f;select 接口第一个参数 nfds的理解什么是 输入 输出型参数最后一个参数 timeout 的理解readfds writefds exceptfds 参数的理解select的返回值 2. select的使用SelectServer_v1start 最初版本start 最终版本HandlerEven…

【成像光敏描记图提取和处理】成像-光电容积描记-提取-脉搏率-估计(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Apollo Planning2.0决策规划算法代码详细解析 (2): vscode gdb单步调试环境搭建

前言: apollo planning2.0 在新版本中在降低学习和二次开发成本上进行了一些重要的优化,重要的优化有接口优化、task插件化、配置参数改造等。 GNU symbolic debugger,简称「GDB 调试器」,是 Linux 平台下最常用的一款程序调试器。GDB 编译器通常以 gdb 命令的形式在终端…

C/C++进程超详细详解【下部分】(系统性学习day8)

目录 前言 一&#xff0c;有名管道通信 1 .概念 2 .创建有名管道 实例代码如下&#xff1a; 二、信号通信 1 .概念 2 .用户进程对信号的响应方式 3. 用户进程对常用信号的缺省操作 4. 信号处理流程 5. 信号相关函数(系统调用) 5.1 kill - 给指定进程发送信号 实例代…

使用云服务器部署SpringBoot+Vue项目

一、购买云服务器并配置安全组 二、准备好前后端项目并先打包好 对于前端文件。新建文件 .env.development VUE_APP_BASEURLhttp://localhost:9090 还有新建文件 .env.production VUE_APP_BASEURLhttp://:9090 main.js 设置全局变量 $baseUrl Vue.prototype.$baseUrlproc…

mysql面试题18:MySQL中为什么要用 B+树,为什么不用二叉树?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:MySQL中为什么要用 B+树,为什么不用二叉树? MySQL数据库索引是一种数据结构,用于提高数据查询的效率。在MySQL中,常用的索引类型包括B+树索引…