1. 程式人生 > >OpenCV背景虛化(前篇)

OpenCV背景虛化(前篇)

最近剛接到這個東西,心想不是太難,前後做了幾種方案。
這裡寫圖片描述

首先想到的方案一:
摳圖得到影象的前景區域,對背景使用高斯模糊,然後將原圖前景區域疊加到第二步得到的圖層對應區域。

第一步摳圖我使用的是圖割演算法,因為手上有現成的程式,當然還有其他許多摳圖演算法,點選這裡來自於這篇部落格開始跳轉
然後模糊的話最好使用一些保邊模糊的濾波器,我以前就接觸過雙邊濾波以及引導濾波器,上面那篇文章還提到了Domain Transform filter

原圖如下:

其實後面背景已經很模糊了,這是液晶透鏡拍攝出來的影象,但是合作方覺得應該模糊量更大,其實以前是可以直接就拍出來的,後面有一些原因引數什麼的我也不怎麼會弄。

完整工程重點內容

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>

using namespace std;
using namespace cv;

static void help()
{
    cout << "\nThis program demonstrates GrabCut segmentation -- select an object in a region\n"
        "and then grabcut will attempt to segment it out.\n"
"Call:\n" "./grabcut <image_name>\n" "\nSelect a rectangular area around the object you want to segment\n" << "\nHot keys: \n" "\tESC - quit the program\n" "\tr - restore the original image\n" "\tn - next iteration\n" "\n" "\tleft mouse button - set rectangle\n"
"\n" "\tCTRL+left mouse button - set GC_BGD pixels\n" "\tSHIFT+left mouse button - set CG_FGD pixels\n" "\n" "\tCTRL+right mouse button - set GC_PR_BGD pixels\n" "\tSHIFT+right mouse button - set CG_PR_FGD pixels\n" << endl; } const Scalar RED = Scalar(0,0,255); const Scalar PINK = Scalar(230,130,255); const Scalar BLUE = Scalar(255,0,0); const Scalar LIGHTBLUE = Scalar(255,255,160); const Scalar GREEN = Scalar(0,255,0); const int BGD_KEY = CV_EVENT_FLAG_CTRLKEY; //Ctrl鍵 const int FGD_KEY = CV_EVENT_FLAG_SHIFTKEY; //Shift鍵 static void getBinMask( const Mat& comMask, Mat& binMask ) { if( comMask.empty() || comMask.type()!=CV_8UC1 ) CV_Error( CV_StsBadArg, "comMask is empty or has incorrect type (not CV_8UC1)" ); if( binMask.empty() || binMask.rows!=comMask.rows || binMask.cols!=comMask.cols ) binMask.create( comMask.size(), CV_8UC1 ); binMask = comMask & 1; //得到mask的最低位,實際上是隻保留確定的或者有可能的前景點當做mask } class GCApplication { public: enum{ NOT_SET = 0, IN_PROCESS = 1, SET = 2 }; static const int radius = 2; static const int thickness = -1; void reset(); void setImageAndWinName( const Mat& _image, const string& _winName ); void showImage() const; void mouseClick( int event, int x, int y, int flags, void* param ); int nextIter(); int getIterCount() const { return iterCount; } private: void setRectInMask(); void setLblsInMask( int flags, Point p, bool isPr ); const string* winName; const Mat* image; Mat mask; Mat bgdModel, fgdModel; uchar rectState, lblsState, prLblsState; bool isInitialized; Rect rect; vector<Point> fgdPxls, bgdPxls, prFgdPxls, prBgdPxls; int iterCount; }; /*給類的變數賦值*/ void GCApplication::reset() { if( !mask.empty() ) mask.setTo(Scalar::all(GC_BGD)); bgdPxls.clear(); fgdPxls.clear(); prBgdPxls.clear(); prFgdPxls.clear(); isInitialized = false; rectState = NOT_SET; //NOT_SET == 0 lblsState = NOT_SET; prLblsState = NOT_SET; iterCount = 0; } /*給類的成員變數賦值而已*/ void GCApplication::setImageAndWinName( const Mat& _image, const string& _winName ) { if( _image.empty() || _winName.empty() ) return; image = &_image; winName = &_winName; mask.create( image->size(), CV_8UC1); reset(); } /*顯示4個點,一個矩形和影象內容,因為後面的步驟很多地方都要用到這個函式,所以單獨拿出來*/ void GCApplication::showImage() const { if( image->empty() || winName->empty() ) return; Mat res; Mat binMask; if( !isInitialized ) image->copyTo( res ); else { getBinMask( mask, binMask ); image->copyTo( res, binMask ); //按照最低位是0還是1來複制,只保留跟前景有關的影象,比如說可能的前景,可能的背景 } vector<Point>::const_iterator it; /*下面4句程式碼是將選中的4個點用不同的顏色顯示出來*/ for( it = bgdPxls.begin(); it != bgdPxls.end(); ++it ) //迭代器可以看成是一個指標 circle( res, *it, radius, BLUE, thickness ); for( it = fgdPxls.begin(); it != fgdPxls.end(); ++it ) //確定的前景用紅色表示 circle( res, *it, radius, RED, thickness ); for( it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it ) circle( res, *it, radius, LIGHTBLUE, thickness ); for( it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it ) circle( res, *it, radius, PINK, thickness ); /*畫矩形*/ if( rectState == IN_PROCESS || rectState == SET ) rectangle( res, Point( rect.x, rect.y ), Point(rect.x + rect.width, rect.y + rect.height ), (0,0,0), 2); //這裡的矩形框顏色本來是綠色的,我改為(0,0,0)黑色 imshow( *winName, res ); imwrite("C:\\Users\\ltc\\Desktop\\testdata_3.jpg",res); } /*該步驟完成後,mask影象中rect內部是3,外面全是0*/ void GCApplication::setRectInMask() { assert( !mask.empty() ); mask.setTo( GC_BGD ); //GC_BGD == 0 rect.x = max(0, rect.x); rect.y = max(0, rect.y); rect.width = min(rect.width, image->cols-rect.x); rect.height = min(rect.height, image->rows-rect.y); (mask(rect)).setTo( Scalar(GC_PR_FGD) ); //GC_PR_FGD == 3,矩形內部,為可能的前景點 } void GCApplication::setLblsInMask( int flags, Point p, bool isPr ) { vector<Point> *bpxls, *fpxls; uchar bvalue, fvalue; if( !isPr ) //確定的點 { bpxls = &bgdPxls; fpxls = &fgdPxls; bvalue = GC_BGD; //0 fvalue = GC_FGD; //1 } else //概率點 { bpxls = &prBgdPxls; fpxls = &prFgdPxls; bvalue = GC_PR_BGD; //2 fvalue = GC_PR_FGD; //3 } if( flags & BGD_KEY ) { bpxls->push_back(p); circle( mask, p, radius, bvalue, thickness ); //該點處為2 } if( flags & FGD_KEY ) { fpxls->push_back(p); circle( mask, p, radius, fvalue, thickness ); //該點處為3 } } /*滑鼠響應函式,引數flags為CV_EVENT_FLAG的組合*/ void GCApplication::mouseClick( int event, int x, int y, int flags, void* ) { // TODO add bad args check switch( event ) { case CV_EVENT_LBUTTONDOWN: // set rect or GC_BGD(GC_FGD) labels { bool isb = (flags & BGD_KEY) != 0, isf = (flags & FGD_KEY) != 0; if( rectState == NOT_SET && !isb && !isf )//只有左鍵按下時 { rectState = IN_PROCESS; //表示正在畫矩形 rect = Rect( x, y, 1, 1 ); } if ( (isb || isf) && rectState == SET ) //按下了alt鍵或者shift鍵,且畫好了矩形,表示正在畫前景背景點 lblsState = IN_PROCESS; } break; case CV_EVENT_RBUTTONDOWN: // set GC_PR_BGD(GC_PR_FGD) labels { bool isb = (flags & BGD_KEY) != 0, isf = (flags & FGD_KEY) != 0; if ( (isb || isf) && rectState == SET ) //正在畫可能的前景背景點 prLblsState = IN_PROCESS; } break; case CV_EVENT_LBUTTONUP: if( rectState == IN_PROCESS ) { rect = Rect( Point(rect.x, rect.y), Point(x,y) ); //矩形結束 rectState = SET; setRectInMask(); assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() ); showImage(); } if( lblsState == IN_PROCESS ) //已畫了前後景點 { setLblsInMask(flags, Point(x,y), false); //畫出前景點 lblsState = SET; showImage(); } break; case CV_EVENT_RBUTTONUP: if( prLblsState == IN_PROCESS ) { setLblsInMask(flags, Point(x,y), true); //畫出背景點 prLblsState = SET; showImage(); } break; case CV_EVENT_MOUSEMOVE: if( rectState == IN_PROCESS ) { rect = Rect( Point(rect.x, rect.y), Point(x,y) ); assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() ); showImage(); //不斷的顯示圖片 } else if( lblsState == IN_PROCESS ) { setLblsInMask(flags, Point(x,y), false); showImage(); } else if( prLblsState == IN_PROCESS ) { setLblsInMask(flags, Point(x,y), true); showImage(); } break; } } /*該函式進行grabcut演算法,並且返回演算法執行迭代的次數*/ int GCApplication::nextIter() { if( isInitialized ) //使用grab演算法進行一次迭代,引數2為mask,裡面存的mask位是:矩形內部除掉那些可能是背景或者已經確定是背景後的所有的點,且mask同時也為輸出 //儲存的是分割後的前景影象 grabCut( *image, mask, rect, bgdModel, fgdModel, 1 ); else { if( rectState != SET ) return iterCount; if( lblsState == SET || prLblsState == SET ) grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK ); else grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT ); isInitialized = true; } iterCount++; bgdPxls.clear(); fgdPxls.clear(); prBgdPxls.clear(); prFgdPxls.clear(); return iterCount; } GCApplication gcapp; static void on_mouse( int event, int x, int y, int flags, void* param ) { gcapp.mouseClick( event, x, y, flags, param ); } int main( int argc, char** argv ) { string filename = "C:\\Users\\ltc\\Desktop\\data3\\taowa.jpg"; Mat image = imread( filename, 1 ); if( image.empty() ) { cout << "\n Durn, couldn't read image filename " << filename << endl; return 1; } help(); const string winName = "image"; cvNamedWindow( winName.c_str(), CV_WINDOW_NORMAL ); cvSetMouseCallback( winName.c_str(), on_mouse, 0 ); gcapp.setImageAndWinName( image, winName ); gcapp.showImage(); for(;;) { int c = cvWaitKey(0); switch( (char) c ) { case '\x1b': cout << "Exiting ..." << endl; goto exit_main; case 'r': cout << endl; gcapp.reset(); gcapp.showImage(); break; case 'n': int iterCount = gcapp.getIterCount(); cout << "<" << iterCount << "... "; int newIterCount = gcapp.nextIter(); if( newIterCount > iterCount ) { gcapp.showImage(); cout << iterCount << ">" << endl; } else cout << "rect must be determined>" << endl; break; } } exit_main: cvDestroyWindow( winName.c_str() ); return 0; }

