话说,平凡之处显真格,这一点也没错!  比如,对旋转图像进行双线性插值,很简单吧?  可,对我,折腾了大半天,也没有达到预期效果!  尤其是三个误区让我抓瞎好久:

1,坐标旋转公式。   这东西,要用的时候查资料,抄过来,从不记清,猛地一下让人写正确,确实不容易,虽然只是正余弦的排列问题。画图推导的方法也是知道,但是,奈何又记不得三角形的和角展开公式。没办法,只好逐一测试验证了,心血经验,45、90,135,180这几个角度最好都验证一下。

2,双插的数据来源。 一开始,思维上习惯地数据来源认定应该是旋转之后的,为此施展多种手段都不能较好克服数据有效性、配对性等异常。搞个带掩模的3*3滤波吧,却使图像变模糊了。  绝境反思,数据来源取自源图数据,该是多好的事呀。 仿射变换 warpAffine() 函数中的仿射矩阵就是默认为逆向。

3,双插的方法。 一直来,都知道X、Y方向要各插值一次,但却不明确它们的相互关系是 串行,而非并行!

以下贴出我后来完善出的旋转部分代码,有路过的高手请帮忙指点优化一下:

//旋转 平移 点坐标,依据旋转矩阵而来
void rotatePoint(const Point2d& src, Point2d& dst, const double angle, const Point2d& offset=Point2f(0,0))
{
const double cosAngle =cos(angle);
const double sinAngle =sin(angle);
dst.x = src.x * cosAngle + src.y * sinAngle + offset.x;
dst.y = src.y * cosAngle - src.x * sinAngle + offset.y; } //旋转 平移 点坐标 angle中的x值为 cos(angle) y为sin(angle)
inline void rotatePoint(const Point2d& src, Point2d& dst, const Point2d& angle, const Point2d& offset=Point2f(0,0))
{
//dst.x = src.dot(angle) + offset.x;
dst.x = src.x * angle.x + src.y * angle.y + offset.x;
dst.y = src.y * angle.x - src.x * angle.y + offset.y; } //双线性插值 a为左上点 b右上 c左下 d右下 权重因子Sx Sy 的取值范围为(0 , 1)由小坐标指向大坐标距离比
template<typename T>
inline double insertDLine(const T a, const T b, const T c, const T d, const double Sx, const double Sy)
{
const double Sx1 = 1 -Sx;
const double Sy1 = 1 -Sy;
return (a *Sx1 *Sy1 + b *Sx *Sy1 + c *Sx1 *Sy + d *Sx *Sy);
} //双线性插值 dst为data图像中的2*2子块 权重因子Sx Sy 的取值范围为(0 , 1)由小坐标指向大坐标距离比
void insertDLine(const Mat& src, Scalar& dst,const double Sx, const double Sy)
{
const int channels =src.channels(); const uchar *pU = src.ptr(0);
const uchar *pD = src.ptr(1);
const int depth =min(4, channels);
for (int i = 0; i < depth; i++)
{
dst[i] = insertDLine(pU[i], pU[i + channels], pD[i], pD[i + channels], Sx, Sy);
} } //将源图像旋转一定的角度
int rotateImage(const Mat& src, Mat& dst, double angle, const bool isDegree)
{
const int channels =src.channels();
if(channels > 4)
{
//dst=src; //
return -1;
} //将角度化为弧度
if(isDegree)
{
angle *=CV_PI/180;
} //参数初始化 const double cosAngle =cos(angle);
const double sinAngle =sin(angle); const int srcRows = src.rows;
const int srcCols = src.cols; const int dstCols =srcRows *abs(sinAngle) + srcCols *abs(cosAngle);
const int dstRows =srcRows *abs(cosAngle) + srcCols *abs(sinAngle); const int srcRowsLess2 = srcRows -2;
const int srcColsLess2 = srcCols -2; const Point2d centerA(srcCols/2 +0.5, srcRows/2 +0.5);
const Point2d centerB(dstCols/2 +0.5, dstRows/2 +0.5);
const Point2d rotateAngle(cosAngle, -sinAngle); //用于从目标图回旋转到初始图,角度取反 Point2d hitPoint;
int xL, yL;
Rect insertROI(0, 0, 2, 2);
Scalar insertVaule; //申请内存空间,并设置为 0
dst.create(dstRows, dstCols, CV_8UC(channels));
dst.setTo(Scalar(0,0,0,0)); for (int i = 0; i < dstRows; i++)
{
uchar *pDst=dst.ptr(i);
for (int j = 0; j < dstCols; j ++)
{
rotatePoint(Point2d(j, i)-centerB, hitPoint, rotateAngle, centerA);
xL =floor(hitPoint.x);
yL =floor(hitPoint.y); //从目标图中回转至源图像,不在区域内的直接跳过
if (xL < 0 || xL > srcColsLess2 || yL < 0 || yL > srcRowsLess2)
{
continue;
} insertROI.x =xL;
insertROI.y =yL;
insertDLine(src(insertROI), insertVaule, hitPoint.x -xL, hitPoint.y -yL); int base =j *channels;
for (int z = 0; z < channels; z++)
{
pDst[base +z] = insertVaule[z];
} }
} return 0; }

为了展示我的手写旋转函数rotateImage() 与仿射变换 warpAffine() 函数的效果比较,有如下代码段:

    double angle =90.0 * CV_PI/180; //将角度化为弧度 30
Mat rotateImg;
rotateImage(colorImg, rotateImg, angle); const double cosAngle =cos(angle);
const double sinAngle =sin(angle); Mat rrM =(Mat_<double>(2,3) << cosAngle, sinAngle, cosAngle *colorImg.rows, -sinAngle, cosAngle, sinAngle *colorImg.cols);
Mat rotatewarpAffine;
warpAffine(colorImg, rotatewarpAffine, rrM, colorImg.size()*2);

