【裸眼3D】 图形图像空洞填充

深度图是一种由灰度值介于0到255的像素所构成的影像。 灰度值为0的像素代表这个3D像素是位于最远的地方;而灰阶值为255的像素代表这个3D像素是位于最近的地方。在深度图里,每一个像素将定义其所对应的2D像素在Z-轴的位置。因为深度图可以一个像素对应一个像素(Pixel-by-Pixel)方式被它的原始2D影像参考来制作栩栩如生的3D立体影像,所以目前它已非常普遍地被使用在制作裸眼3D显示器(或裸眼3D广告机)所需要的多视角3D内容;也是制作多视角3D内容的主流技术。

有了深度图,就相当于2D图像有了第三维的信息描述(深度信息),我们便可以利用算法来对2D图像虚拟出多个不同视角来。简单理解,一张平面图我们远看会变小,走近了看又会变大。人向左边移动,图像在我们视野中的位置又会相对右移。因此,对于不同的视角来说,目标图像只是相当于进行了平移及一系列简单的变换。然而,目标场景并不仅仅处在同一深度,反应在深度图中就是深度图中各个像素对应的值并不完全不同,因此,在视角前后左右变换时,不同深度的图像平移变换的量是不同的,离视角越近的场景变换越小。这样一来,当我们视角变化时,远处的物体难免会被较近处的物体所遮挡。虚拟视角的时候,这些像素点的信息就丢失了(从2D图上我们并不知道被遮挡的后边到底是什么),因此,需要我们对这些被遮挡的空洞进行填充。这里,使用了两种填充算法作为比较:镜面反射与梯度方向填充:

使用算法通过一个视角产生的另一个虚拟视角后存在空洞:

 

一、镜面反射

对每行像素进行单独处理,如果存在需要填补遮挡像素,则以连续空洞的最右侧 x 值为对称轴,将右侧信息镜像填充空洞。OpenCV代码如下:

//镜面反射填充空洞
int mirrorFill(char* imagePath)
{
    IplImage* pImg;
 
    int i,j,k;  //循环变量  遍历图像的高、宽、通道
    int flag=0;  //连续黑点数
    int m,n; //临时变量
 
    pImg=cvLoadImage(imagePath);
 
    uchar* data=(uchar*)pImg->imageData;  //获取图像数据
 
    cvNamedWindow("Image1",CV_WINDOW_AUTOSIZE);
    cvShowImage("Image1",pImg);
    cvWaitKey(0);
 
    for (i=0;i<pImg->height;i++)
        for (j=0;j<pImg->width;j++)
        {
            flag=1;
            for (k=0;k<pImg->nChannels;k++)
                if (data[i*pImg->widthStep+j*pImg->nChannels+k]!=0) 
                { 
                    flag=0;
                    break;
                }
                if (flag>0) 
                {
                    for (m=j+1; m<=pImg->width; m++)
                        if ((data[i*pImg->widthStep+m*pImg->nChannels+0]!=0)||
                            (data[i*pImg->widthStep+m*pImg->nChannels+1]!=0)||
                            (data[i*pImg->widthStep+m*pImg->nChannels+2]!=0))
                            break;
                        else flag++;
                        for (m=0;m<flag;m++)
                            for (k=0;k<pImg->nChannels;k++)
                                data[i*pImg->widthStep+(j+m)*pImg->nChannels+k]=
                                data[i*pImg->widthStep+(j-m+flag*2-1)*pImg->nChannels+k];
                }
        }
 
        cvNamedWindow("Image2",CV_WINDOW_AUTOSIZE);
        cvShowImage("Image2",pImg);
        cvSaveImage("copy5.bmp",pImg);
 
        printf("%d  %d  %d\n",pImg->width,pImg->height,pImg->nChannels);
        cvWaitKey(0);
 
        cvDestroyWindow("Image");
        cvReleaseImage(&pImg);
 
        return 0;
}


效果图:

存在问题:镜像填充方法只能对简单的空洞进行填补,对于一些较复杂的则无法较好恢复。并且,如果对于一行像素连续的N个空洞信息如果右侧相邻的非空洞像素个数小于N,则无法完全填充空洞处。

二、梯度方向填充:

考虑到图像本身的梯度信息,按照不同的梯度方向来选取不同的像素点进行填充能使得空洞处所填充的像素与图像本身在纹理方面表现出更高的一致性,从而填补后更平滑,更符合场景本身的特征。填充方法简述如下:

先将RGB图像转换为灰度图,然后计算出灰度图中每个像素点的 x 方向与 y 方向的梯度(一阶导)。然后对图像从左上向右下进行填充,对于待填充的空洞点用梯度较大的方向进行填充(x 或 y)。比如,一个像素点的 x 梯度大于 y 方向的梯度,则用该点 x 方向(左边)的点进行填充,否则用 y 方向的点进行填充。 参考代码如下:

//对图像进行空洞填充(n次迭代扫描)
void Darkness_Filling(IplImage* src,IplImage* src_gray,IplImage* dst)
{
    int i,j,count=0;
    char* ptr1;
    char* ptr2;
    char* ptr3;
    char* ptr_gray;
 
 
    //x和y方向的梯度
    IplImage* gradient_x = cvCreateImage(cvSize(src_gray->width,src_gray->height),IPL_DEPTH_16S,1);
    IplImage* gradient_y = cvCreateImage(cvSize(src_gray->width,src_gray->height),IPL_DEPTH_16S,1);
 
    char* gradientx;
    char* gradienty;
    char* tempx;
    char* tempy;
 
 
    //计算x和y方向的梯度图
    cvSobel(src_gray,gradient_x,1,0,3);
    cvSobel(src_gray,gradient_y,0,1,3);
 
 
    for(i = 0;i < src_gray->height;i++)
    {
        //指向源图像src数据的指针
        ptr1 = src->imageData + i * src->widthStep;
        ptr2 = src->imageData + i * src->widthStep;
        ptr3 = src_gray->imageData + i * src_gray->widthStep;
        ptr_gray=src_gray->imageData + i * src_gray->widthStep;
 
        //计算x和y方向上的对应点的梯度
        gradientx = gradient_x->imageData + i * gradient_x->widthStep;
        gradienty = gradient_y->imageData + i * gradient_y->widthStep;
        tempx = gradient_x->imageData + i * gradient_x->widthStep;
        tempy = gradient_y->imageData + i * gradient_y->widthStep;
 
        //在灰度图像上寻找空洞点
        for(j = 0;j < src_gray->width;j++)
        {
            if(ptr3[j]!=0)
                continue;
            //若纹理在y方向
            if(gradientx[j]>gradienty[j])
            {
                //该点值判定为上方一点的值
                ptr1 = src->imageData + (i-1) * src->widthStep;//得到原图上方像素指针
                ptr_gray=src_gray->imageData + (i-1) * src_gray->widthStep;//得到灰度图上方像素指针
                //填灰度图,为下次判断进行填充
                ptr3[j]=ptr_gray[j];
                //填RGB图
                ptr2[3*j]=ptr1[3*j];
                ptr2[3*j+1]=ptr1[3*j+1];
                ptr2[3*j+2]=ptr1[3*j+2];
 
                if(gradientx[j+1]==gradienty[j+1])
                {
                    tempx = gradient_x->imageData + (i-1) * gradient_x->widthStep;
                    tempy = gradient_y->imageData + i * gradient_y->widthStep;
                    gradientx[j+1] = (gradientx[j] + tempx[j])/2;
                    gradienty[j+1] = (gradienty[j] + tempy[j])/2;
                }
            }
            //若纹理在x方向
            else 
            {
                //该点值判定为左方一点的值
                ptr1 = src->imageData + i * src->widthStep;//得到原图左方像素指针
                ptr_gray=src_gray->imageData + i * src_gray->widthStep;//得到灰度图左方像素指针
                //填灰度图,为下次判断进行填充
                ptr3[j]=ptr_gray[j-1];
                //填RGB图
                ptr2[3*j] = ptr1[3*(j-1)];
                ptr2[3*j+1] = ptr1[3*(j-1)+1];
                ptr2[3*j+2] = ptr1[3*(j-1)+2];
 
                if(gradientx[j+1]==gradienty[j+1])
                {
                    tempx = gradient_x->imageData + (i-1) * gradient_x->widthStep;
                    tempy = gradient_y->imageData + i * gradient_y->widthStep;
                    gradientx[j+1] = (gradientx[j] + tempx[j])/2;
                    gradienty[j+1] = (gradienty[j] + tempy[j])/2;
                }
 
            }
        }
 
    }
    //复制图像
    cvCopy(src,dst);
}


效果图:

了解更多关于《计算机视觉与图形学》相关知识,请关注公众号:

在这里插入图片描述
下载我们代码和数据,请关注上方公众号

 

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值