GOCVHelper圖像處理算法庫

    GOCVHelper(GreenOpen Computer Version Helper )是我在這幾年編寫圖像處理程序的過程中積累下來的函數庫。主要是對Opencv的適當擴展和在實現Mfc程序時候的功能增強。

    這裏將算法庫開放源代碼,並且編寫一系列blog對函數實現進行說明。目的是在於“取之於互聯網,用之於互聯網”。並且也希望該庫能夠繼續發展下去。
    由於算法庫基於Opencv和Mfc進行編寫,所以要求閱讀使用者具備一定基礎。
    最終提交的是GOCVHelper.h 和GOCVHelper版本號.cpp兩個文件。通過閱讀頭文件,能夠對算法庫實現的功能加以瞭解:
    代碼最新版本,請上Github或者Gitee搜索名稱即可。當前博客中不一定是最新的。
   
 //名稱:GOCVHelper0.7b.cpp
    //功能:圖像處理和MFC增強
    //作者:jsxyhelu([email protected] http://jsxyhelu.cnblogs.com)
    //組織:GREENOPEN
    //日期:2016-09-24
    #include "stdafx.h"
    #include <windows.h>
    #include <iostream>
    #include <fstream>
    #include <cstdlib>
    #include <io.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <vector>
    #include "opencv2/core/core.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    using namespace std;
    using namespace cv;
    #define  VP  vector<cv::Point>  //用VP符號代替 vector<point>
    #define  DIRECTION_X 0
    #define  DIRECTION_Y 1
    //調用算法庫請在Opencv和Mfc正確配置的環境下。
    //並且配置 項目-屬性-配置屬性-常規-字符集 設置爲 使用多字節字符集
    //和 項目-屬性-配置屬性-c/c++-預處理器-預處理器定義 加入 _CRT_SECURE_NO_WARNINGS
    namespace GO{
        //讀取灰度或彩色圖片到灰度
        Mat imread2gray(string path);
        //帶有上下限的threshold
        Mat threshold2(Mat src,int minvalue,int maxvalue);
        //自適應門限的canny算法 
        Mat canny2(Mat src);
        void AdaptiveFindThreshold( Mat src,double *low,double *high,int aperture_size=3);
        void _AdaptiveFindThreshold(CvMat *dx, CvMat *dy, double *low, double *high);
        //填充孔洞
        Mat fillHoles(Mat src);
        float getWhiteRate(Mat src);
        Mat getInnerHoles(Mat src);
        //頂帽去光差,radius爲模板半徑
        Mat moveLightDiff(Mat src,int radius = 40);
        //將 DEPTH_8U型二值圖像進行細化  經典的Zhang並行快速細化算法
        void thin(const Mat &src, Mat &dst, const int iterations=100);
        //使得rect區域半透明
        Mat translucence(Mat src,Rect rect,int idepth = 90);
        //使得rect區域打上馬賽克
        Mat mosaic(Mat src,Rect rect,int W = 18,int H = 18);
        //----------------------------------------------------------------------------------------------------------------------------------------//
        //尋找最大的輪廓
        VP FindBigestContour(Mat src);
        //尋找並繪製出彩色聯通區域
        vector<VP> connection2(Mat src,Mat& draw);
        vector<VP> connection2(Mat src);
        //根據輪廓的面積大小進行選擇
        vector<VP>  selectShapeArea(Mat src,Mat& draw,vector<VP> contours,int minvalue,int maxvalue);
        vector<VP>  selectShapeArea(vector<VP> contours,int minvalue,int maxvalue);
        //根據輪廓的圓的特性進行選擇
        vector<VP>  selectShapeArea(Mat src,Mat& draw,vector<VP> contours,int minvalue,int maxvalue);
        vector<VP>  selectShapeArea(vector<VP> contours,int minvalue,int maxvalue);
        //計算輪廓的圓的特性
        float calculateCircularity(VP contour);
        //返回兩點之間的距離
        float getDistance(Point2f f1,Point2f f2);
        //----------------------------------------------------------------------------------------------------------------------------------------//
        //投影到x或Y軸上,上波形爲vup,下波形爲vdown,gap爲誤差間隔
        void projection2(Mat src,vector<int>& vup,vector<int>& vdown,int direction = DIRECTION_X,int gap = 10);
        //----------------------------------------------------------------------------------------------------------------------------------------//
        //遞歸讀取目錄下全部文件
        void getFiles(string path, vector<string>& files,string flag ="r"/*如果不想遞歸這裏不寫r就可以*/);
        //遞歸讀取目錄下全部圖片
        void getFiles(string path, vector<Mat>& files,string flag = "r");
        //遞歸讀取目錄下全部圖片和名稱
        void getFiles(string path, vector<pair<Mat,string>>& files,string flag="r");
        //刪除目錄下的全部文件
        void deleteFiles(string path,string flag = "r");
        //創建或續寫目錄下的csv文件,填寫“文件位置-分類”對
        int writeCsv(const string& filename,const Vector<pair<string,string>>srcVect,char separator=';');
        //讀取目錄下的csv文件,獲得“文件位置-分類”對
        vector<pair<string,string>> readCsv(const string& filename, char separator = ';') ;
        //----------------------------------------------------------------------------------------------------------------------------------------//
        //C++的spilt函數
        void SplitString(const string& s, vector<string>& v, const string& c);
        //! 通過文件夾名稱獲取文件名,不包括後綴
        void getFileName(const string& filepath, string& name,string& lastname);
        //-----------------------------------------------------------------------------------------------------------------------------------------//
        //ini 操作
        CString  GetInitString( CString Name1 ,CString Name2);
        void WriteInitString( CString Name1 ,CString Name2 ,CString strvalue);
        //excel操作
        CString ExportListToExcel(CString  sExcelFile,CListCtrl* pList, CString strTitle);
        BOOL GetDefaultXlsFileName(CString& sExcelFile);
    }

 一、圖像處理部分

   增強後的圖像需要通過圖像處理獲得定量的值。在實際程序設計過程中,輪廓很多時候都是重要的分析變量。參考Halcon的相關函數,我增強了Opencv在這塊的相關功能。

   
   //尋找最大的輪廓
    VP FindBigestContour(Mat src){    
        int imax = 0; //代表最大輪廓的序號
        int imaxcontour = -1; //代表最大輪廓的大小
        std::vector<std::vector<cv::Point>>contours;    
        findContours(src,contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);
        for (int i=0;i<contours.size();i++){
            int itmp =  contourArea(contours[i]);//這裏採用的是輪廓大小
            if (imaxcontour < itmp ){
                imax = i;
                imaxcontour = itmp;
            }
        }
        return contours[imax];
    }

