您的位置:首页 > 编程语言 > C语言/C++

光场子孔径图像提取(C++实现)

2018-03-08 14:17 549 查看

光场

光线在一个空间中的分布,不仅可以记录光线的强度,还可以记录光线的方向,光场相机与普通相机的不同之处是微透镜阵列,也就是在主透镜 和传感器之间加入了微透镜阵列,如图所示:



可以通过下面链接了解一下光场:

漫谈计算摄影学

论文:

《Light Field Photography with a Hand-held Plenoptic Camera》——Ren NG

概念:

1.光场图像空间分辨率:

直观理解即光场相机微透镜阵列中微透镜的个数,或者子孔径图像的像素数

2.光场角度分辨率:

每一个微透镜下面所覆盖的像素个数,即为角度分辨率

3.子孔径图像

光场子孔径图像就是光场每一个角度形成的图像,因此角度分辨的大小就是子孔径图像的数量

子孔径图像提取步骤:

1.微透镜中心点提取

2.微透镜中心点排序

3.生成子孔径图像

一丶微透镜中心点提取

1)利用光场相机拍摄白墙或者匀光板获取白图像

白图像如下图所示:



2)求出白图像最佳阈值(otsu方法)

otsu法(最大类间方差法,有时也称之为大津算法)使用的是聚类的思想,把图像的灰度数按灰度级分成2个部分,使得两个部分之间的灰度值差异最大,每个部分之间的灰度差异最小,通过方差的计算来寻找一个合适的灰度级别来划分,otsu算法被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响。因此,使类间方差最大的分割意味着错分概率最小。

int SubtoRaw::Otsu(Mat &image)
{
int width = image.cols;
int height = image.rows;
int x = 0, y = 0;
int pixelCount[256];
float pixelPro[256];
int i, j, pixelSum = width * height, threshold = 0;

uchar* data = (uchar*)image.data;

//初始化
for (i = 0; i < 256; i++)
{
pixelCount[i] = 0;
pixelPro[i] = 0;
}

//统计灰度级中每个像素在整幅图像中的个数
for (i = y; i < height; i++)
{
for (j = x; j<width; j++)
{
pixelCount[data[i * image.step + j]]++;
}
}

//计算每个像素在整幅图像中的比例
for (i = 0; i < 256; i++)
{
pixelPro[i] = (float)(pixelCount[i]) / (float)(pixelSum);
}

//经典ostu算法,得到前景和背景的分割
//遍历灰度级[0,255],计算出方差最大的灰度值,为最佳阈值
float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
for (i = 0; i < 256; i++)
{
w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;

for (j = 0; j < 256; j++)
{
if (j <= i) //背景部分
{
//以i为阈值分类,第一类总的概率
w0 += pixelPro[j];
u0tmp += j * pixelPro[j];
}
else       //前景部分
{
//以i为阈值分类,第二类总的概率
w1 += pixelPro[j];
u1tmp += j * pixelPro[j];
}
}

u0 = u0tmp / w0;        //第一类的平均灰度
u1 = u1tmp / w1;        //第二类的平均灰度
u = u0tmp + u1tmp;      //整幅图像的平均灰度

4000
//计算类间方差
deltaTmp = w0 * (u0 - u)*(u0 - u) + w1 * (u1 - u)*(u1 - u);
//找出最大类间方差以及对应的阈值
if (deltaTmp > deltaMax)
{
deltaMax = deltaTmp;
threshold = i;
}
}
//返回最佳阈值;
return threshold;
}


3)将图像二值化,之后计算轮廓

利用opencv函数:threshold和findContours函数

threshold(White, thresholded, threshold_white , 255, CV_THRESH_BINARY);//二值化
findContours(thresholded, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);// 查找轮廓


具体代码可参考下面我的另一篇博客,这里不重复码代码了

OpenCV求取连通区域中心

4)计算微透镜中心点

利用灰度重心法计算每一个微透镜的中心点,循环每一个轮廓计算出中心点,代码如下:

bool SubtoRaw::ComputeCenter(Mat& thresholded,Mat image, vector<vector<Point>>contours, vector<Vec4i>hierarchy, vector<Point>&Center, ofstream&outfile1)//参数分别为二值化图像,白图像,轮廓,轮廓hierarchy,中心点向量,文件输出对象
{

int i = 0;
int count = 0;
Point pt[30000];//点对象数组   <类名>< 数组名> [大小]
Moments moment;//创建一个类对象
for (; i >= 0; i = hierarchy[i][0])//读取每一个轮廓求取重心
{
Mat temp(contours.at(i));
Scalar color(0, 0, 255);
moment = moments(temp, false);
if (moment.m00 != 0)
{
pt[i].x = cvRound(moment.m10 / moment.m00);//中心点横坐标
pt[i].y = cvRound(moment.m01 / moment.m00);//中心点纵坐标

}

Point p = Point(pt[i].x, pt[i].y);
circle(image, p, 1, color, 1, 8);//将原始白图像的中心点画出来
count++;
outfile1 << p << "#" << count << endl;//中心点坐标输出到文本
Center.push_back(p);//将重心坐标保存到Center向量中
}
cout << "中心点个数:" << Center.size() << endl;
cout << "轮廓数量:" << contours.size() << endl;
imwrite("Center.tif", image);//保存画了中心点的白图像
return true;
}


微透镜中心点:



二丶微透镜中心点排序

目的:将微透镜的中心点坐标按从左到右,从上到下进行排序

方法:
vector<vector<Point>>


可参考我的另一篇关于二维坐标排序的博客:

二维坐标排序



观察我们输出的中心点坐标,分析可知如果是同一行微透镜的中心点则纵坐标相差比较小,可能在一两个像素之内,当进入下一行时微透镜中心点纵坐标会发生较大变化,大概在30左右,因为我们的微透镜半径是30像素,可以根据自己相机微透镜的半径进行判断。当进入到下一行时,我们把上一行的中心点进行从左到右排序,每一行都进行排序,这样我们就得到了一个排好序的中心点坐标。

bool SubtoRaw::SortCenter(vector<Point>&Center, vector<vector<Point>>¢er_row_all, ofstream& outfile2, ofstream& outfile3)//参数分别为:上一个函数求出的中心点向量,排好序的双重vector,输出对象1,输出对象2
{
cout << "Center:" << Center.size() << endl;
vector<Point>exchange;//用于清空每一行的中心点坐标的vector
vector<Point>center_row;//每一行的中心点坐标的vector
for (int i = Center.size() - 1; i > 0; i--)
{
if (abs(Center[i].y - Center[i - 1].y) < 15)//纵坐标相差小于15代表是同一行()&&(Center[i].x-Center[i-1].x)>3000//横坐标突然变化很大,也可以判断进入下一行,两个可以同时进行判断)
{
center_row.push_back(Center[i]);//第一行加入到向量中
}
else//当纵坐标变化大于15,一般可能是30左右时
{
center_row.push_back(Center[i]);//将这一行最后一个微透镜中心点加入vector
//cout << "Center:" << center_row.size() << endl;
sort(center_row.begin(), center_row.end(),SetSortRule);//进行排序
center_row_all.push_back(center_row);//将每一行的排好序的中心点vector加入到双重向量中
center_row.clear();//清空每一行的微透镜中心点,用于下一行
}
}
cout << "行数:" << center_row_all.size() << endl;//有多少行微透镜

for (int i = 0; i < center_row_all.size(); i++)
{
outfile2 << "///////////////////////////////////////////////////////////" << i << "行   " << endl;//输出第几行
outfile3 << "第" << i << "行有" << center_row_all[i].size() << endl;//第几行有多少个微透镜

for (int j = 0; j < center_row_all[i].size(); j++)
{
outfile2 << center_row_all[i][j] << "#" << i << "#" << j << endl;//输出坐标
}
}

cout << "center_row_all:" << center_row_all.size() << endl;
cout << "center_row:" << center_row_all[1].size() << endl;
return true;
}


三丶提取子孔径图像

子孔径图像提取原理:

假设微透镜阵列有300行400列,也就是有1.2w个微透镜,每个微透镜下面有30*30个像素,也就是半径大约为30个像素,那我们我们就可以提取30×30张子孔径图像,每张子孔径图像为300×400像素,中心角度的子孔径图像我们就是按顺序提取每一个微透镜中心位置的像素值,按顺序重新拼成一幅新的300×400的图像,就是子孔径图像。

更直白点讲,我们提取(0,0)位置的微透镜中心的像素值,也就是子孔径图像(0,0)位置的像素值,(0,1)位置的微透镜中心的像素值,就是子孔径图像(0,1)位置的像素值,以此按顺序类推,直到所有的微透镜遍历完。

bool GetSubAperture(vector<vector<Point>>¢er_row_all)//参数为排好序的中心坐标,
{
char filename2[100];
Mat srcImage;
char str1[100] = "C:/Users/苏博/source/repos/子孔径/子孔径/Image/image.tif";//图片位置
srcImage = imread(str1);//读取图像
if (srcImage.data == 0)//判断图像存在否
{
printf("没有该图片!");
}
int ptx, pty;//中心点坐标值
Mat sub_image = Mat::zeros(115, 178, srcImage.type());//创建一个空矩阵,用于保存子孔径图像,115为行,178为列,因为我们的空间分辨是115*178,也就是微透镜有115*178个
for (int i = 0; i < center_row_all.size(); i++)//读取每一行坐标
{
for (int j = 0; j < center_row_all[i].size(); j++)   //读取某一行总的每一列中心点坐标
{
ptx = center_row_all[i][j].x ;//横坐标 列
pty = center_row_all[i][j].y ;//纵坐标 行
sub_image.at<Vec3b>(i, j) = srcImage.at<Vec3b>(pty, ptx);//给子孔径图像赋值
}
}
sprintf(filename2, "C:/Users/苏问博/source/repos/12_多张子孔径到原始图像/12_多张子孔径到原始图像/subaperture.tif");
imwrite(filename2, sub_image_ROI);//保存子孔径图像
return true;
}


原始图像:



中心子孔径图像:



左上角角度子孔径图像:



由于微透镜渐晕的原因,所以微透镜边缘比较暗,因此子孔径图像比较暗。

四丶提取所有角度子孔径图像

和提取中心子孔径图像原理一样,我们只需和之前一样按顺序遍历所有微透镜,根据微透镜中心坐标点,推测某一个角度的坐标,提取像素值,重新拼成一幅新的图像,即为该角度的子孔径图像

bool SubtoRaw::GetSubAperture(vector<vector<Point>>¢er_row_all, vector<Mat>&subImage)//参数为排好序的坐标向量,子孔径图像向量(用于保存几百张子孔径图像)
{
char filename2[100];
Mat srcImage;
char str1[100] = "C:/Users/苏博/source/repos/子孔径/子孔径/Image/image.tif";//图片位置
srcImage = imread(str1);//读取图像
if (srcImage.data == 0)//判断图像存在否
{
printf("没有该图片!");
}
int ptx, pty;//中心点坐标
for (int m = -radius; m <= radius; m++)//行 radius为微透镜半径,我这里为14
{
for (int n = -radius; n <= radius; n++)//列
{
Mat sub_image = Mat::zeros(115, 178, srcImage.type());
for (int i = 0; i < center_row_all.size(); i++)//判断坐标  m是行,n是列      //117
{
for (int j = 0; j < center_row_all[i].size(); j++)                    //178
{
ptx = center_row_all[i][j].x + n;//横坐标 列 推测该角度坐标
pty = center_row_all[i][j].y + m;//纵坐标 行 同理
sub_image.at<Vec3b>(i, j) = srcImage.at<Vec3b>(pty, ptx);//给子孔径图像赋值
}
}
sprintf(filename2, "C:/Users/苏博/source/repos/12_多张子孔径到原始图像/12_多张子孔径到原始图像/subaperture/subImage%d_%d.tif", m + radius + 1, n + radius + 1);//对每张子孔径图像命名
imwrite(filename2, sub_image_ROI);保存
}
}
return true;
}


以后子孔径图像的完整实现步骤。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息