該有的註釋都有,然後如何操作也在help資訊裡可以看到,基本上就是框個框畫幾筆然後迭代幾次就可以了。
現在肯定有很多更好的影象分割方法,任務緊,就沒時間去折騰了。
效果如下:
這裡寫圖片描述

然後相減得到背景,對背景進行高斯模糊:
高斯模糊滑動條控制如下:可以選擇一個你覺得合適的模糊

#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
#include<cstdlib>
#include<math.h>

using namespace cv;
using namespace std;

int x=0;
int y=0;
int size=3;
Mat scrImage;
Mat tempImage;


void onTrack(int, void*)
{
    scrImage = tempImage.clone();
    GaussianBlur(scrImage, scrImage, Size(2*size+1, 2*size+1), x, y);
    imshow("【原圖】", scrImage);

}

int main()
{
    scrImage = imread("C:\\Users\\ltc\\Desktop\\data3\\taowa.jpg");
    tempImage = scrImage.clone();
    namedWindow("【原圖】",WINDOW_NORMAL);
    imshow("【原圖】", scrImage);
    createTrackbar("gaussion x", "【原圖】", &x, 100, onTrack);
    createTrackbar("gaussion y", "【原圖】", &y, 100, onTrack);
    createTrackbar("gaussion size", "【原圖】", &size, 100, onTrack);
    waitKey();
    return 0;
}