就是直接返回最大的輪廓。

  //尋找並繪製出彩色聯通區域
    vector<VP> connection2(Mat src,Mat& draw){    
        draw = Mat::zeros(src.rows,src.cols,CV_8UC3);
        vector<VP>contours;    
        findContours(src.clone(),contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);
        //由於給大的區域着色會覆蓋小的區域,所以首先進行排序操作
        //冒泡排序,由小到大排序
        VP vptmp;
        for(int i=1;i<contours.size();i++){
            for(int j=contours.size()-1;j>=i;j--){
                if(contours[j].size()<contours[j-1].size()){    
                    vptmp = contours[j-1];
                    contours[j-1] = contours[j];
                    contours[j] = vptmp;
                }
            }
        }
        //打印結果
        for (int i=contours.size()-1;i>=0;i--){
            Scalar  color  = Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255));
            drawContours(draw,contours,i,color,-1);
        }
        return contours;
    }
    vector<VP> connection2(Mat src){
        Mat draw;
        return connection2(src,draw);
    }
尋找聯通區域是經典的圖像處理過程。我採用輪廓分析方法進行解決;並且最後對不能區域繪製不同顏色,非常直觀。

 

//根據輪廓的面積大小進行選擇
    vector<VP>  selectShapeArea(Mat src,Mat& draw,vector<VP> contours,int minvalue,int maxvalue){
        vector<VP> result_contours;
        draw = Mat::zeros(src.rows,src.cols,CV_8UC3);
        for (int i=0;i<contours.size();i++){ 
            double countour_area = contourArea(contours[i]);
            if (countour_area >minvalue && countour_area<maxvalue)
                result_contours.push_back(contours[i]);
        }
        for (int i=0;i<result_contours.size();i++){
            int iRandB = rng.uniform(0,255);
            int iRandG = rng.uniform(0,255);
            int iRandR = rng.uniform(0,255);
            Scalar  color  = Scalar(iRandB,iRandG,iRandR);
            drawContours(draw,result_contours,i,color,-1);
            char cbuf[100];sprintf_s(cbuf,"%d",i+1);
            //尋找最小覆蓋圓,求出圓心。使用反色打印輪廓序號
            float radius;
            cv::Point2f center;
            cv::minEnclosingCircle(result_contours[i],center,radius);
            putText(draw,cbuf,center, FONT_HERSHEY_PLAIN ,5,Scalar(255-iRandB,255-iRandG,255-iRandR),5);
        }
        return result_contours;
    }
    vector<VP>  selectShapeArea(vector<VP> contours,int minvalue,int maxvalue)
    {
        vector<VP> result_contours;
        for (int i=0;i<contours.size();i++){ 
            double countour_area = contourArea(contours[i]);
            if (countour_area >minvalue && countour_area<maxvalue)
                result_contours.push_back(contours[i]);
        }
        return result_contours;
    }

 

在Halcon中,運用非常廣泛的SelectShape的Opencv實現,能夠根據輪廓的大小,直接挑選出所需要的部分。我在這個基礎上進行了強化,能夠把每個輪廓的序號標註出來。並且依然提供draw打印。
 
 
//根據輪廓的圓的特性進行選擇
    vector<VP> selectShapeCircularity(Mat src,Mat& draw,vector<VP> contours,float minvalue,float maxvalue){
        vector<VP> result_contours;
        draw = Mat::zeros(src.rows,src.cols,CV_8UC3);
        for (int i=0;i<contours.size();i++){
            float fcompare = calculateCircularity(contours[i]);
            if (fcompare >=minvalue && fcompare <=maxvalue)
                result_contours.push_back(contours[i]);
        }
        for (int i=0;i<result_contours.size();i++){
            Scalar  color  = Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255));
            drawContours(draw,result_contours,i,color,-1);
        }
        return result_contours;
    }
    vector<VP> selectShapeCircularity(vector<VP> contours,float minvalue,float maxvalue){
        vector<VP> result_contours;
        for (int i=0;i<contours.size();i++){
            float fcompare = calculateCircularity(contours[i]);
            if (fcompare >=minvalue && fcompare <=maxvalue)
                result_contours.push_back(contours[i]);
        }
        return result_contours;
    }
    //計算輪廓的圓的特性
    float calculateCircularity(VP contour){
        Point2f center;
        float radius = 0;
        minEnclosingCircle((Mat)contour,center,radius);
        //以最小外接圓半徑作爲數學期望,計算輪廓上各點到圓心距離的標準差
        float fsum = 0;
        float fcompare = 0;
        for (int i=0;i<contour.size();i++){   
            Point2f ptmp = contour[i];
            float fdistenct = sqrt((float)((ptmp.x - center.x)*(ptmp.x - center.x)+(ptmp.y - center.y)*(ptmp.y-center.y)));
            float fdiff = abs(fdistenct - radius);
            fsum = fsum + fdiff;
        }
        fcompare = fsum/(float)contour.size();
        return fcompare;
    }
 
    //返回兩點之間的距離
    float getDistance(Point2f f1,Point2f f2)
    {
        return sqrt((float)(f1.x - f2.x)*(f1.x - f2.x) + (f1.y -f2.y)*(f1.y- f2.y));
    }

