2019-01-08 15:59:25 Aidam_Bo 阅读数 254

透镜由于制造精度以及组装工艺的偏差会引入畸变,导致原始图像的失真。镜头的畸变分为径向畸变和切向畸变两类。

1. 径向畸变

顾名思义,径向畸变就是沿着透镜半径方向分布的畸变,产生原因是光线在原理透镜中心的地方比靠近中心的地方更加弯曲,这种畸变在普通廉价的镜头中表现更加明显,径向畸变主要包括桶形畸变和枕形畸变两种。以下分别是枕形和桶形畸变示意图:

成像仪光轴中心的畸变为0,沿着镜头半径方向向边缘移动,畸变越来越严重。畸变的数学模型可以用主点(principle point)周围的泰勒级数展开式的前几项进行描述,通常使用前两项,即k1和k2,对于畸变很大的镜头,如鱼眼镜头,可以增加使用第三项k3来进行描述,成像仪上某点根据其在径向方向上的分布位置,调节公式为:

公式里(x0,y0)是畸变点在成像仪上的原始位置,(x,y)是畸变较真后新的位置,下图是距离光心不同距离上的点经过透镜径向畸变后点位的偏移示意图,可以看到,距离光心越远,径向位移越大,表示畸变也越大,在光心附近,几乎没有偏移。

2. 切向畸变

切向畸变是由于透镜本身与相机传感器平面(成像平面)或图像平面不平行而产生的,这种情况多是由于透镜被粘贴到镜头模组上的安装偏差导致。畸变模型可以用两个额外的参数p1和p2来描述:

下图显示某个透镜的切向畸变示意图,大体上畸变位移相对于左下——右上角的连线是对称的,说明该镜头在垂直于该方向上有一个旋转角度。

径向畸变和切向畸变模型中一共有5个畸变参数,在Opencv中他们被排列成一个5*1的矩阵,依次包含k1、k2、p1、p2、k3,经常被定义为Mat矩阵的形式,如Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0));这5个参数就是相机标定中需要确定的相机的5个畸变系数。求得这5个参数后,就可以校正由于镜头畸变引起的图像的变形失真,下图显示根据镜头畸变系数校正后的效果:

2019-01-03 15:44:13 qq_27637587 阅读数 802

几何畸变图像恢复 OpenCV3 - 数字图像处理作业3

作业3:相同条件下拍到的棋盘图和日历钟表图,尝试建立几何畸变关系,并对它们进行恢复。注意:不能采用椭圆的变换。
在这里插入图片描述
在这里插入图片描述

算法步骤

  1. 坐标变换: 在畸变和正常图象之间建立坐标关系。
    在这里插入图片描述
  2. 灰度级插值,最邻近像素灰级代替法
    在这里插入图片描述

实际操作过程:

第一步. 角点检测

调用openCV角点检测函数获取棋盘格角点。

	//cornerMat CV_32FC2 56 rows * 1 cols * 2channel 取值用Point2f 或者 Vec2f
	findChessboardCorners(chessImg, Size(8, 7), cornerMat, CALIB_CB_FILTER_QUADS);
	drawChessboardCorners(chessCorner, Size(8, 7), cornerMat, true);
	imshow("chess with corner", chessCorner);

在这里插入图片描述

第二步. 得到畸变前的网格点位置

通过图片中心畸变小(基本无畸变)的特性,计算得出棋盘格角点畸变前的坐标,见下图绿色点。
畸变前网格点

第三步. 控制网格法得出畸变函数

为了拟合该图的函数,双线性变换参数不够,增加参数数量得到如下公式:
x=a0+a1x+a2y+a3xy+a4x2+a5y2+a6xy2+a7x2yx'=a_0+a_1x+a_2y+a_3xy+a_4x^2+a_5y^2+a_6xy^2+a_7x^2y y=b0+b1x+b2y+b3xy+b4x2+b5y2+b6xy2+b7x2yy'=b_0+b_1x+b_2y+b_3xy+b_4x^2+b_5y^2+b_6xy^2+b_7x^2y
一共16个未知数,通过梯度下降算法迭代来拟合函数,计算参数。由于畸变的中心对称性质,注意在参数计算中,将原点设为画面中心的点。

