2012-11-07 21:13:15 jia20003 阅读数 9872
  • 学习OpenCV3.2+QT5+ffmpeg实战开发视频编辑器视频教程

    OpenCV3.2+QT5+ffmpeg实战开发视频编辑器视频培训课程概况:教程中会讲解到基于opencv视频和摄像机录制、播放和播放进度控制,多视频图像合并、多视频图像融合、剪切、视频亮度、对比度、尺寸(近邻插值(手动实现),双线性插值,图像金字塔)、颜色格式(灰度图,二值化(阈值)),旋转镜像,视频裁剪(ROI),视频水印(ROI+weight),导出处理后的视频(包含音频,使用ffmpeg工具对音频进行抽取、剪切和终于opencv处理的视频合并)。

    19572 人正在学习 去看看 夏曹俊

图像处理之线性插值旋转算法

基本数学知识:

1.      三角函数基本知识,sin, cosin

2.      反三角函数基本知识,知道任意一点坐标P(x, y)求取该点的角度a = atag2(y/x)

3.      极坐标与笛卡尔坐标系转换知识

图像旋转矩阵:由此可以计算图像旋转以后的新的高度与宽度。


相关算法:

双线性插值算法,实现图像旋转反锯齿效果,同时是一种高质量的图像图像旋转方法,

缺点是计算量比较大。但是对现在的计算机硬件来说,速度还可以。

关于角度旋转:

1.      90度,180度,270度可以直接旋转坐标取得,像素直接映射取得。

2.      对于任何角度angle可以如下处理n = mod(angle, 90) = 1, 2, 3, 然后

        将角度旋转90,180,270然后再旋转角度(angle– n * 90)。

程序实现:

1.      首先根据输入角度参数angle, 背景填充颜色bgcolor初始化

2.      计算出旋转以后的图像width与height

3.      循环每个输出像素,计算机坐标

4.      反旋转输入角度到输入的目标像素浮点数坐标

5.      使用双线性插值完成目标像素填充,如果不在范围之内填充背景色。

6.      得到输出像素数据,返回旋转后图像

原图:


旋转45度的效果,背景填充为灰色:


程序代码(特殊角度旋转自己实现吧,有点懒)

package com.gloomyfish.filter.study;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;

public class RotateFilter extends AbstractBufferedImageOp {
	private double angle;
	private Color background;
	private int outw;
	private int outh;
	
	public RotateFilter() {
		this.angle = (45.0d/180.0d) * Math.PI;
		background = Color.BLACK;
		outw = -1;
		outh = -1;
	}
	
	public void setDegree(double angle) {
		this.angle = (angle/180.0d) * Math.PI;
	}
	
	public void setBackgroud(Color background) {
		this.background = background;
	}
	