基於Opencv論壇上提供的關於圓的尺度的評判算法,編寫Opencv的圓的特性判斷算法。主要就是“ 以最小外接圓半徑作爲數學期望,計算輪廓上各點到圓心距離的標準差 ”這個標準差達到一定的範圍,則可以認定輪廓是爲圓形的。

輪廓處理的兩種方法在實際使用的過程中,用途非常廣泛。
 
//投影到x或Y軸上,上波形爲vup,下波形爲vdown,gap爲誤差間隔
    void projection2(Mat src,vector<int>& vup,vector<int>& vdown,int direction,int gap){
        Mat tmp = src.clone();
        vector<int> vdate;
        if (DIRECTION_X == direction){
            for (int i=0;i<tmp.cols;i++){
                Mat data = tmp.col(i);
                int itmp = countNonZero(data);
                vdate.push_back(itmp);
            }
        }else{
            for (int i=0;i<tmp.rows;i++){
                Mat data = tmp.row(i);
                int itmp = countNonZero(data);
                vdate.push_back(itmp);
            }
        }
        //整形,去除長度小於gap的零的空洞
        if (vdate.size()<=gap)
            return;
        for (int i=0;i<vdate.size()-gap;i++){
            if (vdate[i]>0 && vdate[i+gap]>0){
                for (int j=i;j<i+gap;j++){
                    vdate[j] = 1;
                }
                i = i+gap-1;
            }
        }
        //記錄上下沿
        for (int i=1;i<vdate.size();i++){
            if (vdate[i-1] == 0 && vdate[i]>0)
                vup.push_back(i);
            if (vdate[i-1]>0 && vdate[i] == 0)
                vdown.push_back(i);
        }
    }

投影變換。投影分析是非常重要的分析方式。這裏的投影分析是從書上扒下來的,能夠直接對圖像進行投影分析,效果非常好。當然工具具備了,如何靈活使用也是需要經驗的。

二、圖像增強部分

圖像增強是圖像處理的第一步。這裏集成了一些實際使用過程中有用的函數。

  //讀取灰度或彩色圖片到灰度
    Mat imread2gray(string path){
        Mat src = imread(path);
        Mat srcClone = src.clone();
        if (CV_8UC3 == srcClone.type() )
            cvtColor(srcClone,srcClone,CV_BGR2GRAY);
        return srcClone;
    }
   算法核心在於判斷讀入圖片的通道數,如果是灰度圖片則保持;如果是彩色圖片則轉換爲灰度圖片。通過這樣一個函數,就能夠直接獲得灰度圖片。
    //帶有上下限的threshold
    Mat threshold2(Mat src,int minvalue,int maxvalue){
        Mat thresh1;
        Mat thresh2;
        Mat dst;
        threshold(src,thresh1,minvalue,255, THRESH_BINARY);
        threshold(src,thresh2,maxvalue,255,THRESH_BINARY_INV);
        dst = thresh1 & thresh2;
        return dst;
    }
    Opencv提供的threshold算法很強大,但是隻能夠取單門限。這裏修改成可以取雙門限的形式。
   //自適應門限的canny算法 
    //canny2
    Mat canny2(Mat src){
        Mat imagetmp = src.clone();
        double low_thresh = 0.0;  
        double high_thresh = 0.0;  
        AdaptiveFindThreshold(imagetmp,&low_thresh,&high_thresh);
        Canny(imagetmp,imagetmp,low_thresh,high_thresh);   
        return imagetmp;}
    void AdaptiveFindThreshold( Mat src,double *low,double *high,int aperture_size){
        const int cn = src.channels();
        Mat dx(src.rows,src.cols,CV_16SC(cn));
        Mat dy(src.rows,src.cols,CV_16SC(cn));
        Sobel(src,dx,CV_16S,1,0,aperture_size,1,0,BORDER_REPLICATE);
        Sobel(src,dy,CV_16S,0,1,aperture_size,1,0,BORDER_REPLICATE);
        CvMat _dx = dx;
        CvMat _dy = dy;
        _AdaptiveFindThreshold(&_dx, &_dy, low, high); }  
    void _AdaptiveFindThreshold(CvMat *dx, CvMat *dy, double *low, double *high){                                                                                
        CvSize size;                                                             
        IplImage *imge=0;                                                        
        int i,j;                                                                 
        CvHistogram *hist;                                                       
        int hist_size = 255;                                                     
        float range_0[]={0,256};                                                 
        float* ranges[] = { range_0 };                                           
        double PercentOfPixelsNotEdges = 0.7;                                    
        size = cvGetSize(dx);                                                    
        imge = cvCreateImage(size, IPL_DEPTH_32F, 1);                            
        // 計算邊緣的強度, 並存於圖像中                                          
        float maxv = 0;                                                          
        for(i = 0; i < size.height; i++ ){                                                                        
            const short* _dx = (short*)(dx->data.ptr + dx->step*i);          
            const short* _dy = (short*)(dy->data.ptr + dy->step*i);          
            float* _image = (float *)(imge->imageData + imge->widthStep*i);  
            for(j = 0; j < size.width; j++){                                                                
                _image[j] = (float)(abs(_dx[j]) + abs(_dy[j]));          
                maxv = maxv < _image[j] ? _image[j]: maxv;}}                                                                        
        if(maxv == 0){                                                           
            *high = 0;                                                       
            *low = 0;                                                        
            cvReleaseImage( &imge );                                         
            return;}                                                                        
        // 計算直方圖                                                            
        range_0[1] = maxv;                                                       
        hist_size = (int)(hist_size > maxv ? maxv:hist_size);                    
        hist = cvCreateHist(1, &hist_size, CV_HIST_ARRAY, ranges, 1);            
        cvCalcHist( &imge, hist, 0, NULL );                                      
        int total = (int)(size.height * size.width * PercentOfPixelsNotEdges);   
        float sum=0;                                                             
        int icount = hist->mat.dim[0].size;                                     
        float *h = (float*)cvPtr1D( hist->bins, 0 );                             
        for(i = 0; i < icount; i++){                                                                        
            sum += h[i];                                                     
            if( sum > total )                                                
                break; }                                                                        
        // 計算高低門限                                                          
        *high = (i+1) * maxv / hist_size ;                                       
        *low = *high * 0.4;                                                      
        cvReleaseImage( &imge );                                                 
        cvReleaseHist(&hist); }     
