【图像处理】基于OpenCV底层实现的图片旋转
image processing 系列
图片旋转,本质上是对旋转后的图片中每一个像素点计算在原图的位置。然后照搬过来就好。
(多说一句。假设计算出来在原图中的位置不是整数而是小数,由于像素点个数都是整数,就须要小数到整数的转换。
这个转换过程是有讲究的,须要用到插值:近期邻插值、双线性插值等等。这里我使用的是最简单的近期邻插值。即对小数四舍五入成整数。C/C++ 实现四舍五入见这里)
完整 github 代码:image-processing (里面同一时候包括OSTU / 大津算法、直方图均衡化、滤波器等算法。还包括两种測试图片)。
图形图像课上通常会介绍旋转变换矩阵,当中 t 为须要旋转的角度,[x'; y']是变换后坐标(当中分号表示上下关系):
即表示为:[x'; y'] = [cos(t) sin(t); -sin(t) cos(t)][x; y]
由于我个人兴趣爱好(放P就是老师逼的。。。)。不同意使用 OpenCV 封装好的旋转函数。仅仅能自己实现,我開始的想法是:先求变换矩阵逆矩阵。然后将一张全黑图中每一个点一一相应插值到原图中。
结果发现转换后图片全黑了……
后来发现原点设置不正确。用OpenCV中的Mat格式存储(或二维数组)的图片。原点在左上角。
可是想要实现的旋转原点在图片中心。
同一时候。Mat格式存储(或二维数组)的坐标系中y轴正方向向下。这样人类视觉上的顺时针旋转,在二维数组的坐标系中是逆时针旋转。
最重要的一点,也是二维数组操作中极易忽略的一点:数组操作的是数组下标,不是坐标系(数组的行数rows是矩形的宽width。列数cols是矩形的长length)。比方坐标系(此时为了更贴近数组布局,我们如果
y 轴坐标系是向下的)中,矩形顶点是:
可是在数组中。由于是行优先,所以四个点的下标取值为:
有没有发现,两种坐标是相反的!
总结下来,我们的图片旋转须要注意下面几点:
- 变换后图片中的每一个像素点(i; j),须要平移到相对旋转中心的新坐标,即(i - Mat.rows/2; j - Mat.cols/2)。计算完毕之后,须要再次还原到相对左上角原点的旧坐标。
- 本来须要 变换后图片 乘以 原图变换矩阵的逆矩阵 相应到原图中坐标。可是由于y轴方向向下,所以 变换后图片 乘以原图变换矩阵(无需逆矩阵) 就可以相应到原图中坐标(顺时针旋转50度,还原操作是逆时针旋转50度)。
- 矩阵下标与原图变换矩阵相乘之前。须要将矩阵下标两值互换。相乘之后,须要再次互换下标值还原成矩阵下标。
因此对于一个经过旋转 t 度之后数组下标为[m‘, n’]的像素值,还原成原图中的数组下标[m; n]计算为:
[cos(t) -sin(t); sin(t) cos(t)] ([m'; n'] - [Mat.rows/2; Mat.cols/2]) = [m; n] - [Mat.rows/2; Mat.cols/2]
源码附上:
Mat nearestNeighRotate(cv::Mat img, float angle)
{
int len = (int)(sqrtf(pow(img.rows, 2) + pow(img.cols, 2)) + 0.5); Mat retMat = Mat::zeros(len, len, CV_8UC3);
float anglePI = angle * CV_PI / 180;
int xSm, ySm; for(int i = 0; i < retMat.rows; i++)
for(int j = 0; j < retMat.cols; j++)
{
xSm = (int)((i-retMat.rows/2)*cos(anglePI) - (j-retMat.cols/2)*sin(anglePI) + 0.5);
ySm = (int)((i-retMat.rows/2)*sin(anglePI) + (j-retMat.cols/2)*cos(anglePI) + 0.5);
xSm += img.rows / 2;
ySm += img.cols / 2; if(xSm >= img.rows || ySm >= img.cols || xSm <= 0 || ySm <= 0){
retMat.at<Vec3b>(i, j) = Vec3b(0, 0);
}
else{
retMat.at<Vec3b>(i, j) = img.at<Vec3b>(xSm, ySm);
}
} return retMat;
}
好,我们来測试看看:
int main()
{
Mat img = imread("../HelloWorld.png");
retImg = nearestNeighRotate(img, -20.f);
namedWindow("nearNeigh", CV_WINDOW_AUTOSIZE);
imshow("nearNeigh", retImg); waitKey();
cvDestroyAllWindows();
return 0;
}
结果(旋转了20度)为
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXJvbnlvdW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
【图像处理】基于OpenCV底层实现的图片旋转的更多相关文章
- 基于OpenCv和swing的图片/视频展示Java实现
基于OpenCv和swing实现图片/视频的展示 图片的展示 swing展示图片,多为操作BufferedImage,这里要关注的核心是将Mat转为BufferedImage. 代码如下: publi ...
- 【图像处理】基于OpenCV底层实现的直方图匹配
image processing 系列: [图像处理]图片旋转 [图像处理]高斯滤波.中值滤波.均值滤波 直方图匹配算法.又称直方图规定化.简单说.就是依据某函数.或者另外一张图片的引导,使得原图改变 ...
- android图像处理系列之四-- 给图片添加边框(上)
图片处理时,有时需要为图片加一些边框,下面介绍一种为图片添加简单边框的方法. 基本思路是:将边框图片裁剪成八张小图片(图片大小最好一致,不然后面处理会很麻烦),分别对应左上角,左边,左下角,下边,右下 ...
- android图像处理系列之五-- 给图片添加边框(中)
前面一篇讲到给图片加边框的方式,只能给图片加一些有规则的边框,如果想加一些比较精美的效果,就有点麻烦了.下面就给出解决这个问题的思路. 思路是:一些比较精美的花边图片我们是很难用代码控制,就目前本人水 ...
- android图像处理系列之四--给图片添加边框(上)
图片处理时,有时需要为图片加一些边框,下面介绍一种为图片添加简单边框的方法. 基本思路是:将边框图片裁剪成八张小图片(图片大小最好一致,不然后面处理会很麻烦),分别对应左上角,左边,左下角,下边,右下 ...
- android图像处理系列之五--给图片添加边框(中)
前面一篇讲到给图片加边框的方式,只能给图片加一些有规则的边框,如果想加一些比较精美的效果,就有点麻烦了.下面就给出解决这个问题的思路. 思路是:一些比较精美的花边图片我们是很难用代码控制,就目前本人水 ...
- 基于 opencv 的图像处理入门教程
前言 虽然计算机视觉领域目前基本是以深度学习算法为主,但实际上很多时候对图片的很多处理方法,并不需要采用深度学习的网络模型,采用目前成熟的图像处理库即可实现,比如 OpenCV 和 PIL ,对图片进 ...
- OpenCV2学习笔记(十四):基于OpenCV卡通图片处理
得知OpenCV有一段时间.除了研究的各种算法的内容.除了从备用,据导游书籍和资料,尝试结合链接的图像处理算法和日常生活,第一桌面上(随着摄像头)完成了一系列的视频流处理功能.开发平台Qt5.3.2+ ...
- Python图像处理丨基于OpenCV和像素处理的图像灰度化处理
摘要:本篇文章讲解图像灰度化处理的知识,结合OpenCV调用cv2.cvtColor()函数实现图像灰度操作,使用像素处理方法对图像进行灰度化处理. 本文分享自华为云社区<[Python图像处理 ...
随机推荐
- CRegKey
1.简介 CRegKey提供了对系统注册表的操作方法,通过CRegKey类,可以方便的打开注册表的某个分支或子键(CRegKey::Open),可以方便的修改一个键的键值(CRegKey::SetVa ...
- vue工程化:返回顶部和底部的动画效果
. <template> <div> <div class="scroll" :class="{show:isActive}"&g ...
- HYSBZ - 2763 飞行路线(分层图最短路线)
题目: Alice和Bob现在要乘飞机旅行,他们选择了一家相对便宜的航空公司.该航空公司一共在n个城市设有业务,设这些城市分别标记为0到n-1,一共有m种航线,每种航线连接两个城市,并且航线有一定的价 ...
- leds-gpio driver
我们还是先看看platform device是如何define的 platform device 是如何定义的 example1 在板级驱动中定义, 通过platform_add_devices()函 ...
- Python之协程函数
Python之协程函数 什么是协程函数:如果一个函数内部yield的使用方法是表达式形式的话,如x=yield,那么该函数成为协程函数. def eater(name): print('%s star ...
- rbac组件之角色操作(二)
为了与stark组件分离,形成独立的模块,所以rbac数据表的操作需要单独进行操作,对角色表的操作. urls.py urlpatterns = [ re_path(r'^roles/list/$', ...
- YOLOv3测试命令
一.老规矩 在darknet\build\darknet\x6下按住shift键,点击鼠标右键选择“在此处打开Powershell 窗口(s)” 二.测试图片命令: .\darknet detect ...
- 初学数位DP
所谓数位dp,字面意思就是在数位上进行dp,数位的含义:一个数有个位.十位.百位.千位.等等,数的每一位就是数位. 数位DP一般应用于: 求出给定区间[A,B]内,符合条件P[i]的数 i 的个数. ...
- UVALive 6430 (水dp)
https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_probl ...
- MTK平台 GPU 相关知识
一.什么是Render script,以及mtk平台GPU support情况 [DESCRIPTION] 1.什么是RenderScript ? 2.RenderScript 干什么? 3.MTK平 ...