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中的方法,可供参考。 标签:src,vector,Mat,int,CString,算法,图像处理,GOCVHelper,string From: https://www.cnblogs.com/jsxyhelu/p/16948085.html