国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

這篇具有很好參考價值的文章主要介紹了【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

  泊松融合我自己寫的第一版程序大概是2016年在某個小房間里折騰出來的,當(dāng)時是用的迭代的方式,記得似乎效果不怎么樣,沒有達到論文的效果。前段時間又有網(wǎng)友問我有沒有這方面的程序,我說Opencv已經(jīng)有了,可以直接使用,他說opencv的框架太大,不想為了一個功能的需求而背上這么一座大山,看能否做個脫離那個環(huán)境的算法出來,當(dāng)時,覺得工作量挺大,就沒有去折騰,最近年底了,項目漸漸少了一點,公司上面又在搞辦公室政治,我地位不高,沒有參與權(quán),所以樂的閑,就抽空把這個算法從opencv里給剝離開來,做到了完全不依賴其他庫實現(xiàn)泊松融合樂,前前后后也折騰進半個月,這里還是做個開發(fā)記錄和分享。

  在翻譯算法過程中,除了參考了opencv的代碼,還看到了很多參考資料,主要有以下幾篇:

? ? ? ? ? ? ? ? 1、http://takuti.me/dev/poisson/demo/              ? ??這個似乎打不開了,早期的代碼好像是主要參考了這里

?    2、http://blog.csdn.net/baimafujinji/article/details/46787837      圖像的泊松(Poisson)編輯、泊松融合完全詳解

? ? ? ? ? ? ? ?3、http://blog.csdn.net/hjimce/article/details/45716603        圖像處理(十二)圖像融合(1)Seamless cloning泊松克隆-Siggraph 2004

    4、http://www.wangt.cc/2022/09/%E3%80%8Apoisson-image-editing%E3%80%8B%E8%AE%BA%E6%96%87%E7%90%86%E8%A7%A3%E4%B8%8E%E5%A4%8D%E7%8E%B0/#google_vignette  《Poisson Image Editing》論文理解和實現(xiàn)

? ? ? ? ? ? ? ?5、https://www.baidu.com/link?url=GgbzGxsNBzdTewEEXY4lx7RH5hB4KWxODUF79-cdVnNT4siKaGx5JSqh-pR3l7N9rXufCnyXWj2Fl40KvfRuTq&wd=&eqid=d200bfec000c06300000000665a61134    從泊松方程的解法,聊到泊松圖像融合

    6、https://blog.csdn.net/weixin_43194305/article/details/104928378    泊松圖像編輯(Possion Image Edit)原理、實現(xiàn)與應(yīng)用

?

  對應(yīng)的論文為:Poisson Image Editing,可以從百度上下載到。

  泊松融合的代碼在opencv的目錄如下:

    opencv-4.9.0\源代碼\modules\photo\src,其中的seamless_cloning_impl.cpp以及seamless_cloning.cpp為主要算法代碼。

  我們總結(jié)下opencv的泊松融合主要是由以下幾個步驟組成的:

    1、計算前景和背景圖像的梯度場;

    2、根據(jù)一定的原則計算融合后的圖像的梯度場(這一步是最靈活的,通過改變他可以實現(xiàn)各種效果);

    3、對融合后的梯度偏導(dǎo),獲取對應(yīng)的散度。

    4、由散度及邊界像素值求解泊松方程(最為復(fù)雜)。

  那么我們就一步一步的進行扣取和講解。

  一、計算前景和背景圖像的梯度場。

  這一部分在CV中對應(yīng)的函數(shù)名為:computeGradientX及computeGradientY,在CV中的調(diào)用代碼為:

    computeGradientX(destination, destinationGradientX);
    computeGradientY(destination, destinationGradientY);

    computeGradientX(patch, patchGradientX);
    computeGradientY(patch, patchGradientY);

  以X方向的梯度為例, 其相應(yīng)的代碼為:

void Cloning::computeGradientX( const Mat &img, Mat &gx)
{
    Mat kernel = Mat::zeros(1, 3, CV_8S);
    kernel.at<char>(0,2) = 1;
    kernel.at<char>(0,1) = -1;
    if(img.channels() == 3)
    {
        filter2D(img, gx, CV_32F, kernel);
    }
    else if (img.channels() == 1)
    {
        filter2D(img, gx, CV_32F, kernel);
        cvtColor(gx, gx, COLOR_GRAY2BGR);
    }
}

  可以看到就是簡單的一個卷積,卷積核心為[0, -1, 1],然后使用filter2D函數(shù)進行處理。在這里opencv為了減少代碼量,把灰度版本的算法也直接用彩色的處理了。

  這個要拋棄CV,其實是個很簡單的過程,一個簡單的代碼如下:

//    邊緣部分采用了反射101方式,這個要和Opencv的代碼一致,支持單通道和3通道
void IM_ComputeGradientX_PureC(unsigned char *Src, short *Dest, int Width, int Height, int Stride)
{
    int Channel = Stride / Width;
    if (Channel == 1)
    {
        for (int Y = 0; Y < Height; Y++)
        {
            unsigned char* LinePS = Src + Y * Stride;
            short* LinePD = Dest + Y * Width;
            for (int X = 0; X < Width - 1; X++)
            {
                LinePD[X] = LinePS[X + 1] - LinePS[X];
            }
            //    LinePD[Width - 2] = LinePS[Width - 1] - LinePS[Width - 2]
            //    LinePD[Width - 1] = LinePS[Width - 2] - LinePS[Width - 1]            101方式的鏡像就是這個結(jié)果
            LinePD[Width - 1] = -LinePD[Width - 2];                //    最后一列
        }
    }
    else
    {
       //  三通道代碼
    }
}

  我這里的Dest沒有用float類型,而是用的short,我的原則是用最小的內(nèi)存量+合適的數(shù)據(jù)類型來保存目標(biāo)。?

  注意,opencv里默認的邊緣采用的是101的鏡像方式的,因此,對于[0, -1, 1]這種卷積和,最右側(cè)一列的值就是右側(cè)倒數(shù)第二列的負值。

  2、根據(jù)一定的原則計算融合后的圖像的梯度場

  這部分算法opencv寫的很分散,他把代碼放置到了好幾個函數(shù)里,這里把他們集中一下大概就是如下幾行:

 1     Mat Kernel(Size(3, 3), CV_8UC1);
 2     Kernel.setTo(Scalar(1));
 3     erode(binaryMask, binaryMask, Kernel, Point(-1,-1), 3);
 4     binaryMask.convertTo(binaryMaskFloat, CV_32FC1, 1.0/255.0);
 5     arrayProduct(patchGradientX, binaryMaskFloat, patchGradientX);
 6     arrayProduct(patchGradientY, binaryMaskFloat, patchGradientY);
 7     bitwise_not(wmask,wmask);
 8     wmask.convertTo(binaryMaskFloatInverted,CV_32FC1,1.0/255.0);
 9     arrayProduct(destinationGradientX, binaryMaskFloatInverted, destinationGradientX);
10     arrayProduct(destinationGradientY, binaryMaskFloatInverted, destinationGradientY);
11     Mat laplacianX = destinationGradientX + patchGradientX;
12     Mat laplacianY = destinationGradientY + patchGradientY;

?  這個代碼里的前面是三行一開始我感覺很納悶,這個是干啥呢,為什么要對mask進行一個收縮呢,后面想一想,如果是一個純白的mask,那么下面的融合整個融合后的梯度場就完全是前景的梯度場了,和背景就毫無關(guān)系了,而進行erode后,則邊緣部分使用的就是背景的梯度場,這樣就有了有效的邊界條件,不過?erode(binaryMask, binaryMask, Kernel, Point(-1, -1), 3);最后一個參數(shù)要是3呢,這個3可是表示重復(fù)執(zhí)行三次,如果配合上前面的Kernel參數(shù),對于一個純白的圖,邊緣就會出現(xiàn)3行和3列的純黑的像素了(我測試默認參數(shù)下erode在處理邊緣時,是用了0值代替超出邊界的值),我個人感覺這里使用參數(shù)1就可以了。

  從第四到第十二行其實就是很簡單的一個線性融合過程,Opencv的代碼呢寫的很向量化,我們要自己實現(xiàn)其實就下面幾句代碼:

for (int Y = 0; Y < Height; Y++)
{
    int Index = Y * Width * Channel;
    int Speed = Y * Width;
    for (int X = 0; X < Width; X++)
    {
        float MaskF = MaskS[Speed + X] * IM_INV255;
        float InvMaskF = 1.0f - MaskF;
        if (Channel == 1)
        {
            LaplacianX[Index + X] = GradientX_B[Index + X] * InvMaskF + GradientX_F[Index + X] * MaskF;
            LaplacianY[Index + X] = GradientY_B[Index + X] * InvMaskF + GradientY_F[Index + X] * MaskF;
        }
        else
        {
            //    三通道
        }
    }
}

  3、對融合后的梯度偏導(dǎo),獲取對應(yīng)的散度。

  這一部分對應(yīng)的CV的代碼為:

    computeLaplacianX(laplacianX,laplacianX);
    computeLaplacianY(laplacianY,laplacianY);

  以X方向的散度計算為例,其代碼如下:

void Cloning::computeLaplacianX( const Mat &img, Mat &laplacianX)
{
    Mat kernel = Mat::zeros(1, 3, CV_8S);
    kernel.at<char>(0,0) = -1;
    kernel.at<char>(0,1) = 1;
    filter2D(img, laplacianX, CV_32F, kernel);
}

  也是個卷積,沒有啥特別的,翻譯成普通的C代碼可以用如下方式:

void IM_ComputeLaplacianX_PureC(float* Src, float* Dest, int Width, int Height, int Channel)
{
    if (Channel == 1)
    {
        for (int Y = 0; Y < Height; Y++)
        {
            float* LinePS = Src + Y * Width;
            float* LinePD = Dest + Y * Width;
            for (int X = Width - 1; X >= 1; X--)
            {
                LinePD[X] = LinePS[X] - LinePS[X - 1];
            }
            LinePD[0] = -LinePD[1];            //    第一列
        }
    }
    else
    {
            //    三通道
    }
}

  注意到Opencv的這個函數(shù)是支持Inplace操作的,即Src=Dest時,也能得到正確的結(jié)果,為了實現(xiàn)這個結(jié)果,我們注意到這個卷積核是偏左的,即核中心偏左的元素有用,利用這個特性可以在X方向上從右向左循環(huán),就可以避免數(shù)據(jù)被覆蓋的,當(dāng)然對于每行的第一個元素就要特別處理了,同時注意這里采用了101格式的邊緣處理。

  4、由散度及邊界像素值求解泊松方程。

  有了以上的散度的計算,后面就是求解一個很大的稀疏矩方程的過程了,如果直接求解,將會是一個非常耗時的過程,即使利用稀疏的特性,也將對編碼者提出很高的技術(shù)要求。自己寫個稀疏矩陣的求解過程也是需要很大的勇氣的。

  在opencv里,上面的求解是借助了傅里葉變換實現(xiàn)的,這個的原理我在某個論文中看到過,現(xiàn)在時間關(guān)系,我也沒有找到那一片論文了,如果后續(xù)有機會看到,我在分享出來。

  CV的求解過程涉及到了3個函數(shù),分別是poissonSolver、solve、dst,也是一個調(diào)用另外一個的關(guān)系,具體的這個代碼能實現(xiàn)求解泊松方程的原理,我們也不去追究吧,僅僅從代碼層面說說大概得事情。

  首先是poissonSolver函數(shù),其具體代碼如下

 1 void Cloning::poissonSolver(const Mat &img, Mat &laplacianX , Mat &laplacianY, Mat &result)
 2 {
 3     const int w = img.cols;
 4     const int h = img.rows;
 5     Mat lap = laplacianX + laplacianY;
 6     Mat bound = img.clone();
 7     rectangle(bound, Point(1, 1), Point(img.cols-2, img.rows-2), Scalar::all(0), -1);
 8     Mat boundary_points;
 9     Laplacian(bound, boundary_points, CV_32F);
10     boundary_points = lap - boundary_points;
11     Mat mod_diff = boundary_points(Rect(1, 1, w-2, h-2));
12     solve(img,mod_diff,result);
13 }

  這里,Opencv有一次把他的代碼藝術(shù)展現(xiàn)的活靈活現(xiàn)。從低6到第11行,我們看到了一個藝術(shù)家為了獲取最后的結(jié)果所做的各種行為藝術(shù)。

  首先是復(fù)制原圖,然后把原圖除了第一行、最后一行、第一列、最后一列填充為黑色(rectangle函數(shù)),然后對這個填充后的圖進行拉普拉斯邊緣檢測,然后第10行做個減法,最后第11行呢,又直接裁剪了去掉周邊一個像素寬范圍內(nèi)的結(jié)果。

  行為藝術(shù)家。

  如果只從結(jié)果考慮,我們完全沒有必要有這么多的中間過程,我們把邊緣用*表示,中間值都為0,則一個二維平面圖如下所示:

    //    拉普拉斯卷積核如下                對應(yīng)標(biāo)識如下
    //        1                                Q1
    //    1    -4    1                   Q3    Q4    Q5    
    //        1                                Q7
    // 
    //    *    *    *    *    *    *    *    *    *
    //    *    0    0    0    0    0    0    0    *    
    //    *    0    0    0    0    0    0    0    *    
    //    *    0    0    0    0    0    0    0    *    
    //    *    0    0    0    0    0    0    0    *    
    //    *    0    0    0    0    0    0    0    *    
    //    *    *    *    *    *    *    *    *    *

  所有為0的部位的計算值為我們需要的結(jié)果,很明顯,除了最外一圈0值的拉普拉斯邊緣檢測不為0,其他的都為0,不需要計算,而周邊一圈的0值的拉普拉斯邊緣檢測涉及到的3*3又恰好在原圖的有效范圍內(nèi),不需要考慮邊緣的值,因此,我們可以直接一步到位寫出mod_diff的值的。

    ModDiff[0] = Laplacian[Width + 1] - (Image[1] + Image[Stride]);        //    對應(yīng)的拉普拉斯有效值為:    Q1 + Q3
    for (int X = 2; X < Width - 2; X++)
    {
        ModDiff[X - 1] = Laplacian[Width + X] - Image[X];                //    Q1
    }
    ModDiff[Width - 3] = Laplacian[Width + Width - 2] - (Image[Width - 2] + Image[Stride + Width - 1]);        //    Q1 + Q5

    for (int Y = 2; Y < Height - 2; Y++)
    {
        unsigned char* LinePI = Image + Y * Stride;
        float* LinePL = Laplacian + Y * Width;
        float* LinePD = ModDiff + (Y - 1) * (Width - 2);
        LinePD[0] = LinePL[1] - LinePI[0];                                //    Q3
        for (int X = 2; X < Width - 2; X++)
        {
            LinePD[X - 1] = LinePL[X];                                    //    0
        }
        LinePD[Width - 3] = LinePL[Width - 2] - LinePI[Width - 1];        //    Q5
    }
    //    最后一行
    ModDiff[(Height - 3) * (Width - 2)] = Laplacian[(Height - 2) * Width + 1] - (Image[(Height - 2) * Stride] + Image[(Height - 1) * Stride + 1]);    //    Q3 + Q7
    for (int X = 2; X < Width - 2; X++)
    {
        ModDiff[(Height - 3) * (Width - 2) + X - 1] = Laplacian[(Height - 2) * Width + X] - Image[(Height - 1) * Stride + X];                        //    Q7
    }
    ModDiff[(Height - 3) * (Width - 2) + Width - 3] = Laplacian[(Height - 2) * Width + Width - 2] - (Image[(Height - 2) * Stride + Width - 1] + Image[(Height - 1) * Stride + Width - 2]);    //    Q5 + Q7

  接下來是我們的solve函數(shù),這個函數(shù)不是核心,所以稍微提及一下:

