GOCVHelper图像处理算法库

    GOCVHelper(GreenOpen Computer Version Helper )是我在这几年编写图像处理程序的过程中积累下来的函数库。主要是对Opencv的适当扩展和在实现Mfc程序时候的功能增强。

    这里将算法库开放源代码,并且编写一系列blog对函数实现进行说明。目的是在于“取之于互联网,用之于互联网”。并且也希望该库能够继续发展下去。
    由于算法库基于Opencv和Mfc进行编写,所以要求阅读使用者具备一定基础。
    最终提交的是GOCVHelper.h 和GOCVHelper版本号.cpp两个文件。通过阅读头文件,能够对算法库实现的功能加以了解:
    代码最新版本,请上Github或者Gitee搜索名称即可。当前博客中不一定是最新的。
   
 //名称:GOCVHelper0.7b.cpp
    //功能:图像处理和MFC增强
    //作者:jsxyhelu(1755311380@qq.com 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中的方法,可供参考。
 

posted on 2022-12-22 08:48  jsxyhelu  阅读(149)  评论(0编辑  收藏  举报

导航