// end of canny2

         我們在使用Opencv的canny算法的時候,一般是按照經驗填寫上下門限值。爲了解決這個問題,通過自適應算法,自動計算出上下門限。能夠取得不錯效果。

  //填充孔洞
    //fillholes
    Mat fillHoles(Mat src){
        Mat dst = getInnerHoles(src);
        threshold(dst,dst,0,255,THRESH_BINARY_INV);
        dst = src + dst;
        return dst;
    }
    //獲得圖像中白色的比率
    float getWhiteRate(Mat src){
        int iWhiteSum = 0;
        for (int x =0;x<src.rows;x++){
            for (int y=0;y<src.cols;y++){
                if (src.at<uchar>(x,y) != 0)
                    iWhiteSum = iWhiteSum +1;
            }
        }
        return (float)iWhiteSum/(float)(src.rows*src.cols);
    }
    //獲得內部孔洞圖像
    Mat getInnerHoles(Mat src){ 
        Mat clone = src.clone();
        srand((unsigned)time(NULL));  // 生成時間種子
        float fPreRate = getWhiteRate(clone);
        float fAftRate = 0;
        do {
            clone = src.clone();
            // x y 對於 cols rows
            floodFill(clone,Point((int)rand()%src.cols,(int)rand()%src.rows),Scalar(255));
            fAftRate = getWhiteRate(clone);
        } while ( fAftRate < 0.6);
        return clone;
    }
   // end of fillHoles
    填充孔洞算法是我參考相關資料自己實現的。填充孔洞的關鍵在於獲得“內部孔洞圖像”。我採用的方法是在圖像上隨機尋找一個點作爲floodfill的初始點,然後以scalar(255)來進行填充。重複這個過程,直到整個圖片的白色值佔到了全部圖像的60%.
填充前
填充後
//頂帽去光差,radius爲模板半徑
    Mat moveLightDiff(Mat src,int radius){
        Mat dst;
        Mat srcclone = src.clone();
        Mat mask = Mat::zeros(radius*2,radius*2,CV_8U);
        circle(mask,Point(radius,radius),radius,Scalar(255),-1);
        //頂帽
        erode(srcclone,srcclone,mask);
        dilate(srcclone,srcclone,mask);
        dst =  src - srcclone;
        return dst;}
