2018-12-23 16:12:03 linghugoolge 阅读数 393

一、成像原理

鱼眼镜头一般是由十几个不同的透镜组合而成的,在成像的过程中,入射光线经过不同程度的折射,投影到尺寸有限的成像平面上,使得鱼眼镜头与普通镜头相比起来拥有了更大的视野范围。下图表示出了鱼眼相机的一般组成结构。最前面的两个镜头发生折射,使入射角减小,其余的镜头相当于一个成像镜头,这种多元件的构造结构使对鱼眼相机的折射关系的分析变得相当复杂。

研究表明鱼眼相机成像时遵循的模型可以近似为单位球面投影模型。可以将鱼眼相机的成像过程分解成两步:第一步,三维空间点线性地投影到一个球面上,它是一个虚拟的单位球面,它的球心与相机坐标系的原点重合;第二步,单位球面上的点投影到图像平面上,这个过程是非线性的。

我们知道,普通相机成像遵循的是针孔相机模型,在成像过程中实际场景中的直线仍被投影为图像平面上的直线。但是鱼眼相机如果按照针孔相机模型成像的话,投影图像会变得非常大,当相机视场角达到180°时,图像甚至会变为无穷大。所以,鱼眼相机的投影模型为了将尽可能大的场景投影到有限的图像平面内,允许了相机畸变的存在。并且由于鱼眼相机的径向畸变非常严重,所以鱼眼相机主要的是考虑径向畸变,而忽略其余类型的畸变。

二、畸变矫正效果

 

三、鱼眼相机模型

鱼眼相机的内参模型依然可以表示为:

这与普通镜头的成像模型没有区别。两者之间的区别主要体现在畸变系数,鱼眼相机的畸变系数为{k1,k2,k3,k4k1,k2,k3,k4},畸变系数不同,就导致鱼眼相机的投影关系也发生了变化,主要变化发生在考虑畸变情况下的投影关系转化:

设(X,Y,Z)为空间中一个三维点,它在成像平面内的成像坐标为(u,v),在考虑畸变的情况下,

 

四、标定和畸变矫正过程

1、检测角点

cv::findChessboardCorners(InputArray image, Size patternSize, OutputArray corners, int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE}

获得棋盘标定板的角点位置,使用

cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria)获取角点更精细的检测结果

2、初始化标定板上角点的三维坐标

3、开始标定

double fisheye::calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size, InputOutputArray K, InputOutputArray D,

OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags=0, TermCriteria criteria=TermCriteria(TermCriteria::COUNT + TermCriteria:: EPS, 100, DBL_EPSILON))

注意:K,D 分别表示内参矩阵和畸变系数向量,在定义时要定义为double型,这里推荐使用Matx33d和Vec4d数据类型,更为方便简单。objectPoints,imagePoints可以是float型,也可以是double型,但是再stereorectify中需要时double型。flags的可选项有很多,其中需要注意的是必须要指定CALIB_FIX_SKEW,代表求解时假设内参中fx=fyfx=fy.

4、评定误差(可选项)

五、代码

#include <opencv2/opencv.hpp>
#include <fstream>
#include <cv.h>
#include <highgui.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>

using namespace std;
using namespace cv;