    public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) {
        if ( dstCM == null )
            dstCM = src.getColorModel();
        return new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(outw, outh), dstCM.isAlphaPremultiplied(), null);
    }

	@Override
	public BufferedImage filter(BufferedImage src, BufferedImage dest) {
		int width = src.getWidth();
        int height = src.getHeight();

        int[] inPixels = new int[width*height];
        outw = (int)(width*Math.cos(angle)+height*Math.sin(angle)); 
        outh = (int)(height*Math.cos(angle)+width*Math.sin(angle));
        System.out.println("after rotate, new width : " + outw);
        System.out.println("after rotate, new height: " + outh);
        
        int[] outPixels = new int[outw*outh];
        getRGB( src, 0, 0, width, height, inPixels );
        int index = 0;
        
        int centerPixel = inPixels[height/2 * width + width/2];
        
        // calculate new center coordinate
        float centerX = outw / 2.0f + 0.5f;
        float centerY = outh /2.0f + 0.5f;
        
        // calculate the original center coordinate
        float ocenterX = width / 2.0f + 0.5f;
        float ocenterY = height /2.0f + 0.5f;
        
        float rx =0, ry = 0; //after rotated coordinate
        float px = 0, py = 0; // original coordinate
        float prow = 0, pcol = 0;
        for(int row=0; row<outh; row++) {
        	for(int col=0; col<outw; col++) {
        		rx = col - centerX;
        		ry = centerY - row;
        		float fDistance = (float)Math.sqrt(rx * rx + ry * ry);
        		float fPolarAngle = 0; //;
        		if(rx != 0) {
        			fPolarAngle = (float)Math.atan2((double)ry, (double)rx);
        		} else {
        			if(rx == 0) {
        				if(ry == 0) {
        					outPixels[index] = centerPixel;
        					continue; 
        				} 
        				else if(ry < 0) {
            				fPolarAngle = 1.5f * (float)Math.PI;
            			} else {
            				fPolarAngle = 0.5f * (float)Math.PI;
            			}
        			}
        		}
        		
        		// "reverse" rotate, so minus instead of plus
                fPolarAngle -= angle;
                px = fDistance * (float)Math.cos(fPolarAngle);
                py = fDistance * (float)Math.sin(fPolarAngle);

                // get original pixel float point
                prow = ((float)ocenterY) - py;
                pcol = ((float)ocenterX) + px;

                // now start the biline-interpolation algorithm here!!!
                int[] rgb = bilineInterpolation(inPixels, width, height, prow, pcol);
                
                index = row * outw + col;
                outPixels[index] = (255 << 24) | (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
                
        	}
        }

        if ( dest == null )
        	dest = createCompatibleDestImage( src, null );
        setRGB( dest, 0, 0, outw, outh, outPixels );
        return dest;
	}

	private int[] bilineInterpolation(int[] input, int width, int height, float prow, float pcol) {
		double row = Math.floor(prow);
		double col = Math.floor(pcol);
		if(row < 0 || row >= height) {
			return new int[]{background.getRed(), background.getGreen(), background.getBlue()};
		}
		if(col < 0 || col >= width) {
			return new int[]{background.getRed(), background.getGreen(), background.getBlue()};
		}
		
		int rowNext = (int)row + 1, colNext = (int)col + 1;
		if((row + 1) >= height) {
			rowNext = (int)row;
		}
		
		if((col + 1) >= width) {
			colNext = (int)col;
		}
		double t = prow - row;
		double u = pcol - col;
		double coffiecent1 = (1.0d-t)*(1.0d-u);
		double coffiecent2 = (t)*(1.0d-u);
		double coffiecent3 = t*u;
		double coffiecent4 = (1.0d-t)*u;
		
		int index1 = (int)(row * width + col);
		int index2 = (int)(row * width + colNext);
		
		int index3 = (int)(rowNext * width + col);
		int index4 = (int)(rowNext * width + colNext);
		int tr1, tr2, tr3, tr4;
		int tg1, tg2, tg3, tg4;
		int tb1, tb2, tb3, tb4;
		
        tr1 = (input[index1] >> 16) & 0xff;
        tg1 = (input[index1] >> 8) & 0xff;
        tb1 = input[index1] & 0xff;
        
        tr2 = (input[index2] >> 16) & 0xff;
        tg2 = (input[index2] >> 8) & 0xff;
        tb2 = input[index2] & 0xff;
        
        tr3 = (input[index3] >> 16) & 0xff;
        tg3 = (input[index3] >> 8) & 0xff;
        tb3 = input[index3] & 0xff;
        
        tr4 = (input[index4] >> 16) & 0xff;
        tg4 = (input[index4] >> 8) & 0xff;
        tb4 = input[index4] & 0xff;

        int tr = (int)(tr1 * coffiecent1 + tr2 * coffiecent4 + tr3 * coffiecent2 + tr4 * coffiecent3);
        int tg = (int)(tg1 * coffiecent1 + tg2 * coffiecent4 + tg3 * coffiecent2 + tg4 * coffiecent3);
        int tb = (int)(tb1 * coffiecent1 + tb2 * coffiecent4 + tb3 * coffiecent2 + tb4 * coffiecent3);

		return new int[]{tr, tg, tb};
	}

}
转载请务必注明

2016-05-28 18:37:46 sinat_31261757 阅读数 5667
  • 学习OpenCV3.2+QT5+ffmpeg实战开发视频编辑器视频教程

    OpenCV3.2+QT5+ffmpeg实战开发视频编辑器视频培训课程概况:教程中会讲解到基于opencv视频和摄像机录制、播放和播放进度控制,多视频图像合并、多视频图像融合、剪切、视频亮度、对比度、尺寸(近邻插值(手动实现),双线性插值,图像金字塔)、颜色格式(灰度图,二值化(阈值)),旋转镜像,视频裁剪(ROI),视频水印(ROI+weight),导出处理后的视频(包含音频,使用ffmpeg工具对音频进行抽取、剪切和终于opencv处理的视频合并)。

    19572 人正在学习 去看看 夏曹俊