算法來自於岡薩雷斯《數字圖像處理教程》形態學篇章。完全按照教程實現,具備一定作用。
 
 
   //將 DEPTH_8U型二值圖像進行細化  經典的Zhang並行快速細化算法
    //細化算法
    void thin(const Mat &src, Mat &dst, const int iterations){
        const int height =src.rows -1;
        const int width  =src.cols -1;
        //拷貝一個數組給另一個數組
        if(src.data != dst.data)
            src.copyTo(dst);
        int n = 0,i = 0,j = 0;
        Mat tmpImg;
        uchar *pU, *pC, *pD;
        bool isFinished =FALSE;
        for(n=0; n<iterations; n++){
            dst.copyTo(tmpImg); 
            isFinished =FALSE;   //一次 先行後列掃描 開始
            //掃描過程一 開始
            for(i=1; i<height;  i++) {
                pU = tmpImg.ptr<uchar>(i-1);
                pC = tmpImg.ptr<uchar>(i);
                pD = tmpImg.ptr<uchar>(i+1);
                for(int j=1; j<width; j++){
                    if(pC[j] > 0){
                        int ap=0;
                        int p2 = (pU[j] >0);
                        int p3 = (pU[j+1] >0);
                        if (p2==0 && p3==1)
                            ap++;
                        int p4 = (pC[j+1] >0);
                        if(p3==0 && p4==1)
                            ap++;
                        int p5 = (pD[j+1] >0);
                        if(p4==0 && p5==1)
                            ap++;
                        int p6 = (pD[j] >0);
                        if(p5==0 && p6==1)
                            ap++;
                        int p7 = (pD[j-1] >0);
                        if(p6==0 && p7==1)
                            ap++;
                        int p8 = (pC[j-1] >0);
                        if(p7==0 && p8==1)
                            ap++;
                        int p9 = (pU[j-1] >0);
                        if(p8==0 && p9==1)
                            ap++;
                        if(p9==0 && p2==1)
                            ap++;
                        if((p2+p3+p4+p5+p6+p7+p8+p9)>1 && (p2+p3+p4+p5+p6+p7+p8+p9)<7){
                            if(ap==1){
                                if((p2*p4*p6==0)&&(p4*p6*p8==0)){                           
                                    dst.ptr<uchar>(i)[j]=0;
                                    isFinished =TRUE;                            
                                }
                            }
                        }                    
                    }
 
                } //掃描過程一 結束
                dst.copyTo(tmpImg); 
                //掃描過程二 開始
                for(i=1; i<height;  i++){
                    pU = tmpImg.ptr<uchar>(i-1);
                    pC = tmpImg.ptr<uchar>(i);
                    pD = tmpImg.ptr<uchar>(i+1);
                    for(int j=1; j<width; j++){
                        if(pC[j] > 0){
                            int ap=0;
                            int p2 = (pU[j] >0);
                            int p3 = (pU[j+1] >0);
                            if (p2==0 && p3==1)
                                ap++;
                            int p4 = (pC[j+1] >0);
                            if(p3==0 && p4==1)
                                ap++;
                            int p5 = (pD[j+1] >0);
                            if(p4==0 && p5==1)
                                ap++;
                            int p6 = (pD[j] >0);
                            if(p5==0 && p6==1)
                                ap++;
                            int p7 = (pD[j-1] >0);
                            if(p6==0 && p7==1)
                                ap++;
                            int p8 = (pC[j-1] >0);
                            if(p7==0 && p8==1)
                                ap++;
                            int p9 = (pU[j-1] >0);
                            if(p8==0 && p9==1)
                                ap++;
                            if(p9==0 && p2==1)
                                ap++;
                            if((p2+p3+p4+p5+p6+p7+p8+p9)>1 && (p2+p3+p4+p5+p6+p7+p8+p9)<7){
                                if(ap==1){
                                    if((p2*p4*p8==0)&&(p2*p6*p8==0)){                           
                                        dst.ptr<uchar>(i)[j]=0;
                                        isFinished =TRUE;                            
                                    }
                                }
                            }                    
                        }
                    }
                } //一次 先行後列掃描完成          
                //如果在掃描過程中沒有刪除點,則提前退出
                if(isFinished ==FALSE)
                    break; 
            }
        }
    }
#end of thin
細化算法,在處理毛筆字一類的時候效果很好。使用的過程中,注意需要保留的部分要處理爲白色,也就是scalar(255)
//使得rect區域半透明
    Mat translucence(Mat src,Rect rect,int idepth){
        Mat dst = src.clone();
        Mat roi = dst(rect);
        roi += cv::Scalar(idepth,idepth,idepth);
        return dst;
    }