旋转30度时:

rotateImage30

warpAffine 30

旋转90度时:

rotateImage90.jpg

warpAffine 90

【OpenCV学习笔记】之六 手写图像旋转函数---万丈高楼平地起的更多相关文章

  1. 【opencv学习笔记七】访问图像中的像素与图像亮度对比度调整

    今天我们来看一下如何访问图像的像素,以及如何改变图像的亮度与对比度. 在之前我们先来看一下图像矩阵数据的排列方式.我们以一个简单的矩阵来说明: 对单通道图像排列如下: 对于双通道图像排列如下: 那么对 ...

  2. (转) OpenCV学习笔记大集锦 与 图像视觉博客资源2之MIT斯坦福CMU

          首页 视界智尚 算法技术 每日技术 来打我呀 注册     OpenCV学习笔记大集锦 整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的 ...

  3. OpenCV学习笔记:如何扫描图像、利用查找表和计时

    目的 我们将探索以下问题的答案: 如何遍历图像中的每一个像素? OpenCV的矩阵值是如何存储的? 如何测试我们所实现算法的性能? 查找表是什么?为什么要用它? 测试用例 这里我们测试的,是一种简单的 ...

  4. OpenCV 学习笔记(13)图像转换成视频

    关键 1参数里的分辨率是图像本身的分辨率,而不是指定生成的视频分辨率.如果要修改分辨率,要么后期软件处理,要么读图的时候resize 2要正常退出,不要强制退出. 3生成的只能是avi格式. #inc ...

  5. OpenCV学习笔记(4)——图像上的算术运算

    学习图像上的算术运算,加法,减法,位运算等 1.图像加法 使用cv2.add()将两幅图像进行加法运算,也可以用numpy运算,直接img+img1.两幅图像的大小和类型必须一致,或者第二个图像可以是 ...

  6. OpenCV学习笔记(七) 图像金字塔 阈值 边界

    转自: OpenCV 教程 使用 图像金字塔 进行缩放 图像金字塔是视觉运用中广泛采用的一项技术.一个图像金字塔是一系列图像的集合 - 所有图像来源于同一张原始图像 - 通过梯次向下采样获得,直到达到 ...

  7. OpenCV学习笔记(一) - 边界填充、Rect函数

    边界填充: c++实现,测试在mac pro里,输入720p时间0.4ms: cv::copyMakeBorder(image, dst, , , , , cv::BORDER_REPLICATE); ...

  8. 【opencv学习笔记六】图像的ROI区域选择与复制

    图像的数据量还是比较大的,对整张图片进行处理会影响我们的处理效率,因此常常只对图像中我们需要的部分进行处理,也就是感兴趣区域ROI.今天我们来看一下如何设置图像的感兴趣区域ROI.以及对ROI区域图像 ...

  9. 【opencv学习笔记五】一个简单程序:图像读取与显示

    今天我们来学习一个最简单的程序,即从文件读取图像并且创建窗口显示该图像. 目录 [imread]图像读取 [namedWindow]创建window窗口 [imshow]图像显示 [imwrite]图 ...

随机推荐

  1. <一> jQuery 简单介绍

    jQuery 库位于一个 JavaScript 文件中,其中包含了所有的 jQuery 函数. 可以通过下面的标记把 jQuery 添加到网页中: <head> <script ty ...

  2. MongoDB索引介绍

    MongoDB中的索引其实类似于关系型数据库,都是为了提高查询和排序的效率的,并且实现原理也基本一致.由于集合中的键(字段)可以是普通数据类型,也可以是子文档.MongoDB可以在各种类型的键上创建索 ...

  3. Linux系统架设支持自助开通Shado wsocks及VPN前端的教程

    程序实现:通过网页端注册,自助开通VPN帐号及Shadowsocks帐号.并可实现流量统计 系统要求 Debian 6 x64 纯净系统 by: Lop ①配置环境 apt-get updateapt ...

  4. codeforces 387C George and Number

    这道题目的意思是对于每个要删除的数字,向前或向后找到一块连续的数字,而它是其中最小的: 很容易看出对于所有要先删除的数字要从大到小删除: 然后对于每个要删除的字母,要找到比他小的,但是在原数列中又靠它 ...

  5. Python安装模块出错(ImportError: No module named setuptools)解决方法

    原地址:http://www.cnblogs.com/BeginMan/archive/2013/05/28/3104928.html 在window平台下安装第三方模块时,出现这样的错误:

  6. Android-java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

    章出自:luchg技术交流 http://www.luchg.com 版权所有.本站文章除注明出处外,皆为作者原创文章,可自由引用,但请注明来源,谢谢. Android-java.lang.Runti ...

  7. JavaScript 弹出窗体点击按钮返回选择数据的实现

    首先是父页面的代码: <head runat="server"> <title>无标题页</title> <%-- <script ...

  8. SPRING IN ACTION 第4版笔记-第四章ASPECT-ORIENTED SPRING-008-带参数的ADVICE

    一. 假设有情形如:cd里有很多轨,当播放音乐时,要统计每个音轨的播放次数,这些统计操作不应放在播放方法里,因为统计不是播放音乐的主要职责,这种情况适合应用AOP. 二. 1. package sou ...

  9. Spring事务管理--多个ORM框架在使用时的情况分析

    公司的项目已经接近尾声了,总结一下项目中用到的技术,我发现项目中的有些东西还是挺模糊的,只是知道这么用就行了.并不清楚其中的原理.由于公司的项目比较老,是7年前的一个项目了,中间一直有人在维护,也是在 ...

  10. Java实现查看当前目录下的文件

    package com.tfj.file; import java.io.File; import java.util.ArrayList; /** * @author JackSile E-mail ...