在对图像进行空间变换的过程中,典型的情况是在对图像进行放大,旋转处理的时候,图像会出现失真的现象。这是由于在变换之后的图像中,存在着一些变换之前的图像中没有的像素位置。处理这一问题的方法被称为图像灰度级插值。常用的插值方式有三种:最近邻域插值、双线性插值、双三次插值。理论上来讲,最近邻域插值的效果最差,双三次插值的效果最好,双线性插值的效果介于两者之间。不过对于要求不是非常严格的图像插值而言,使用双线性插值通常就足够了。


图像的最近邻域插值很好理解,就是谁近就取谁的灰度值,分为前向映射和后向映射。


图像的双线性插值比较麻烦,不过如果理解了线性插值,则双线性插值也就不难理解了。

线性插值比较浅显易懂,就是在A,B两点连线之间插入第三个点C,C的值为C=aA+bB

但是如果C不在AB的线上该怎么办?于是就有了双线性插值。图像的双线性插值是基于与其相邻的四个端点的像素值来确定的。

如图,已知Q12,Q22,Q11,Q21,但是要插值的点为P点,P点不在任意两点的连线上,这就要用双线性插值了,首先在x轴方向上,对R1和R2两个点进行插值,这个很简单,然后根据R1和R2对P点进行插值,这就是所谓的双线性插值。

https://upload.wikimedia.org/wikipedia/commons/e/e7/Bilinear_interpolation.png

假如我们想得到未知函数 f 在点 P=\left( x, y\right) 的值,假设我们已知函数 fQ_{11} = \left( x_1, y_1 \right) , Q_{12} = \left( x_1, y_2 \right) , Q_{21} = \left( x_2, y_1 \right) , 及 Q_{22} = \left( x_2, y_2 \right) 四个点的值。

首先在 x 方向进行线性插值,得到

 f(R_1) \approx \frac{x_2-x}{x_2-x_1} f(Q_{11}) + \frac{x-x_1}{x_2-x_1} f(Q_{21}) \quad\mbox{Where}\quad R_1 = (x,y_1),
 f(R_2) \approx \frac{x_2-x}{x_2-x_1} f(Q_{12}) + \frac{x-x_1}{x_2-x_1} f(Q_{22}) \quad\mbox{Where}\quad R_2 = (x,y_2).

然后在 y 方向进行线性插值,得到

 f(P) \approx \frac{y_2-y}{y_2-y_1} f(R_1) + \frac{y-y_1}{y_2-y_1} f(R_2).

这样就得到所要的结果 f \left( x, y \right),

 f(x,y) \approx \frac{f(Q_{11})}{(x_2-x_1)(y_2-y_1)} (x_2-x)(y_2-y) + \frac{f(Q_{21})}{(x_2-x_1)(y_2-y_1)} (x-x_1)(y_2-y)
  + \frac{f(Q_{12})}{(x_2-x_1)(y_2-y_1)} (x_2-x)(y-y_1) + \frac{f(Q_{22})}{(x_2-x_1)(y_2-y_1)} (x-x_1)(y-y_1).

如果选择一个坐标系统使得 f 的四个已知点坐标分别为 (0, 0)、(0, 1)、(1, 0) 和 (1, 1),那么插值公式就可以化简为

 f(x,y) \approx f(0,0) \, (1-x)(1-y) + f(1,0) \, x(1-y) + f(0,1) \, (1-x)y + f(1,1) xy.

或者用矩阵运算表示为

 f(x,y) \approx \begin{bmatrix}1-x & x \end{bmatrix} \begin{bmatrix}f(0,0) & f(0,1) \\f(1,0) & f(1,1) \end{bmatrix} \begin{bmatrix}1-y \\y \end{bmatrix}

与这种插值方法名称不同的是,这种插值方法的结果通常不是线性的,它的形式是

 b_1 + b_2 x + b_3 y + b_4 x y. \,

常数的数目都对应于给定的 f 的数据点数目

 b_1 = f(0,0)
 b_2 = f(1,0) - f(0,0)
 b_3 = f(0,1) - f(0,0)
 b_4 = f(1,1) - f(1,0) - f(0,1) + f(0,0)

线性插值的结果与插值的顺序无关。首先进行 y 方向的插值,然后进行 x 方向的插值,所得到的结果是一样的。