int main(int argc, char* argv[])
{

	ofstream fout("caliberation_result.txt");

	int image_count=  10;                       
	Size board_size = Size(9,6);             
	vector<Point2f> corners;                  
	vector<vector<Point2f> > corners_Seq;      
	vector<Mat>  image_Seq;
	int successImageNum = 0;                
	int count = 0;

    	//if you want to calibrate online
	VideoCapture cap(0);
	Mat image;
	
	while(successImageNum < image_count){

        //if you want to calibrate online
		cap >> image;
		if(!image.empty())
			imshow("caliberation", image);

        //if you want to use image file to calibrate
        /*
        cout<<"Frame #"<<i+1<<"..."<<endl;
        string imageFileName;
        std::stringstream StrStm;
        StrStm<<i+1;
        StrStm>>imageFileName;
        imageFileName += ".jpg";
        cv::Mat image = imread("fish"+imageFileName); 
        */

	cv::Mat imageGray;   
        cvtColor(image, imageGray, CV_BGR2GRAY);
	bool patternfound = findChessboardCorners(imageGray, board_size, corners, CALIB_CB_ADAPTIVE_THRESH );
        if (!patternfound)   
        {   
            cout<<"can not find chessboard corners!\n";     
        } 
        else
        {   

            cornerSubPix(imageGray, corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));

            Mat imageTemp = image.clone();
            for (int j = 0; j < corners.size(); j++)
            {
                circle( imageTemp, corners[j], 10, Scalar(0,0,255), 2, 8, 0);
            }
            string imageFileName;
            std::stringstream StrStm;
            StrStm<<successImageNum;
            StrStm>>imageFileName;
            imwrite(imageFileName+".jpg",image);
            imageFileName += "_corner.jpg";
            imwrite(imageFileName,imageTemp);
            cout<<"Frame corner#"<<successImageNum<<"...end"<<endl;

            count = count + corners.size();
            successImageNum = successImageNum + 1;
            corners_Seq.push_back(corners);
        }   
        image_Seq.push_back(image);
	    
	    if( cvWaitKey(15) == 27 )
	        return 0;
	    waitKey(30);
	}
	cout<<"角点提取完成!\n";

	/************************************************************************  
           				摄像机定标  
    *************************************************************************/   
    cout<<"开始定标………………"<<endl;  
    Size square_size = Size(20,20);     
    vector<vector<Point3f> >  object_Points;        /****  保存定标板上角点的三维坐标   ****/

    Mat image_points = Mat(1, count, CV_32FC2, Scalar::all(0));  /*****   保存提取的所有角点   *****/
    vector<int>  point_counts;                                                         
    /* 初始化定标板上角点的三维坐标 */
    for (int t = 0; t<successImageNum; t++)
    {
        vector<Point3f> tempPointSet;
        for (int i = 0; i<board_size.height; i++)
        {
            for (int j = 0; j<board_size.width; j++)
            {
                /* 假设定标板放在世界坐标系中z=0的平面上 */
                Point3f tempPoint;
                tempPoint.x = i*square_size.width;
                tempPoint.y = j*square_size.height;
                tempPoint.z = 0;
                tempPointSet.push_back(tempPoint);
            }
        }
        object_Points.push_back(tempPointSet);
    }
    for (int i = 0; i< successImageNum; i++)
    {
        point_counts.push_back(board_size.width*board_size.height);
    }
    /* 开始定标 */
    Size image_size = image_Seq[0].size();
    cv::Matx33d intrinsic_matrix;    /*****    摄像机内参数矩阵    ****/
    cv::Vec4d distortion_coeffs;     /* 摄像机的4个畸变系数:k1,k2,k3,k4*/
    std::vector<cv::Vec3d> rotation_vectors;                           /* 每幅图像的旋转向量 */
    std::vector<cv::Vec3d> translation_vectors;                        /* 每幅图像的平移向量 */
    int flags = 0;
    flags |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC;
    flags |= cv::fisheye::CALIB_CHECK_COND;
    flags |= cv::fisheye::CALIB_FIX_SKEW;
    fisheye::calibrate(object_Points, corners_Seq, image_size, intrinsic_matrix, distortion_coeffs, rotation_vectors, translation_vectors, flags, cv::TermCriteria(3, 20, 1e-6));
    cout<<"定标完成!\n";   

    /************************************************************************  
           			对定标结果进行评价  
    *************************************************************************/   
    cout<<"开始评价定标结果………………"<<endl;   
    double total_err = 0.0;                   /* 所有图像的平均误差的总和 */   
    double err = 0.0;                        /* 每幅图像的平均误差 */   
    vector<Point2f>  image_points2;             /****   保存重新计算得到的投影点    ****/   

    cout<<"每幅图像的定标误差:"<<endl;   
    cout<<"每幅图像的定标误差:"<<endl<<endl;   
    for (int i=0;  i<image_count;  i++) 
    {
        vector<Point3f> tempPointSet = object_Points[i];
        /****    通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点     ****/
        fisheye::projectPoints(tempPointSet, image_points2, rotation_vectors[i], translation_vectors[i], intrinsic_matrix, distortion_coeffs);
        /* 计算新的投影点和旧的投影点之间的误差*/  
        vector<Point2f> tempImagePoint = corners_Seq[i];
        Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);
        Mat image_points2Mat = Mat(1,image_points2.size(), CV_32FC2);
        for (size_t j = 0 ; j != tempImagePoint.size(); j++)
        {
            image_points2Mat.at<Vec2f>(0,j) = Vec2f(image_points2[j].x, image_points2[j].y);
            tempImagePointMat.at<Vec2f>(0,j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
        }
        err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
        total_err += err/=  point_counts[i];   
        cout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;   
        fout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;   
    }   
    cout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl;   
    fout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl<<endl;   
    cout<<"评价完成!"<<endl;   

    /************************************************************************  
           			保存定标结果  
    *************************************************************************/   
    cout<<"开始保存定标结果………………"<<endl;       
    Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */   

    fout<<"相机内参数矩阵:"<<endl;   
    fout<<intrinsic_matrix<<endl;   
    fout<<"畸变系数:\n";   
    fout<<distortion_coeffs<<endl;   
    for (int i=0; i<image_count; i++) 
    { 
        fout<<"第"<<i+1<<"幅图像的旋转向量:"<<endl;   
        fout<<rotation_vectors[i]<<endl;   

        /* 将旋转向量转换为相对应的旋转矩阵 */   
        Rodrigues(rotation_vectors[i],rotation_matrix);   
        fout<<"第"<<i+1<<"幅图像的旋转矩阵:"<<endl;   
        fout<<rotation_matrix<<endl;   
        fout<<"第"<<i+1<<"幅图像的平移向量:"<<endl;   
        fout<<translation_vectors[i]<<endl;   
    }   
    cout<<"完成保存"<<endl; 
    fout<<endl;


    /************************************************************************  
           			显示定标结果  
    *************************************************************************/
    Mat mapx = Mat(image_size,CV_32FC1);
    Mat mapy = Mat(image_size,CV_32FC1);
    Mat R = Mat::eye(3,3,CV_32F);

    cout<<"保存矫正图像"<<endl;
    for (int i = 0 ; i != image_count ; i++)
    {
        cout<<"Frame #"<<i+1<<"..."<<endl;
        //fisheye::initUndistortRectifyMap(intrinsic_matrix,distortion_coeffs,R,intrinsic_matrix,image_size,CV_32FC1,mapx,mapy);
        fisheye::initUndistortRectifyMap(intrinsic_matrix,distortion_coeffs,R,
            getOptimalNewCameraMatrix(intrinsic_matrix, distortion_coeffs, image_size, 1, image_size, 0),image_size,CV_32FC1,mapx,mapy);
        Mat t = image_Seq[i].clone();
        cv::remap(image_Seq[i],t,mapx, mapy, INTER_LINEAR);
        string imageFileName;
        std::stringstream StrStm;
        StrStm<<i+1;
        StrStm>>imageFileName;
        imageFileName += "_d.jpg";
        imwrite(imageFileName,t);
    }
    cout<<"保存结束"<<endl;


	return 0;
}