【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化
void Cloning::solve(const Mat &img, Mat& mod_diff, Mat &result)
{
    const int w = img.cols;
    const int h = img.rows;

    Mat res;
    dst(mod_diff, res);

    for(int j = 0 ; j < h-2; j++)
    {
        float * resLinePtr = res.ptr<float>(j);
        for(int i = 0 ; i < w-2; i++)
        {
            resLinePtr[i] /= (filter_X[i] + filter_Y[j] - 4);
        }
    }

    dst(res, mod_diff, true);

    unsigned char *  resLinePtr = result.ptr<unsigned char>(0);
    const unsigned char * imgLinePtr = img.ptr<unsigned char>(0);
    const float * interpLinePtr = NULL;

     //first col
    for(int i = 0 ; i < w ; ++i)
        result.ptr<unsigned char>(0)[i] = img.ptr<unsigned char>(0)[i];

    for(int j = 1 ; j < h-1 ; ++j)
    {
        resLinePtr = result.ptr<unsigned char>(j);
        imgLinePtr  = img.ptr<unsigned char>(j);
        interpLinePtr = mod_diff.ptr<float>(j-1);

        //first row
        resLinePtr[0] = imgLinePtr[0];

        for(int i = 1 ; i < w-1 ; ++i)
        {
            //saturate cast is not used here, because it behaves differently from the previous implementation
            //most notable, saturate_cast rounds before truncating, here it's the opposite.
            float value = interpLinePtr[i-1];
            if(value < 0.)
                resLinePtr[i] = 0;
            else if (value > 255.0)
                resLinePtr[i] = 255;
            else
                resLinePtr[i] = static_cast<unsigned char>(value);
        }

        //last row
        resLinePtr[w-1] = imgLinePtr[w-1];
    }

    //last col
    resLinePtr = result.ptr<unsigned char>(h-1);
    imgLinePtr = img.ptr<unsigned char>(h-1);
    for(int i = 0 ; i < w ; ++i)
        resLinePtr[i] = imgLinePtr[i];
}
View Code

  他主要調(diào)用dst函數(shù),然后對dst函數(shù)處理的結(jié)果再進行濾波,然后再調(diào)用dst函數(shù),最后得到的結(jié)果進行圖像化。 不過第一次dst是使用FFT正變換,第二次使用了FFT逆變換。

  從他的恢復(fù)圖像的過程看,他也是對最周邊的一圈像素不做處理,直接使用背景的圖像值。

  那么我們再看看dst函數(shù),這個是解泊松方程的關(guān)鍵所在,opencv的代碼如下:

void Cloning::dst(const Mat& src, Mat& dest, bool invert)
{
    Mat temp = Mat::zeros(src.rows, 2 * src.cols + 2, CV_32F);
    int flag = invert ? DFT_ROWS + DFT_SCALE + DFT_INVERSE: DFT_ROWS;
    src.copyTo(temp(Rect(1,0, src.cols, src.rows)));
    for(int j = 0 ; j < src.rows ; ++j)
    {
        float * tempLinePtr = temp.ptr<float>(j);
        const float * srcLinePtr = src.ptr<float>(j);
        for(int i = 0 ; i < src.cols ; ++i)
        {
            tempLinePtr[src.cols + 2 + i] = - srcLinePtr[src.cols - 1 - i];
        }
    }
    Mat planes[] = {temp, Mat::zeros(temp.size(), CV_32F)};
    Mat complex;
    merge(planes, 2, complex);
    dft(complex, complex, flag);
    split(complex, planes);
    temp = Mat::zeros(src.cols, 2 * src.rows + 2, CV_32F);
    for(int j = 0 ; j < src.cols ; ++j)
    {
        float * tempLinePtr = temp.ptr<float>(j);
        for(int i = 0 ; i < src.rows ; ++i)
        {
            float val = planes[1].ptr<float>(i)[j + 1];
            tempLinePtr[i + 1] = val;
            tempLinePtr[temp.cols - 1 - i] = - val;
        }
    }
    Mat planes2[] = {temp, Mat::zeros(temp.size(), CV_32F)};
    merge(planes2, 2, complex);
    dft(complex, complex, flag);
    split(complex, planes2);
    temp = planes2[1].t();
    temp(Rect( 0, 1, src.cols, src.rows)).copyTo(dest);
}

  對Temp的數(shù)據(jù)填充中,我們看到他臨時創(chuàng)建了寬度2*Width + 2大小的數(shù)據(jù),高度為Height,其中第0列,第Width + 1列的數(shù)據(jù)為都為0,第1列到第Width +1列之間的數(shù)據(jù)為原是數(shù)據(jù),第Width + 2到2*Width + 1列的數(shù)據(jù)為原始數(shù)據(jù)鏡像后的負值。

  填充完之后,在構(gòu)造一個復(fù)數(shù),然后調(diào)用FFT變換,當(dāng)invert為false時,使用的DFT_ROWS參數(shù),為true時,使用的是DFT_ROWS + DFT_SCALE + DFT_INVERSE參數(shù),那么其實這里就是一維的FFT正變換和逆變換,即對數(shù)據(jù)的每一行單獨處理,行于行之間是無關(guān)的,是可以并行的。

  再進行了第一次FFT變換后,我們有創(chuàng)建一副寬度為2*Height+ 2,高度為Width大小的數(shù)據(jù),這個時候數(shù)據(jù)里的填充值依舊分為2塊,也是用黑色的處置條分開,同樣右側(cè)值的為鏡像負值分布。但是這個時候原始值是從前面進行FFT變換后的數(shù)據(jù)中獲取,而且還需要轉(zhuǎn)置獲取,其獲取的是FFT變換的虛部的值。?

  填充完這個數(shù)據(jù)后,再次進行FFT變換,變換完之后,我們?nèi)∽儞Q后的虛部的值的轉(zhuǎn)置,并且舍棄第一列的值,作為我們處理后的結(jié)果。?

  整個OPENCV的代碼從邏輯上是比較清晰的,他通過各種內(nèi)嵌的函數(shù)組合,實現(xiàn)了清晰的思路。但是如果從代碼效率角度來說,是非常不可取的,從內(nèi)存占用上來說,也存在著過多的浪費。這也是opencv中非核心函數(shù)通用的問題,基本上就是只在意結(jié)果,不怎么在乎過程和內(nèi)存占用。?

  談到這里,核心的泊松融合基本就講完了,其各種不同的應(yīng)用也是基于上述過程。

  那么我們再稍微談?wù)勊惴ǖ膬?yōu)化和加速。?

  整個算法流程不算特別長,前面三個步驟的計算都比較簡單,計算量也不是很大,慢的還是在于泊松方程的求解,而求解中最耗時還是那個DFT變換,簡單的測試表面,DFT占整個算法耗時的80%(單線程下)。前面說過,這個內(nèi)部使用的是一維行方向的DFT變換,行于行之間的處理是無關(guān)的,而且,他的數(shù)據(jù)量也比較大,特別適合于并行處理,我們可以直接用簡單的omp就可以實現(xiàn)加速。

  另外,我們再進行FFT時,常用的一個加速手段就是GetOptimalDftSize獲得一個和原始尺寸最為接近而又能更快實現(xiàn)FFT的大小,通常他們是3或者4或者5的倍數(shù)。有時候,這個加速也非常的明顯,比如尺寸為1023的FFT和尺寸為1024的FFT,速度可以相差好幾倍。這里我也嘗試使用這個函數(shù),但是經(jīng)過多次嘗試(包括適當(dāng)?shù)母淖償?shù)據(jù)布局),都存在一個嚴(yán)重的問題,得到的結(jié)果圖像有著不可忽視的誤差,基本無法恢復(fù)。因此,這個優(yōu)化的步驟不得已只能放棄。

  前面說了很多,還忘記了一個最重要的函數(shù)的扣取,dft函數(shù),這個函數(shù)在opencv的目錄如下:  opencv-4.9.0\源代碼\modules\core\src\dxt.cpp,居然是用的dxt這個文件名,開始我怎么搜都搜不到他。?

  關(guān)于這個功能的扣取,我大概也花了半個月的時間,時間上OPENCV也有很多版本,比如CPU的、opencl的等等,我這里扣取的是純CPU的,而且還是從早期的CV的代碼中扣的,現(xiàn)在的版本的代碼里有太多不相關(guān)的東西了,扣取的難度估計還要更大。而且在扣取中我還做了一些優(yōu)化,這個就不在這里多說了,總之,opencv的FFT在各種開源版本的代碼中算是一份非常不錯的代碼。

  具體的應(yīng)用:

  1、無縫的圖像合成,對應(yīng)CV的seamlessClone函數(shù),他支持背景圖和前景圖圖不一樣大小,也可以沒有蒙版等等特性,其具體的代碼如下:

void cv::seamlessClone(InputArray _src, InputArray _dst, InputArray _mask, Point p, OutputArray _blend, int flags)
{
    CV_INSTRUMENT_REGION();
    CV_Assert(!_src.empty());
    const Mat src  = _src.getMat();
    const Mat dest = _dst.getMat();
    Mat mask = checkMask(_mask, src.size());
    dest.copyTo(_blend);
    Mat blend = _blend.getMat();
    Mat mask_inner = mask(Rect(1, 1, mask.cols - 2, mask.rows - 2));
    copyMakeBorder(mask_inner, mask, 1, 1, 1, 1, BORDER_ISOLATED | BORDER_CONSTANT, Scalar(0));
    Rect roi_s = boundingRect(mask);
    if (roi_s.empty()) return;
    Rect roi_d(p.x - roi_s.width / 2, p.y - roi_s.height / 2, roi_s.width, roi_s.height);
    Mat destinationROI = dest(roi_d).clone();
    Mat sourceROI = Mat::zeros(roi_s.height, roi_s.width, src.type());
    src(roi_s).copyTo(sourceROI,mask(roi_s));
    Mat maskROI = mask(roi_s);
    Mat recoveredROI = blend(roi_d);
    Cloning obj;
    obj.normalClone(destinationROI,sourceROI,maskROI,recoveredROI,flags);
}

 ? ? copyMakeBorder這個東西,呵呵,和前面講的那個rectangle的作用正好想法,把周邊一圈設(shè)置為黑色,然后再提取出實際有效的邊界(不為0的區(qū)域),以便減少計算量,后續(xù)再根據(jù)邊界裁剪出有效的區(qū)域,交給具體的融合的函數(shù)處理。
  opencv的這個函數(shù)寫的實在不怎么好,當(dāng)我們不小心設(shè)置了錯誤的p參數(shù)時,就會出現(xiàn)內(nèi)存錯誤,這個參數(shù)主要是指定前景圖像在背景圖像中的位置的, 我們必須保證前景圖像不能有任何部分跑到背景圖像的外部,在我自己寫的版本中已經(jīng)校正了這個小錯誤。