2019-10-23 01:12:15 weixin_43981769 阅读数 27
  • 学习OpenCV3.2+QT5+ffmpeg实战开发视频编辑器视频教程

    OpenCV3.2+QT5+ffmpeg实战开发视频编辑器视频培训课程概况:教程中会讲解到基于opencv视频和摄像机录制、播放和播放进度控制,多视频图像合并、多视频图像融合、剪切、视频亮度、对比度、尺寸(近邻插值(手动实现),双线性插值,图像金字塔)、颜色格式(灰度图,二值化(阈值)),旋转镜像,视频裁剪(ROI),视频水印(ROI+weight),导出处理后的视频(包含音频,使用ffmpeg工具对音频进行抽取、剪切和终于opencv处理的视频合并)。

    19572 人正在学习 去看看 夏曹俊

数字图像处理实验——图像旋转

原理

首先定义图像的坐标系,在OpenCV中以左上角第一个像素为原点,向下为x轴,向右为y轴;图像旋转即将图像中的每一个像素旋转相同的角度。

但图像旋转后会导致图像信息丢失,所以需要扩大画布;并且,在旋转过程中还会出现空洞点,可以利用双线性插值算法填充空洞点。

前期准备

  1. 旋转公式如下:(逆旋转将theta变为-theta即可):

  2. 双线性插值:(其实就是与距离成反比)

编程时首先计算四个角点旋转后的位置,分别用x、y方向的最大值减去最小值得到新图像的画布大小,并建立新画布,此时新画布中的坐标系与旧画布中的坐标系之间存在一次平移变换,如图:

即设老坐标系为(X, Y),新坐标系为(X’, Y’),则有:

X’ = X + dx,Y’ = Y + dy;

然后对新图像的每个像素进行遍历,并把每个像素转换到旧坐标系中再进行一次逆旋转找到其对应原图像的位置,若已经超出了原图像的范围则将其置为0(或其他值);否则,利用双线性插值的方法获取新图像像素(向后映射算法)。


代码实现

#include <iostream>
#include <opencv2/opencv.hpp>
#include <cmath>

#define pi 3.1415926535
using namespace std;
using namespace cv;

/**
 * @param img 待旋转图像
 * @param angle 旋转角度,以逆时针旋转,单位为 度
 * 
 * 此函数完成对图片旋转任意角度
 */
Mat imgRotate(Mat img, double angle);

/**
 * 用于比较四个角点x,y坐标的最大最小值
 * 返回值中前两个值为x方向的最大最小值
 */
Vec4d minmax(Point2d[4]);  

