1. 程式人生 > >C++ opencv 識別火焰 (程式碼)

C++ opencv 識別火焰 (程式碼)

/*****************************************************************************                                                                      *                                                     *
 *  @brief    火焰識別                                                                                                                            
 *  @email    
[email protected]
* @version 1.0.0.1(版本號) * @date 2018.10.6 * 概述:此程式碼用於識別圖片中火焰,整體思路為通過對影象的顏色處理找出圖中感興趣區域(可能為火焰的區域)及其 * 輪廓, 再提取輪廓的幾何特徵判斷是否為火焰 * 優點:在絕大多數場景下都能較為準確恰當的提取感性興趣區域的輪廓. * 缺點:處理時間較慢,平均時間0.1~0.3s,個別圖片1s. 對於輪廓幾何特徵判斷做的不夠完善有待提升. * &&&提取感興趣區域及其輪廓: * (1)由資料得知火焰畫素滿足以下關係:(R > redThre &&R >= G && G>= B && S >((255 - R) * saturationTh / redThre),詳細見函式findfire,由此函式實現尋找感興趣畫素點. * (2)將圖片轉化為灰度圖,直接轉化為灰度圖將損失大量資訊.所以通過以下方式在保留有用資訊同時將圖片轉化為灰度圖: * 藉助第一部獲得的感興趣畫素點,並求出感興趣畫素點三個通道的均值 rgb[0],rgb[1],rgb[2] * 原圖象任一點畫素(B,G,R)的灰度值k表示式為 k=int(sqrt((R-rgb[0])*(R-rgb[0])+(G-rgb[1])*(G-rgb[1])+(B-rgb[2])*(B-rgb[2]))) * 詳細見函式firethreold,由此生成的灰度圖既能較好地保留火焰資訊,以進行進一步操作 * (3) 對firethreold生成的灰度圖依據灰度值進行顏色聚類,使用kmeans演算法,聚類數量取3(處理過的灰度圖聚類速度較快,且絕大多數情況下有較好的聚類效果).詳細見函式kmean_color. * (4) 對聚類後的各個區域用隨機顏色填充,方便後續處理,詳細見函式drawcc. * (5)得到的感興趣畫素點和聚類區域分佈,得到最終的感興趣區域,詳細見函式sort_po. * &&&提取感興趣區域輪廓的幾何特徵並判斷是否為火焰: * 通過感興趣區域輪廓的不規則度,形態比例,邊緣層次,直線角度判斷是否為火焰,詳細見函式judgefire.目前此部分有待完善,可後續加入尖角判定. * @PS:主函式註釋部分 為用meanshift進行顏色區域聚類.此演算法聚類效果較好,不過基於三通道影象聚類,耗時過長,詳細見函式 ShiftFiltering. * @參考資料: * &https://docs.opencv.org/3.4/index.html * &https://blog.csdn.net/coldplayplay/article/details/70212483 * &https://blog.csdn.net/qq_23968185/article/details/51804574 * *****************************************************************************/ #include <iostream> #include <opencv2/imgcodecs.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp> #include <iostream> #include <math.h> #include <time.h> const int po_cout=4; using namespace std; using namespace cv; Mat find_fire(Mat &inImg ,int rgb[4],int redThre=49,int saturationTh=7);//尋找感興趣畫素點 Mat ShiftFiltering(Mat img1);//實現opencv meanshift聚類演算法. Mat firethreold(Mat inImg,int* rgb);//歐式距離 灰度圖 Rect min_Rect(Mat data);//返回一群點的最小外接矩形 void sort_po(Mat src,Mat res, vector <Mat> &out );//根據感興趣畫素點和聚類後的區域分佈提取感興趣區域 void kmean_color(Mat pic ,Mat &src,int color_Counts);//kmean聚類演算法 void drawcc(Mat &res);//對聚類後的圖片填充 int judgefire(Mat fire);//判斷輪廓是否為火焰 int bSums(Mat src);//求白色畫素的數量 int main() { clock_t start,finish; double totaltime; start=clock(); Mat data,binary;//位置分類之後的矩陣陣列 Mat original_src=imread("../f0.jpeg"); imshow("原圖",original_src); Mat src=original_src.clone(); int rgb[4]={0,0,0,0}; Mat fireimg=find_fire(src,rgb); imshow("感興趣點",fireimg); //縮小查詢範圍 Rect rect1; rect1=min_Rect(fireimg); rect1.x=rect1.x-0.1*rect1.width>0 ? rect1.x-0.1*rect1.width:rect1.x ; rect1.y=rect1.y-0.1*rect1.height>0 ? rect1.y-0.1*rect1.height:rect1.y ; rect1.width=rect1.x+rect1.width+0.2*rect1.width<src.size[1] ? rect1.width+0.2*rect1.width:rect1.width; rect1.height=rect1.y+rect1.height+0.2*rect1.height<src.size[0] ? rect1.height+0.2*rect1.height:rect1.height; binary=fireimg(rect1)/255; fireimg=firethreold(original_src,rgb); imshow("火焰色素歐式距離灰度圖",fireimg); fireimg=fireimg(rect1); kmean_color(fireimg,fireimg,3); imshow("kmean聚類結果", fireimg); // dilate(fireimg, fireimg, Mat(5, 5, CV_8UC1)); // erode(fireimg,fireimg,Mat(5, 5, CV_8UC1)); cvtColor(fireimg,fireimg,CV_GRAY2BGR); drawcc(fireimg); imshow("彩色填充聚類結果",fireimg); //聚類範圍與感興趣點分佈矩陣相乘 Mat colorfire=fireimg.clone(); for(int i=0;i<fireimg.rows;i++){ for(int j=0;j<fireimg.cols;j++){ if (binary.at<uchar>(i, j) == 0){ fireimg.at<Vec3b>(i, j)=Vec3b(0, 0, 0); } } } imshow("感興趣點與聚類區域相乘法",fireimg); vector <Mat> out; sort_po(fireimg,colorfire,out); RNG rng=theRNG(); //感興趣區域輪廓判斷 Mat interesting(original_src.rows, original_src.cols, CV_8UC3, Scalar(0, 0, 0)); for(int i=0;i<out.size();i++) { Scalar newVal( rng(256), rng(256), rng(256) ); Mat original_src1=original_src.clone(); // resize(out[i], out[i], Size(rect1.width, rect1.height)); Mat tmp_bin(original_src1.rows, original_src1.cols, CV_8UC3, Scalar(0, 0, 0)); Mat maskrect = tmp_bin(rect1); out[i].copyTo(maskrect); int dilation_size = 1; Mat element = getStructuringElement(MORPH_ELLIPSE, Size(2 * dilation_size + 1, 2 * dilation_size + 1), Point(dilation_size, dilation_size)); dilate(tmp_bin, tmp_bin, element); original_src1.setTo(0, ~tmp_bin); cvtColor(tmp_bin,tmp_bin,CV_BGR2GRAY); vector<vector<Point> > contours; vector<Vec4i> hierarchy; findContours(tmp_bin, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE ); if(judgefire(original_src1)==1){ for( size_t i1 = 0; i1< contours.size(); i1++ ) { drawContours(original_src, contours, (int) i1, Scalar(0, 0, 255), 3, LINE_8, hierarchy, 0); drawContours(interesting, contours, (int) i1, newVal, 2, LINE_8, hierarchy, 0); cout<<"\n火焰!!!"<<endl; } } else{ for( size_t i1 = 0; i1< contours.size(); i1++ ) { drawContours(original_src, contours, (int) i1, Scalar(0, 255, 0), 1, LINE_8, hierarchy, 0); drawContours(interesting, contours, (int) i1, newVal, 2, LINE_8, hierarchy, 0); } } } imshow("感興趣區域",interesting); imshow("result(紅色輪廓為火焰)",original_src); waitKey(); finish=clock(); totaltime=(double)(finish-start)/CLOCKS_PER_SEC; cout<<"\n此程式的執行時間為"<<totaltime<<"秒!"<<endl; return 0; //meanshift 顏色聚類 //// Rect rect1; // rect1=min_Rect(fireimg); // rect1.x=rect1.x-0.1*rect1.width>0 ? rect1.x-0.1*rect1.width:rect1.x ; // rect1.y=rect1.y-0.1*rect1.height>0 ? rect1.y-0.1*rect1.height:rect1.y ; // rect1.width=rect1.x+rect1.width+0.2*rect1.width<src.size[1] ? rect1.width+0.2*rect1.width:rect1.width; // rect1.height=rect1.y+rect1.height+0.2*rect1.height<src.size[0] ? rect1.height+0.2*rect1.height:rect1.height; // Mat tmp=src(rect1); //// medianBlur ( tmp, tmp, 5 ); // waitKey(); // binary=fireimg(rect1)/255; // if(tmp.cols>200&&tmp.rows>200) { // resize(binary, binary, Size(200, binary.rows * 200 / binary.cols)); // resize(tmp, tmp, Size(200, tmp.rows * 200 / tmp.cols)); // } // Mat res,res1; // res1=ShiftFiltering(tmp); // res=res1.clone(); // imshow("8",res); // waitKey(0); // for(int i=0;i<res.rows;i++){ // for(int j=0;j<res.cols;j++){ // if (binary.at<uchar>(i, j) == 0){ // res.at<Vec3b>(i, j)=Vec3b(0, 0, 0); // } // } // } //// clock_t start,finish; //// double totaltime; //// start=clock(); //// vector <Mat> out; // sort_po(res,res1,out); // for(int i=0;i<out.size();i++) { // resize(out[0], out[0], Size(rect1.width, rect1.height)); // Mat tmp_bin(original_src.rows, original_src.cols, CV_8UC3, Scalar(0, 0, 0)); // Mat maskrect = tmp_bin(rect1); // out[0].copyTo(maskrect); // int dilation_size = 1; // Mat element = getStructuringElement(MORPH_ELLIPSE, // Size(2 * dilation_size + 1, 2 * dilation_size + 1), // Point(dilation_size, dilation_size)); // dilate(tmp_bin, tmp_bin, element); // original_src.setTo(0, ~tmp_bin); //// imshow("233", original_src); //// waitKey(); // // if(judgefire(original_src)==1){ // // // }; // // } ////// Mat tmp=src(rect1); //// rect1=min_Rect(pom[i]); //// for(int i=0;i<po_cout;i++){ //// Rect rect1; //// rect1=min_Rect(pom[i]); //// Mat tmp=src(rect1); //// Point point1=Point(rect1.x,rect1.y); //// Point point2=Point(rect1.x+rect1.width,rect1.y+rect1.height); //// //// rectangle(src,point1,point2, Scalar(0, 255, 255), 2, 8, 0 ); //// //// } ////; // finish=clock(); // totaltime=(double)(finish-start)/CLOCKS_PER_SEC; // cout<<"\n此程式的執行時間為"<<totaltime<<"秒!"<<endl; // return 0; } Mat find_fire(Mat &inImg ,int* rgb,int redThre,int saturationTh){ Mat fireImg; fireImg.create(inImg.size(), CV_8UC1); Mat multiRGB[3]; int a = inImg.channels(); split(inImg, multiRGB); //將圖片拆分成R,G,B,三通道的顏色 for (int i = 0; i < inImg.rows; i++) { for (int j = 0; j < inImg.cols; j++) { float B, G, R; B = multiRGB[0].at<uchar>(i, j); //每個畫素的R,G,B值,動態地址計演算法 G = multiRGB[1].at<uchar>(i, j); R = multiRGB[2].at<uchar>(i, j); float maxValue = max(max(B, G), R); float minValue = min(min(B, G), R); //與HSI中S分量的計算公式 double S = (1 - 3.0*minValue / (R + G + B));// //R > RT R>=G>=B S>=((255-R)*ST/RT) if (R > redThre &&R >= G && G>= B && S >((255 - R) * saturationTh / redThre)) { fireImg.at<uchar>(i, j) = 255; rgb[0]+=R; rgb[1]+=G; rgb[2]+=B; rgb[3]+=1; } else { fireImg.at<uchar>(i, j) = 0; } } } medianBlur(fireImg, fireImg, 5); dilate(fireImg, fireImg, Mat(5, 5, CV_8UC1)); for (int i=0;i<fireImg.rows;i++){ for(int j=0;j<fireImg.cols;j++) { if(fireImg.at<uchar>(i, j) ==255){ rgb[0]+=multiRGB[2].at<uchar>(i, j);; rgb[1]+=multiRGB[1].at<uchar>(i, j); rgb[2]+=multiRGB[0].at<uchar>(i, j); rgb[3]+=1; } } } rgb[0]=rgb[0]/rgb[3]; rgb[1]=rgb[1]/rgb[3]; rgb[2]=rgb[2]/rgb[3]; cout<<rgb[0]<<"w"<<rgb[1]<<"w"<<rgb[2]<<"w"; return fireImg; //erode(fireImg, fireImg, Mat(3, 3, CV_8UC1)); //GaussianBlur(fireImg, fireImg, Size(5, 5), 0, 0); // kmean_Point(fireImg,inImg); // DrawFire(inImg, fireImg); // return fireImg; } Rect min_Rect(Mat M){ Mat data; if (M.channels()!=1){ cvtColor(M,M,CV_BGR2GRAY);} for(int i=0;i<M.rows;i++){ for(int j=0;j<M.cols;j++){ if ((int)M.at<uchar>(i, j)!=0){ Mat tmp = (Mat_<float>(1, 2)<< i, j); data.push_back(tmp); } } } double minvx,maxvx; double* minpx = &minvx; double* maxpx = &maxvx; double minvy,maxvy; double* minpy = &minvy; double* maxpy = &maxvy; minMaxIdx(data.col(1),minpx,maxpx); minMaxIdx(data.col(0),minpy,maxpy); Rect g_rectangle; g_rectangle=Rect(minvx,minvy,maxvx-minvx,maxvy-minvy); return g_rectangle; } Mat ShiftFiltering(Mat img1){ clock_t start,finish; double totaltime; start=clock(); Mat img(img1); // resize(img1,img,Size(150,150)); Mat res; //分割後圖像 int spatialRad,colorRad; if(img1.rows*img1.cols>40000) { cout<<112; spatialRad = 50; //空間視窗大小 colorRad = 50; //色彩視窗大小} }else{ spatialRad = 40; //空間視窗大小 colorRad = 30; } int maxPyrLevel = 2; //金字塔層數 pyrMeanShiftFiltering( img, res, spatialRad, colorRad, maxPyrLevel); //色彩聚類平滑濾波 RNG rng = theRNG(); Mat mask( res.rows+2, res.cols+2, CV_8UC1, Scalar::all(0) ); //掩模 int n=0; for( int y = 0; y < res.rows; y++ ) { for( int x = 0; x < res.cols; x++ ) { if( mask.at<uchar>(y+1, x+1) == 0 ) //非0處即為1,表示已經經過填充,不再處理 { Scalar newVal( rng(256), rng(256), rng(256) ); floodFill( res, mask, Point(x,y), newVal, 0, Scalar::all(5), Scalar::all(5) ); //執行漫水填充 } } } finish=clock(); totaltime=(double)(finish-start)/CLOCKS_PER_SEC; cout<<"\n此程式的執行時間為"<<totaltime<<"秒!"<<endl; // imshow("meanShift影象分割", res ); //// imshow("res",res); // waitKey(); return res; } void sort_po(Mat src,Mat res, vector <Mat> &outlist ){ vector <Vec3f> Po_value; vector <Mat> Po; int count=0; for(int i=0;i<src.rows;i++){ for(int j=0;j<src.cols;j++){ Vec3f point =src.at<Vec3b>(i, j); Vec3f point1=(0,0,0); if (point!=point1) { count++; int ju1 = 0; for (int it = 0; it < Po_value.size(); it++) { if (point == Po_value[it]) { Mat tmp = (Mat_<float>(1, 2) << i, j); Po[it].push_back(tmp); // cout << 1; ju1=1; } } if (ju1 == 0) { Po_value.push_back(point); Mat tmp2 = (Mat_<float>(1, 2) << i, j); Po.push_back(tmp2); } } else{} } } for (int i1=0 ;i1<Po.size();i1++){ if (Po[i1].rows<count/10){ continue; } else{ vector<Point > contours; vector<Point>hull; for(int i=0;i<Po[i1].rows;i++){ Point tmp=Point(Po[i1].at<float>(i,1),Po[i1].at<float>(i,0)); contours.push_back(tmp); } convexHull( contours, hull ); vector<vector<Point> > h={hull}; // Mat tp(res.rows,res.cols,CV_8UC3); // drawContours( res, h, (int)0, Scalar(255,255,255) ,2); // imshow("3",res); // waitKey(); double s=0; double g_dConArea = contourArea(hull, true); Mat out=res.clone(); for (int i=0;i<res.rows;i++){ for(int j=0;j<res.cols;j++){ Vec3f value=out.at<Vec3b>(i,j); if(value==Po_value[i1]){ s++; out.at<Vec3b>(i,j)=Vec3b(255,255,255); } else{out.at<Vec3b>(i,j)=Vec3f(0,0,0);} } } if (g_dConArea>0.7*s&&s>0.5*g_dConArea){ outlist.push_back(out); } } } } int bSums(Mat src) { int counter = 0; //迭代器訪問畫素點 Mat_<uchar>::iterator it = src.begin<uchar>(); Mat_<uchar>::iterator itend = src.end<uchar>(); for (; it!=itend; ++it) { if((*it)>0) counter+=1;//二值化後,畫素點是0或者255 } return counter; } Mat firethreold(Mat inImg,int rgb[4]){ Mat fireImg; fireImg.create(inImg.size(), CV_8UC1); Mat multiRGB[3]; int a = inImg.channels(); split(inImg, multiRGB); //將圖片拆分成R,G,B,三通道的顏色 for (int i = 0; i < inImg.rows; i++) { for (int j = 0; j < inImg.cols; j++) { float B, G, R; B = multiRGB[0].at<uchar>(i, j); //每個畫素的R,G,B值,動態地址計演算法 G = multiRGB[1].at<uchar>(i, j); R = multiRGB[2].at<uchar>(i, j); int k=int(sqrt((R-rgb[0])*(R-rgb[0])+(G-rgb[1])*(G-rgb[1])+(B-rgb[2])*(B-rgb[2]))); if (k>255){ k=255; } k=255-k; fireImg.at<uchar>(i, j) = k; } } return fireImg; } void kmean_color(Mat pic ,Mat &src,int color_Counts){ const int MAX_CLUSTERS = 5; float colorTab[] = { // Vec3b(0, 0, 255), // Vec3b(0, 255, 0), // Vec3b(255, 100, 100), // Vec3b(255, 0, 255), // Vec3b(0, 255, 255) 0, 100, 255, 150 }; Mat data,labels; // Mat pic = imread("d:/woman.png"); for (int i = 0; i < pic.rows;i++) for (int j = 0; j < pic.cols; j++) { uchar point = pic.at<uchar >(i, j); Mat tmp = (Mat_<float >(1, 1) << point); data.push_back(tmp); } //根據瀏覽圖片,確定k=3 kmeans(data, 3, labels, TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 10, 1.0), 3, KMEANS_RANDOM_CENTERS); int n = 0; //顯示聚類結果,不同的類別用不同的顏色顯示 for (int i = 0; i < pic.rows; i++) for (int j = 0; j < pic.cols; j++) { int clusterIdx = labels.at<int>(n); pic.at<uchar >(i, j) =(uchar)colorTab[clusterIdx]; n++; } } void drawcc(Mat &res){ RNG rng = theRNG(); Mat mask( res.rows+2, res.cols+2, CV_8UC1, Scalar::all(0) ); //掩模 int n=0; for( int y = 0; y < res.rows; y++ ) { for( int x = 0; x < res.cols; x++ ) { if( mask.at<uchar>(y+1, x+1) == 0 ) //非0處即為1,表示已經經過填充,不再處理 { Scalar newVal( rng(256), rng(256), rng(256) ); floodFill( res, mask, Point(x,y), newVal, 0, Scalar::all(5), Scalar::all(5) ); //執行漫水填充 } } } } int judgefire(Mat fire){ Mat gray,bin ,candy; int judge; cvtColor(fire,gray,CV_BGR2GRAY); threshold(gray, bin,1, 255, THRESH_BINARY ); Canny( bin, candy, 40, 40*3, 3 ); vector<vector<Point> > contours; vector<Vec4i> hierarchy; findContours( bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE ); Mat drawing = Mat::zeros( bin.size(), CV_8UC3 ); double k; // int maxCorners = 5; // Mat c(bin.rows,bin.cols,CV_8UC1); // findcorner(c,contours,hierarchy); // RNG rng(12345); // vector<Point2f> corners; // double qualityLevel = 3; // double minDistance = 50; // int blockSize = 20, gradientSize = 3; // bool useHarrisDetector = false; // double k1 = 0.04; // Mat copy =bin.clone(); // goodFeaturesToTrack( bin, // corners, // maxCorners, // qualityLevel, // minDistance, // Mat(), // blockSize, // gradientSize, // useHarrisDetector, // k1 ); // cout << "** Number of corners detected: " << corners.size() << endl; // int radius = 4; // for( size_t i = 0; i < corners.size(); i++ ) // { // cout<<corners[i]<<"\n"; // circle( fire, corners[i], radius, Scalar(rng.uniform(0,255), rng.uniform(0, 256), rng.uniform(0, 256)), FILLED ); // } int coutc=0; for( size_t i = 0; i< contours.size(); i++ ) { drawContours(drawing, contours, (int) i, Scalar(255, 0, 0), 2, LINE_8, hierarchy, 0); if (abs(contourArea(contours[i], true)) > 300) { if (coutc==1){ judge=0 ; break; } coutc++; k = arcLength(contours[i], false) * arcLength(contours[i], false) / contourArea(contours[i], true);//k 為不規則度,k越大越不規則; vector<Vec4i> linesP; // will hold the results of the detection HoughLinesP(candy, linesP, 1, CV_PI / 180, 50, 50, 10); // runs the actual detection // Draw the lines for (size_t i = 0; i < linesP.size(); i++) { Vec4i l = linesP[i]; line(drawing, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 3, LINE_AA); } // imshow("asd", drawing); // waitKey(); if (abs(k) <= 40) { judge = 0; for (size_t i1 = 0; i1 < linesP.size(); i1++) { Vec4i l = linesP[i1]; // line( drawing, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, LINE_AA); if (abs((l[1] - l[3]) / (l[0] - l[2]+0.1) < 1) ){ judge = 0; break; } // double sita=atan(); } Rect rect = min_Rect(bin); cout<<rect.height <<"dwdqa"<< rect.width; if (rect.height > rect.width * 1.2) { judge = 1; } else { judge = 0; break; } Rect recth = Rect(rect.x, rect.y, (int) (rect.height / 2), rect.width); Rect rectl = Rect(rect.x, rect.y + (int) (rect.height / 2), (int) (rect.height / 2), rect.width); int sl = bSums(bin(rectl)); int sh = bSums(bin(recth)); if (sl > 1.2 * sh) { judge = 1; } else { judge = 0; } } else { judge=1; vector<Point> hull1; convexHull( contours[i], hull1 ); int sh=contourArea(hull1, true); int sa=bSums(bin); if(sa<0.5*sh){ judge=0; break; } for (size_t i1 = 0; i1 < linesP.size(); i1++) { Vec4i l = linesP[i1]; // line( drawing, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, LINE_AA); if (abs((l[1] - l[3]) / (l[0] - l[2]+0.1)) < 1 || (l[1] - l[3]) / (l[0] - l[2]+0.1) > 20) { if(sqrt((l[1] - l[3])*(l[1] - l[3])+(l[0] - l[2])*(l[0] - l[2]))>arcLength(contours[i], false)*0.1) { judge = 0; break; } } } } } } // HoughLinesP(candy, linesP, 1, CV_PI/90, 30, 40, 10); // runs the actual detection // // Draw the lines // for( size_t i = 0; i < linesP.size(); i++ ) // { // Vec4i l = linesP[i]; // line(fire, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, LINE_AA); // } // imshow("sads",fire); // cout<<"adwdaw"<<judge<<"\n"; return judge; }