六、参考

鱼眼相机成像模型:https://blog.csdn.net/u010128736/article/details/52864024

鱼眼相机标定及OpenCV实现:https://blog.csdn.net/u010784534/article/details/50474371

Github代码下载:https://github.com/linghugoogle/Fisheye_Calibrate

七、Matlab算法

代码下载:https://sites.google.com/site/scarabotix/ocamcalib-toolbox

2010-06-06 22:57:00 tandychao 阅读数 5854

这两天在做鱼眼图像的校正,也就是鱼眼镜头拍摄的照片的校正。

首先,先贴两张图,学学siggraph,哈哈哈。开玩笑。梦寐以求的图形学年会啊!

 

这里采用的方法,是从鱼眼图像成像的原理入手,反投影到平面图像,所以,很简单。而且景深可以调节,调节景深,可以看到不同的方位的图像。注意,可以发现,校正后的图像,似乎缺少了很多原图的信息。是的。因为,从成像的原理入手,那么,原来视角有200多度的鱼眼图像,校正到平面图像,肯定会有一些景物,在平面图像上是显示不出来的。但是,我们可以调节景深,从而可以达到看到更多景物的目的。所以,要从根本上理解成像的原理,才可以理解。也正应了那句老话,理论是最根本的,数学是最根本的。