int main()
{
    Mat test = imread("/home/xz/下载/lab2/duck.png", 0);
    Mat rotation = imgRotate(test, 180);
    imshow("original", test);
    imshow("after rotation", rotation);    
    waitKey(0);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

Mat imgRotate(Mat img, double angle )
{
    // 旋转公式为
    // x = x0 * cos + y0 * sin
    // y = -x0 * sin + y0 * cos

    //逆变换为
    // x0 = x * cos - y * sin
    // y0 = y * cos + x * sin
   // assert(!img.data);
    angle *= pi / 180.0;  // 化为弧度
    double rows = img.rows;
    double cols = img.cols;
    cout << rows << endl;
    cout << cols << endl;
    Point2d point[4] = {Point2f(0, 0 ), Point2f(0 , cols - 1), Point2f(rows - 1, 0), Point2f(rows - 1, cols - 1)};  //定义四个角点,在新的坐标系下的坐标

    for (int i = 0; i < 4; ++i)
    {
        double tempx = point[i].x;
        double tempy = point[i].y;
        point[i].x = tempx * cos(-angle) + tempy * sin(-angle);
        point[i].y = -tempx * sin(-angle) + tempy * cos(-angle);
    }
    Vec4f min_max = minmax(point);
    int width = abs((int)min_max[2]) + abs((int)min_max[3]) + 1 ;// 获得新坐标系的宽
    int height = abs((int)min_max[0]) + abs((int)min_max[1]) + 1; // 获得新坐标系的高

    cout << width << endl;
    cout << height << endl;

    int dx = min_max[1];
    int dy = min_max[3];

    Mat afterrotate(height, width,CV_8U, Scalar(0));

    for (int i = 0; i < afterrotate.rows; ++i)
    {
        for (int j = 0; j < afterrotate.cols; ++j)
        {
            double tempx = ((i + dx)* cos(angle) + (j + dy) * sin(angle)) ; // 新图像逆旋转后在旧坐标系下的坐标
            double tempy = (-(i + dx) * sin(angle) + (j + dy) * cos(angle));
        
            if((int)tempx < 0 || (int)tempx >= img.rows)
            {

            }
            else if((int)tempy < 0 || (int)tempy >= img.cols)
            {

            }
            else
            {
                double x_distance = tempx - int(tempx);// 记录小数部分,也即距离
                double y_distance = tempy - int(tempy);
                afterrotate.at<uchar>(i , j) = (1 - y_distance) *((1 - x_distance) * (int)img.at<uchar>((int)tempx, (int)tempy) + x_distance * (int)img.at<uchar>((int)tempx + 1, (int)tempy)) + y_distance * (
                    (1 - x_distance) * (int)img.at<uchar>((int)tempx, (int)tempy + 1) + x_distance * (int)img.at<uchar>((int)tempx + 1, (int)tempy + 1)
                );
            }
        }
    }
    
    return afterrotate;
}

Vec4d minmax(Point2d a[4])
{
    Vec4f temp;
    float x_max = a[0].x;
    float x_min = a[0].x;

    float y_max = a[0].y;
    float y_min = a[0].y;
    for(int i = 0; i < 4; ++i)
    {
        if(a[i].x > x_max)
            x_max = a[i].x;

        if(a[i].x < x_min)
            x_min = a[i].x;

        if(a[i].y > y_max)
            y_max = a[i].y;

        if(a[i].y < y_min)
            y_min = a[i].y;
    }
    temp[0] = x_max;
    temp[1] = x_min;
    temp[2] = y_max;
    temp[3] = y_min;

    cout << temp << endl;
    return temp;
}

输出结果如下:

总结

写程序的时候注意横纵坐标,写着写着自己就晕了(哈哈哈哈哈),学到了双线性插值和向后映射的方法,nice~

2019-01-27 21:35:08 qq_39475211 阅读数 272
  • 学习OpenCV3.2+QT5+ffmpeg实战开发视频编辑器视频教程

    OpenCV3.2+QT5+ffmpeg实战开发视频编辑器视频培训课程概况:教程中会讲解到基于opencv视频和摄像机录制、播放和播放进度控制,多视频图像合并、多视频图像融合、剪切、视频亮度、对比度、尺寸(近邻插值(手动实现),双线性插值,图像金字塔)、颜色格式(灰度图,二值化(阈值)),旋转镜像,视频裁剪(ROI),视频水印(ROI+weight),导出处理后的视频(包含音频,使用ffmpeg工具对音频进行抽取、剪切和终于opencv处理的视频合并)。

    19572 人正在学习 去看看 夏曹俊

为什么图像旋转需要插值?

图像旋转或者放大后,原本大小将发生改变,需要对一些新的像素点进行计算。MATLAB自带函数imrotate()可以实现图像旋转(MATLAB使用双三次插值)。感兴趣还可以了解flipdim、mirror、transp等函数。

常用的插值方法

最近邻插值

在一维空间中,最近邻插值就相当于四舍五入取整。在二维图像中,像素点的坐标都是整数,该方法就是选取离目标点最近的点。
最近相邻插值算法的优点是计算量很小,算法也简单,因此运算速度较快。但它仅使用离待测采样点最近的像素的灰度值作为该采样点的灰度值,而没考虑其他相邻像素点的影响,因而重新采样后灰度值有明显的不连续性,图像质量损失较大,会产生明显的马赛克和锯齿现象。最近邻插值
例如:我们约定,使用左/上原有像素点对放大的图像插值,将得到类似的结果。

双线性插值

两次线性插值算法(Bilinear Interpolation)是一种通过平均周围像素颜色值来添加像素的方法。该方法可生成中等品质的图像。

两次线性插值算法输出的图像的每个像素都是原图中四个像素(2×2)运算的结果,由于它是从原图四个像素中运算的,因此这种算法很大程度上消除了锯齿现象,而且效果也比较好。只是计算量稍大一些,但缩放后图像质量高,基本克服了最近邻插值法灰度值不连续的特点,因为它考虑了待测采样点周围四个直接邻点对该采样点的相关性影响。

但是,此方法仅考虑待测样点周围四个直接邻点灰度值的影响,而未考虑到各邻点间灰度值变化率的影响,因此具有低通滤波器的性质,从而导致缩放后图像的高频分量受到损失,图像边缘在一定程度上变得较为模糊。

双线性插值
在这里插入图片描述
可以直观地像上图那样理解双线性插值。
在这里插入图片描述
严谨的计算公式:
f(i+u,j+v)=(1u)(1v)f(i,j)+(1u)vf(i,j+1)+u(1v)f(i+1,j)+uvf(i+1,j+1)f( i+u, j+v)=(1-u)(1-v) f(i,j) + (1-u) v f(i,j+1) + u (1-v) f( i+1,j ) + u v f( i+1, j+1)

具体的实现还要结合下面讲的映射方法再操作。

双三次插值

这里涉及到复杂的计算公式,详细可以查看大佬的文章

常见的映射方法

通常情况下,一个整数位置(x,y)经过图像变换后,往往都位于非整数位置。此时就要采用插值技术。此时对于向前映射和向后映射,需要采取不同的策略。

前向映射(前向插值)

前向映射
即:通过公式(变换函数U、V)计算出目标像素位置,将灰度映射过去。(x0,y0)是原图某点,经过计算,得到旋转/放大后对应像素点(x,y),所以将灰度从原图映射到新图。

存在问题:

  1. 浮点坐标:如(1,1)映射成(0.5,0.5),这是无效坐标,这是可以用插值计算法进一步处理;
  2. 映射不完全:输入图像的像素总数小于输出的像素总数,会使得输出图像的部分像素与原始图像并没有映射关系,如放大操作;
  3. 映射重叠:与映射不完全正好相反,输出图像可能会存在映射后的像素重叠。

通常不建议使用前向映射。

后向映射

后向映射
旋转/放大后像素点(x,y)经过反推寻找,得到对应原图点(x0,y0)。
通过变换函数反函数 U’、V’ ,在新图像 每一个像素反推寻找它在原图的位置,进行映射。

这避免了映射不完全、映射重叠的问题,是常用做法。

图像旋转方法

旋转的矩阵公式:旋转变换的矩阵公式:

[x1y11]\begin{bmatrix} {x_{1}}&amp;{y_{1}}&amp;{1}\\ \end{bmatrix} = [x0y01]\begin{bmatrix} {x_{0}}&amp;{y_{0}}&amp;{1}\\ \end{bmatrix} [cos(θ)sin(θ)0sin(θ)cos(θ)00)01]\begin{bmatrix} {cos(\theta)}&amp;{sin(\theta)}&amp;{0}\\ {-sin(\theta)}&amp;{cos(\theta)}&amp;{0}\\ {0)}&amp;{0}&amp;{1}\\ \end{bmatrix}