? ? ? ? 注意,這里有個Flag參數(shù),當(dāng)Flag為NORMAL_CLONE時,就是我們前面的標(biāo)準(zhǔn)過程,當(dāng)為MIXED_CLONE時,則在第二步體現(xiàn)了不同:
  用原文的公式表示即為:

? ? ? ? ?【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

?  翻譯為我們能看懂的意思就是:?Mixed的模式下,如果前景的梯度差異大于背景的差異,則直接進行線性混合,否則就直接用背景的梯度。這個模式下可以獲得更為理想的融合效果。

   我想辦法把論文中的一些測試圖像摳出來,然后進行了一系列測試,確實能獲得一些不錯的效果。

 【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化? ? ? ? ??【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化? ? ? ??【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

            字符                       ? ? ? ? 背景紋理                        融合結(jié)果  

? ??【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化? ??【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化? ? ??【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

          海景                              彩虹                              融合后

  上面為不帶mask時全圖進行融合,可以看到融合后前景基本完美的融合到了背景中,但是前景的顏色還是發(fā)生了一些改變。

  【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化???【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化???【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化? ?【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

        背景圖                  前景圖                    蒙版圖                  合成圖

  上面這一幅測試圖中,太陽以及太陽在水中的倒影也完美的融合到背景圖中,相當(dāng)?shù)淖匀弧?/span>

  以下為多福圖像和成到一幅中的效果。

  【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化? ? ??【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化?

            前景1                          前景1蒙版

? ? ? ?【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化? ? ??【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

            前景2                          前景2蒙版

? ? ? ?【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化? ? ? ? ?【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

            背景圖                          融合后結(jié)果

? ? 上面所有的融合方式都是選擇的MIXED_CLONE。

  其實注意到MIXED_CLONE里梯度的混合原則,要達到上圖這樣較好的融合效果,對前景圖實際上還是有一絲絲特別的要求的,那就是在前景圖中,我們不希望保留的特征一定要是梯度變化比較小的區(qū)域,比如純色范圍,或者很類似的顏色這樣的東西。

?  opencv里還有個MONOCHROME_TRANSFER這個Flag可以選,這個其實直接把前景圖像變?yōu)椴噬J降幕叶葓D就能得到一樣的結(jié)果了。

  對于任意的兩幅圖,進行這中無縫的泊松融合,也能出現(xiàn)一些奇葩的效果,比如下面這樣的圖。

  【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

  不過這種圖并沒有什么實際意義。

  2、圖像的亮度的改變,對應(yīng)illuminationChange函數(shù),其具體代碼為:

void Cloning::illuminationChange(Mat &I, Mat &mask, Mat &wmask, Mat &cloned, float alpha, float beta)
{
    CV_INSTRUMENT_REGION();
    computeDerivatives(I,mask,wmask);
    arrayProduct(patchGradientX,binaryMaskFloat, patchGradientX);
    arrayProduct(patchGradientY,binaryMaskFloat, patchGradientY);
    Mat mag;
    magnitude(patchGradientX,patchGradientY,mag);
    Mat multX, multY, multx_temp, multy_temp;
    multiply(patchGradientX,pow(alpha,beta),multX);
    pow(mag,-1*beta, multx_temp);
    multiply(multX,multx_temp, patchGradientX);
    patchNaNs(patchGradientX);
    multiply(patchGradientY,pow(alpha,beta),multY);
    pow(mag,-1*beta, multy_temp);
    multiply(multY,multy_temp,patchGradientY);
    patchNaNs(patchGradientY);
    Mat zeroMask = (patchGradientX != 0);
    patchGradientX.copyTo(patchGradientX, zeroMask);
    patchGradientY.copyTo(patchGradientY, zeroMask);
    evaluate(I,wmask,cloned);
}

  這個的基礎(chǔ)是下面的公式:

? ? ? ? ? ?【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

?  通過調(diào)整Alpha和Beta值,改變原始的亮度,然后再將改變亮度后的圖和原始的圖進行泊松融合,所以這里的前景圖是由背景圖生成的。

   這里的代碼再一次體現(xiàn)opencv的藝術(shù)家的特性:pow(mag,-1*beta, multx_temp);這么耗時的操作居然執(zhí)行了兩次。

   這里的核心其實還是在算法的第二步:根據(jù)一定的原則計算融合后的圖像的梯度場,其他的過程和標(biāo)準(zhǔn)的無縫融合是一樣的。

  不過不可理解的是,為什么這個函數(shù)opencv不使用類似seamlessclone的boundingRect函數(shù)縮小需要計算的范圍了,這樣實際是可以提速很多的。

  這個函數(shù)用論文提供的自帶圖像確實有較為不錯的效果,比如下面這個橙子的高光部分從視覺上看確實去掉的比較完美,但是也不是所有的高光都能完美去掉。

  【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化??【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化???【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

          原始圖                          蒙版                      結(jié)果圖(alpha = 0.2, beta = 0.3)

  論文里還提到了可以對偏黑的圖進行適度調(diào)亮,這個我倒是沒有測試成功。

  3、圖像顏色調(diào)整,對應(yīng)函數(shù)localColorChange,這個函數(shù)就更為簡單了。