调节景深,可以得到如下的结果图:

 

可以看到,发生了变化。所以,从理论上讲,就很容易理解。

 

这个方法,与一般的校正不一样,尤其是采用多项式的图像校正,那些完全是基于图像,而与图像的内容以及畸变的机理压根没有关系,没有思考。

不过,话说回来,这里还是有一些问题的,因为投影面,我假设为球,所以,还有很多值得思考,和去完善。

 

下一步工作:

1. 如果有时间,采用GPU加速,并达到实时,还可以调节窗口。

2. 基于鱼眼图像的三维重建。

 

/(^o^)/ 欧耶!

再来两张图吧,

 

 

 

 

 

 

2018-11-05 19:33:27 linghugoolge 阅读数 742

原地址:OpenCV实现鱼眼图像径向展开

#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <math.h>
using namespace std;
using namespace cv;
 
#pragma comment(lib, "opencv_core300d.lib")
#pragma comment(lib, "opencv_highgui300d.lib")
#pragma comment(lib, "opencv_imgcodecs300d.lib")
#pragma comment(lib, "opencv_imgproc300d.lib")
 
int main()
{
	//读取图片
	Mat Src = imread("1.png") ;
	//imshow("Src",Src);
	cout<< Src.size()<<endl;
	int nbottom = 0;
	int ntop = 0;
	int nright = 0;
	int nleft = 0;
 
	//遍历寻找上边界
	int nflag = 0;
	for (int i=0 ;i< Src.rows -1;i++)
	{
		for (int j=0; j<Src.cols -1; j++)
		{
			uchar I = 0.59*Src.at<Vec3b>(i,j)[0] + 0.11*Src.at<Vec3b>(i,j)[1] + 0.3*Src.at<Vec3b>(i,j)[2];
			if (I > 20)
			{
				I = 0.59*Src.at<Vec3b>(i+1,j)[0] + 0.11*Src.at<Vec3b>(i+1,j)[1] + 0.3*Src.at<Vec3b>(i+1,j)[2];
				if (I > 20)
				{
					ntop = i;
					nflag = 1;
					break;
				}
			}
		}
		if (nflag ==1)
		{
			break;
		}
	}
	//遍历寻找下边界
	nflag = 0;
	for (int i= Src.rows-1;i > 1;i--)
	{
		for (int j=0; j < Src.cols -1; j++)
		{
			uchar I = 0.59*Src.at<Vec3b>(i,j)[0] + 0.11*Src.at<Vec3b>(i,j)[1] + 0.3*Src.at<Vec3b>(i,j)[2];
			if (I > 20)
			{
				I = 0.59*Src.at<Vec3b>(i-1,j)[0] + 0.11*Src.at<Vec3b>(i-1,j)[1] + 0.3*Src.at<Vec3b>(i-1,j)[2];
				if (I > 20)
				{
					nbottom = i;
					nflag = 1;
					break;
				}
			}
		}
		if (nflag ==1)
		{
			break;
		}
	}
	//遍历寻找左边界
	nflag = 0;
	for (int j=0; j<Src.cols -1; j++)
	{
		for (int i=0 ;i< Src.rows ;i++)
		{
			uchar I = 0.59*Src.at<Vec3b>(i,j)[0] + 0.11*Src.at<Vec3b>(i,j)[1] + 0.3*Src.at<Vec3b>(i,j)[2];
			if (I > 20)
			{
				I = 0.59*Src.at<Vec3b>(i,j+1)[0] + 0.11*Src.at<Vec3b>(i,j+1)[1] + 0.3*Src.at<Vec3b>(i,j+1)[2];
				if (I > 20)
				{
					nleft = j;
					nflag = 1;
					break;
				}
			}
		}
		if (nflag ==1)
		{
			break;
		}
	}
	//遍历寻找右边界
	nflag = 0;
	for (int j=Src.cols -1; j >0; j--)
	{
		for (int i= 0;i <Src.rows ;i++)
		{
			uchar I = 0.59*Src.at<Vec3b>(i,j)[0] + 0.11*Src.at<Vec3b>(i,j)[1] + 0.3*Src.at<Vec3b>(i,j)[2];
			if (I > 20)
			{
				I = 0.59*Src.at<Vec3b>(i,j-1)[0] + 0.11*Src.at<Vec3b>(i,j-1)[1] + 0.3*Src.at<Vec3b>(i,j-1)[2];
				if (I > 20)
				{
					nright = j;
					nflag = 1;
					break;
				}
			}
		}
		if (nflag ==1)
		{
			break;
		}
	}
	cout<< ntop<<endl;
	cout<< nbottom<<endl;
	cout<< nleft << endl;
	cout<< nright <<endl;
 
	//根据边界值来获得直径
	int d = min(nright-nleft,nbottom-ntop);
 
	Mat imgRoi ;
	imgRoi = Src(Rect( nleft, ntop, d, d ));
	imwrite("C:/aa.jpg", imgRoi);
 
	Mat dst( imgRoi.size(), CV_8UC3, Scalar(255,255,255));
 
	//建立映射表
	Mat map_x,map_y;
	map_x.create( imgRoi.size(), CV_32FC1 );
	map_y.create( imgRoi.size(), CV_32FC1 );
	for (int j=0; j< d-1;j++)
	{
		for (int i=0; i< d-1; i++ )
		{
			map_x.at<float>(i,j) = static_cast<float>( d/2.0 + i/2.0*cos(1.0*j/d*2*CV_PI));
			map_y.at<float>(i,j) = static_cast<float>( d/2.0 + i/2.0*sin(1.0*j/d*2*CV_PI));
		}
	}
 
	remap(imgRoi, dst, map_x, map_y, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0,0));
	//重设大小
	resize(dst, dst, Size(), 2.0, 1.0);
 
	imwrite("C:/bb.jpg",dst);
	waitKey();
	return 0;
}

 