梯度下降拟合函数曲线过程

第四步. 最近邻像素插值

通过上一步得到的参数,计算原图中每个点的畸变后位置坐标,取最近邻像素值替代,即可恢复出原图像。
在这里插入图片描述
源代码下载链接

2014-04-23 16:34:02 liu_forever 阅读数 5783

1. 最近遇到图像畸变矫正的问题,


因此学习了张正友的"A Flexible New Technique for CameraCalibration"一文,只考虑二阶径向畸变,描述如下:

2. 校正径向畸变

径向畸变的校正过程就是给理想像素点位置赋予畸变后的坐标处的像素值的过程。

其中(u,v)为理想的像素坐标,由于径向畸变的存在,实际得到的有畸变的位置变成了u',v'. 为了达到矫正的目的,将理想图像坐标(u,v)处的坐标值赋为实际得到的图像中的(u'.v')处的像素值即可。

畸变校正对比如下图:


校正后:


实现代码如下:

void undistortion(Mat &sourceImg, Mat &compensatedImg)
{
	
	float normalizedX;				//nomalizeX = Xw / Zw
	float normalizedY;				//nomalizeY = Yw / Zw
	float distance;					//radius
	float radialDistor;				//radial distortion factor


	int CAM_U0=269;
	int CAM_V0=244;
	float CAM_FX=1674.549990;
	float CAM_FY=1675.318550;
	float CAM_K1=-0.273573;
	float CAM_K2=-0.790143;


	uchar sourceV;				//gray value of current position
	int distorPixelPositionX;			//畸变后的位置
	int distorPixelPositionY;              //畸变后的位置


	//compensatedImg = cvCreateImage(cvSize(sourceImg->height, sourceImg->width), IPL_DEPTH_8U, 1);
	//show_image(sourceImg);


	for(int h = 0; h < sourceImg.rows; h++)
	{
		for(int w = 0; w < sourceImg.cols; w++)
		{
			normalizedX = ((float)w - CAM_U0) / CAM_FX;
			normalizedY = ((float)h - CAM_V0) / CAM_FY;


			distance = normalizedX * normalizedX + normalizedY * normalizedY;
			radialDistor = CAM_K1 * distance + CAM_K2 * pow(distance , 2) ;


			//compensatedX = (((float)w - CAM_U0) / CAM_FX ) / radialDistor;
			//compensatedY = (((float)h - CAM_V0) / CAM_FY ) / radialDistor;


			distorPixelPositionX = w+((float)w-CAM_U0)*radialDistor;
			distorPixelPositionY = h+((float)h-CAM_V0)*radialDistor;


			if(distorPixelPositionX < 0 || distorPixelPositionX > sourceImg.cols - 1)
			{
				distorPixelPositionX = 0;
			}
			if(distorPixelPositionY < 0 || distorPixelPositionY > sourceImg.rows- 1)
			{
				distorPixelPositionY = 0;
			}


			//sourceV = cvGet2D(sourceImg, h, w);
			sourceV = sourceImg.at<uchar>(distorPixelPositionY,distorPixelPositionX);;
			compensatedImg.at<uchar>(h,w)=sourceV;


			//cvSet2D(compensatedImg, newPixelPositionY, newPixelPositionX, sourceV);
		}
	}
}
2019-04-16 15:13:24 tuuzhang 阅读数 463

        最近在研究畸变图像的畸变矫正问题。网上关于畸变矫正的方法多是采用OpenCV镜头标定的方法,标定出畸变模型参数和镜头内参,再根据参数后向映射得到矫正图像。但是存在这样一种情况:当我们手上只有畸变的图像,没有广角镜头可标定,或者是镜头没法拍到标定板图像。这时候需要考虑能否直接从畸变图像本身得到参数,然后畸变矫正呢?

        在IPOL网站上,有大神提供了只根据畸变图像中原本为直线,经过镜头畸变后变为曲线,然后根据曲线上的点计算出畸变参数。根据文献,算法采用HOUGH变换思想进行畸变参数的计算,IPOL上提供了文献和source code,源码依赖libpng实现图像的读取和写入。在这里我将libpng配置编译成一个VS solution,在solution基础上添加畸变参数计算和矫正代码,成为一个project。代码直接clone下来即可编译运行。下图是代码运行结果。