將選擇的區域打上變成半透明。雖然這只是一個簡單的函數,但是使用起來靈活多變。
比如說,可以將圖像某個區域變成半透明,然後在上面寫字,這樣起到強化作用;
也可以將一個區域圖片在半透明和不透明之間切換,起到強掉作用。
    //使得rect區域打上馬賽克
    Mat mosaic(Mat src,Rect rect,int W,int H){
        Mat dst = src.clone();
        Mat roi = dst(rect);
        for (int i=W; i<roi.cols; i+=W) {
            for (int j=H; j<roi.rows; j+=H) {
                uchar s=roi.at<uchar>(j-H/2,(i-W/2)*3);
                uchar s1=roi.at<uchar>(j-H/2,(i-W/2)*3+1);
                uchar s2=roi.at<uchar>(j-H/2,(i-W/2)*3+2);
                for (int ii=i-W; ii<=i; ii++) {
                    for (int jj=j-H; jj<=j; jj++) {
                        roi.at<uchar>(jj,ii*3+0)=s;
                        roi.at<uchar>(jj,ii*3+1)=s1;
                        roi.at<uchar>(jj,ii*3+2)=s2;
                    }
                }
            }
        }
        return dst;

}
將選擇的區域打上馬賽克,也就是常見的所謂打碼。
//基於顏色直方圖的距離計算
double GetHsVDistance(Mat src_base,Mat src_test1){
    Mat   hsv_base;
    Mat   hsv_test1;
    ///  Convert  to  HSV
    cvtColor(  src_base,  hsv_base,  COLOR_BGR2HSV  );
    cvtColor(  src_test1,  hsv_test1,  COLOR_BGR2HSV  );
    ///  Using  50  bins  for  hue  and  60  for  saturation
    int  h_bins  =  50;  int  s_bins  =  60;
    int  histSize[]  =  {  h_bins,  s_bins  };
    //  hue  varies  from  0  to  179,  saturation  from  0  to  255
    float  h_ranges[]  =  {  0,  180  };
    float  s_ranges[]  =  {  0,  256  };
    const  float*  ranges[]  =  {  h_ranges,  s_ranges  };
    //  Use  the  o-th  and  1-st  channels
    int  channels[]  =  {  0,  1  };
    ///  Histograms
    MatND  hist_base;
    MatND  hist_test1;
    ///  Calculate  the  histograms  for  the  HSV  images
    calcHist(  &hsv_base,  1,  channels,  Mat(),  hist_base,  2,  histSize,  ranges,  true,  false  );
    normalize(  hist_base,  hist_base,  0,  1,  NORM_MINMAX,  -1,  Mat()  );
    calcHist(  &hsv_test1,  1,  channels,  Mat(),  hist_test1,  2,  histSize,  ranges,  true,  false  );
    normalize(  hist_test1,  hist_test1,  0,  1,  NORM_MINMAX,  -1,  Mat()  );
    ///  Apply  the  histogram  comparison  methods
    double  base_test1  =  compareHist(  hist_base,  hist_test1,  0  );
    return base_test1;
}
基於顏色直方圖的增強算法是一種經典的圖像增強算法。這裏提供了opencv實現。這個部分應該是從gimp中扒出來的。
// Multiply 正片疊底
void Multiply(Mat& src1, Mat& src2, Mat& dst)
{
    for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
                dst.at<Vec3f>(index_row, index_col)[index_c]=
                src1.at<Vec3f>(index_row, index_col)[index_c]*
                src2.at<Vec3f>(index_row, index_col)[index_c];
        }
    }
}
// Color_Burn 顏色加深
void Color_Burn(Mat& src1, Mat& src2, Mat& dst)
{
    for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
                dst.at<Vec3f>(index_row, index_col)[index_c]=1-
                (1-src1.at<Vec3f>(index_row, index_col)[index_c])/
                src2.at<Vec3f>(index_row, index_col)[index_c];
        }
    }
}
// 線性增強
void Linear_Burn(Mat& src1, Mat& src2, Mat& dst)
{
    for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
                dst.at<Vec3f>(index_row, index_col)[index_c]=max(
                src1.at<Vec3f>(index_row, index_col)[index_c]+
                src2.at<Vec3f>(index_row, index_col)[index_c]-1, (float)0.0);
        }
    }
}

 

模擬ps中的圖像疊加操作,實現同樣的效果
 
三、MFC輔助相關 
//遞歸讀取目錄下全部文件(flag爲r的時候遞歸)
    void getFiles(string path, vector<string>& files,string flag){
        //文件句柄
        long   hFile   =   0;
        //文件信息
        struct _finddata_t fileinfo;
        string p;
        if((hFile = _findfirst(p.assign(path).append("\\*").c_str(),&fileinfo)) !=  -1){
            do{
                //如果是目錄,迭代之,如果不是,加入列表
                if((fileinfo.attrib &  _A_SUBDIR)){
                    if(strcmp(fileinfo.name,".") != 0  &&  strcmp(fileinfo.name,"..") != 0 && flag=="r")
                        getFiles( p.assign(path).append("\\").append(fileinfo.name), files,flag );
                }
                else{
                    files.push_back(p.assign(path).append("\\").append(fileinfo.name) );
                }
            }while(_findnext(hFile, &fileinfo)  == 0);
            _findclose(hFile);
        }
    }

    //遞歸讀取目錄下全部圖片
    void getFiles(string path, vector<Mat>& files,string flag){
        vector<string> fileNames;
        getFiles(path,fileNames,flag);
        for (int i=0;i<fileNames.size();i++){
            Mat tmp = imread(fileNames[i]);
            if (tmp.rows>0)//如果是圖片
                files.push_back(tmp);
        }
    }  
    //遞歸讀取目錄下全部圖片和名稱
    void getFiles(string path, vector<pair<Mat,string>>& files,string flag){
        vector<string> fileNames;
        getFiles(path,fileNames,flag);
        for (int i=0;i<fileNames.size();i++){
            Mat tmp = imread(fileNames[i]);
            if (tmp.rows>0){
                pair<Mat,string> apir;
                apir.first = tmp;
                apir.second = fileNames[i];
                files.push_back(apir);
            }
        }
    }  
       在結合MFC的程序設計中,經常涉及到圖片文件輸入輸出的情況。所以我編寫集成了一些算法,在這個方面進行增強。getFiles函數能夠遞歸地讀取某個目錄下面所有文件的據對路徑。這樣就能夠一次性獲得所有的圖片;對getFiles進行重載,這樣能夠直接將圖片讀入mat,或者讀入pair<Mat,string>,更加方便。
       可能你會問,既然已經讀入Mat了,但是爲什麼還要讀出pair<Mat,string>了?這是因爲很多時候在獲得圖片的時候還需要獲得圖片的名稱。刪除目錄下的全部文件
    