逆运算矩阵公式:
[x0y01]\begin{bmatrix} {x_{0}}&amp;{y_{0}}&amp;{1}\\ \end{bmatrix} = [x1y11]\begin{bmatrix} {x_{1}}&amp;{y_{1}}&amp;{1}\\ \end{bmatrix} [cos(θ)sin(θ)0sin(θ)cos(θ)00)01]\begin{bmatrix} {cos(\theta)}&amp;{-sin(\theta)}&amp;{0}\\ {sin(\theta)}&amp;{cos(\theta)}&amp;{0}\\ {0)}&amp;{0}&amp;{1}\\ \end{bmatrix}

对数学推导有兴趣可以看看大佬的文章:详细的推导过程

利用这个矩阵计算公式,我们可以对像素直接计算出图像旋转后的结果。有需要则再进行插值。

小实验示例代码

小实验:

  1. 读取cameraman.tif,自行编写代码,分别使用前向插值(使用最邻近法),后向插值(自己选择合适的插值方法),将图像绕左上角逆时针旋转30度,再顺时针旋转30度,显示并计算旋转前后图像的MSE,分析原因。
    MSE
    在这里插入图片描述

前向映射:旋转30°,再插值比较:需要对映射不完全像素(黑色小点)处理。
在这里插入图片描述

后向映射:
在这里插入图片描述
计算结果: MSE = 8.9182,旋转时需要对一些像素进行插值,故再次转回来的时候,插值产生的灰度值(计算产生的)将会与原本的图像的不同。

clc
clear;
I = imread('cameraman.tif');
[r1,c1]=size(I); 
J = uint8(myimrotatef(I,30));%rotate 30 degrees clockwise
K = uint8(myimrotatef(J,-30));%rotate 30 degrees anticlockwise
[r2,c2]=size(K); 
K = K((r2-r1)/2+1:(r2+r1)/2,(c2-c1)/2+1:(c2+c1)/2);%cut the picture as large as the sample
subplot(1,3,1),imshow(I),title("Sample 256*256"),
subplot(1,3,2),imshow(J),title("Rotation1 350*350"),
subplot(1,3,3),imshow(K),title("Rotation2 256*256");
[PSNR, MSE] = psnr_mse(I, K);%calculate MSE and display MSE

