OpenMVG 系列 (2):Image 和 Numeric
OpenMVG 的功能模块由若干核心库组成,本文主要介绍 Image 和 Numeric 两个库
1 Image
Image 库包含图像容器 Image<T>、图像IO读写函数 ReadImage() 和 WriteImage()、基本绘图操作 DrawLine()、DrawCircle() 和 DrawEllipse() 等
1.1 图像容器
Image<T> 是一个图像类泛型容器,T 代表像素类型,可以是单通道的灰度图
// 8bit and 32bit gray images
Image<unsigned char> gray_img_8bit;
Image<double> gray_img_32bit;
也可以是 RGB 和 RGBA 等多通道的彩色图
Image<Rgb<unsigned char>> rgb_img_8bit; // 8bit RGB
Image<Rgb<double> > rgb_img_32bit; // 32bit RGB Image<Rgba<unsigned char> > rgba_img_8bit; // 8bit RGBA
Image<T> 也是一个模板类,继承自 Eigen 中的“行优先”模板类 Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>,所谓“行优先”,指的是矩阵内元素的存储顺序
以 $A=\begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}$ 为例,行优先时元素在内存中的存储顺序为 1-2-3-4-5-6,列优先为 1-4-2-5-3-6
template <typename T>
class Image : public Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
{
// ...
};
Image<T> 的完整类视图如下,包含构造函数、析构函数、运算符重载函数、获取高度(行)函数等

1.2 读写操作
图像的 IO 读写函数,使用比较简单,如下:
// Read a grayscale image
Image<unit8_t> gray_img;
bool bRet = ReadImage("Foo.imgExtension", &gray_img); // Read a color image
Image<RGBColor> rgb_img;
bool bRet = ReadImage("Foo.imgExtension", &rgb_img);
图像 IO 读写函数的实现,稍微复杂,要根据不同的图像格式 (如 jpeg、tiff、png等),调用各自的库来实现 (如 libjpeg、libpng、libtiff 等),ReadImage() -> ReadJpg() -> ReadJpgStream() -> libjpeg
笔者刚接触图像处理时,并不知道 libjpeg 等库的存在,曾花了不少时间,尝试用 c 语言读写 jpeg 图片,现在看来是浪费了时间,并无多大的实际用处
在此摘录 OpenMVG 中 ReadJpgStream() 的实现代码,仅供阅读参考,希望不要投入过多精力
int ReadJpgStream(FILE * file, std::vector<unsigned char> * ptr, int * w, int * h, int * depth)
{
jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = &jpeg_error; if (setjmp(jerr.setjmp_buffer)) {
std::cerr << "Error JPG: Failed to decompress.";
jpeg_destroy_decompress(&cinfo);
return 0;
} jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, file);
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo); int row_stride = cinfo.output_width * cinfo.output_components; *h = cinfo.output_height;
*w = cinfo.output_width;
*depth = cinfo.output_components;
ptr->resize((*h)*(*w)*(*depth)); unsigned char *ptrCpy = &(*ptr)[0]; while (cinfo.output_scanline < cinfo.output_height) {
JSAMPROW scanline[1] = { ptrCpy };
jpeg_read_scanlines(&cinfo, scanline, 1);
ptrCpy += row_stride;
} jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return 1;
}
2 Numeric
Numeric 的实现,主要是基于一个开源的 C++ 模板库 Eigen,它包含了线性代数的基本运算:向量、矩阵、矩阵运算等
2.1 向量和矩阵
Vec2f 和 Vec2 分别表示类型为 float 和 double 的 2d 点 (x, y)
// 2d vector using float internal format
using Vec2f = Eigen::Vector2f; // 2d vector using double internal format
using Vec2 = Eigen::Vector2d;
Vec3f 和 Vec3 分别表示类型为 float 和 double 的 3d 点 (x, y, z)
// 3d vector using float internal format
using Vec3f =Eigen::Vector3f; // 3d vector using double internal format
using Vec3 = Eigen::Vector3d;
Mat 表示通用的一个矩阵;Mat2X 是列存储形式的一组 2d 点;Mat3X 则是列存储形式的一组 3d 点
// Unconstrained matrix using double internal format
using Mat = Eigen::MatrixXd; // 2xN matrix using double internal format
using Mat2X = Eigen::Matrix<double, 2, Eigen::Dynamic>;
// 3xN matrix using double internal format
using Mat3X = Eigen::Matrix<double, 3, Eigen::Dynamic>;
2.2 奇异值分解 - SVD
SVD 将一个矩阵分解成三个矩阵的乘积 $ A_{m \times n} = UDV^T$,其中,$U_{m\times m}$ 和 $V_{n \times n}$ 都是正交矩阵, $D_{m \times n}$ 是对角矩阵
在图像的几何变换中,仿射变换可视为一个奇异值分解的过程,参见博文 OpenCV 之 图像几何变换
变换过程如下:
$\begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{bmatrix} = \begin{bmatrix} \cos \theta & -\sin \theta \\ \sin \theta & \cos \theta \end{bmatrix} \begin{bmatrix} \sigma_{1} & \\ & \sigma_2 \end{bmatrix} \begin{bmatrix} \cos \phi & sin \phi \\ -\sin \phi & \cos \phi \end{bmatrix} = UDV^T$
更为形象的描述:第1个圆旋转 $V^T$得到第2个圆,再经过 $D$ 的拉伸得到第3个椭圆,最后旋转 $U$ 得到第4个椭圆