void deleteFiles(string path,string flag){
        //文件句柄
        long   hFile   =   0;
        //文件信息
        struct _finddata_t fileinfo;
        string p;
        if((hFile = _findfirst(p.assign(path).append("\\*").c_str(),&fileinfo)) !=  -1){
            do{
                //如果是目錄,迭代之,如果不是,加入列表
                if((fileinfo.attrib &  _A_SUBDIR)){
                    if(strcmp(fileinfo.name,".") != 0  &&  strcmp(fileinfo.name,"..") != 0 && flag=="r")
                        deleteFiles(p.assign(path).append("\\").append(fileinfo.name).c_str(),flag );
                }
                else{
                    deleteFiles(p.assign(path).append("\\").append(fileinfo.name).c_str());
                }
            }while(_findnext(hFile, &fileinfo)  == 0);
            _findclose(hFile);
        }
    }
      刪除目錄下的全部文件。
//創建或續寫目錄下的csv文件,填寫“文件位置-分類”對
    int writeCsv(const string& filename,const Vector<pair<string,string>>srcVect,char separator ){
        ofstream file(filename.c_str(),ofstream::app);
        if (!file)
            return 0;
        for (int i=0;i<srcVect.size();i++){
            file<<srcVect[i].first<<separator<<srcVect[i].second<<endl;
        }
        return srcVect.size();
    }
//讀取目錄下的csv文件,獲得“文件位置-分類”對
    vector<pair<string,string>> readCsv(const string& filename, char separator) {
        pair<string,string> apair;
        string line, path, classlabel;
        vector<pair<string,string>> retVect;
        ifstream file(filename.c_str(), ifstream::in);
        if (!file) 
            return retVect;
        while (getline(file, line)) {
            stringstream liness(line);
            getline(liness, path, separator);
            getline(liness, classlabel);
            if(!path.empty() && !classlabel.empty()) {
                apair.first = path;
                apair.second = classlabel;
                retVect.push_back(apair);
            }
 
        }
        return retVect;
    }
csv文件是最爲簡單的帶格式文件。這種格式在opencv的人臉識別教程中得到了使用,這裏是扣的它的代碼。
//獲得ini文件中的值
     CString  GetInitString( CString Name1 ,CString Name2){
        char c[100] ;
        memset( c ,0 ,100) ;
        CString csCfgFilePath;
        GetModuleFileName(NULL, csCfgFilePath.GetBufferSetLength(MAX_PATH+1), MAX_PATH); 
        csCfgFilePath.ReleaseBuffer(); 
        int nPos = csCfgFilePath.ReverseFind ('\\');
        csCfgFilePath = csCfgFilePath.Left (nPos);
        csCfgFilePath += "\\Config" ;
        BOOL br = GetPrivateProfileString(Name1,Name2 ,"0",c, 100 , csCfgFilePath) ;
        CString rstr ;
        rstr .Format("%s" , c) ;
        return rstr ;
    }
     //寫入ini問價中的值
     void WriteInitString( CString Name1 ,CString Name2 ,CString strvalue){
        CString csCfgFilePath;
        GetModuleFileName(NULL, csCfgFilePath.GetBufferSetLength(MAX_PATH+1), MAX_PATH); 
        csCfgFilePath.ReleaseBuffer(); 
        int nPos = csCfgFilePath.ReverseFind ('\\');
        csCfgFilePath = csCfgFilePath.Left (nPos);
        csCfgFilePath += "\\Config" ;
        BOOL br = WritePrivateProfileString(Name1 ,Name2 ,strvalue ,csCfgFilePath) ;
        if ( !br)
            TRACE("savewrong") ;
    }
這兩個函數主要是用來保存和修改配置文件的。通過直接將配置文件寫入.ini中達到這個目標。 
//獲得當前目錄路徑
    static CString GetLocalPath(){
        CString csCfgFilePath;
        GetModuleFileName(NULL, csCfgFilePath.GetBufferSetLength(MAX_PATH+1), MAX_PATH); 
        csCfgFilePath.ReleaseBuffer(); 
        int nPos = csCfgFilePath.ReverseFind ('\\');
        csCfgFilePath = csCfgFilePath.Left (nPos);
        return csCfgFilePath;
    }
 
    //獲得.exe路徑
    static CString GetExePath()
    {
        CString strPath;
        GetModuleFileName(NULL,strPath.GetBufferSetLength(MAX_PATH+1),MAX_PATH);
        strPath.ReleaseBuffer();
        return strPath;
    }
MFC程序涉及的時候存在一個問題,就是某些操作會修改自動"當前路徑"的值。這兩個函數能夠獲得當前的路徑。
//開機自動運行
    static BOOL SetAutoRun(CString strPath,bool flag)
    {
        CString str;
        HKEY hRegKey;
        BOOL bResult;
        str=_T("Software\\Microsoft\\Windows\\CurrentVersion\\Run");
        if(RegOpenKey(HKEY_LOCAL_MACHINE, str, &hRegKey) != ERROR_SUCCESS) 
            bResult=FALSE;
        else
        {
            _splitpath(strPath.GetBuffer(0),NULL,NULL,str.GetBufferSetLength(MAX_PATH+1),NULL);
            strPath.ReleaseBuffer();
            str.ReleaseBuffer();//str是鍵的名字
            if (flag){
                if(::RegSetValueEx( hRegKey,str,0,REG_SZ,(CONST BYTE *)strPath.GetBuffer(0),strPath.GetLength() ) != ERROR_SUCCESS)
                    bResult=FALSE;
                else
                    bResult=TRUE;
            }else{
                if(    ::RegDeleteValue(hRegKey,str) != ERROR_SUCCESS)
                    bResult=FALSE;
                else
                    bResult=TRUE;
            }
            strPath.ReleaseBuffer();
        }
        return bResult;
    }        