完整代码包地址,没办法修改C币……实在要下载可以找某宝代下

by Morol_
cambridge.mo@foxmail.com

2017-10-20 18:41:19 u010385790 阅读数 1403
  • 学习OpenCV3.2+QT5+ffmpeg实战开发视频编辑器视频教程

    OpenCV3.2+QT5+ffmpeg实战开发视频编辑器视频培训课程概况:教程中会讲解到基于opencv视频和摄像机录制、播放和播放进度控制,多视频图像合并、多视频图像融合、剪切、视频亮度、对比度、尺寸(近邻插值(手动实现),双线性插值,图像金字塔)、颜色格式(灰度图,二值化(阈值)),旋转镜像,视频裁剪(ROI),视频水印(ROI+weight),导出处理后的视频(包含音频,使用ffmpeg工具对音频进行抽取、剪切和终于opencv处理的视频合并)。

    19572 人正在学习 去看看 夏曹俊

关于图像处理中的插值和旋转

一.插值
参考:http://blog.csdn.net/Real_Myth/article/details/49275999

在模板匹配问题中,涉及到角度插值和坐标插值(亚像素精度)问题。

角度插值:得到最佳匹配模板左右相邻的模板的信息,二次拟合(x为角度,y为得分)

坐标插值:得到最佳匹配点的3*3邻域点的匹配情况,三次拟合(xy坐标,y为得分)

确定拟合模型(什么方程)——》找到需要的信息(多少个点及其对应的值)——》求极值

注意: 相邻元素的确定。在我遇到的实际问题中,要找到同一尺度下的相邻角度的模板,而所有模板在制作保存时用到了并行,故需要遍历搜索。以三个角度为x,以它们同一点(变量的控制)处的得分为y

【将点一次带入得到方程组/最小二乘法列出方程组】

三次拟合示例:a0+a1x+a2y+a3x^2+a4y^2+a5xy=z

voidfittingCurve3D(double *x,double *y,double *z,intn,double *index)

{

    //直接把点带入方程,把方程组写成矩阵形式,结果用伪逆表示[inv(A'A)]A'b

    double *a =newdouble[n * 6];//已知点的信息矩阵,含xy

    double *b =newdouble[n];//已知点的信息矩阵,含z

   ///////构造a的一维数组,n*6

    for (int i = 0; i <n * 6; ++i)

    {

        if (i % 6 == 0)

        {

            a[i]= 1;

            a[i+ 1] = x[i / 6];//x

            a[i+ 2] = y[i / 6];//y

            a[i+ 3] = x[i / 6] * x[i / 6];//x^2

            a[i+ 4] = y[i / 6] * y[i / 6];//y^2

            a[i+ 5] = x[i / 6] * y[i / 6];//x*y

        }

    }

    CvMat AMat = cvMat(n, 6,CV_64FC1, a);//变成矩阵AMat

    CvMat *ATransposedMat =cvCreateMat(AMat.cols, AMat.rows,CV_64FC1);

    cvTranspose(&AMat,ATransposedMat);//转置ATransposedMat

///用于存储矩阵相乘结果,构造的数组n*n

    double *c =newdouble[6 * 6];

    for (int i = 0; i < 6 *6; ++i)

    {

        c[i]= 0;

    }

    CvMat invMat1 = cvMat(6,6,CV_64FC1, c);//变成矩阵invMat1

    cvGEMM(ATransposedMat,&AMat, 1, NULL, 0,&invMat1, 0);// ATransposedMat(转置)*AMat=invMat1

    cvInvert(&invMat1,&invMat1, 0);//求invMat1的逆矩阵,invMat1 

 

    ///////构造b的一维数组,n*1

    for (int i = 0; i <n; ++i)

    {

        b[i]= z[i];

    }

    CvMat BMat = cvMat(n, 1,CV_64FC1, b);//变成矩阵BMat

//用于存储矩阵相乘结果,构造的数组6*1

    double *d =newdouble[6];

    for (int i = 0; i < 6;++i)

    {

        d[i]= 0;

    }

    CvMat invMat2 = cvMat(6,1,CV_64FC1, d);//变成矩阵invMat2

    cvGEMM(ATransposedMat,&BMat, 1, NULL, 0,&invMat2, 0);//AMat的转置矩阵 * BMat矩阵 = invMat2

    CvMat indexMat = cvMat(6,1,CV_64FC1, index);

    cvGEMM(&invMat1,&invMat2, 1, NULL, 0,&indexMat, 0);//invMat1 * invMat2 = indexMat

    cvReleaseMat(&ATransposedMat);

    delete[] a;

    delete[] b;

    delete[] c;

    delete[] d;

}

