[深度学习] 增加样本——弹性变换算法实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lhanchao/article/details/54234490

我们都知道,深度学习的成功的原因主要有两点:
(1)当前计算机的计算能力有很大提升;
(2)随着大数据时代的到来,当前的训练样本数目有很大的提升。
然而深度学习的一大问题是,有的问题并没有大量的训练数据,而由于深度神经网络具有非常强的学习能力,如果没有大量的训练数据,会造成过拟合,训练出的模型难以应用。

因此对于一些没有足够样本数量的问题,可以通过已有的样本,对其进行变化,人工增加训练样本。

对于图像而言,常用的增加训练样本的方法主要有对图像进行旋转、移位等仿射变换,也可以使用镜像变换等等,这里介绍一种常用于字符样本上的变换方法,弹性变换算法(Elastic Distortion)。该算法最先是由Patrice等人在2003年的ICDAR上发表的《Best Practices for Convolutional Neural Networks Applied to Visual Document Analysis》提出的,最开始应用在mnist手写体数字识别数据集中,发现对原图像进行弹性变换的操作扩充样本以后,对于手写体数字的识别效果有明显的提升。当前也有很多人把该方法应用到手写体汉字的识别问题中,是一种很普遍的扩充字符样本图像的方式。

下面来详细介绍一下算法流程:

(1)首先需要对图像中的每个像素点(x,y)产生两个-1~1之间的随机数,Δx(x,y)和Δy(x,y),分别表示该像素点的x方向和y方向的移动距离;

(2)生成一个以0为均值,以σ为标准差的高斯核k_nn,并用前面的随机数与之做卷积,并将结果作用于原图像。

作者在这里提出σ的大小与弹性变换的处理结果息息相关,如果σ过小,则生成的结果类似与对图像每个像素进行随机移动,而如果σ过大,则生成的结果与原图基本类似。

但是作者在文中并没有解释高斯核k_nn中n的大小对图像处理结果的影响,下面我的操作结果以n和σ为变量,进行了实验,我的原图是573*573大小,产生的卷积结果实验结果Δx(x,y)和Δy(x,y)各自扩大了100倍(因为Δx(x,y)和Δy(x,y)是-1~1的,如果不扩大的话基本看不出变化),实验结果如下图所示:

(1)n = 5,σ=4,8,16时:


(2)n = 21,σ= 4,8,16时:

(3)n = 105,σ = 4,8,16时:


可以看出来,只有在n足够大(与要处理的图像相比),且σ大小合适时才能够到合适的扭曲图像,如图所示,这里n = 105且σ=8时比较合适。

现在的论文中,只是介绍了算法流程,但具体的实现方法并没有介绍,我在实现的过程中一直很不清楚把Δx(x,y)和Δy(x,y)和卷积核做卷积具体是怎样做,而且也不太清楚卷积核的大小是多少,最终自己摸索这得到结果,应该是像我代码的方法。我的代码如下,对于高斯卷积和图像的读取使用的Opencv的函数,最终也可以自己在实现的函数中添加变量控制n和σ,我这里图方便,懒得加了。

Mat elasticDistort(Mat& img)
{
    //转成灰度图像
    cvtColor(img,img,CV_RGB2GRAY);
    //分别存放原图各个像素点对应的Δx(x,y)和Δy(x,y)
    Mat Xmv(img.rows,img.cols,CV_32FC1);
    Mat Ymv(img.rows,img.cols,CV_32FC1);
    //初始化Xmv
    srand(time(NULL));
    for(int i = 0; i<img.rows; i++)
    {
        float* p = Xmv.ptr<float>(i);
        for(int j = 0; j<img.cols; j++)
        {
            p[j] = (rand() % 3 - 1) * 100;//注意,Xmv和Ymv两个矩阵是产生随机数的基础上进行高斯模糊得到的,这里是产生随机数。2017-04-26
        }
    }
    //初始化Ymv
    srand(time(NULL) + 100);
    for(int i = 0; i<img.rows; i++)
    {
        float* p = Ymv.ptr<float>(i);
        for(int j = 0; j<img.cols; j++)
        {
            p[j] = (rand() % 3 - 1) * 100;//注意,Xmv和Ymv两个矩阵是产生随机数的基础上进行高斯模糊得到的,这里是产生随机数。2017-04-26
        }
    }

    //使用Opencv函数,对Xmv和Ymv分别做卷积,这里的105和4分别为n和σ
    GaussianBlur(Xmv,Xmv,Size(105,105),4,4);
    GaussianBlur(Ymv,Ymv,Size(105,105),4,4);

    //处理结果图像
    Mat result(img.rows,img.cols,img.type());
    result.setTo(255);
    //把Xmv和Ymv作用到原图像中,得到结果图像
    for(int i = 0; i<img.rows; i++)
    {
        for(int j = 0; j<img.cols; j++)
        {
            int newX = i + cvRound(Xmv.at<float>(i,j));
            int newY = j + cvRound(Ymv.at<float>(i,j));

            newX = newX < 0 ? 0 : newX;
            newY = newY < 0 ? 0 : newY;
            newX = newX >= img.rows ? img.rows - 1 : newX;
            newY = newY >= img.cols ? img.cols - 1 : newY;

            result.at<uchar>(newX,newY) = img.at<uchar>(i,j);
        }
    }
    
    //先对图像做腐蚀操作,再做膨胀操作,可以去掉字符中的白色纹理
    //Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));
    //erode(result,result,element);
    //dilate(result,result, element);
    
    return result;
}


