一、概述
案例:使用kmeans算法實現證件照背景替換
算法實現步驟:
1.加載原圖像
2.制作kmeans輸入參數所需要的數據(kmeans的輸入數據類型是CV_32F,所以不能直接使用原始圖像的數據,因為原始圖像的數據類型為CV_8UC1)
3.使用kmeans算法實現圖像分類,並得到分類標簽
4.創建遮罩:通過分類標簽,將背景部分的顏色標記位0,將前景(人物)像素值標記位255
5.先對mask執行形態學操作去除干擾的白點,在使用高斯模糊平滑前景和背景之前的過度
6.創建一個3通道的目標輸出結果Mat,然后將目標背景填充到背景區域,將前景部分填充到前景區域。
7.輸出圖像
ps:算法的核心步驟其實就是找到mask,當mask找到之后就可以使用分類標簽將背景和前景替換成為自己想要的像素。
二、代碼示例
Id_Photo_Background_Replacement::Id_Photo_Background_Replacement(QWidget *parent) : MyGraphicsView{parent} { this->setWindowTitle("證件照背景替換"); } void Id_Photo_Background_Replacement::dropEvent(QDropEvent *event){ QString filePath = event->mimeData()->urls().at(0).toLocalFile(); showIdPhotoBackgroundReplacement(filePath.toStdString().c_str()); } void Id_Photo_Background_Replacement::showIdPhotoBackgroundReplacement(const char* filePath){ Mat src = imread(filePath); if(src.empty()){ qDebug()<<"圖像為空"; return; } imshow("src",src); //制作kmeans需要的數據 int width = src.cols; int height = src.rows; int dims = src.channels(); int sampleCount = width*height;//總共的像素點 Mat points(sampleCount,dims,CV_32F,Scalar(10)); int index = 0; for(int row = 0;row<height;row++){ for(int col = 0;col<width;col++){ index = row*width +col; Vec3b bgr = src.at<Vec3b>(row,col); points.at<float>(index,0) = static_cast<int>(bgr[0]); points.at<float>(index,1) = static_cast<int>(bgr[1]); points.at<float>(index,2) = static_cast<int>(bgr[2]); } } int numCluster = 4;//多少個分類 Mat labels;//分類標簽 Mat centers;//中心點 TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 0.1); kmeans(points,numCluster,labels,criteria,3,KMEANS_PP_CENTERS,centers); //創建遮罩 Mat mask = Mat::zeros(src.size(),CV_8UC1); index = src.rows*2+2;//找到背景像素的像素點位置 int cIndex = labels.at<int>(index,0);//找到像素點位置在labels中所對應的標簽,找到這個標簽以后就可以根據這個標簽來判斷前景和背景 for(int row=0;row<height;row++){ for(int col=0;col<width;col++){ index = row*width+col; int label = labels.at<int>(index,0); if(label==cIndex){//背景 mask.at<uchar>(row,col) = 0; }else{//前景 mask.at<uchar>(row,col) = 255; } } } imshow("mask",mask); //使用形態學腐蝕操作取出遮罩中的可能干擾正常結果的白點 Mat kernel = getStructuringElement(MORPH_RECT,Size(3,3),Point(-1,-1)); erode(mask,mask,kernel); //使用高斯模糊平滑邊緣像素 GaussianBlur(mask,mask,Size(3,3),0,0); //執行圖像像素融合,執行最終的背景替換 //定義背景顏色 Vec3b bgColor; bgColor[0] = 217;//rng.uniform(0, 255); bgColor[1] = 60;// rng.uniform(0, 255); bgColor[2] = 160; Mat result = Mat::zeros(src.size(),CV_8UC3);//定義一個空的彩色圖片 //下面是背景融合的代碼 double w = 0.0; int b = 0, g = 0, r = 0; int b1 = 0, g1 = 0, r1 = 0; int b2 = 0, g2 = 0, r2 = 0; for(int row = 0;row<height;row++){ for(int col=0;col<width;col++){ int pix = mask.at<uchar>(row,col);//獲取像素值 if(pix==255){//前景 result.at<Vec3b>(row,col) = src.at<Vec3b>(row,col); }else if(pix==0){//背景 result.at<Vec3b>(row,col) = bgColor; }else{//需要像素融合的部分 w = pix/255.0;//權重 b1 = src.at<Vec3b>(row,col)[0]; g1 = src.at<Vec3b>(row,col)[1]; r1 = src.at<Vec3b>(row,col)[2]; b2 = bgColor[0]; g2 = bgColor[1]; r2 = bgColor[2]; b = b1*w+b2*(1.0-w); g = g1*w+g2*(1.0-w); r = r1*w+r2*(1.0-w); result.at<Vec3b>(row,col)[0] = b; result.at<Vec3b>(row,col)[1] = g; result.at<Vec3b>(row,col)[2] = r; } } } imshow("result",result); }
三、圖片示例演示