各种曲面的拟合:如拟合球面 :x^2+ay^2+bz^2+cx+dy+ez+f=0

http://blog.csdn.net/hj199404182515/article/details/59480954

 

二.旋转

旋转算法原理:http://blog.csdn.net/liyuan02/article/details/6750828

正向映射://旋转后dst图像坐标系

double xtemp0 = c*x - s*y + (-0.5*c*(srcW - 1) + 0.5*s*(srcH - 1) +0.5*dstW);

double ytemp0 = s*x + c*y + (-0.5*s*(srcW - 1) -0.5*c*(srcH - 1) + 0.5*dstH);

逆向映射:(映射回原图的放大图temp

double xtemp = c*i + s*j + (-c*(dstW - 1)*0.5 - s* (dstH - 1)*0.5+0.5*temp->width);

double ytemp = -s*i + c*j + (s*(dstW - 1)*0.5 - c* (dstH -1)*0.5+ 0.5*temp->height);

//将原图复制到临时图像temp中心  (保证原图可以任意角度旋转的最小尺寸 )

int tempLength =int( sqrt((double)width *width + (double)height *height) + 10);

int tempX = (tempLength + 1) / 2 - width /2;

int tempY = (tempLength + 1) / 2 - height /2;

IplImage* temp =cvCreateImage(cvSize(tempLength, tempLength), src->depth,src->nChannels);

memset(temp->imageData, 255,  sizeof(char)*temp->imageSize);

cvSetImageROI(temp, cvRect(tempX, tempY,width, height));

cvCopy(src, temp, NULL);

cvResetImageROI(temp);

cvReleaseImage(&temp);

 

正向映射:输入图像上整数点坐标映射到输出图像之后,变成了非整数点坐标。因此,需要将其像素值按一定权重分配到其周围四个像素点上。遍历输出图像,对每个点,将每次映射分配而来的像素值叠加。

图像旋转中的插值:一般采用反向双线性插值

http://www.cnblogs.com/mlv5/archive/2012/02/02/2336321.html

示例:

intImgBilinearInter(floatfx,floatfy,IplImage * img) {

    //参考函数cv::PicRotaryBilInear——双线性插值

    int value;

    int x = floor(fx);

    int y = floor(fy);

    cv::Point P0 = Pixels_Bound(img, x, y);

    cv::Point P2 = Pixels_Bound(img, x + 1, y);

    cv::Point P1 = Pixels_Bound(img, x, y + 1);

    cv::Point P3 = Pixels_Bound(img, x + 1, y + 1);//边界饱和

    float u =fx - x;

    float v =fy - y;

    float pm3 = u*v;

    float pm2 = u*(1 - v);

    float pm1 = v*(1 - u);

    float pm0 = (1 - u)*(1 -v);

    // 指向图像的指针

    uchar* lp = (uchar *)img->imageData;

    int step =img->widthStep /sizeof(uchar);

    //value = (pm0*lp[P0.y*step + P0.x] + pm1*lp[P1.y *step +P1.x] + pm2*lp[P2.y *step + P2.x ] + pm3*lp[P3.y *step + P3.x ]);

    float value_float =(pm0*lp[P0.y*step + P0.x] + pm1*lp[P1.y *step + P1.x] + pm2*lp[P2.y *step +P2.x] + pm3*lp[P3.y *step + P3.x]);

    value= round(value_float);

    return value;

}

cv::Point Pixels_Bound(IplImage *pic, intx0,inty0) {

    if (x0<0) {x0 = 0; }

    elseif (x0 >=pic->width) { x0 = pic->width - 1; }

    if (y0<0) {y0 = 0; }

    elseif (y0 >=pic->height) { y0 = pic->height - 1; }

    cv::Point P(x0,y0);

    return P;

}

双线性插值部分,边界元素的处理需注意。

 

没有更多推荐了,返回首页