2.3 代码示例
SVD 的经典应用:求线性方程组 Ax=b 的最小二乘解
MatrixXf A = MatrixXf(3, 2);
A << -1, -0.0827, -0.737, 0.0655, 0.511, -0.562;
cout << "The matrix m:" << endl << A << endl; // SVD decomposition
JacobiSVD<MatrixXf> svd(A, ComputeThinU | ComputeThinV);
cout << "Singular values are:" << endl << svd.singularValues() << endl;
cout << "Left singular vectors U :" << endl << svd.matrixU() << endl;
cout << "Right singular vectors V :" << endl << svd.matrixV() << endl; // solve Ax=b
Vector3f b(1, 0, 0);
cout << "A least-squares solution of m*x = rhs is:" << endl << svd.solve(b) << endl;
OpenCV 中也有求解 Ax=b 最小二乘解的函数 solve(InputArray src1, InputArray src2, OutpuArray dst, int flags = DECOMP_LU)
cv::Mat A = (cv::Mat_<float>(3, 2) << -1, - 0.0827, -0.737, 0.0655, 0.511, -0.562);
cv::Mat b = (cv::Mat_<float>(3, 1) << 1.0, 0.0, 0.0);
cv::Mat x; // solve Ax=b
cv::solve(A, b, x, cv::DECOMP_SVD);
cout << "An OpenCV solution of Ax=b is: " << endl << x << endl;
从结果来看,Eigen 和 OpenCV 的求解基本一致

3 与 OpenCV 的转换
OpenCV 中也有一个表示图像容器的模板类 Mat,参见博文 OpenCV 之 Mat 类,二者的转换关系如下:
1)cv::Mat 转换为 Image (灰度图)
// cv Mat -> mvg Image
cv::Mat img_cv = cv::imread("messi.jpg", cv::IMREAD_GRAYSCALE); Image<uint8_t> img_mvg;
img_mvg.resize(img_cv.cols, img_cv.rows); // convert and save
cv::cv2eigen(img_cv, *(Image<uint8_t>::Base*) &img_mvg);
WriteImage("messi_mvg.jpg", img_mvg);
2)cv::Mat 转换为 Image (彩色图)
cv::Mat img_cv;
img_cv = cv::imread("messi.jpg"); Image<RGBColor> img_mvg;
img_mvg.resize(img_cv.cols, img_cv.rows);
cv::cvtColor(img_cv, img_cv, cv::COLOR_BGR2RGB); // convert and save
memcpy(img_mvg.data(), static_cast<unsigned char*>(img_cv.data), img_cv.cols * img_cv.rows * 3);
WriteImage("messi_mvg.jpg", img_mvg);
3)Image 转换为 cv::Mat
// Read a grayscale image
Image<unsigned char> img_mvg;
bool bRet = ReadImage("messi.jpg", &img_mvg); // mvg Image -> cv Mat
cv::Mat img_cv;
cv::eigen2cv(img_mvg.GetMat(), img_cv); // show image
cv::imshow("messi", img_cv);
cv::waitKey();
转换后的图片结果:

参考资料
《Introduction to Linear Algebra》 7.4 The Geometry of the SVD
OpenMVG 系列 (2):Image 和 Numeric的更多相关文章
- OpenMVG 系列 (1):入门简介
1 OpenMVG 简介 全称 Open Multiple View Geometry,是法国人 Pierre Moulon 读博期间开源的一个 C++ 库 最早版本 OpenMVG 0.1 是 ...
- WebGoat系列实验Injection Flaws
WebGoat系列实验Injection Flaws Numeric SQL Injection 下列表单允许用户查看天气信息,尝试注入SQL语句显示所有天气信息. 选择一个位置的天气,如Columb ...
- Python数据分析实战:使用pyecharts进行数据可视化
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:刘早起 开始使用 基本套路就是先创建一个你需要的空图层,然后使用.s ...
- 3.Code-First 约定(EF Code-First系列)
前面,我们已经了解了Code-First利用领域类,怎么为我们创建数据库的简单示例.现在我们来学习一下Code-First约定吧. 什么是约定 约定说白了,就是基于一套规矩办事,这里就是基于你定义好的 ...
- 深入浅出Mybatis系列(五)---TypeHandler简介及配置(mybatis源码篇)
上篇文章<深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)>为大家介绍了mybatis中别名的使用,以及其源码.本篇将为大家介绍TypeH ...
- SQLServer学习笔记系列2
一.写在前面的话 继上一次SQLServer学习笔记系列1http://www.cnblogs.com/liupeng61624/p/4354983.html以后,继续学习Sqlserver,一步一步 ...
- 【ASP.NET Identity系列教程(一)】ASP.NET Identity入门
注:本文是[ASP.NET Identity系列教程]的第一篇.本系列教程详细.完整.深入地介绍了微软的ASP.NET Identity技术,描述了如何运用ASP.NET Identity实现应用程序 ...
- SpringMVC学习系列(6) 之 数据验证
在系列(4).(5)中我们展示了如何绑定数据,绑定完数据之后如何确保我们得到的数据的正确性?这就是我们本篇要说的内容 —> 数据验证. 这里我们采用Hibernate-validator来进行验 ...
- String详解, String和CharSequence区别, StringBuilder和StringBuffer的区别 (String系列之1)
本章主要介绍String和CharSequence的区别,以及它们的API详细使用方法. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/string01. ...
随机推荐
- Spring 实例化方式有几种?为什么会用到 Cglib?
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! <Spring 手撸专栏>目录 [x] 第 1 章:开篇介绍,我要带你撸 Spri ...
- 解决mysql无法远程连接的问题
前言 最近开发中遇到一个问题,mysql在服务器本地可以登录,但是远程通过3306端口却不可以.这个问题困扰了我一周之久,终于在今天解决了.在解决的过程中试了很多的方法,遂记录下来,希望能给大家一些提 ...
- GO语言异常处理03---自定义异常
package main import ( "fmt" "time" ) /* type error interface { Error() string } ...
- Selenium八种元素定位方法源码阅读
接触过Selenium的都知道元素定位有八种方法,但用不同的方法在执行时有什么区别呢? 元素定位8种方法(Python版),当然还有每一个方法对应的find_elements方法 find_eleme ...
- 异步编程CompletableFuture
多线程优化性能,串行操作并行化 串行操作 // 以下2个都是耗时操作 doBizA(); doBizB(); 修改变为并行化 new Thread(() -> doBizA()).start() ...
- ES6中的数组常用方法
数组在JS中虽然没有函数地位那么高,但是也有着举足轻重的地位,下面我就结合这ES5中的一些常用的方法,与ES6中的一些方法做一些说明和实际用途.大家也可以关注我的微信公众号,蜗牛全栈. 一.ES5中数 ...
- LongAdder源码阅读笔记
功能描述 LongAdder通过创建多个副本对象,解决了多线程使用CAS更新同一个对象造成的CPU阻塞,加快了对线程处理的速度.当多个线程同一时刻更新一个AtomicLong类型的变量时,只有一个线程 ...
- jmeter--文件上传和下载
文件下载 文件下载的method一般是get.本例中导出excel文件. 下载文件如果要求下载到本地,需要另写脚本.采用jsr223或者beashell PostProcessor都可以. 代码如下: ...
- 还在担心写的一手烂SQL,送你4款工具
对于正在运行的mysql,性能如何,参数设置的是否合理,账号设置的是否存在安全隐患,你是否了然于胸呢? 俗话说工欲善其事,必先利其器,定期对你的MYSQL数据库进行一个体检,是保证数据库安全运行的重要 ...
- 「10.29」数列(exgxd)·数对(线段树优化DP)·最小距离(最短路,树上直径思想)
好久没碰到这么友好乱搞的题了.... A. 数列 考察的是exgcd的相关知识,最后的答案直接O(1)求即可 B. 数对 本来以为是原题,然后仔细看了看发现不是,发现不会只好乱搞骗分了 事实上直接按$ ...