输入图像:

输出图像:

GitHub地址:https://github.com/tuuzhang/lens-undistortion-with-automatic-method

Tips:

1、libpng库采用的是以图像左下角作为图像坐标原图,这一点需要特别注意。

2、当图像的畸变程度较为轻微时,模型选择需要注意一下,可以选择div模型且畸变参数值选择2(实际上只有一个参数)

2019-07-09 18:49:24 baidu_32048673 阅读数 161

    广角镜头的摄像设备拍摄出来的图像经常会有桶形畸变的问题。原因在于广角镜头使用的是凸透镜,初中物理知识告诉我们凸透镜会对光线起汇聚作用,这是光的折射造成的。而离镜头中心越远,折射效果越强,因而其拍出来的照片会以镜头中心为圆心,呈圆形向外扩展失真,如下图所示:

    像上面这样的图像,如果用在一些还原性要求较高的场景是不行的,需要对图像做畸变矫正。而由于很多时候我们并不知道摄像头的物理参数和其他一些信息,只是拿到一个可以输出画面的摄像头,因而比较常采取的桶形畸变矫正算法为多项式修正算法。下面本文将展开介绍该算法并给出矫正后的结果,如果感觉本文的描述不够清楚,也可以参考这个网址中的文章(本文也是参考该文章的)。

    下面进入主题。

    首先,我们需要制作或者购买一个同心圆标定板,如下图所示:

    

    然后将该同心圆标定板的圆心和镜头中心(一般镜头中心就是图像的中心点,当然也有像本文章实验时这样比较奇葩的,镜头中心点在图像中心点下方的情况)对准获得一张待处理图像(在采集图像的时候要将同心圆标定板靠近镜头,使得图像中的同心圆覆盖到图像边界,不然图像的边界位置矫正效果会不大好),如下图所示:

    由上面图像我们可以看到,越外围的同心圆凑的越紧,也就是说偏离镜头中心越远,图像畸变越厉害,与上面的论述一致。

    这里我们认为离镜头中心最近的同心圆没有畸变,然后测出其与镜头中心的距离为143,则在没有畸变的图像中从近到远的同心圆(1-7)半径应该为R1(143,286,429,572,715,858,1001)。我们在上面图像中测出来的同心圆半径为R(143,260.5,341.5,395,430.5,454,469)。

    对图像做畸变矫正,实际上就是将图像中的像素点放到其理论上应该在的位置而已,因此我们可以由上面的两组值推算出计算出畸变后图像像素与镜头中心的距离和理论距离的比值K,再根据这一组K值来拟合出一道R与K的关系式,从而得到图像中像素与镜头中心距离与实际距离的映射关系,以此矫正图像。

    我们将理论值与实际畸变后的值相除,得到畸变矫正系数K(1,1.0979,1.2562,1.4481,1.6609,1.8899,2.1343),将R-K绘制成折线图如下所示:

    由于上面我们以第一个圆半径作为标准,半径之前的数据为空,不利于后面计算,因此我们将多取一个(0,1)点,折线图修正如下:

    粗略地,有了上图,我们已经可以求出图像像素点距离镜头中心各个距离所对应的K值。但由于该图只是折线图,拟合的并不好,且要分段编程比较麻烦,所以我们用四次多项式重新拟合这些点(MATLAB实现),得到四项多项式公式:

    拟合结果如下图所示:

    有了这个公式,便有了畸变图像像素点与正常图像像素点的映射关系了。借由这个映射关系编写下面的代码我们便可得到初步的矫正图像。

 