2019-10-09 22:22:00 weixin_37244486 阅读数 59

自己略读了一下,网上资源也不多,有读相同论文的小伙伴,欢迎补充。

一、解决的问题:

Moving Object Detection (MOD)

a fisheye surround-view system that captures a 360 view of the scene

that were captured in autonomous driving environment.

二、技术方案

1、we will make an improved version of the current dataset public to encourage further research.

2、To target embedded deployment, we design a lightweight encoder sharing weights across sequential images.

 

                                                                                         网络结构

三、创新点

1Generation of the first public automotive dataset for fisheye images with MOD annotations.

2、 Implementation of an efficient two-stream network architecture suitable for embedded systems.

3、 Empirical study of different training and data augmentation schemes.

四、实验方案与结果

五、可能存在的问题

1、鱼眼图像与普通图像不一样,训练时,能否根据成像原理,对损失函数进行优化?

In future work, we plan to incorporate geometric priors into the loss function to improve accuracy.

暂时就到这里,欢迎大家补充讨论。在另一篇文章中,将会鱼眼图像公布数据集 WoodScape

2014-01-15 14:28:53 digent 阅读数 2724


以前多数DSP做,这种方案功耗很大,并且高级一点的DSP不可能做的很小,也有专用的soc芯片,集成度都很高,刚拿到了一款soc的芯片,低功耗,小封装,非常适合需要做鱼眼矫正的应用场合。下面是我拿到的一款芯片做的鱼眼矫正的效果,当然手机拍的照片不是很好,如下所示:

下面的图像是采用了一款鱼眼矫正的芯片做了一个矫正效果:如下所示

1.原始图像:


2.只做X轴的矫正如下所示


3.XY 均做了一点,但是不是很精致如下所示,由于鱼眼镜头的图像为180度的半球面,所以图像会被压缩,第一幅图的鱼眼不是完整的,所以造成了如下要实现x,y轴的矫正会产生黑色的图像区域,这也是圆形展开后的残缺部分。


4.除了鱼眼矫正外,还可以选择FOV内感兴趣的ROI ,最多可以显示8个ROI,如下图所示:





总体感觉还不错,提供做矫正图的UI界面,蛮方便的~

对了如果你有鱼眼图,可以发给我我帮你做下矫正后的图像(只能是拍到的,因为只能是输给板子然后送到显示器)。邮箱:digent@163.com

机器视觉(总结)

阅读数 607

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