最後結合的話我是直接相加,效果如下:
這裡寫圖片描述
可以看到馬周身有很大一圈白色邊緣。
後面我對原圖直接模糊,然後把摳出來的馬copyTo貼上去。
最後效果如下:
這裡寫圖片描述
可以看到馬的周身有很多黑色畫素冒出來。

後面我用

#include<iostream>
#include<opencv2/opencv.hpp>

using namespace std;
using namespace cv;

bool g_bDrawing = false;
Point g_CurrPoint, g_OrgPoint;
int g_nThick = 5, g_nBlue = 255, g_nGreen = 255, g_nRed = 0;
int g_nImageOneValue = 49;
Mat srcImage;
Mat grayImage;
Mat maskImage;

/*注意:不能在毀掉函式中寫入未初始化的矩陣類,所以需要用時,需要寫一個標誌位,然後再在while(1)迴圈內使用*/
void onMouse(int event, int x, int y, int flag, void *param)
{
    Mat &img = *(cv::Mat*)param;

    switch (event)
    {
        //移動滑鼠的時候
    case CV_EVENT_MOUSEMOVE:
        {
            g_OrgPoint = g_CurrPoint;
            g_CurrPoint = Point(x, y);

            if (g_bDrawing == 1)
            {
                line(srcImage, g_CurrPoint, g_OrgPoint, Scalar(g_nBlue, g_nGreen, g_nRed), g_nThick);
                imshow("【滑鼠事件視窗】", srcImage);

                //在掩膜圖上進行顯示
                line(maskImage, g_CurrPoint, g_OrgPoint, Scalar(g_nBlue, g_nGreen, g_nRed), g_nThick);
                imshow("【掩膜影象】", maskImage);
            }
        }
        break;
        //點選滑鼠左鍵時
    case CV_EVENT_LBUTTONDOWN:
        {
            g_bDrawing = true;
            g_OrgPoint = Point(x, y);
            g_CurrPoint = g_OrgPoint;
        }
        break;
        //鬆開滑鼠左鍵時
    case CV_EVENT_LBUTTONUP:
        {
            g_bDrawing = false;
        }
        break;
    }
}