char *filename = "2542.bmp";//图像路径  
img = cvLoadImage(filename);
paintObj.drawpicinit(img, IDC_STATIC_SHOW, this);
CPoint lenscenter(480,295);//镜头中心在图像中的位置
uchar *data = (uchar*)img->imageData;//原图像数据
uchar *newimgdata = new uchar[img->widthStep*img->height];//新图像数据
for (int row = 0; row<img->height; row++)//操作数据区,要注意OpenCV的RGB的存储顺序为GBR  
    for (int cols = 0; cols<img->width; cols++)//示例为亮度调节  
    {
        int pointsrc = row*img->widthStep + cols * 3;
        double r = sqrt((row- lenscenter.y)*(row - lenscenter.y) + (cols - lenscenter.x)*(cols - lenscenter.x));    
        double s = 1.0008 - 0.0031*r + 3.7101*pow(10, -5)*pow(r, 2) - 1.3491*pow(10, -7)*pow(r, 3)+1.7184*pow(10, -10)*pow(r, 4);//比例
        int x = (cols - lenscenter.x)*s + lenscenter.x;
        int y = (row - lenscenter.y)*s + lenscenter.y;
        
        if (x >= 0 && x < img->width && y >= 0 && y < img->height)
        {
            int pointdrc = y*img->widthStep + x * 3;
            newimgdata[pointdrc + 0] = data[pointsrc + 0];
            newimgdata[pointdrc + 1] = data[pointsrc + 1];
            newimgdata[pointdrc + 2] = data[pointsrc + 2];
        }
    }
memset(data, 0, img->widthStep*img->height);
memcpy(data, newimgdata, img->widthStep*img->height);
cvSaveImage("save33.bmp", img);
cvReleaseImage(&img);
 

    由上图可以看到图像的桶形畸变基本被矫正了,但是多出了很多波纹,这个是没有用差值算法造成的。通过改变R-K成R1-K,然后再进行拟合,得到如下的四项多项式公式:

    拟合结果如下图所示:

    有了这个公式便可以算出畸变矫正后的图像像素点与畸变图像的像素点的映射关系,与之前不同的是这个公式以畸变矫正后的图像像素点距离镜头中心的距离作为输入值,方便我们后面对图像进行双线性插值算法的处理。

    那么我们依据这个公式编写代码如下:

//畸变矫正
char *filename = "2542.bmp";//图像路径  
img = cvLoadImage(filename);
paintObj.drawpicinit(img, IDC_STATIC_SHOW, this);
CPoint lenscenter(480,295);//镜头中心在图像中的位置
uchar *data = (uchar*)img->imageData;//原图像数据
uchar *newimgdata = new uchar[img->widthStep*img->height];//新图像数据
for (int row = 0; row<img->height; row++)//操作数据区,要注意OpenCV的RGB的存储顺序为GBR  
    for (int cols = 0; cols<img->width; cols++)//示例为亮度调节  
    {
        int pointsrc = row*img->widthStep + cols * 3;
        double r = sqrt((row- lenscenter.y)*(row - lenscenter.y) + (cols - lenscenter.x)*(cols - lenscenter.x));    
        double s = 0.9998 - 4.2932*pow(10, -4)*r + 3.4327*pow(10, -6)*pow(r, 2) -2.8526*pow(10, -9)*pow(r, 3)+9.8223*pow(10, -13)*pow(r, 4);//比例
        double d_original_img_wnum = (cols - lenscenter.x)/s + lenscenter.x;
        double d_original_img_hnum = (row - lenscenter.y)/s + lenscenter.y;
        
        int i_original_img_hnum = d_original_img_hnum;
        int i_original_img_wnum = d_original_img_wnum;
        double distance_to_a_x = d_original_img_wnum - i_original_img_wnum;//在原图像中与a点的水平距离  
        double distance_to_a_y = d_original_img_hnum - i_original_img_hnum;//在原图像中与a点的垂直距离  
 
        int original_point_a = i_original_img_hnum*img->widthStep + i_original_img_wnum * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点A    
        int original_point_b = i_original_img_hnum*img->widthStep + (i_original_img_wnum + 1) * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点B  
        int original_point_c = (i_original_img_hnum + 1)*img->widthStep + i_original_img_wnum * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点C   
        int original_point_d = (i_original_img_hnum + 1)*img->widthStep + (i_original_img_wnum + 1) * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点D  
        if (row == img->height - 1)
        {
            original_point_c = original_point_a;
            original_point_d = original_point_b;
        }
        if (cols == img->width - 1)
        {
            original_point_a = original_point_b;
            original_point_c = original_point_d;
        }
        newimgdata[pointsrc] =
            data[original_point_a] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
            data[original_point_b] * distance_to_a_x*(1 - distance_to_a_y) +
            data[original_point_c] * distance_to_a_y*(1 - distance_to_a_x) +
            data[original_point_c] * distance_to_a_y*distance_to_a_x;
        newimgdata[pointsrc + 1] =
            data[original_point_a + 1] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
            data[original_point_b + 1] * distance_to_a_x*(1 - distance_to_a_y) +
            data[original_point_c + 1] * distance_to_a_y*(1 - distance_to_a_x) +
            data[original_point_c + 1] * distance_to_a_y*distance_to_a_x;
        newimgdata[pointsrc + 2] =
            data[original_point_a + 2] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
            data[original_point_b + 2] * distance_to_a_x*(1 - distance_to_a_y) +
            data[original_point_c + 2] * distance_to_a_y*(1 - distance_to_a_x) +
            data[original_point_c + 2] * distance_to_a_y*distance_to_a_x;
 
    }