這個無需更多說明。
//string替換
    void string_replace(string & strBig, const string & strsrc, const string &strdst)
    {
        string::size_type pos=0;
        string::size_type srclen=strsrc.size();
        string::size_type dstlen=strdst.size();
        while( (pos=strBig.find(strsrc, pos)) != string::npos)
        {
            strBig.replace(pos, srclen, strdst);
            pos += dstlen;
        }
    }
字符串操作一直都是重要的基礎操作。在圖像處理的過程中,涉及到文件名等變換都需要字符串操作。string_replace中能夠成塊地換字符。雖然在std中可能已經有相關函數,不過既然我自己的這個用的比較熟悉,就是使用了。
//C++的spilt函數
    void SplitString(const string& s, vector<string>& v, const string& c){
        std::string::size_type pos1, pos2;
        pos2 = s.find(c);
        pos1 = 0;
        while(std::string::npos != pos2){
            v.push_back(s.substr(pos1, pos2-pos1));
            pos1 = pos2 + c.size();
            pos2 = s.find(c, pos1);
        }
        if(pos1 != s.length())
            v.push_back(s.substr(pos1));
    }
依然是增強了std中的相關功能,實際使用的時候非常有用。
//! 通過文件夾名稱獲取文件名,不包括後綴
    void getFileName(const string& filepath, string& name,string& lastname){
        vector<string> spilt_path;
        SplitString(filepath, spilt_path, "\\");
        int spiltsize = spilt_path.size();
        string filename = "";
        if (spiltsize != 0){
            filename = spilt_path[spiltsize-1];
            vector<string> spilt_name;
            SplitString(filename, spilt_name, ".");
            int name_size = spilt_name.size();
            if (name_size != 0)
                name = spilt_name[0];
            lastname = spilt_name[name_size-1];
        }
    }
前面的函數getfiles能夠獲得文件的真實路徑。那麼getFileName能夠進一步處理,直接獲得圖片的名稱。很多時候,圖片讀取了,需要處理一下,這個都是需要的。
CString ExportListToExcel(CString  sExcelFile,CListCtrl* pList, CString strTitle)
    {
        CString warningStr;
        if (pList->GetItemCount ()>0) {    
            CDatabase database;
            
            
            CString sSql;
            CString tableName = strTitle;
 
            // 檢索是否安裝有Excel驅動 "Microsoft Excel Driver (*.xls)" 
            CString sDriver;
            sDriver = GetExcelDriver();
            if (sDriver.IsEmpty())
            {
                // 沒有發現Excel驅動
                AfxMessageBox("沒有安裝Excel!\n請先安裝Excel軟件才能使用導出功能!");
                return NULL;
            }
 
            ///默認文件名
        /*    CString sExcelFile; 
            if (!GetDefaultXlsFileName(sExcelFile))
                return NULL;*/
 
            // 創建進行存取的字符串
            sSql.Format("DRIVER={%s};DSN='';FIRSTROWHASNAMES=1;READONLY=FALSE;CREATE_DB=\"%s\";DBQ=%s",sDriver, sExcelFile, sExcelFile);
 
            // 創建數據庫 (既Excel表格文件)
            if( database.OpenEx(sSql,CDatabase::noOdbcDialog) )
            {
                // 創建表結構
                int i;
                LVCOLUMN columnData;
                CString columnName;
                int columnNum = 0;
                CString strH;
                CString strV;
 
                sSql = "";
                strH = "";
                columnData.mask = LVCF_TEXT;
                columnData.cchTextMax =100;
                columnData.pszText = columnName.GetBuffer (100);
                for(i=0;pList->GetColumn(i,&columnData);i++)
                {
                    if (i!=0)
                    {
                        sSql = sSql + ", " ;
                        strH = strH + ", " ;
                    }
                    sSql = sSql + " " + columnData.pszText +" TEXT";
                    strH = strH + " " + columnData.pszText +" ";
                }
                columnName.ReleaseBuffer ();
                columnNum = i;
 
                sSql = "CREATE TABLE " + tableName + " ( " + sSql +  " ) ";
                database.ExecuteSQL(sSql);
 
 
                // 插入數據項
                int nItemIndex;
                for (nItemIndex=0;nItemIndex<pList->GetItemCount ();nItemIndex++){
                    strV = "";
                    for(i=0;i<columnNum;i++)
                    {
                        if (i!=0)
                        {
                            strV = strV + ", " ;
                        }
                        strV = strV + " '" + pList->GetItemText(nItemIndex,i) +"' ";
                    }
 
                    sSql = "INSERT INTO "+ tableName 
                        +" ("+ strH + ")"
                        +" VALUES("+ strV + ")";
                    database.ExecuteSQL(sSql);
                }
 
            }      
 
            // 關閉數據庫
            database.Close();
            return sExcelFile;
        }
    }
圖像處理生成了結果,如果需要保存爲報表文件,或者想進一步存入數據庫中,Excel都是非常好的選擇。在這裏集成了vc知識庫中提供的代碼。版權爲
使用的時候,只需要將生成的結果填入 CListCtrl 控件中,而後直接到處就可以。也就是把excel輸出的問題變成了向 CListCtrl 控件輸出的問題。同時代碼中還提供了能夠直接寫到兩個sheet中的方法,可供參考。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章