int main()
{
    Mat tempImage;
    RNG &rng = theRNG();

    srcImage = imread("C:\\Users\\ltc\\Desktop\\data3\\dstbig.jpg");
    resize(srcImage,srcImage,Size(640,480));
    //用一個變數來儲存原影象
    Mat g_srcImage;
    srcImage.copyTo(g_srcImage);

    //為掩膜圖 分配空間
    maskImage.create(srcImage.size(), CV_8UC1);
    maskImage = Scalar::all(0);

    namedWindow("【滑鼠事件視窗】",WINDOW_NORMAL);
    setMouseCallback("【滑鼠事件視窗】", onMouse, 0);

    namedWindow("【滾動條視窗】", 0);
    createTrackbar("thick", "【滾動條視窗】", &g_nThick, 100, 0);
    createTrackbar("Blue", "【滾動條視窗】", &g_nBlue, 255, 0);
    createTrackbar("Green", "【滾動條視窗】", &g_nGreen, 255, 0);
    createTrackbar("Red", "【滾動條視窗】", &g_nRed, 255, 0);

    char key;
    while (1)
    {
        imshow("【滑鼠事件視窗】", srcImage);
        key = waitKey();
        if (key == 27)
            break;

        //如果檢測到 鍵值是1 則恢復原圖
        if (key == '1')
        {
            g_srcImage.copyTo(srcImage);
            maskImage = Scalar::all(0);
            imshow("【滑鼠事件視窗】", srcImage);
        }

        //如果檢測到空格 則開始執行影象修復
        Mat dstImage;
        dstImage.create(srcImage.size(), srcImage.type());
        if (key == ' ')
        {
            inpaint(srcImage, maskImage, dstImage, 3, INPAINT_TELEA);
            imshow("【修補後的影象】", dstImage);
        }
    }

    return 0;
}

下面這個程式碼對馬的周身(針對黑色畫素情況)進行一個修補,這是無奈的辦法,我也不想手動。

最終效果如下:
這裡寫圖片描述

其實我也用Imageshop這個軟體手動磨皮,發現效果和這個差不多,先就這樣,交給老師,老師
覺得還不夠,老師希望恐龍和背景的模糊層次能夠分開。基於我們的深度圖來做。

放在後面寫了。。。。