memset(data, 0, img->widthStep*img->height);
memcpy(data, newimgdata, img->widthStep*img->height);
cvSaveImage("save33.bmp", img);
cvReleaseImage(&img);  
  
    最近发现用OpenCV2.0的变量操作快感提升了一个层次,因此贴出用2.0变量所写的代码以供参考

#include <opencv2/opencv.hpp>    
#include <time.h> 
using namespace cv;
 
void main()
{
    long time = clock();  
    char *filename = "4.bmp";//图像路径    
    Mat img = imread(filename);
    Mat drcimg(img.rows, img.cols, CV_8UC3);
    cv::imshow("矫正前", img);    
    CvPoint lenscenter(img.cols / 2, img.rows / 2);//镜头中心在图像中的位置  
    CvPoint src_a, src_b, src_c, src_d;//a、b、c、d四个顶点
    //矫正参数
    double r;//矫正前像素点跟镜头中心的距离
    double s;//矫正后像素点跟镜头中心的距离
    CvPoint2D32f mCorrectPoint;//矫正后点坐标
    double distance_to_a_x, distance_to_a_y;//求得中心点和边界的距离
 
    for (int row = 0; row< img.rows; row++)//操作数据区,要注意OpenCV的RGB的存储顺序为GBR    
        for (int cols = 0; cols<img.cols; cols++)//示例为亮度调节    
        {    
            r = sqrt((row - lenscenter.y)*(row - lenscenter.y) + (cols - lenscenter.x)*(cols - lenscenter.x));
            s = 0.9998 - 4.2932*pow(10, -4)*r + 3.4327*pow(10, -6)*pow(r, 2) - 2.8526*pow(10, -9)*pow(r, 3) + 9.8223*pow(10, -13)*pow(r, 4);//比例  
            mCorrectPoint = cvPoint2D32f((cols - lenscenter.x) / s *1.35 + lenscenter.x, (row - lenscenter.y) / s*1.35 + lenscenter.y);
            //越界判断
            if (mCorrectPoint.y < 0 || mCorrectPoint.y >= img.rows-1)
            {
                continue;
            }
            if (mCorrectPoint.x < 0 || mCorrectPoint.x >= img.cols-1)
            {
                continue;
            }
            src_a = cvPoint((int)mCorrectPoint.x, (int)mCorrectPoint.y);
            src_b = cvPoint(src_a.x + 1, src_a.y);
            src_c = cvPoint(src_a.x, src_a.y + 1);
            src_d = cvPoint(src_a.x + 1, src_a.y + 1);
            distance_to_a_x = mCorrectPoint.x - src_a.x;//在原图像中与a点的水平距离    
            distance_to_a_y = mCorrectPoint.y - src_a.y;//在原图像中与a点的垂直距离        
    
            drcimg.at<Vec3b>(row, cols)[0] =
                img.at<Vec3b>(src_a.y, src_a.x)[0] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
                img.at<Vec3b>(src_b.y, src_b.x)[0] * distance_to_a_x*(1 - distance_to_a_y) +
                img.at<Vec3b>(src_c.y, src_c.x)[0] * distance_to_a_y*(1 - distance_to_a_x) +
                img.at<Vec3b>(src_d.y, src_d.x)[0] * distance_to_a_y*distance_to_a_x;
            drcimg.at<Vec3b>(row, cols)[1] =
                img.at<Vec3b>(src_a.y, src_a.x)[1] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
                img.at<Vec3b>(src_b.y, src_b.x)[1] * distance_to_a_x*(1 - distance_to_a_y) +
                img.at<Vec3b>(src_c.y, src_c.x)[1] * distance_to_a_y*(1 - distance_to_a_x) +
                img.at<Vec3b>(src_d.y, src_d.x)[1] * distance_to_a_y*distance_to_a_x;
            drcimg.at<Vec3b>(row, cols)[2] =
                img.at<Vec3b>(src_a.y, src_a.x)[2] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
                img.at<Vec3b>(src_b.y, src_b.x)[2] * distance_to_a_x*(1 - distance_to_a_y) +
                img.at<Vec3b>(src_c.y, src_c.x)[2] * distance_to_a_y*(1 - distance_to_a_x) +
                img.at<Vec3b>(src_d.y, src_d.x)[2] * distance_to_a_y*distance_to_a_x;
        }
    printf("时间花费%dms\n",clock()-time);//修改前200左右,修改成2.0版本的变量之后在210左右
    cv::imwrite("矫正完成.bmp", drcimg);
    cv::imshow("矫正完成", drcimg);
    cv::waitKey(0);
    img.release();
    drcimg.release();
}
    处理结果如下图所示:

    由上图看到,图像的桶形畸变矫正已经完成了。由于桶形畸变越往外畸变的越严重,因此矫正过后的图像会比原图像大,而我们以图像的原尺寸来放置矫正后的图像,因此视角会比原图像小。这个问题如果需要处理的话可以通过缩小矫正后图像的大小等方法让图像有更大的视角,本文没做处理。

 

 

    另外,如果觉得这个方法矫正的不是很准确,可以利用Matlab的矫正工具箱对图像进行标定,得到图像的校准参数,然后导出xml文件,再用OpenCV读取这些导出的文件进行矫正。OpenCV的实现代码如下:

#include <opencv2/opencv.hpp>
using namespace cv;
 
int main()
{
    //从xml文件中读入校准参数,注意这里的xml文件可以是用matlab的校准工具箱生成的
    CvMat *Intrinsics_Camera = (CvMat*)cvLoad("Intrinsics_Camera.xml");
    CvMat *Distortion_Camera = (CvMat*)cvLoad("Distortion_Camera.xml");
    //读入图像
    IplImage *img = cvLoadImage("待带矫正图像.jpg");
    IplImage *img_Rect = cvCloneImage(img);
    //在校准参数的基础上创建映射矩阵
    CvMat *Mapx = cvCreateMat(img->height, img->width, CV_32F);
    CvMat *Mapy = cvCreateMat(img->height, img->width, CV_32F);
    cvInitUndistortMap(Intrinsics_Camera, Distortion_Camera, Mapx, Mapy);
    //矫正图像
    cvRemap(img, img_Rect, Mapx, Mapy);
    //显示
    cvNamedWindow("Image");
    cvShowImage("Image", img_Rect);
    cvSaveImage("矫正后图像.jpg", img_Rect);
    //等待
    cvWaitKey();
    //释放对象内存
    cvDestroyAllWindows();
    cvReleaseImage(&img);
    cvReleaseImage(&img_Rect);
    cvReleaseMat(&Intrinsics_Camera);
    cvReleaseMat(&Distortion_Camera);
    cvReleaseMat(&Mapx);
    cvReleaseMat(&Mapy);
    return 0;
}
 



原文地址:https://blog.csdn.net/weixinhum/article/details/50719031 
 

图像矫正去畸变

阅读数 8047

图像畸变与去畸变

阅读数 1854

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