——————————————————————————————————分割线——————————————————————————————————

更新:添加了一个average的过程,这样可以起到一定的平滑作用使得生成的图像更自然

//对图像进行弹性变换,扩充训练数据量,这种方法相比对图像进行仿射变换效果要好
Mat elasticDistort(Mat& img,int gaussianWindowSize,double gaussianKernelSize)
{
    cvtColor(img,img,CV_RGB2GRAY);
    Mat Xmv(img.rows,img.cols,CV_32FC1);
    Mat Ymv(img.rows,img.cols,CV_32FC1);
    //srand(time(NULL));
    for(int i = 0; i<img.rows; i++)
    {
        float* p = Xmv.ptr<float>(i);
        for(int j = 0; j<img.cols; j++)
        {
            p[j] = (rand() % 3 - 1) * 10;//注意,Xmv和Ymv两个矩阵是产生随机数的基础上进行高斯模糊得到的,这里是产生随机数。2017-04-26
        }
    }
    //srand(time(NULL) + 100);
    srand(clock());
    for(int i = 0; i<img.rows; i++)
    {
        float* p = Ymv.ptr<float>(i);
        for(int j = 0; j<img.cols; j++)
        {
            p[j] = (rand() % 3 - 1) * 10;//注意,Xmv和Ymv两个矩阵是产生随机数的基础上进行高斯模糊得到的,这里是产生随机数。2017-04-26
        }
    }
    GaussianBlur(Xmv,Xmv,Size(gaussianWindowSize,gaussianWindowSize),gaussianKernelSize,gaussianKernelSize);
    GaussianBlur(Ymv,Ymv,Size(gaussianWindowSize,gaussianWindowSize),gaussianKernelSize,gaussianKernelSize);

    
    Mat result(img.rows,img.cols,img.type());
    result.setTo(255);
    for(int i = 0; i<img.rows; i++)
    {
        for(int j = 0; j<img.cols; j++)//这里进行了更改!
        {   
            int newX_low = i + cvFloor(Xmv.at<float>(i,j));
            int newX_high = i + cvCeil(Xmv.at<float>(i,j));
            int newY_low = j + cvFloor(Ymv.at<float>(i,j));
            int newY_high = j + cvCeil(Ymv.at<float>(i,j));


            newX_low = newX_low < 0 ? 0 : newX_low;
            newX_high = newX_high < 0 ? 0 : newX_high;
            newY_low = newY_low < 0 ? 0 : newY_low;
            newY_high = newY_high < 0 ? 0 : newY_high;


            newX_low = newX_low >= img.rows ? img.rows - 1 : newX_low;
            newX_high = newX_high >= img.rows ? img.rows - 1 : newX_high;
            newY_low = newY_low >= img.cols ? img.cols - 1 : newY_low;
            newY_high = newY_high >= img.cols ? img.cols - 1 : newY_high;


            int sum = img.at<uchar>(newX_low,newY_low) + img.at<uchar>(newX_low,newY_high)+
                         img.at<uchar>(newX_high,newY_low) + img.at<uchar>(newX_high,newY_high);
            double avg = sum * 1.0 * 0.25;
            if(avg < 110)
            {
                avg = 0;
            }
            else if(avg > 145)
                avg = 255;
            result.at<uchar>(i,j) = avg;
            //result.at<uchar>(newX,newY) = img.at<uchar>(i,j);
        }
    }
    //cout<<result<<endl;
    return result;
}

————————————————————2017年4月26日更新——————————————————————

    Mat Xmv(img.rows,img.cols,CV_32FC1);
    Mat Ymv(img.rows,img.cols,CV_32FC1);
    //srand(time(NULL));
    for(int i = 0; i<img.rows; i++)
    {
        float* p = Xmv.ptr<float>(i);
        for(int j = 0; j<img.cols; j++)
        {
            p[j] = (rand() % 3 - 1) * 10;//注意,Xmv和Ymv两个矩阵是产生随机数的基础上进行高斯模糊得到的,这里是产生随机数。2017-04-26
        }
    }
    //srand(time(NULL) + 100);
    srand(clock());
    for(int i = 0; i<img.rows; i++)
    {
        float* p = Ymv.ptr<float>(i);
        for(int j = 0; j<img.cols; j++)
        {
            p[j] = (rand() % 3 - 1) * 10;//注意,Xmv和Ymv两个矩阵是产生随机数的基础上进行高斯模糊得到的,这里是产生随机数。2017-04-26
        }
    }
    GaussianBlur(Xmv,Xmv,Size(gaussianWindowSize,gaussianWindowSize),gaussianKernelSize,gaussianKernelSize);
    GaussianBlur(Ymv,Ymv,Size(gaussianWindowSize,gaussianWindowSize),gaussianKernelSize,gaussianKernelSize);

上面的代码是Xmv和Ymv矩阵的生成代码,从代码中Xmv和Ymv的生成方法实际上就是先产生两个随机矩阵,然后在该随机矩阵上进行高斯模糊,上面两个for循环做的就是生成随机矩阵,如果要弹性变形的图像比较大,可以考虑在较大的范围内生成随机数,如p[j] = (rand() % 5 - 2) * 10,即生成-20到20之间的随机数,这样会更适合较大的图像的随机弹性形变样本生成。



猜你喜欢

转载自blog.csdn.net/lhanchao/article/details/54234490