void Cloning::localColorChange(Mat &I, Mat &mask, Mat &wmask, Mat &cloned, float red_mul=1.0,
                                 float green_mul=1.0, float blue_mul=1.0)
{
    computeDerivatives(I,mask,wmask);
    arrayProduct(patchGradientX,binaryMaskFloat, patchGradientX);
    arrayProduct(patchGradientY,binaryMaskFloat, patchGradientY);
    scalarProduct(patchGradientX,red_mul,green_mul,blue_mul);
    scalarProduct(patchGradientY,red_mul,green_mul,blue_mul);
    evaluate(I,wmask,cloned);
}

  這個其實就在前景圖上的梯度上乘上不同的系數(shù),然后再和原圖融合,這種調(diào)整可能比直接調(diào)整顏色要自然一些。

  【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化??【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化???【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

  4、cv里還提供了一個紋理平整化的算法,叫textureFlatten,我沒感覺到這個算法有多大的作用。所以就沒有怎么去實現(xiàn)。

  其實算法論文里還有個Seamless tilingg功能的,我自己嘗試去實現(xiàn),暫時沒有獲取正確的結(jié)果,如下圖所示:

? ? ? ? ? ? ? ? ? ? ? ?【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

  這個效果再有些場景下還是很有用的。

  最后談及下算法速度吧,因為整體都是翻譯自opencv,而且核心最耗時的FFT部分也基本是直接翻譯的,所以不會有本質(zhì)的區(qū)別,在默認情況下,我用opencv 4.0版本去測試,同樣大小的圖,如果我不開openmp,耗時比大概是10:6,其中10是我的耗時,我估計這個于CV內(nèi)部調(diào)用的DFT算法版本有關(guān)。此時我們觀察到使用CV時,CPU的使用率在35%(4核),當(dāng)在CV下加入setNumThreads(1)指令后,可以看到CPU使用在25%,此時耗時比大概是10:8。當(dāng)我使用2個線程加速我的FFT1D時,CV也使用默認設(shè)置,耗時比約為5:6,此時我的CPU占用率約為40%,因此比cv版本的還是要快一些的。

  以上對比僅限于seamlessclone,對于其他的函數(shù),我做了boundRect,那就不是塊一點點了。

  為了方便測試,我做了一個可視化的UI,有興趣的朋友可以自行測試看看效果。

  總的來說,這個泊松融合要想獲取自己需要的結(jié)果,還是要有針對性的針對第二步梯度的融合多做些考慮和調(diào)整,才能獲取到自己需要的結(jié)果。?

【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

??  測試Demo及測試圖片下載地址: https://files.cnblogs.com/files/Imageshop/PossionBlending.rar?t=1705395766&download=true

? ? ? ? ?如果想時刻關(guān)注本人的最新文章,也可關(guān)注公眾號或者添加本人微信:? laviewpbt

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化

翻譯

搜索

復(fù)制文章來源地址http://www.zghlxwxcb.cn/news/detail-794645.html

到了這里,關(guān)于【快速閱讀二】從OpenCv的代碼中扣取泊松融合算子(Poisson Image Editing)并稍作優(yōu)化的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 神經(jīng)網(wǎng)絡(luò)算子融合的幾種方式

    Kernel融合:將多個卷積核合并為一個更大的卷積核,從而減少計算量。 層融合:將多個連續(xù)的神經(jīng)網(wǎng)絡(luò)層合并為一個更大的層,減少內(nèi)存訪問和計算開銷。 數(shù)據(jù)重用:在計算過程中,將多個算子共享輸入數(shù)據(jù),減少數(shù)據(jù)讀取次數(shù)。 并行計算:將多個獨立的神經(jīng)網(wǎng)絡(luò)算子并行

    2024年02月16日
    瀏覽(25)
  • AI編譯器-圖常見優(yōu)化算法-算子融合

    算子融合(Operator Fusion)是深度學(xué)習(xí)編譯器中的一種優(yōu)化技術(shù),它可以將多個算子合并為一個更大的算子,以減少計算和內(nèi)存訪問的開銷。以下是一些常見的算子融合例子: 卷積和池化融合:將卷積層和池化層融合為一個算子,減少內(nèi)存訪問和計算的開銷。 多個全連接層融

    2024年02月10日
    瀏覽(24)
  • python使用opencv對圖像添加(高斯/椒鹽/泊松/斑點)噪聲

    python使用opencv對圖像添加(高斯/椒鹽/泊松/斑點)噪聲

    導(dǎo)讀 這篇文章主要介紹如何利用opencv來對圖像添加各類噪聲,原圖 高斯噪聲 高斯噪聲就是給圖片添加一個服從 高斯分布的噪聲 ,可以通過調(diào)節(jié)高斯分布 標(biāo)準(zhǔn)差(sigma) 的大小來控制添加噪聲程度, sigma 越大添加的噪聲越多圖片損壞的越厲害 椒鹽噪聲 椒鹽噪聲就是給圖片添

    2024年02月15日
    瀏覽(22)
  • 【精選】OpenCV多視角攝像頭融合的目標(biāo)檢測系統(tǒng):全面部署指南&源代碼

    【精選】OpenCV多視角攝像頭融合的目標(biāo)檢測系統(tǒng):全面部署指南&源代碼

    隨著計算機視覺和圖像處理技術(shù)的快速發(fā)展,人們對于多攝像頭拼接行人檢測系統(tǒng)的需求日益增加。這種系統(tǒng)可以利用多個攝像頭的視角,實時監(jiān)測和跟蹤行人的活動,為公共安全、交通管理、視頻監(jiān)控等領(lǐng)域提供重要的支持和幫助。 在傳統(tǒng)的行人檢測系統(tǒng)中,通常只使用單

    2024年01月22日
    瀏覽(27)
  • 《opencv實用探索·十一》opencv之Prewitt算子邊緣檢測,Roberts算子邊緣檢測和Sobel算子邊緣檢測

    《opencv實用探索·十一》opencv之Prewitt算子邊緣檢測,Roberts算子邊緣檢測和Sobel算子邊緣檢測

    1、前言 邊緣檢測: 圖像邊緣檢測是指在圖像中尋找灰度、顏色、紋理等變化比較劇烈的區(qū)域,它們可能代表著物體之間的邊界或物體內(nèi)部的特征。邊緣檢測是圖像處理中的一項基本操作,可以用于人臉識別、物體識別、圖像分割等多個領(lǐng)域。 邊緣檢測實質(zhì)上是計算當(dāng)前點和

    2024年02月22日
    瀏覽(19)
  • OpenCV-25sobel算子(索貝爾算子)

    OpenCV-25sobel算子(索貝爾算子)

    前面所提到的濾波都是用于降噪的,去掉噪聲,而算子是用來找邊界,來識別圖像的邊緣。 邊緣是 像素值發(fā)生躍遷 的值,是圖像的顯著特點之一,在圖像特征提取,對象檢測,模式識別等方面都有重要的作用。 人眼如何識別圖像的邊緣呢? 比如有一幅畫,圖里面有一條線

    2024年01月20日
    瀏覽(15)
  • 改進YOLOv8 | 特征融合篇 | YOLOv8 應(yīng)用輕量級通用上采樣算子CARAFE | 《特征的內(nèi)容感知重組》

    改進YOLOv8 | 特征融合篇 | YOLOv8 應(yīng)用輕量級通用上采樣算子CARAFE | 《特征的內(nèi)容感知重組》

    特征上采樣是現(xiàn)代卷積神經(jīng)網(wǎng)絡(luò)架構(gòu)中的關(guān)鍵操作,例如特征金字塔。其設(shè)計對于密集預(yù)測任務(wù),如目標(biāo)檢測和語義/實例分割至關(guān)重要。在本研究中,我們提出了一種稱為內(nèi)容感知特征重組(CARAFE)的通用、輕量級且高效的操作符,以實現(xiàn)這一目標(biāo)。CARAFE具有以下幾個優(yōu)點:

    2024年02月07日
    瀏覽(95)
  • OpenCV(7):邊緣檢測之Sobel算子,Scharr算子,Laplacian算子和Canny算子邊緣檢測

    OpenCV(7):邊緣檢測之Sobel算子,Scharr算子,Laplacian算子和Canny算子邊緣檢測

    Sobel算子、Scharr算子、Laplacian算子和Canny算子都是常用的圖像邊緣檢測算法。它們可以用來識別圖像中物體之間的邊界,從而對物體進行定位、跟蹤、分割、識別等處理。 Sobel算子和Scharr算子都是基于卷積運算實現(xiàn)的邊緣檢測算法。Sobel算子使用兩個3×3的矩陣對原始圖像進行卷

    2024年02月05日
    瀏覽(28)
  • Opencv圖像邊緣檢測——Roberts算子(手寫)、Sobel算子(手寫和調(diào)包)、Scharr算子、Laplacian算子

    Opencv圖像邊緣檢測——Roberts算子(手寫)、Sobel算子(手寫和調(diào)包)、Scharr算子、Laplacian算子

    Roberts算子即交叉微分算子,是基于交叉差分的梯度算子。此算法通過局部差分來計算檢測圖像的邊緣線條,對噪聲敏感。 Roberts 交叉微分算子分別為主對角線和副對角線方向的算子,有兩個2*2的濾波算子組成: 對于圖像而言,如果im表示圖像像素矩陣,則可以如下計算(i,

    2024年02月04日
    瀏覽(23)
  • openCV實戰(zhàn)-系列教程4:圖像梯度計算(Sobel算子/開運算/梯度計算方法/scharr算子/lapkacian算子)、源碼解讀
?????OpenCV實戰(zhàn)系列總目錄

    openCV實戰(zhàn)-系列教程4:圖像梯度計算(Sobel算子/開運算/梯度計算方法/scharr算子/lapkacian算子)、源碼解讀 ?????OpenCV實戰(zhàn)系列總目錄

    打印一個圖片單獨做出一個函數(shù): 先讀進來一個原型白色圖 打印結(jié)果: 如圖有兩個3*3的卷積核,其中A是原始圖片中的一個3*3的區(qū)域,這個A和3*3的卷積核所謂對應(yīng)位置相乘的結(jié)果就分別是左右梯度和上下梯度 假如A是這個矩陣: ?那么Gx的計算結(jié)果就為:-x1+x3-2x4+2x6-x7+x9 代碼實

    2024年02月10日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包