opencv图像处理基本操作
1. 矩阵数据类型
通用矩阵数据类型:
CV_<bit_depth>(S|U|F)C<number_of_channels>
其中,S表示带符号整数;
U表示无符号整数;
F表示浮点数;
例如:CV_8UC1 表示8位无符号单通道矩阵;
CV_32FC2 表示32位浮点数双通道矩阵;
2. 图像数据类型
通用图像数据类型为:
IPL_DEPTH_<bit_depth>(S|U|F)
如:IPL_DEPTH_8U 表示8位无符号整数图像;
IPL_DEPTH_32F 表示32位浮点数图像;
3. 分配和释放图像
3.1 分配一幅图像
IpIImage * cvCreateImage(cvSize size, int depth, int channels);
其中size可以用cvSize(width, height)得到。
depth为像素的单位,包括:
IPL_DEPTH_8U
IPL_DEPTH_8S
IPL_DEPTH_16U
IPL_DEPTH_16S
IPL_DEPTH_32S
IPL_DEPTH_32F
IPL_DEPTH_64F
channels为每个像素的通道数,可以是1,2,3或4。通道是交叉排列的,一幅彩色
图像的通常的排列顺序是:
b0 g0 r0 b1 g1 r1 ...
例如:分配一个单通道单字节图像的语句是:
IpIImage* img1 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1);
分配一个三通道浮点数图像语句是:
IpIImage* img2 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_32F, 3);
3.2 释放图像
void cvReleaseImage(IpIImage **);
3.3 复制一幅图像
IpIImage* cvCloneImage(IpIImage *);
如:
IpIImage* img1 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1);
IpIImage* img2;
img2 = cvCloneImage(img1);
3.4 设置或得到感兴趣区域ROI
void cvSetImageROI(IpIImage* image, cvRect rect);
void cvResetImageROI(IpIImage* image);
vRect cvGetImageROI(const IpIImage* image);
4. 图像的读写
4.1 从文件中获取图像
从文件中读取图像可以采用下面的语句:
IpIImage* img = 0;
img = cvLoadImage(filename);
if (!img)
printf("Could not load image file: %s\n", filename);
默认为读取三通道图像。如果改变设置则采用如下的方式:
img = cvLoadImage(filename, flag);
当flag > 0时,表示载入图像为3通道彩色图像;
当flag = 0时,表示载入图像为单通道灰色图像;
当flag < 0时,表示载入图像由文件中的图像通道数决定。
5. 图像转换
5.1 将灰度图像转换为彩色图像
cvConvertImage(src, dst, flags = 0);
其中,src表示浮点(单字节)灰度(彩色)图像;
dst表示单字节灰度(彩色)图像;
flags表示
+--- CV_CVTIMG_FLIP, 垂直翻转
flags = |
+--- CV_CVTIMG_SWAP_RB, 交换R和B通道
5.2 将彩色图像转换为灰度图像
cvCvtColor(cimg, gimg, CV_RGB2GRAY);
5.3 彩色空间的转换
cvCvtColor(src, dst, code);
其中code为:CV_<X>2<Y>,而<X>,<Y> = RGB, BGR, GRAY, HSV, YCrCb, XYZ, Lab, Luv, HLS。
6. 绘制命令
绘图语句为:
cvRectangle, cvCircle, cvLine, cvPolyLine, cvFillPoly, cvInitFont, cvPutText。
先把需要缩放的部分用cvcopy出来,cvresize,然后再cvcopy回去
cvSetImageROI(img, roi1);IPLImage tempimg= cvCreateImage(//size must be the resized image size//);cvResizeImage(img,rempimg....);cvSetImageROI(img,newroi);cvCopy(tempimg,img);
其实他把这个问题复杂化了,对指定部分缩放,首先要说明自己对哪个部分
感兴趣cvSetImageROI,通过这个函数,图像就仅仅剩下了ROI部分,然后
通过cvResize()把这个ROI区域按照自己的意愿放大缩小,我自己编程如下:
::cvSetImageROI(src,cvRect(src->width/4,src->height/4,src->width/2,src->height/2));
IplImage* temp=::cvCreateImage(cvSize(src->width,src->height),src->depth,src->nChannels);
::cvResize(src,temp);
::cvNamedWindow(wndName1,1);
::cvShowImage(wndName1,temp);
::cvWaitKey(0);
感兴趣区域为中间的区域,大小为原来的1/2,重新划分后感兴趣区域为原来
大小,搞定。
========
OpenCv入门-图像处理基本函数
1、图像的内存分配与释放
(1) 分配内存给一幅新图像:
IplImage* cvCreateImage(CvSize size, int depth, int channels);
size: cvSize(width,height);
depth: 像素深度: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U,
IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F, IPL_DEPTH_64F
channels: 像素通道数. Can be 1, 2, 3 or 4.
各通道是交错排列的. 一幅彩色图像的数据排列格式如下:
b0 g0 r0 b1 g1 r1 ...
示例:
// Allocate a 1-channel byte image
IplImage* img1=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
// Allocate a 3-channel float image
IplImage* img2=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);
(2) 释放图像:
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
cvReleaseImage(&img);
(3) 复制图像:
IplImage* img1=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
IplImage* img2;
img2=cvCloneImage(img1); // 注意通过cvCloneImage得到的图像
// 也要用 cvReleaseImage 释放,否则容易产生内存泄漏
(4) 设置/获取感兴趣区域ROI: (ROI:Region Of Interest)
void cvSetImageROI(IplImage* image, CvRect rect);
void cvResetImageROI(IplImage* image);
CvRect cvGetImageROI(const IplImage* image);
大多数OpenCV函数都支持 ROI.
(5) 设置/获取感兴趣通道COI: (COI:channel of interest)
void cvSetImageCOI(IplImage* image, int coi); // 0=all
int cvGetImageCOI(const IplImage* image);
大多数OpenCV函数不支持 COI.
2、图像读写
(1) 从文件中读入图像:
IplImage* img=0;
img=cvLoadImage(fileName);
if(!img) printf("Could not load image file: %s\n",fileName);
支持的图像格式: BMP, DIB, JPEG, JPG, JPE, PNG, PBM, PGM, PPM,
SR, RAS, TIFF, TIF
OpenCV默认将读入的图像强制转换为一幅三通道彩色图像. 不过可以按以下方法修改读入方式:
img=cvLoadImage(fileName,flag);
flag: >0 将读入的图像强制转换为一幅三通道彩色图像
=0 将读入的图像强制转换为一幅单通道灰度图像
<0 读入的图像通道数与所读入的文件相同.
(2) 保存图像:
if(!cvSaveImage(outFileName,img)) printf("Could not save: %s\n", outFileName);
保存的图像格式由 outFileName 中的扩展名确定.
3、访问图像像素
(1) 假设你要访问第k通道、第i行、第j列的像素。
(2) 间接访问: (通用,但效率低,可访问任意格式的图像)
对于单通道字节型图像
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
CvScalar s;
s=cvGet2D(img,i,j); // get the (j,i) pixel value, 注意cvGet2D与cvSet2D中坐标参数的顺序与其它opencv函数坐标参数顺序恰好相反.本函数中i代表y轴,即height;j代表x轴,即weight.
printf("intensity=%f\n",s.val[0]);
s.val[0]=111;
cvSet2D(img,i,j,s); // set the (j,i) pixel value
对于多通道字节型/浮点型图像:
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);
CvScalar s;
s=cvGet2D(img,i,j); // get the (j,i) pixel value
printf("B=%f, G=%f, R=%f\n",s.val[0],s.val[1],s.val[2]);
s.val[0]=111;
s.val[1]=111;
s.val[2]=111;
cvSet2D(img,i,j,s); // set the (j,i) pixel value
(3) 直接访问: (效率高,但容易出错)
对于单通道字节型图像:
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
((uchar *)(img->imageData + i*img->widthStep))[j]=111; (img->imageData即数组首指针,i为行数,img->widthStep每行所占字节数)
对于多通道字节型图像:
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);
((uchar *)(img->imageData + i*img->widthStep))[j*img->nChannels + 0]=111; // B
((uchar *)(img->imageData + i*img->widthStep))[j*img->nChannels + 1]=112; // G
((uchar *)(img->imageData + i*img->widthStep))[j*img->nChannels + 2]=113; // R
对于多通道浮点型图像:
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);
((float *)(img->imageData + i*img->widthStep))[j*img->nChannels + 0]=111; // B
((float *)(img->imageData + i*img->widthStep))[j*img->nChannels + 1]=112; // G
((float *)(img->imageData + i*img->widthStep))[j*img->nChannels + 2]=113; // R
(4) 基于指针的直接访问: (简单高效)
对于单通道字节型图像:
IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
int height = img->height;
int width = img->width;
int step = img->widthStep;
uchar* data = (uchar *)img->imageData;
data[i*step+j] = 111;
对于多通道字节型图像:
IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);
int height = img->height;
int width = img->width;
int step = img->widthStep;
int channels = img->nChannels;
uchar* data = (uchar *)img->imageData;
data[i*step+j*channels+k] = 111;
对于多通道浮点型图像(假设图像数据采用4字节(32位)行对齐方式):
IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);
int height = img->height;
int width = img->width;
int step = img->widthStep;
int channels = img->nChannels;
float * data = (float *)img->imageData;
data[i*step+j*channels+k] = 111;
(5) 基于 c++ wrapper 的直接访问: (更简单高效) (封装?C++封装没怎么学,要用再仔细学)
首先定义一个 c++ wrapper ‘Image’,然后基于Image定义不同类型的图像:
template<class T> class Image
{
private:
IplImage* imgp;
public:
Image(IplImage* img=0) {imgp=img;}
~Image(){imgp=0;}
void operator=(IplImage* img) {imgp=img;}
inline T* operator[](const int rowIndx) {
return ((T *)(imgp->imageData + rowIndx*imgp->widthStep));}
};
typedef struct{
unsigned char b,g,r;
} RgbPixel;
typedef struct{
float b,g,r;
} RgbPixelFloat;
typedef Image<RgbPixel> RgbImage;
typedef Image<RgbPixelFloat> RgbImageFloat;
typedef Image<unsigned char> BwImage;
typedef Image<float> BwImageFloat;
对于单通道字节型图像:
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
BwImage imgA(img);
imgA[i][j] = 111;
对于多通道字节型图像:
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);
RgbImage imgA(img);
imgA[i][j].b = 111;
imgA[i][j].g = 111;
imgA[i][j].r = 111;
对于多通道浮点型图像:
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);
RgbImageFloat imgA(img);
imgA[i][j].b = 111;
imgA[i][j].g = 111;
imgA[i][j].r = 111;
========
OpenCV中图像处理函数
1。滤波 Filtering
filter2D() 用核函数对图像做卷积
sepFilter2D() 用分解的核函数对图像做卷积。首先,图像的每一行与一维的核kernelX做卷积;然后,运算结果的每一列与一维的核kernelY做卷积
boxFilter() 就是滑动窗口平均滤波的二维版。
GaussianBlur() 高斯平均,也就是高斯模糊。
medianBlur() 中值滤波,个人最爱的滤波函数。
bilateralFilter() 双线性滤波。
前面这四个函数是原来OpenCV里的cvSmooth()取不同参数的应用。
Sobel() 使用扩展 Sobel 算子计算一阶、二阶、三阶或混合图像差分。
Scharr() 计算一阶导,x方向或y方向,以前这个方法是放在cvSobel里的。
Laplacian() 拉普拉斯变换。
erode(), dilate() 腐蚀、膨胀。
示例:
filter2D(image, image, image.depth(), (Mat<float>(3,3)<<-1, -1, -1, -1, 9, -1, -1, -1, -1), Point(1,1), 128);
构造了一个如下所示的核对图像做卷积:
-1 -1 -1
-1 9 -1
-1 -1 -1
核的锚点在(1,1)位置,卷积之后每个像素加上128.
2。几何变换 Geometrical Transformations
resize() 改变图像尺寸,可以指定x方向和y方向上的缩放比例,可以指定插值方法。
getRectSubPix() 以亚像素精度从图像中提取矩形。 dst(x,y)=src(x+center.x-(dst.cols-1)*0.5,y+center.y-(dst.rows-1)*0.5) 其中非整数象素点坐标采用双线性插值提取。
warpAffine() 仿射变换。
warpPerspective() 透射变换。
remap() 几何变换。
convertMaps() 将图像从一种类型,转换成另一种类型。
示例:
Mat dst;
resize(src, dst, Size(), 1./sqrt(2), 1./sqrt(2)); // 把图像缩小到原来的根号二分之一。
3。 图像变换 Various Image Transformations
cvtColor()色彩空间转换。这个函数可以用于把CCD的raw格式转换为RGB,请参考,但是不能用于把灰度图转成伪彩图,请参考。
threshold() 二值化,常用操作,一般应用时建议用大津算法,即使用THRESH_OTSU参数。
adaptivethreshold() 自适应阈值的二值化。
floodFill() 填充连通域。
integral() 计算积分图像,一次或者二次。
distanceTransform() 距离变换,对原图像的每一个像素计算到最近非零像素的距离。
watershed() 分水岭图像分割。
grabCut()
一种彩色图像分割算法,效果可以参考这里。See the samples watershed.cpp and grabcut.cpp.
4。 直方图 Histograms
calcHist() 计算直方图。
calcBackProject() 计算反向投影。
equalizeHist() 灰度图像的直方图均衡化,常用操作。
compareHist() 比较两个直方图。
例子:计算图像的色调-饱和度直方图。
Mat hsv, H;
cvtColor(image, hsv, CVBGR2HSV);
int planes[]=f0, 1g, hsize[] = f32, 32g;
calcHist(&hsv, 1, planes, Mat(), H, 2, hsize, 0);
========
形态学图像处理 膨胀与腐蚀
http://blog.csdn.net/poem_qianmo/article/details/23710721
写作当前博文时配套使用的OpenCV版本: 2.4.8
一、理论与概念讲解——从现象到本质
1.1 形态学概述
形态学(morphology)一词通常表示生物学的一个分支,该分支主要研究动植物的形态和结构。而我们图像处理中指的形态学,往往表示的是数学形态学。下面一起来了解数学形态学的概念。
数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本理论。其基本的运算包括:二值腐蚀和膨胀、二值开闭运算、骨架抽取、极限腐蚀、击中击不中变换、形态学梯度、Top-hat变换、颗粒分析、
流域变换、灰值腐蚀和膨胀、灰值开闭运算、灰值形态学梯度等。
简单来讲,形态学操作就是基于形状的一系列图像处理操作。OpenCV为进行图像的形态学变换提供了快捷、方便的函数。最基本的形态学操作有二种,他们是:膨胀与腐蚀(Dilation与Erosion)。
膨胀与腐蚀能实现多种多样的功能,主要如下:
消除噪声
分割(isolate)出独立的图像元素,在图像中连接(join)相邻的元素。
寻找图像中的明显的极大值区域或极小值区域
求出图像的梯度
我们在这里给出下文会用到的,用于对比膨胀与腐蚀运算的“浅墨”字样毛笔字原图:
在进行腐蚀和膨胀的讲解之前,首先需要注意,腐蚀和膨胀是对白色部分(高亮部分)而言的,不是黑色部分。膨胀就是图像中的高亮部分进行膨胀,“领域扩张”,效果图拥有比原图更大的高亮区域。腐蚀就是原图中的高亮部分被腐蚀,“领域被蚕食”,效果图拥有
比原图更小的高亮区域。
1.2 膨胀
其实,膨胀就是求局部最大值的操作。
按数学方面来说,膨胀或者腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与核(我们称之为B)进行卷积。
核可以是任何的形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点(anchorpoint)。多数情况下,核是一个小的中间带有参考点和实心正方形或者圆盘,其实,我们可以把核视为模板或者掩码。
而膨胀就是求局部最大值的操作,核B与图形卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。如下图所示,这就是膨胀操作的初衷。
膨胀的数学表达式:
膨胀效果图(毛笔字):
照片膨胀效果图:
1.3 腐蚀
再来看一下腐蚀,大家应该知道,膨胀和腐蚀是一对好基友,是相反的一对操作,所以腐蚀就是求局部最小值的操作。
我们一般都会把腐蚀和膨胀对应起来理解和学习。下文就可以看到,两者的函数原型也是基本上一样的。
原理图:
腐蚀的数学表达式:
腐蚀效果图(毛笔字):
照片腐蚀效果图:
二、深入——OpenCV源码分析溯源
直接上源码吧,在 …\opencv\sources\modules\imgproc\src\ morph.cpp路径中 的第 1353行开始就为 erode(腐蚀)函数的源码, 1361行为 dilate(膨胀)函数的源码。
//-----------------------------------【erode()函数中文注释版源代码】----------------------------
// 说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码
// OpenCV源代码版本:2.4.8
// 源码路径:…\opencv\sources\modules\imgproc\src\ morph.cpp
// 源文件中如下代码的起始行数:1353行
// 中文注释by浅墨
//--------------------------------------------------------------------------------------------------------
void cv::erode( InputArray src, OutputArraydst, InputArray kernel,
Point anchor, int iterations,
int borderType, constScalar& borderValue )
{
//调用morphOp函数,并设定标识符为MORPH_ERODE
morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType,borderValue );
}
//-----------------------------------【dilate()函数中文注释版源代码】----------------------------
// 说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码
// OpenCV源代码版本:2.4.8
// 源码路径:…\opencv\sources\modules\imgproc\src\ morph.cpp
// 源文件中如下代码的起始行数:1361行
// 中文注释by浅墨
//--------------------------------------------------------------------------------------------------------
void cv::dilate( InputArray src,OutputArray dst, InputArray kernel,
Point anchor, int iterations,
int borderType, constScalar& borderValue )
{
//调用morphOp函数,并设定标识符为MORPH_DILATE
morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType,borderValue );
}
可以发现erode和dilate这两个函数内部就是调用了一下morphOp,只是他们调用morphOp时,第一个参数标识符不同,一个为MORPH_ERODE(腐蚀),一个为MORPH_DILATE(膨胀)。
morphOp函数的源码在…\opencv\sources\modules\imgproc\src\morph.cpp中的第1286行,有兴趣的朋友们可以研究研究,这里就不费时费力花篇幅展开分析了。
三、浅出——API函数快速上手
3.1 形态学膨胀——dilate函数
erode函数,使用像素邻域内的局部极大运算符来膨胀一张图片,从src输入,由dst输出。支持就地(in-place)操作。
函数原型:
C++: void dilate(
InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor=Point(-1,-1),
int iterations=1,
int borderType=BORDER_CONSTANT,
const Scalar& borderValue=morphologyDefaultBorderValue()
);
参数详解:
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
第三个参数,InputArray类型的kernel,膨胀操作的核。若为NULL时,表示的是使用参考点位于中心3x3的核。
我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。
其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:
矩形: MORPH_RECT
交叉形: MORPH_CROSS
椭圆形: MORPH_ELLIPSE
而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。
我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是
影响了形态学运算结果的偏移。
getStructuringElement函数相关的调用示例代码如下:
int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT,
Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),
Point( g_nStructElementSize, g_nStructElementSize ));
调用这样之后,我们便可以在接下来调用erode或dilate函数时,第三个参数填保存了getStructuringElement返回值的Mat类型变量。对应于我们上面的示例,就是填element变量。
第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。
使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。
调用范例:
//载入原图
Mat image = imread("1.jpg");
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
Mat out;
//进行膨胀操作
dilate(image, out, element);
用上面核心代码架起来的完整程序代码:
//-----------------------------------【头文件包含部分】---------------------------------------
// 描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include <opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include <iostream>
//-----------------------------------【命名空间声明部分】---------------------------------------
// 描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace std;
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
//载入原图
Mat image = imread("1.jpg");
//创建窗口
namedWindow("【原图】膨胀操作");
namedWindow("【效果图】膨胀操作");
//显示原图
imshow("【原图】膨胀操作", image);
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
Mat out;
//进行膨胀操作
dilate(image,out, element);
//显示效果图
imshow("【效果图】膨胀操作", out);
waitKey(0);
return 0;
}
运行截图:
3.2 形态学腐蚀——erode函数
erode函数,使用像素邻域内的局部极小运算符来腐蚀一张图片,从src输入,由dst输出。支持就地(in-place)操作。
看一下函数原型:
C++: void erode(
InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor=Point(-1,-1),
int iterations=1,
int borderType=BORDER_CONSTANT,
const Scalar& borderValue=morphologyDefaultBorderValue()
);
参数详解:
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
第三个参数,InputArray类型的kernel,腐蚀操作的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。(具体看上
文中浅出部分dilate函数的第三个参数讲解部分)
第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于单位(element)的中心,我们一般不用管它。
第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。
同样的,使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。
调用范例:
//载入原图
Mat image = imread("1.jpg");
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
Mat out;
//进行腐蚀操作
erode(image,out, element);
用上面核心代码架起来的完整程序代码:
//-----------------------------------【头文件包含部分】---------------------------------------
// 描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include <opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include <iostream>
//-----------------------------------【命名空间声明部分】---------------------------------------
// 描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace std;
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
//载入原图
Matimage = imread("1.jpg");
//创建窗口
namedWindow("【原图】腐蚀操作");
namedWindow("【效果图】腐蚀操作");
//显示原图
imshow("【原图】腐蚀操作", image);
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
Mat out;
//进行腐蚀操作
erode(image,out, element);
//显示效果图
imshow("【效果图】腐蚀操作", out);
waitKey(0);
return 0;
}
运行结果:
四、综合示例——在实战中熟稔
这个示例程序中的效果图窗口有两个滚动条,顾名思义,第一个滚动条“腐蚀/膨胀”用于在腐蚀/膨胀之间进行切换;第二个滚动条”内核尺寸”用于调节形态学操作时的内核尺寸,以得到效果不同的图像,有一定的可玩性。废话不多说,上代码吧:
//-----------------------------------【程序说明】----------------------------------------------
//
程序名称::《【OpenCV入门教程之十】形态学图像处理(一):膨胀与腐蚀 》 博文配套源码
//
开发所用IDE版本:Visual Studio 2010
//
开发所用OpenCV版本: 2.4.8
//
2014年4月14日 Create by 浅墨
//
浅墨的微博:@浅墨_毛星云
//------------------------------------------------------------------------------------------------
//-----------------------------------【头文件包含部分】---------------------------------------
//
描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include <iostream>
//-----------------------------------【命名空间声明部分】---------------------------------------
//
描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace std;
using namespace cv;
//-----------------------------------【全局变量声明部分】--------------------------------------
//
描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage;//原始图和效果图
int g_nTrackbarNumer = 0;//0表示腐蚀erode, 1表示膨胀dilate
int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
//-----------------------------------【全局函数声明部分】--------------------------------------
//
描述:全局函数声明
//-----------------------------------------------------------------------------------------------
void Process();//膨胀和腐蚀的处理函数
void on_TrackbarNumChange(int, void *);//回调函数
void on_ElementSizeChange(int, void *);//回调函数
//-----------------------------------【main( )函数】--------------------------------------------
//
描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main( )
{
//改变console字体颜色
system("color5E");
//载入原图
g_srcImage= imread("1.jpg");
if(!g_srcImage.data ) { printf("Oh,no,读取srcImage错误~!\n"); return false; }
//显示原始图
namedWindow("【原始图】");
imshow("【原始图】", g_srcImage);
//进行初次腐蚀操作并显示效果图
namedWindow("【效果图】");
//获取自定义核
Matelement = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));
erode(g_srcImage,g_dstImage, element);
imshow("【效果图】", g_dstImage);
//创建轨迹条
createTrackbar("腐蚀/膨胀", "【效果图】", &g_nTrackbarNumer, 1, on_TrackbarNumChange);
createTrackbar("内核尺寸", "【效果图】",&g_nStructElementSize, 21, on_ElementSizeChange);
//输出一些帮助信息
cout<<endl<<"\t嗯。运行成功,请调整滚动条观察图像效果~\n\n"
<<"\t按下“q”键时,程序退出~!\n"
<<"\n\n\t\t\t\tby浅墨";
//轮询获取按键信息,若下q键,程序退出
while(char(waitKey(1))!= 'q') {}
return 0;
}
//-----------------------------【Process( )函数】------------------------------------
//
描述:进行自定义的腐蚀和膨胀操作
//-----------------------------------------------------------------------------------------
void Process()
{
//获取自定义核
Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));
//进行腐蚀或膨胀操作
if(g_nTrackbarNumer== 0) {
erode(g_srcImage,g_dstImage, element);
}
else{
dilate(g_srcImage,g_dstImage, element);
}
//显示效果图
imshow("【效果图】", g_dstImage);
}
//-----------------------------【on_TrackbarNumChange( )函数】------------------------------------
//
描述:腐蚀和膨胀之间切换开关的回调函数
//-----------------------------------------------------------------------------------------------------
void on_TrackbarNumChange(int, void *)
{
//腐蚀和膨胀之间效果已经切换,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
Process();
}
//-----------------------------【on_ElementSizeChange( )函数】-------------------------------------
//
描述:腐蚀和膨胀操作内核改变时的回调函数
//-----------------------------------------------------------------------------------------------------
void on_ElementSizeChange(int, void *)
{
//内核尺寸已改变,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
Process();
}
放出一些效果图吧。原始 图:
膨胀效果图:
腐蚀效果图:
腐蚀和膨胀得到的图,都特有喜感,但千变万变,还是原图好看:
========
OpenCV图像处理篇之腐蚀与膨胀
腐蚀与膨胀
腐蚀和膨胀是图像的形态学处理中最基本的操作,之后遇见的开操作和闭操作都是腐蚀和膨胀操作的结合运算。腐蚀和膨胀的应用非常广泛,而且效果还很好:
腐蚀可以分割(isolate)独立的图像元素,膨胀用于连接(join)相邻的元素,这也是腐蚀和膨胀后图像最直观的展现
去噪:通过低尺寸结构元素的腐蚀操作很容易去掉分散的椒盐噪声点
图像轮廓提取:腐蚀操作
图像分割
等等...(在文后给出一则简单实用膨胀操作提取车牌数字区域的例子)
结构元素是形态学操作中最重要的概念,
erode_show dilate_show
如上图,B为结构元素。
腐蚀操作描述为:扫描图像的每一个像素,用结构元素与其覆盖的二值图像做“与”操作:如果都为1,结果图像的该像素为1,否则为0。
膨胀操作描述为:扫描图像的每一个像素,用结构元素与其覆盖的二值图像做“与”操作:如果都为0,结果图像的该像素为0,否则为1。
以上都是关于二值图像的形态学操作,对于灰度图像:
腐蚀操作
其中,g(x,y)为腐蚀后的灰度图像,f(x,y)为原灰度图像,B为结构元素。腐蚀运算是由结构元素确定的邻域块中选取图像值与结构元素值的差的最小值。
膨胀操作
其中,g(x,y)为腐蚀后的灰度图像,f(x,y)为原灰度图像,B为结构元素。 膨胀运算是由结构元素确定的邻域块中选取图像值与结构元素值的和的最大值。
在灰度图的形态学操作中,一般选择“平摊”的结构元素,即结构元素B的值为0,则上面对灰度图的形态学操作可简化如下:
好了,这就是基本的形态学操作——腐蚀和膨胀,下面是使用OpenCV对图像进行腐蚀和膨胀的程序,还是秉承我们一贯的原则:搁下理论,先直观地感觉图像处理算法的效果,实际项目需要时再深入挖掘!
程序分析
/*
* FileName : eroding_and_dilating.cpp
* Author : xiahouzuoxin @163.com
* Version : v1.0
* Date : Fri 19 Sep 2014 07:42:12 PM CST
* Brief :
*
* Copyright (C) MICL,USTB
*/
#include "cv.h"
#include "highgui.h"
#include "opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;
#define TYPE_MORPH_RECT (0)
#define TYPE_MORPH_CROSS (1)
#define TYPE_MORPH_ELLIPSE (2)
#define MAX_ELE_TYPE (2)
#define MAX_ELE_SIZE (20)
Mat src, erode_dst, dilate_dst;
const char *erode_wn = "eroding demo";
const char *dilate_wn = "dilating demo";
int erode_ele_type;
int dilate_ele_type;
int erode_ele_size;
int dilate_ele_size;
static void Erosion(int, void *);
static void Dilation(int, void *);
/*
* @brief
* @inputs
* @outputs
* @retval
*/
int main(int argc, char *argv[])
{
if (argc < 2) {
cout<<"Usage: ./eroding_and_dilating [file name]"<<endl;
return -1;
}
src = imread(argv[1]);
if (!src.data) {
cout<<"Read image failure."<<endl;
return -1;
}
// Windows
namedWindow(erode_wn, WINDOW_AUTOSIZE);
namedWindow(dilate_wn, WINDOW_AUTOSIZE);
// Track Bar for Erosion
createTrackbar("Element Type\n0:Rect\n1:Cross\n2:Ellipse", erode_wn,
&erode_ele_type, MAX_ELE_TYPE, Erosion); // callback @Erosion
createTrackbar("Element Size: 2n+1", erode_wn,
&erode_ele_size, MAX_ELE_SIZE, Erosion);
// Track Bar for Dilation
createTrackbar("Element Type\n0:Rect\n1:Cross\n2:Ellipse", dilate_wn,
&dilate_ele_type, MAX_ELE_TYPE, Dilation); // callback @Erosion
createTrackbar("Element Size: 2n+1", dilate_wn,
&dilate_ele_size, MAX_ELE_SIZE, Dilation);
// Default start
Erosion(0, 0);
Dilation(0, 0);
waitKey(0);
return 0;
}
/*
* @brief 腐蚀操作的回调函数
* @inputs
* @outputs
* @retval
*/
static void Erosion(int, void *)
{
int erode_type;
switch (erode_ele_type) {
case TYPE_MORPH_RECT:
erode_type = MORPH_RECT;
break;
case TYPE_MORPH_CROSS:
erode_type = MORPH_CROSS;
break;
case TYPE_MORPH_ELLIPSE:
erode_type = MORPH_ELLIPSE;
break;
default:
erode_type = MORPH_RECT;
break;
}
Mat ele = getStructuringElement(erode_type, Size(2*erode_ele_size+1, 2*erode_ele_size+1),
Point(erode_ele_size, erode_ele_size));
erode(src, erode_dst, ele);
imshow(erode_wn, erode_dst);
}
/*
* @brief 膨胀操作的回调函数
* @inputs
* @outputs
* @retval
*/
static void Dilation(int, void *)
{
int dilate_type;
switch (dilate_ele_type) {
case TYPE_MORPH_RECT:
dilate_type = MORPH_RECT;
break;
case TYPE_MORPH_CROSS:
dilate_type = MORPH_CROSS;
break;
case TYPE_MORPH_ELLIPSE:
dilate_type = MORPH_ELLIPSE;
break;
default:
dilate_type = MORPH_RECT;
break;
}
Mat ele = getStructuringElement(dilate_type, Size(2*dilate_ele_size+1, 2*dilate_ele_size+1),
Point(dilate_ele_size, dilate_ele_size));
dilate(src, dilate_dst, ele);
imshow(dilate_wn, dilate_dst);
}
膨胀和腐蚀操作的函数分别是erode和dilate,传递给他们的参数也都依次是原图像、形态学操作后的图像、结构元素ele。本程序中给出了3种结构元素类型,分别是
#define TYPE_MORPH_RECT (0) // 矩形
#define TYPE_MORPH_CROSS (1) // 十字交叉型
#define TYPE_MORPH_ELLIPSE (2) // 椭圆型
再通过OpenCV提供的getStructuringElement函数创建Mat类型的结构元素。
getStructuringElement的参数依次是结构元素类型(OpenCV中提供了宏定义MORPH_RECT、MORPH_CROSS和MORPH_ELLIPSE表示)、结构元素大小。
这里我们首次接触了createTrackbar函数(声明在highgui.hpp中),该函数的功能是给窗口添加滑动条。其原型是:
CV_EXPORTS int createTrackbar( const string& trackbarname, const string& winname,
int* value, int count,
TrackbarCallback onChange=0,
void* userdata=0);
trackbarname为滑动条的名称,将会显示在滑动条的前面,参见结果中的图片显示; winname为窗口名; value为滑动条关联的变量,如上面程序中第一个滑动条关联到erode_ele_type,表示——当滑动条滑动变化时,erode_ele_type的值发生响应的变化; count表示
滑动条能滑动到的最大值; TrackbarCallback onChange其实是这个函数的关键,是滑动条变化时调用的回调函数。当滑动条滑动时,value值发生变化,系统立刻调用onChange函数,执行相关的操作,回调函数的定义形式是固定的:
void onChange(int, void *)
程序中的回调函数Erosion和Dilation函数的定义都遵循该形式:
static void Erosion(int, void *);
static void Dilation(int, void *);
结果及实际应用
对“黑白小猪”进行膨胀操作的变化(随着结构元素大小的变化)如下图:
dilating_demo
对“黑白小猪”进行腐蚀操作的变化(随着结构元素大小的变化)如下图:
eroding_demo
膨胀与腐蚀在图像处理中具有广泛的用途,比如提取车牌过程中,可以通过膨胀运算确定车牌的区域。如下图为通过sobel算子提取边缘后的车牌,
car_plate
为去掉边界,确定车牌在图中的位置,可以通过膨胀操作,结果如下:
car_plate_dilate
上图中的红线区域就是膨胀后能用于确定车牌的连通区域,再通过对连通区域的搜索及“车牌的矩形特性”即可确定含有车牌数字在图片中的位置。
========
OpenCV&Qt学习之三-图像的初步处理 Qt图像的缩放显示
实现图像缩放的方法很多,在 OpenCV&Qt学习之一——打开图片文件并显示 的例程中,label控件是通过
ui->imagelabel->resize(ui->imagelabel->pixmap()->size());
来实现适应图像显示的,但是由于窗口固定,在打开的图像小于控件大小时就会缩在左上角显示,在打开图像过大时则显示不全。因此这个例程中首先实现图像适合窗口的缩放显示。
由于是基于OpenCV和Qt的图像处理,因此图像的缩放处理在OpenCV和Qt都可以完成,我这里就把OpenCV用作图像的原始处理,Qt用作显示处理,因此缩放显示由Qt完成。
Qt中QImage提供了用于缩放的基本函数,而且功能非常强大,使用Qt自带的帮助可以检索到相关信息。
函数原型:
QImage QImage::scaled ( const QSize & size, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation ) const
这是直接获取大小,还有另一种形式:
QImage QImage::scaled ( int width, int height, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation ) const
函数说明以及参数在文档中已经说的非常清楚了,文档摘录如下:
Returns a copy of the image scaled to a rectangle defined by the given size according to the given aspectRatioMode and transformMode.
image
If aspectRatioMode is Qt::IgnoreAspectRatio, the image is scaled to size.
If aspectRatioMode is Qt::KeepAspectRatio, the image is scaled to a rectangle as large as possible inside size, preserving the aspect ratio.
If aspectRatioMode is Qt::KeepAspectRatioByExpanding, the image is scaled to a rectangle as small as possible outside size, preserving the aspect ratio.
官方文档中已经说的比较清楚了,代码实现也比较简单,代码如下:
{
QImage imgScaled ;
imgScaled = img.scaled(ui->imagelabel->size(),Qt::KeepAspectRatio);
// imgScaled = img.QImage::scaled(ui->imagelabel->width(),ui->imagelabel->height(),Qt::KeepAspectRatio);
ui->imagelabel->setPixmap(QPixmap::fromImage(imgScaled));
}
显示效果如下:
image
QImage的一点疑问与理解
在查找资料时参考了这篇 Qt中图像的显示与基本操作 博客,但是存在一些疑点,博客中相关代码如下:
QImage* imgScaled = new QImage;
*imgScaled=img->scaled(width,height,Qt::KeepAspectRatio);
ui->label->setPixmap(QPixmap::fromImage(*imgScaled));
对于以上代码通过和我之前的代码做简单对比,发现有几点不一样的地方:
图像的定义方式,这里的定义方式为QImage* imgScale = new QImage
scaled函数的调用方式,一个是imgScaled = img.scaled后者为*imgScaled=img->scaled,我最开始也是将.写为->一直没找出错误,提示base operand of '->' has non-pointer type 'QImage'
继续查找Qt的帮助手册,发现QImage的构造函数还真是多:
Public Functions
QImage ()
QImage ( const QSize & size, Format format )
QImage ( int width, int height, Format format )
QImage ( uchar * data, int width, int height, Format format )
QImage ( const uchar * data, int width, int height, Format format )
QImage ( uchar * data, int width, int height, int bytesPerLine, Format format )
QImage ( const uchar * data, int width, int height, int bytesPerLine, Format format )
QImage ( const char * const[] xpm )
QImage ( const QString & fileName, const char * format = 0 )
QImage ( const char * fileName, const char * format = 0 )
QImage ( const QImage & image )
~QImage ()
QImage提供了适用于不同场合的构造方式,在手册中对他们也有具体的应用,但是我仍然没找到QImage image;和QImage* image = new QImage这两种究竟对应的是哪两种,有什么区别和不同。 在上一篇博文 OpenCV&Qt学习之二——QImage的进一步认识 中提到了对于
图像数据的一点认识,其中提到QImage是对现有数据的一种重新整合,是一种格式,但是数据还是指向原来的。从这里来看还需要根据构造方式具体区别,并不完全正确。
凌乱查了查资料,网上的资料就那几个,互相转来转去的,而且多数比较老,仍然没有帮助我想通关于这里面数据结构的一些疑问,Qt 和 OpenCV对C和指针的要求还是比较高的,长时间从单片机类的程序过来那点功底还真不够,具体的C应用都忘光了。这个问题只能暂
时搁置,在后面的学习中慢慢理解。
基于OpenCV的图像初步处理
以下两个例程根据书籍 OpenCV 2 Computer Vision Application Programming Cookbook中的相关例程整理,这是一本比较新也比较基础的入门书籍。
salt-and-pepper noise
关于图像数据的基础知识参见这段介绍:
Fundamentally, an image is a matrix of numerical values. This is why OpenCV 2 manipulates them using the cv::Mat data structure. Each element of the matrix represents one pixel. For a gray-level image (a "black-and-white" image), pixels
are unsigned 8-bit values where 0 corresponds to black and corresponds 255 to white. For a color image, three such values per pixel are required to represent the usual three primary color channels {Red, Green, Blue}. A matrix element is
therefore made, in this case, of a triplet of values.
这儿以想图像中添加saltand-pepper noise为例,来说明如何访问图像矩阵中的独立元素。saltand-pepper noise就是图片中一些像素点,随机的被黑色或者白色的像素点所替代,因此添加saltand-pepper noise也比较简单,只需要随机的产生行和列,将这些行列值对
应的像素值更改即可,当然通过上面的介绍,需要更改RGB3个通道。程序如下:
void Widget::salt(cv::Mat &image, int n)
{
int i,j;
for (int k=0; k<n; k++)
{
i= qrand()%image.cols;
j= qrand()%image.rows;
if (image.channels() == 1) { // gray-level image
image.at<uchar>(j,i)= 255;
} else if (image.channels() == 3) { // color image
image.at<cv::Vec3b>(j,i)[0]= 255;
image.at<cv::Vec3b>(j,i)[1]= 255;
image.at<cv::Vec3b>(j,i)[2]= 255;
}
}
}
对Win 7系统中的自带图像考拉进行处理后的效果如下图所示(程序是Ubuntu 12.04下的):
image
减少色彩位数
在很多处理中需要对图片中的所有像素进行遍历操作,采用什么方式进行这个操作是需要思考的问题,关于这个问题的论述可以参考下面一段简介:
Color images are composed of 3-channel pixels. Each of these channels corresponds to the intensity value of one of the three primary colors (red, green, blue). Since each of these values is an 8-bit unsigned char, the total number of
colors is 256x256x256, which is more than 16 million colors. Consequently, to reduce the complexity of an analysis, it is sometimes useful to reduce the number of colors in an image. One simple way to achieve this goal is to simply
subdivide the RGB space into cubes of equal sizes. For example, if you reduce the number of colors in each dimension by 8, then you would obtain a total of 32x32x32 colors. Each color in the original image is then assigned a new color
value in the color-reduced image that corresponds to the value in the center of the cube to which it belongs.
这个例子就是通过操作每一个像素点来减少色彩的位数,基本内容在以上的英文引文中已经有了介绍,代码的实现也比较直接。在彩色图像中,3个通道的数据是依次排列的,每一行的像素三个通道的值依次排列,cv::Mat中的通道排列顺序为BGR,那么一个图像需要的地
址块空间为uchar 宽×高×3.但是需要注意的是,有些处理器针对行数为4或8的图像处理更有效率,因此为了提高效率就会填充一些额外的像素,这些额外的像素不被显示和保存,值是忽略的。
实现这个功能的代码如下:
// using .ptr and []
void Widget::colorReduce0(cv::Mat &image, int div)
{
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
for (int j=0; j<nl; j++)
{
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++)
{
// process each pixel ---------------------
data[i]= data[i]/div*div+div/2;
// end of pixel processing ----------------
} // end of line
}
}
data[i]= data[i]/div*div+div/2; 通过整除的方式,就像素位数进行减少,这里没明白的是为啥后面还要加上div/2。
效果如下:
image
程序源代码:
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_openButton_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this,tr("Open Image"),
".",tr("Image Files (*.png *.jpg *.bmp)"));
qDebug()<<"filenames:"<<fileName;
image = cv::imread(fileName.toAscii().data());
ui->imgfilelabel->setText(fileName);
//here use 2 ways to make a copy
// image.copyTo(originalimg); //make a copy
originalimg = image.clone(); //clone the img
qimg = Widget::Mat2QImage(image);
display(qimg); //display by the label
if(image.data)
{
ui->saltButton->setEnabled(true);
ui->originalButton->setEnabled(true);
ui->reduceButton->setEnabled(true);
}
}
QImage Widget::Mat2QImage(const cv::Mat &mat)
{
QImage img;
if(mat.channels()==3)
{
//cvt Mat BGR 2 QImage RGB
cvtColor(mat,rgb,CV_BGR2RGB);
img =QImage((const unsigned char*)(rgb.data),
rgb.cols,rgb.rows,
rgb.cols*rgb.channels(),
QImage::Format_RGB888);
}
else
{
img =QImage((const unsigned char*)(mat.data),
mat.cols,mat.rows,
mat.cols*mat.channels(),
QImage::Format_RGB888);
}
return img;
}
void Widget::display(QImage img)
{
QImage imgScaled;
imgScaled = img.scaled(ui->imagelabel->size(),Qt::KeepAspectRatio);
// imgScaled = img.QImage::scaled(ui->imagelabel->width(),ui->imagelabel->height(),Qt::KeepAspectRatio);
ui->imagelabel->setPixmap(QPixmap::fromImage(imgScaled));
}
void Widget::on_originalButton_clicked()
{
qimg = Widget::Mat2QImage(originalimg);
display(qimg);
}
void Widget::on_saltButton_clicked()
{
salt(image,3000);
qimg = Widget::Mat2QImage(image);
display(qimg);
}
void Widget::on_reduceButton_clicked()
{
colorReduce0(image,64);
qimg = Widget::Mat2QImage(image);
display(qimg);
}
void Widget::salt(cv::Mat &image, int n)
{
int i,j;
for (int k=0; k<n; k++)
{
i= qrand()%image.cols;
j= qrand()%image.rows;
if (image.channels() == 1)
{ // gray-level image
image.at<uchar>(j,i)= 255;
}
else if (image.channels() == 3)
{ // color image
image.at<cv::Vec3b>(j,i)[0]= 255;
image.at<cv::Vec3b>(j,i)[1]= 255;
image.at<cv::Vec3b>(j,i)[2]= 255;
}
}
}
// using .ptr and []
void Widget::colorReduce0(cv::Mat &image, int div)
{
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
for (int j=0; j<nl; j++)
{
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++)
{
// process each pixel ---------------------
data[i]= data[i]/div*div+div/2;
// end of pixel processing ----------------
} // end of line
}
}
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QImage>
#include <QFileDialog>
#include <QTimer>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_openButton_clicked();
QImage Mat2QImage(const cv::Mat &mat);
void display(QImage image);
void salt(cv::Mat &image, int n);
void on_saltButton_clicked();
void on_reduceButton_clicked();
void colorReduce0(cv::Mat &image, int div);
void on_originalButton_clicked();
private:
Ui::Widget *ui;
cv::Mat image;
cv::Mat originalimg; //store the original img
QImage qimg;
QImage imgScaled;
cv::Mat rgb;
};
#endif // WIDGET_H
书中还给了其他十余种操作的方法:
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
// using .ptr and []
void colorReduce0(cv::Mat &image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
data[i]= data[i]/div*div + div/2;
// end of pixel processing ----------------
} // end of line
}
}
// using .ptr and * ++
void colorReduce1(cv::Mat &image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
*data++= *data/div*div + div/2;
// end of pixel processing ----------------
} // end of line
}
}
// using .ptr and * ++ and modulo
void colorReduce2(cv::Mat &image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
int v= *data;
*data++= v - v%div + div/2;
// end of pixel processing ----------------
} // end of line
}
}
// using .ptr and * ++ and bitwise
void colorReduce3(cv::Mat &image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
// mask used to round the pixel value
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
*data++= *data&mask + div/2;
// end of pixel processing ----------------
} // end of line
}
}
// direct pointer arithmetic
void colorReduce4(cv::Mat &image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
int step= image.step; // effective width
// mask used to round the pixel value
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
// get the pointer to the image buffer
uchar *data= image.data;
for (int j=0; j<nl; j++) {
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
*(data+i)= *data&mask + div/2;
// end of pixel processing ----------------
} // end of line
data+= step; // next line
}
}
// using .ptr and * ++ and bitwise with image.cols * image.channels()
void colorReduce5(cv::Mat &image, int div=64) {
int nl= image.rows; // number of lines
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
// mask used to round the pixel value
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<image.cols * image.channels(); i++) {
// process each pixel ---------------------
*data++= *data&mask + div/2;
// end of pixel processing ----------------
} // end of line
}
}
// using .ptr and * ++ and bitwise (continuous)
void colorReduce6(cv::Mat &image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
if (image.isContinuous()) {
// then no padded pixels
nc= nc*nl;
nl= 1; // it is now a 1D array
}
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
// mask used to round the pixel value
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
*data++= *data&mask + div/2;
// end of pixel processing ----------------
} // end of line
}
}
// using .ptr and * ++ and bitwise (continuous+channels)
void colorReduce7(cv::Mat &image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols ; // number of columns
if (image.isContinuous()) {
// then no padded pixels
nc= nc*nl;
nl= 1; // it is now a 1D array
}
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
// mask used to round the pixel value
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
*data++= *data&mask + div/2;
*data++= *data&mask + div/2;
*data++= *data&mask + div/2;
// end of pixel processing ----------------
} // end of line
}
}
// using Mat_ iterator
void colorReduce8(cv::Mat &image, int div=64) {
// get iterators
cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::Vec3b>();
for ( ; it!= itend; ++it) {
// process each pixel ---------------------
(*it)[0]= (*it)[0]/div*div + div/2;
(*it)[1]= (*it)[1]/div*div + div/2;
(*it)[2]= (*it)[2]/div*div + div/2;
// end of pixel processing ----------------
}
}
// using Mat_ iterator and bitwise
void colorReduce9(cv::Mat &image, int div=64) {
// div must be a power of 2
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
// mask used to round the pixel value
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
// get iterators
cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::Vec3b>();
// scan all pixels
for ( ; it!= itend; ++it) {
// process each pixel ---------------------
(*it)[0]= (*it)[0]&mask + div/2;
(*it)[1]= (*it)[1]&mask + div/2;
(*it)[2]= (*it)[2]&mask + div/2;
// end of pixel processing ----------------
}
}
// using MatIterator_
void colorReduce10(cv::Mat &image, int div=64) {
// get iterators
cv::Mat_<cv::Vec3b> cimage= image;
cv::Mat_<cv::Vec3b>::iterator it=cimage.begin();
cv::Mat_<cv::Vec3b>::iterator itend=cimage.end();
for ( ; it!= itend; it++) {
// process each pixel ---------------------
(*it)[0]= (*it)[0]/div*div + div/2;
(*it)[1]= (*it)[1]/div*div + div/2;
(*it)[2]= (*it)[2]/div*div + div/2;
// end of pixel processing ----------------
}
}
void colorReduce11(cv::Mat &image, int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols; // number of columns
for (int j=0; j<nl; j++) {
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
image.at<cv::Vec3b>(j,i)[0]= image.at<cv::Vec3b>(j,i)[0]/div*div + div/2;
image.at<cv::Vec3b>(j,i)[1]= image.at<cv::Vec3b>(j,i)[1]/div*div + div/2;
image.at<cv::Vec3b>(j,i)[2]= image.at<cv::Vec3b>(j,i)[2]/div*div + div/2;
// end of pixel processing ----------------
} // end of line
}
}
// with input/ouput images
void colorReduce12(const cv::Mat &image, // input image
cv::Mat &result, // output image
int div=64) {
int nl= image.rows; // number of lines
int nc= image.cols ; // number of columns
// allocate output image if necessary
result.create(image.rows,image.cols,image.type());
// created images have no padded pixels
nc= nc*nl;
nl= 1; // it is now a 1D array
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
// mask used to round the pixel value
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
for (int j=0; j<nl; j++) {
uchar* data= result.ptr<uchar>(j);
const uchar* idata= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// process each pixel ---------------------
*data++= (*idata++)&mask + div/2;
*data++= (*idata++)&mask + div/2;
*data++= (*idata++)&mask + div/2;
// end of pixel processing ----------------
} // end of line
}
}
// using overloaded operators
void colorReduce13(cv::Mat &image, int div=64) {
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
// mask used to round the pixel value
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
// perform color reduction
image=(image&cv::Scalar(mask,mask,mask))+cv::Scalar(div/2,div/2,div/2);
}
========
OpenCV图像处理 空间域图像增强
(图像锐化 1 基于拉普拉斯算子)
OpenCV
OpenCV 图像锐化
拉普拉斯算子 ( Laplacian operator )
Quote :
It is indeed a well-known result in image processing that if you subtract its Laplacian from an image, the image edges are amplified giving a sharper image. [From OpenCV 2 Computer Vision Application Programming Cookbook]
对于求一个锐化后的像素点(sharpened_pixel),这个基于拉普拉斯算子的简单算法主要是遍历图像中的像素点,根据领域像素确定其锐化后的值
计算公式:sharpened_pixel = 5 * current – left – right – up – down ; [见Code1]
OpenCV图像处理 空间域图像增强(图像锐化 1 基于拉普拉斯算子) - ___________杰 - __________Ggicci
当一个运算是通过领域像素进行的时候,我们通常用一个矩阵来表示这种运算关系,也就是我们经常所说的 核 (Kernel) 。那么上面的 锐化滤波器 (Sharpening Filter) 就可以用这个矩阵表示为它的核:
-1
-1
5
-1
-1
因为 滤波 在图像处理中是一个非常普通且常用的操作,所以OpenCV里面已经定义了一个特殊的函数用来执行这个操作。要使用它的话只需要定义一个 核 ,然后作为参数传递就行了。[见Code2]
Code 1 :
/*
Author : Ggicci
Date : 2012.07.19
File : sharp.h
*/
#pragma once
#include <opencv\cv.h>
using namespace cv;
namespace ggicci
{
void sharpen(const Mat& img, Mat& result);
}
/*
Author : Ggicci
Date : 2012.07.19
File : sharp.cpp
*/
#include "sharp.h"
void ggicci::sharpen(const Mat& img, Mat& result)
{
result.create(img.size(), img.type());
//处理边界内部的像素点, 图像最外围的像素点应该额外处理
for (int row = 1; row < img.rows-1; row++)
{
//前一行像素点
const uchar* previous = img.ptr<const uchar>(row-1);
//待处理的当前行
const uchar* current = img.ptr<const uchar>(row);
//下一行
const uchar* next = img.ptr<const uchar>(row+1);
uchar *output = result.ptr<uchar>(row);
int ch = img.channels();
int starts = ch;
int ends = (img.cols - 1) * ch;
for (int col = starts; col < ends; col++)
{
//输出图像的遍历指针与当前行的指针同步递增, 以每行的每一个像素点的每一个通道值为一个递增量, 因为要考虑到图像的通道数
*output++ = saturate_cast<uchar>(5 * current[col] - current[col-ch] - current[col+ch] - previous[col] - next[col]);
}
} //end loop
//处理边界, 外围像素点设为 0
result.row(0).setTo(Scalar::all(0));
result.row(result.rows-1).setTo(Scalar::all(0));
result.col(0).setTo(Scalar::all(0));
result.col(result.cols-1).setTo(Scalar::all(0));
}
/*
Author : Ggicci
Date : 2012.07.19
File : main.cpp
*/
#include <opencv\highgui.h>
#pragma comment(lib, "opencv_core231d.lib")
#pragma comment(lib, "opencv_highgui231d.lib")
#pragma comment(lib, "opencv_imgproc231d.lib")
using namespace cv;
#include "sharp.h"
int main()
{
Mat lena = imread("lena.jpg");
Mat sharpenedLena;
ggicci::sharpen(lena, sharpenedLena);
imshow("lena", lena);
imshow("sharpened lena", sharpenedLena);
cvWaitKey();
return 0;
}
Output 1 :
OpenCV图像处理 空间域图像增强(图像锐化 1 基于拉普拉斯算子) - ___________杰 - __________Ggicci
Code 2 :
1: int main()
2: {
3: Mat lena = imread("lena.jpg");
4: Mat sharpenedLena;
5: Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
6: cv::filter2D(lena, sharpenedLena, lena.depth(), kernel);
7:
8: imshow("lena", lena);
9: imshow("sharpened lena", sharpenedLena);
10: cvWaitKey();
11: return 0;
12: }
Output 2 :
OpenCV图像处理 空间域图像增强(图像锐化 1 基于拉普拉斯算子) - ___________杰 - __________Ggicci
End :
Author : Ggicci
========
OpenCV图像处理 图像的点运算 灰度直方图
OpenCV灰度直方图
Theory :
从图形上看,灰度直方图是一个二维图:
gray_hist
图像的灰度直方图是一个离散函数,它表示图像每一灰度级与该灰度级出现频率的对应关系。假设一幅图像的像素总数为 N,灰度级总数为 L,其中灰度级为 g 的像素总数为 Ng,则这幅数字图像的灰度直方图横坐标即为灰度 g ( 0 ≤ g ≤ L-1 ),纵坐标则为灰度值
出现的次数 Ng。实际上,用 N 去除各个灰度值出现的次数 Ng 即可得到各个灰度级出现的概率 Pg = Ng / N = Ng / ∑Ng ,从而得到归一化的灰度直方图,其纵坐标为概率 Pg 。
Quote : ( From [OpenCV 2 Computer Vision Application Programming Cookbook (Robert Langaniere, 2011) ], 引用作直方图的解释 )
A histogram is a simple table that gives the number of pixels that have a given value in an image (or sometime a set of images). The histogram of a gray-level image will therefore have 256 entries (or bins).
Histograms can also be normalized such that sum of the bins equals 1. In that case, each bin gives the percentage of pixels having this specific value in the image.
Implementation :
利用 OpenCV 提供的 calcHist 函数 :
void calcHist(const Mat* arrays, int narrays, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false );
这个函数用于计算直方图是很强大的,在这里就实现一个最简单的灰度图像的直方图计算。
Code :
int main()
{
Mat img = imread("lena.jpg", CV_LOAD_IMAGE_GRAYSCALE);
Mat* arrays = &img;
int narrays = 1;
int channels[] = { 0 };
InputArray mask = noArray();
Mat hist;
int dims = 1;
int histSize[] = { 256 };
float hranges[] = { 0.0, 255.0 };
const float *ranges[] = { hranges };
//调用 calcHist 计算直方图, 结果存放在 hist 中
calcHist(arrays, narrays, channels, mask, hist, dims, histSize, ranges);
//调用一个我自己写的简单的函数用于获取一张显示直方图数据的图片,
//输入参数为直方图数据 hist 和期望得到的图片的尺寸
Mat histImg = ggicci::getHistogram1DImage(hist, Size(600, 420));
imshow("lena gray image histogram", histImg);
waitKey();
}
Mat ggicci::getHistogram1DImage(const Mat& hist, Size imgSize)
{
Mat histImg(imgSize, CV_8UC3);
int Padding = 10;
int W = imgSize.width - 2 * Padding;
int H = imgSize.height - 2 * Padding;
double _max;
minMaxLoc(hist, NULL, &_max);
double Per = (double)H / _max;
const Point Orig(Padding, imgSize.height-Padding);
int bin = W / (hist.rows + 2);
//画方柱
for (int i = 1; i <= hist.rows; i++)
{
Point pBottom(Orig.x + i * bin, Orig.y);
Point pTop(pBottom.x, pBottom.y - Per * hist.at<float>(i-1));
line(histImg, pBottom, pTop, Scalar(255, 0, 0), bin);
}
//画 3 条红线标明区域
line(histImg, Point(Orig.x + bin, Orig.y - H), Point(Orig.x + hist.rows * bin, Orig.y - H), Scalar(0, 0, 255), 1);
line(histImg, Point(Orig.x + bin, Orig.y), Point(Orig.x + bin, Orig.y - H), Scalar(0, 0, 255), 1);
line(histImg, Point(Orig.x + hist.rows * bin, Orig.y), Point(Orig.x + hist.rows * bin, Orig.y - H), Scalar(0, 0, 255), 1);
drawArrow(histImg, Orig, Orig+Point(W, 0), 10, 30, Scalar::all(0), 2);
drawArrow(histImg, Orig, Orig-Point(0, H), 10, 30, Scalar::all(0), 2);
return histImg;
}
Result :
lenaimage
airplaneimage
End :
Author : Ggicci
========
OpenCV:图像的加载显示及简单变换
摘要(Abstract) 通过笔记一的学习,我们已经能够下载、安装OpenCV并新建VS2010项目进行相关的配置,笔记一也已完成第一个程序HelloCV的演示。本文首先通过详细介绍OpenCV中如何从硬盘加载/读取一幅图像,并在窗口中进行显示来对笔记一中的演示程序做详解
。其次,本文实现了简单的图像变换,将一幅RGB颜色的图片lena.jpg转化成灰度图像,以达到修改的目的,另外,在此变换中,我们还对如何将图片保存到硬盘上进行讲解。实验结果表明,通过笔记二的学习,不但能够增强对OpenCV的学习兴趣,还能有初体验OpenCV的
成就感,吃嘛嘛香,为后续的学习打下坚实的基础。
1、加载并显示图像(Load and Display an Image)
1.1 学习目标
在本节中,我们预期达到以下学习目标:1) 加载一幅图像(采用imread方法);2)创建一个指定的OpenCV窗口(采用namedWindow方法);3)在OpenCV窗口中显示图像(采用imshow方法)。
1.2 源代码
#include “StdAfx.h”
#include <string>
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
using namespace cv;
using namespace std;
int main()
{
string imageName = “lena.jpg”;
//读入图像
Mat img = imread(imageName, CV_LOAD_IMAGE_COLOR);
//如果读入图像失败
if (img.empty())
{
cout<<”Could not open or find the image!”<<endl;
return -1;
}
//创建窗口
namedWindow(“lena”, CV_WINDOW_AUTOSIZE);
//显示图像
imshow(“lena”, img);
//等待按键,按键盘任意键返回
waitKey();
return 0;
}
1.3 源码详解
1.3.1 头文件
OpenCV有许多不同的模块,每个模块关心图像处理中不同的领域及方法(参见:OpenCV学习笔记(基于OpenCV 2.4)一:哈喽CV),在使用之前我们首先需要对相应的头文件进行包含,一般情况下我们都会用到的两个模块:
1)core section. 这里定义了OpenCV的一些基本的块(Blocks);
2)highgui module. 该模块包含了一些图像的输入输出操作(UI)。
另外,为了能够在控制台做输入输出,我们会包含iostream,而string是用于字符串的处理。接下来,为了防止OpenCV的数据结构或命名与其它库函数比如STL有冲突,我们引入命名空间cv,在有冲突的情况下可以用前缀cv::来指定具体使用哪个库(关于命名空间,我们
会在下一讲做详细介绍)。
1.3.2 主函数
--------------------------------------------------------------------------------
Mat img = imread(imageName, CV_LOAD_IMAGE_COLOR);
--------------------------------------------------------------------------------
imread的函数原型是:Mat imread( const string& filename, int flags=1 );
Mat是OpenCV里的一个数据结构,在这里我们定义一个Mat类型的变量img,用于保存读入的图像,在本文开始有写到,我们用imread函数来读取图像,第一个字段标识图像的文件名(包括扩展名),第二个字段用于指定读入图像的颜色和深度,它的取值可以有以下几种:
1) CV_LOAD_IMAGE_UNCHANGED (<0),以原始图像读取(包括alpha通道),
2) CV_LOAD_IMAGE_GRAYSCALE ( 0),以灰度图像读取
3) CV_LOAD_IMAGE_COLOR (>0),以RGB格式读取
这三点是在OpenCV的官方教程(opencv_tutorials.pdf)里摘录并翻译过来的,但是网上还有关于CV_LOAD_IMAGE_ANYDEPTH和CV_LOAD_IMAGE_ANYCOLOR的传说,而且查看OpenCV的源码可以发现,这些取值放在一个枚举(enum)类型中(opencv\build\include
\opencv2\highgui\highgui_c.h),定义如下:
enum
{
/* 8bit, color or not */
CV_LOAD_IMAGE_UNCHANGED =-1,
/* 8bit, gray */
CV_LOAD_IMAGE_GRAYSCALE =0,
/* ?, color */
CV_LOAD_IMAGE_COLOR =1,
/* any depth, ? */
CV_LOAD_IMAGE_ANYDEPTH =2,
/* ?, any color */
CV_LOAD_IMAGE_ANYCOLOR =4
};
关于该枚举类型的详细信息,官方教程的写法跟官方发布的正式版代码描述的不相同,可能是我理解不够深入,或者两者是等价的,这点以后再找时间研究,但这并不影响我们对这一章节的学习。
Note:OpenCV提供了多种格式图像的支持,包括Windows bitmap(bmp),portable image formats (pbm, pgm,ppm) 以及 Sun raster (sr, ras)。关于其它的格式,有JPEG (jpeg, jpg, jpe),JPEG 2000,TIFF 文件 (tiff, tif),portable network graphics (png),
还有openEXR格式,如果OpenCV是自己打包的话,读取这些格式需要有插件支持,如果是官方提供的库,则不需再添加插件。
在检查图像是否读取成功之后,我们需要显示读入的图像,因此,我们采用namedWindow函数来创建一个OpenCV窗口,该函数的定义如下:
CV_EXPORTS_W void namedWindow(const string& winname, int flags = WINDOW_AUTOSIZE);
为此,我们需要指定该窗口的名称(窗口标识符, window identifier)以及如何处理窗口大小。①窗口标识符需要唯一指定,如果已经存在一个该名字的窗口,则此函数不进行任何处理;②flags参数目前只支持CV_WINDOW_AUTOSIZE,在highgui_c.h中,OpenCV定义了
CV_WINDOW_AUTOSIZE= 0×00000001,如果设置该参数,则表示显示的时候窗口自适应于需要显示的图像,而且不能修改窗口大小,如果不设置(省略此参数),可以通过代码进行修改;③如果将OpenCV用于Qt后端开发,该参数还支持其它值,具体可查看OpenCV开发文档
,这里不再赘述。
--------------------------------------------------------------------------------
imshow(“lena”, img);
--------------------------------------------------------------------------------
imshow用于在我们创建的窗口中显示需要显示的图像,其函数原型为:
void imshow(const string& winname, InputArray mat);
第一个参数winname是指窗口的名称,也就是窗口标识符,第二个参数mat就是我们要显示的图像了。正如我们在namedWindow函数中所描述的那样,如果namedWindow指定了参数CV_WINDOW_AUTOSIZE,则图像会按原始大小显示,否则图像会根据窗口大小进行缩放。
--------------------------------------------------------------------------------
waitKey();
--------------------------------------------------------------------------------
这条语句表示等待用户键盘操作,waitKey函数的函数原型如下:
int waitKey(int delay = 0);
我们可以看到,该函数可包含一个整形参数,不设置参数的情况下,默认为0,也就是无限制等待。该整数表示需要等待用户操作的毫秒数。
2 加载、修改并保存图像(Load, Modify, and Save an Image)
2.1 学习目标
在这一章,我们将学习:1)加载一副图像(采用imread函数,同第一章);2)将一副图像从RGB格式转换成灰度图(grayscale,采用cvtColor函数);3)保存转换后的图像到磁盘上(采用imwrite函数)。
2.2 源代码
<pre lang=”cpp”>
#include “StdAfx.h”
#include <cv.h>
#include <highgui.h>
#include <string>
using namespace cv;
using namespace std;
int main()
{
char* imageName = “lena.jpg”;
Mat image = imread(imageName, 1);
if (!image.data)
{
cout<<”Could not open or find the image!”<<endl;
return -1;
}
Mat gray_image;
String grayImageName = “lena_gray”;
cvtColor(image,gray_image,CV_RGB2GRAY);//将RGB图像转换成灰度图像
imwrite(“../../lena_gray.jpg”,gray_image);//保存图像
namedWindow(imageName, CV_WINDOW_AUTOSIZE);//创建用于显示元图像窗口
namedWindow(grayImageName,CV_WINDOW_AUTOSIZE);//创建用于显示转换后图像窗口
imshow(imageName,image);
imshow(“grayImageName”, gray_image);
waitKey(0);
return 0;
}
</pre>
2.3 源码详解
有了第一章的基础后,再来理解本章代码相对就很容易,在这一节,关于imread函数的使用则不再赘述,下面给cvtColor和imwrite来一个特写。
--------------------------------------------------------------------------------
cvtColor(image,gray_image,CV_RGB2GRAY);// 将RGB图像转换成灰度图像
--------------------------------------------------------------------------------
cvtColor函数用于将图像从一个颜色空间转换到另一个颜色空间,其函数原型为:
void cvtColor( InputArray src, OutputArray dst, int code, int dstCn=0 );
参数src:是指需要转化的图像,可以是8位或16位等的无符号型或者是单精度浮点型(Single-Precision Floating-Point);
参数dst:与原始图像具有相同大小和深度的目标图像;
参数code:颜色空间转换代码;
参数dstCn:目标图像的通道数,如果该参数为0,则通道数可由src和code自动获得;
对于一个原图像或目标图像是RGB的转换,我们需要详细地指定通道的顺序(RGB or BGR)。我们注意到,OpenCV默认情况下的颜色格式一般是指RGB,但实际上却进行了一个反转变成BGR,因此对一个标准的24位图像来说,其第一个字节为8位的蓝色部分(Blue
Component),其次是绿色,接着是红色,再然后就是第二个像素,同样以BGR的通道顺序排列。
常规的RGB通道的值的范围如下:
对于8位无符号精度图像(CV_8U Images),其范围是0~255
对于16位无符号精度图像(CV_16U Images),其范围是0~65535
对于32位单精度浮点型图像(CV_32F Images),其范围是0~1
在线性变换的情况下,我们可以不用考虑其通道的取值范围,但对于非线性变换(Non-Linear Transformation),一个RGB输入图像应该先做规格化处理(Normalized),以便得到一个合适的范围来获取正确的结果。比如对于一个RGB颜色空间到LUV颜色空间的变换,如
果我们需要将一副8位图像转换到一副32位的浮点型精度图像而不进行任何缩放,也就是说,我们将一个从0~255的范围替换成0~1的范围,那么我们首先要将图像按比例缩小(Scale the Image Down):
img *= 1./255;
cvtColor(img, img, CV_BGR2Luv);
如果我们采用8位的图像进行转换,该过程中可能会有信息的丢失,尽管在一般的应用中,这种丢失并不明显(Noticeable),但我们强烈建议使用一个32位的图像或者在变换之前先转换成32位。
备注:关于参数code的跟多取值,可以参见OpenCV 2.4.0官方文档:cvtColor函数指南及使用方法
--------------------------------------------------------------------------------
imwrite(“../../lena_gray.jpg”,gray_image);// 保存图像
--------------------------------------------------------------------------------
imwrite函数用于将图像保存到指定的文件,其函数原型为:
bool imwrite(const string& filename, InputArray image, const vector<int>& params=vector<int>())
参数filename:指代需要保存文件的名称
参数image:需要保存的图像
参数params:保存至指定格式图像格式时的参数设置
关于params参数的取值如下:
对JPEG图像,它表示图像质量(CV_IMWRITE_JPEG_QUALITY),取值从1~100,值越大质量越高,默认为95;
对PNG图像,它表示图像压缩率(CV_IMWRITE_PNG_COMPRESSION),取值从0~9,值越大压缩率越大压缩时间越长,默认值为3;
对PPM,PGM或PBM,它是一个二进制标识(CV_IMWRITE_PXM_BINARY),取值为0或1,默认为1。
有关于该函数及参数params的详细信息及应用可参见开发文档:imwrite函数指南及使用方法
========
图像缩放--OpenCV cvResize函数--最近邻插值---双线性插值
void cvResize( const CvArr* src, CvArr* dst, int interpolation=CV_INTER_LINEAR ); src输入图像.dst输出图像.interpolation插值方法:
CV_INTER_NN - 最近邻插值,
CV_INTER_LINEAR - 双线性插值 (缺省使用)
CV_INTER_AREA - 使用象素关系重采样。当图像缩小时候,该方法可以避免波纹出现。当图像放大时,类似于 CV_INTER_NN 方法..
CV_INTER_CUBIC - 立方插值.
函数 cvResize 将图像 src 改变尺寸得到与 dst 同样大小。若设定 ROI,函数将按常规支持 ROI.
最近邻插值:效果(放大4倍)有马赛克现象
双线性插值:效果(放大4倍)比最近邻插值效果好
最近邻插值和双线性插值的基本原理
图像的缩放很好理解,就是图像的放大和缩小。传统的绘画工具中,有一种叫做“放大尺”的绘画工具,画家常用它来放大图画。当然,在计算机上,我们不再需要用放大尺去放大或缩小图像了,把这个工作交给程序来完成就可以了。下面就来讲讲计算机怎么来放大缩小
图象;在本文中,我们所说的图像都是指点阵图,也就是用一个像素矩阵来描述图像的方法,对于另一种图像:用函数来描述图像的矢量图,不在本文讨论之列。
越是简单的模型越适合用来举例子,我们就举个简单的图像:3X3 的256级灰度图,也就是高为3个象素,宽也是3个象素的图像,每个象素的取值可以是 0-255,代表该像素的亮度,255代表最亮,也就是白色,0代表最暗,即黑色。假如图像的象素矩阵如下图所示(这
个原始图把它叫做源图,Source):
234 38 22
67 44 12
89 65 63
这个矩阵中,元素坐标(x,y)是这样确定的,x从左到右,从0开始,y从上到下,也是从零开始,这是图象处理中最常用的坐标系,就是这样一个坐标:
---------------------->X
|
|
|
|
|
∨Y
如果想把这副图放大为 4X4大小的图像,那么该怎么做呢?那么第一步肯定想到的是先把4X4的矩阵先画出来再说,好了矩阵画出来了,如下所示,当然,矩阵的每个像素都是未知数,等待着我们去填充(这个将要被填充的图的叫做目标图,Destination):
? ? ? ?
? ? ? ?
? ? ? ?
? ? ? ?
然后要往这个空的矩阵里面填值了,要填的值从哪里来来呢?是从源图中来,好,先填写目标图最左上角的象素,坐标为(0,0),那么该坐标对应源图中的坐标可以由如下公式得出:
srcX=dstX* (srcWidth/dstWidth) , srcY = dstY * (srcHeight/dstHeight)
好了,套用公式,就可以找到对应的原图的坐标了(0*(3/4),0*(3/4))=>(0*0.75,0*0.75)=>(0,0)
,找到了源图的对应坐标,就可以把源图中坐标为(0,0)处的234象素值填进去目标图的(0,0)这个位置了。
接下来,如法炮制,寻找目标图中坐标为(1,0)的象素对应源图中的坐标,套用公式:
(1*0.75,0*0.75)=>(0.75,0)
结果发现,得到的坐标里面竟然有小数,这可怎么办?计算机里的图像可是数字图像,象素就是最小单位了,象素的坐标都是整数,从来没有小数坐标。这时候采用的一种策略就是采用四舍五入的方法(也可以采用直接舍掉小数位的方法),把非整数坐标转换成整数,好,那
么按照四舍五入的方法就得到坐标(1,0),完整的运算过程就是这样的:
(1*0.75,0*0.75)=>(0.75,0)=>(1,0)
那么就可以再填一个象素到目标矩阵中了,同样是把源图中坐标为(1,0)处的像素值38填入目标图中的坐标。
依次填完每个象素,一幅放大后的图像就诞生了,像素矩阵如下所示:
234 38 22 22
67 44 12 12
89 65 63 63
89 65 63 63
这种放大图像的方法叫做最临近插值算法,这是一种最基本、最简单的图像缩放算法,效果也是最不好的,放大后的图像有很严重的马赛克,缩小后的图像有很严重的失真;效果不好的根源就是其简单的最临近插值方法引入了严重的图像失真,比如,当由目标图的坐标
反推得到的源图的的坐标是一个浮点数的时候,采用了四舍五入的方法,直接采用了和这个浮点数最接近的象素的值,这种方法是很不科学的,当推得坐标值为 0.75的时候,不应该就简单的取为1,既然是0.75,比1要小0.25 ,比0要大0.75 ,那么目标象素值其实应该根
据这个源图中虚拟的点四周的四个真实的点来按照一定的规律计算出来的,这样才能达到更好的缩放效果。双线型内插值算法就是一种比较好的图像缩放算法,它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果
比简单的最邻近插值要好很多。
双线性内插值算法描述如下:
对于一个目的像素,设置坐标通过反向变换得到的浮点坐标为(i+u,j+v) (其中i、j均为浮点坐标的整数部分,u、v为浮点坐标的小数部分,是取值[0,1)区间的浮点数),则这个像素得值 f(i+u,j+v) 可由原图像中坐标为 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所对
应的周围四个像素的值决定,即:
f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1) 公式1
其中f(i,j)表示源图像(i,j)处的的像素值,以此类推。
比如,象刚才的例子,现在假如目标图的象素坐标为(1,1),那么反推得到的对应于源图的坐标是(0.75 , 0.75), 这其实只是一个概念上的虚拟象素,实际在源图中并不存在这样一个象素,那么目标图的象素(1,1)的取值不能够由这个虚拟象素来决定,而只能由源
图的这四个象素共同决定:(0,0)(0,1)(1,0)(1,1),而由于(0.75,0.75)离(1,1)要更近一些,那么(1,1)所起的决定作用更大一些,这从公式1中的系数uv=0.75×0.75就可以体现出来,而(0.75,0.75)离(0,0)最远,所以(0,0)所起的决定作用
就要小一些,公式中系数为(1-u)(1-v)=0.25×0.25也体现出了这一特点。
原理参考link:http://blog.csdn.net/andrew659/article/details/4818988
OpenCV代码:scale是放缩比例
点击(此处)折叠或打开
#include "stdafx.h"
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <cmath>
using namespace std;
using namespace cv;
int main(int argc ,char ** argv)
{
IplImage *scr=0;
IplImage *dst=0;
double scale=4;
CvSize dst_cvsize;
if (argc==2&&(scr=cvLoadImage(argv[1],-1))!=0)
{
dst_cvsize.width=(int)(scr->width*scale);
dst_cvsize.height=(int)(scr->height*scale);
dst=cvCreateImage(dst_cvsize,scr->depth,scr->nChannels);
cvResize(scr,dst,CV_INTER_NN);//
// CV_INTER_NN - 最近邻插值,
// CV_INTER_LINEAR - 双线性插值 (缺省使用)
// CV_INTER_AREA - 使用象素关系重采样。当图像缩小时候,该方法可以避免波纹出现。
/*当图像放大时,类似于 CV_INTER_NN 方法..*/
// CV_INTER_CUBIC - 立方插值.
cvNamedWindow("scr",CV_WINDOW_AUTOSIZE);
cvNamedWindow("dst",CV_WINDOW_AUTOSIZE);
cvShowImage("scr",scr);
cvShowImage("dst",dst);
cvWaitKey();
cvReleaseImage(&scr);
cvReleaseImage(&dst);
cvDestroyWindow("scr");
cvDestroyWindow("dst");
}
return 0;
}
========
opencv初体验-图片滤镜效果
我参考了http://blog.csdn.net/yangtrees/article/details/9116337的代码,
稍作修改,将其变成一个小工具,可将图片加“怀旧色”滤镜保存输出。
不说废话,直接上代码。
#include <opencv/cv.h>
#include <opencv/highgui.h>
using namespace cv;
using namespace std;
int main(int argc, char ** argv)
{
// input args check
if(argc < 3){
printf("please input args.\n");
printf("e.g. : ./test infilepath outfilepath \n");
return 0;
}
char * input = argv[1];
char * output = argv[2];
printf("input: %s, output: %s\n", input, output);
Mat src = imread(input, 1);
int width=src.cols;
int heigh=src.rows;
RNG rng;
Mat img(src.size(),CV_8UC3);
for (int y=0; y<heigh; y++)
{
uchar* P0 = src.ptr<uchar>(y);
uchar* P1 = img.ptr<uchar>(y);
for (int x=0; x<width; x++)
{
float B=P0[3*x];
float G=P0[3*x+1];
float R=P0[3*x+2];
float newB=0.272*R+0.534*G+0.131*B;
float newG=0.349*R+0.686*G+0.168*B;
float newR=0.393*R+0.769*G+0.189*B;
if(newB<0)newB=0;
if(newB>255)newB=255;
if(newG<0)newG=0;
if(newG>255)newG=255;
if(newR<0)newR=0;
if(newR>255)newR=255;
P1[3*x] = (uchar)newB;
P1[3*x+1] = (uchar)newG;
P1[3*x+2] = (uchar)newR;
}
}
//imshow("out",img);
waitKey();
imwrite(output,img);
}
编译时需要注意一下,需要加上`pkg-config opencv --libs --cflags opencv`
例如:g++ -o test opencvtest.cpp `pkg-config opencv --libs --cflags opencv`
OK,编译正常。
从网上下个图片,做个测试。
看下效果,还不错。
原图:
处理后:
========
用opencv将图片变成水波纹效果
将一幅图片变成水波纹效果。我在网上找到一份源码,参考之下,顺着思路用opencv2重写之。
思路如下:
1.将图片中的坐标点(x,y)换成极坐标,有现成的函数。
2.极坐标下,用三角函数算出新半径。
3.在新半径之下,转换成新的坐标(x 0 ,y 0 ),如果新坐标是小数,用双线性插值的方法处理。
关键代码如下:
其中一些变量声明如下:
Mat imageInfo;//原图片
int imageWidth;
int imageHeight;
int imageX;//图像中心点的横坐标
int imageY;//图像中心点的纵坐标
float A;//波纹幅度
float B;//波纹周期 Asin(Bx);
int imageChannels;//通道数
Mat imageWater;//转换后的图片
void reCalcAB(int i,int j,float &a,float &b);//坐标转换
uchar BLIP(float a,float b,int k);
//k为通道数,值为-1为单通道,灰度图
void imagetest::imageprocess()
{
imageInfo.copyTo(imageWater);
float a;
float b;//临时坐标
for(int i=0;i<imageHeight-1;i++)
{
uchar *Data = imageWater.ptr<uchar>(i);
for(int j=0;j<imageWidth-1;j++)
{
reCalcAB(i,j,a,b);
if(imageChannels == 1)//彩色与灰度图像要单独处理,否则
{ //会出现椭圆的情况
*(Data+j) = BLIP(a,b,-1);//-1指灰度图
}
else if(imageChannels == 3)
{
for(int k = 0;k<imageChannels;k++)
{
*(Data+j*imageChannels+k)= BLIP(a,b,k);
}
}
}
}
}
reCalcAB是坐标转换函数:
void imagetest::reCalcAB(int i,int j,float &a,float &b)
{
float y0 = (float)(i-imageY);
float x0 = (float)(j-imageX);//(i,j)相对于原点的坐标
float theta0 = atan2f(y0,x0);//转化成角坐标
float r0 = sqrtf(x0*x0+y0*y0);//初始半径
float r1 = r0+ A*imageWidth*0.01*sin(B*0.1*r0);//计算新的半径
a = imageX + r1*cos(theta0);
b = imageY + r1*sin(theta0);//转换后的坐标
if(a>imageWidth)
a = imageWidth-1;
else if(a<0)
a = 0; //超出边界的处理
if(b>imageHeight)
b = imageHeight-1;
else if(b<0)
b = 0;
}
双线性插值函数:(这个方法看着很高级,实际很简单。仔细看代码就明白怎么回事情了)
uchar imagetest::BLIP(float a,float b,int k)
{
uchar newData;//保存结果
uchar DataTemp1;
uchar DataTemp2;//两个中间变量
int x[2];
int y[2];//存储周围四个点。
x[0] = (int)a;
y[0] = (int)b;
x[1] = x[0]+1;
y[1] = y[0]+1;//(a,b)周围四个整点坐标
//取值
uchar *data1 = imageWater.data + y[0]*imageWater.step + x[0]*imageChannels;
uchar *data2 = imageWater.data + y[0]*imageWater.step + x[1]*imageChannels;
uchar *data3 = imageWater.data + y[1]*imageWater.step + x[0]*imageChannels;
uchar *data4 = imageWater.data + y[1]*imageWater.step + x[1]*imageChannels;
if(k!=-1)//如果是彩色,转换一下
{
data1 += k;
data2 += k;
data3 += k;
data4 += k;
}
if((fabsf(a-x[0])<0.00001) && (fabsf(b-y[0])<0.00001))//整点,直接返回
{
newData = *data1;
return newData;
}
float dx = fabsf(a-x[0]);//x轴的比例
float dy = fabsf(b-y[0]);//y轴的比例
DataTemp1 = (*data1)*(1.0-dx) + (*data2)*dx;
DataTemp2 = (*data3)*(1.0-dx) + (*data4)*dx;
newData = DataTemp1*(1.0-dy) + DataTemp2*dy;//核心插值过程
return newData;
}
这个效果看起来倒是不错,总感觉不是那么真实。
而且,这个程序有严重的问题。如果我换一张图片,重新设置 A和B的参数
就会出现如下的效果:
中间水平方向出现了明显的一条横线。
目前还没有解决的问题主要就是这条横线,然后就是怎么样才能使得水波纹看起来更真实。我想把用一张图片做成视频,不知道这个效果最后做出来是个什么样子。
如果是坐标转换出错了的话,理论上来说应该会水平、竖直都应该出现一条直线,现在只有水平方向有一条直线。
========
在OpenCV中实现特效之浮雕,雕刻和褶皱
下面代码的基础是对图像像素的访问。
实现浮雕和雕刻的代码是统一的,如下
#include <cv.h>
#include <highgui.h>
#pragma comment( lib, "cv.lib" )
#pragma comment( lib, "cxcore.lib" )
#pragma comment( lib, "highgui.lib" )
int main()
{
IplImage *org=cvLoadImage("1.jpg",1);
IplImage *image=cvCloneImage(org);
int width=image->width;
int height=image->height;
int step=image->widthStep;
int channel=image->nChannels;
uchar* data=(uchar *)image->imageData;
for(int i=0;i<width-1;i++)
{
for(int j=0;j<height-1;j++)
{
for(int k=0;k<channel;k++)
{
int temp = data[(j+1)*step+(i+1)*channel+k]-data[j*step+i*channel+k]+128;//浮雕
//int temp = data[j*step+i*channel+k]-data[(j+1)*step+(i+1)*channel+k]+128;//雕刻
if(temp>255)
{
data[j*step+i*channel+k]=255;
}
else if(temp<0)
{
data[j*step+i*channel+k]=0;
}
else
{
data[j*step+i*channel+k]=temp;
}
}
}
}
cvNamedWindow("original",1);
cvShowImage("original",org);
cvNamedWindow("image",1);
cvShowImage("image",image);
cvWaitKey(0);
cvDestroyAllWindows();
cvReleaseImage(&image);
cvReleaseImage(&org);
return 0;
}
原图为
浮雕效果图如下
雕刻效果图如下
下面是实现图像褶皱的代码,效果不是太好,结构过渡不平滑,以后再改进一下。希望能做到波浪化。
#include <cv.h>
#include <highgui.h>
#pragma comment( lib, "cv.lib" )
#pragma comment( lib, "cxcore.lib" )
#pragma comment( lib, "highgui.lib" )
int main()
{
IplImage *org=cvLoadImage("lena.jpg",1);
IplImage *image=cvCloneImage(org);
int width=image->width;
int height=image->height;
int step=image->widthStep;
int channel=image->nChannels;
uchar* data=(uchar *)image->imageData;
int sign=-1;
for(int i=0;i<height;i++)
{
int cycle=10;
int margin=(i%cycle);
if((i/cycle)%2==0)
{
sign=-1;
}
else
{
sign=1;
}
if(sign==-1)
{
margin=cycle-margin;
for(int j=0;j<width-margin;j++)
{
for(int k=0;k<channel;k++)
{
data[i*step+j*channel+k]=data[i*step+(j+margin)*channel+k];
}
}
}
else if(sign==1)
{
for(int j=0;j<width-margin;j++)
{
for(int k=0;k<channel;k++)
{
data[i*step+j*channel+k]=data[i*step+(j+margin)*channel+k];
}
}
}
}
cvNamedWindow("original",1);
cvShowImage("original",org);
cvNamedWindow("image",1);
cvShowImage("image",image);
cvSaveImage("image.jpg",image);
cvWaitKey(0);
cvDestroyAllWindows();
cvReleaseImage(&image);
cvReleaseImage(&org);
return 0;
}
测试图是标准的lena图,效果图如下
========
基于GraphCuts图割算法的图像分割----OpenCV代码与实现
图切算法是组合图论的经典算法之一。近年来,许多学者将其应用到图像和视频分割中,取得了很好的效果。本文简单介绍了图切算法和交互式图像分割技术,以及图切算法在交互式图像分割中的应用。
图像分割指图像分成各具特性的区域并提取出感兴趣目标的技术和过程,它是由图像处理到图像分析的关键步骤,是一种基本的计算机视觉技术。只有在图像分割的基础上才能对目标进行特征提取和参数测量,使得更高层的图像分析和理解成为可能。因此对图像分割方
法的研究具有十分重要的意义。
图像分割技术的研究已有几十年的历史,但至今人们并不能找到通用的方法能够适合于所有类型的图像。常用的图像分割技术可划分为四类:特征阈值或聚类、边缘检测、区域生长或区域提取。虽然这些方法分割灰度图像效果较好,但用于彩色图像的分割往往达不到理
想的效果。
交互式图像分割是指,首先由用户以某种交互手段指定图像的部分前景与部分背景,然后算法以用户的输入作为分割的约束条件自动地计算出满足约束条件下的最佳分割。典型的交互手段包括用一把画刷在前景和背景处各画几笔(如[1][4]等)以及在前景的周围画一个
方框(如[2])等。
基于图切算法的图像分割技术是近年来国际上图像分割领域的一个新的研究热点。该类方法将图像映射为赋权无向图,把像素视作节点,利用最小切割得到图像的最佳分割。
Graph Cut[1]算法是一种直接基于图切算法的图像分割技术。它仅需要在前景和背景处各画几笔作为输入,算法将建立各个像素点与前景背景相似度的赋权图,并通过求解最小切割区分前景和背景。
Grabcut[2]算法方法的用户交互量很少,仅仅需要指定一个包含前景的矩形,随后用基于图切算法在图像中提取前景。
Lazy Snapping[4]系统则是对[1]的改进。通过预计算和聚类技术,该方法提供了一个即时反馈的平台,方便用户进行交互分割。
文档说明:
http://download.csdn.net/detail/wangyaninglm/8484301
代码实现效果:
graphcuts代码:
http://download.csdn.net/detail/wangyaninglm/8484243
ICCV'2001论文"Interactive graph cuts for optimal boundary and region segmentation of objects in N-D images"。
Graph Cut方法是基于颜色统计采样的方法,因此对前背景相差较大的图像效果较佳。
同时,比例系数lambda的调节直接影响到最终的分割效果。
grabcut代码:
// Grabcut.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include "ComputeTime.h"
#include "windows.h"
using namespace std;
using namespace cv;
static void help()
{
cout << "\nThis program demonstrates GrabCut segmentation -- select an object in a region\n"
"and then grabcut will attempt to segment it out.\n"
"Call:\n"
"./grabcut <image_name>\n"
"\nSelect a rectangular area around the object you want to segment\n" <<
"\nHot keys: \n"
"\tESC - quit the program\n"
"\tr - restore the original image\n"
"\tn - next iteration\n"
"\n"
"\tleft mouse button - set rectangle\n"
"\n"
"\tCTRL+left mouse button - set GC_BGD pixels\n"
"\tSHIFT+left mouse button - set CG_FGD pixels\n"
"\n"
"\tCTRL+right mouse button - set GC_PR_BGD pixels\n"
"\tSHIFT+right mouse button - set CG_PR_FGD pixels\n" << endl;
}
const Scalar RED = Scalar(0,0,255);
const Scalar PINK = Scalar(230,130,255);
const Scalar BLUE = Scalar(255,0,0);
const Scalar LIGHTBLUE = Scalar(255,255,160);
const Scalar GREEN = Scalar(0,255,0);
const int BGD_KEY = CV_EVENT_FLAG_CTRLKEY; //Ctrl键
const int FGD_KEY = CV_EVENT_FLAG_SHIFTKEY; //Shift键
static void getBinMask( const Mat& comMask, Mat& binMask )
{
if( comMask.empty() || comMask.type()!=CV_8UC1 )
CV_Error( CV_StsBadArg, "comMask is empty or has incorrect type (not CV_8UC1)" );
if( binMask.empty() || binMask.rows!=comMask.rows || binMask.cols!=comMask.cols )
binMask.create( comMask.size(), CV_8UC1 );
binMask = comMask & 1; //得到mask的最低位,实际上是只保留确定的或者有可能的前景点当做mask
}
class GCApplication
{
public:
enum{ NOT_SET = 0, IN_PROCESS = 1, SET = 2 };
static const int radius = 2;
static const int thickness = -1;
void reset();
void setImageAndWinName( const Mat& _image, const string& _winName );
void showImage() const;
void mouseClick( int event, int x, int y, int flags, void* param );
int nextIter();
int getIterCount() const { return iterCount; }
private:
void setRectInMask();
void setLblsInMask( int flags, Point p, bool isPr );
const string* winName;
const Mat* image;
Mat mask;
Mat bgdModel, fgdModel;
uchar rectState, lblsState, prLblsState;
bool isInitialized;
Rect rect;
vector<Point> fgdPxls, bgdPxls, prFgdPxls, prBgdPxls;
int iterCount;
};
/*给类的变量赋值*/
void GCApplication::reset()
{
if( !mask.empty() )
mask.setTo(Scalar::all(GC_BGD));
bgdPxls.clear(); fgdPxls.clear();
prBgdPxls.clear(); prFgdPxls.clear();
isInitialized = false;
rectState = NOT_SET; //NOT_SET == 0
lblsState = NOT_SET;
prLblsState = NOT_SET;
iterCount = 0;
}
/*给类的成员变量赋值而已*/
void GCApplication::setImageAndWinName( const Mat& _image, const string& _winName )
{
if( _image.empty() || _winName.empty() )
return;
image = &_image;
winName = &_winName;
mask.create( image->size(), CV_8UC1);
reset();
}
/*显示4个点,一个矩形和图像内容,因为后面的步骤很多地方都要用到这个函数,所以单独拿出来*/
void GCApplication::showImage() const
{
if( image->empty() || winName->empty() )
return;
Mat res;
Mat binMask;
if( !isInitialized )
image->copyTo( res );
else
{
getBinMask( mask, binMask );
image->copyTo( res, binMask ); //按照最低位是0还是1来复制,只保留跟前景有关的图像,比如说可能的前景,可能的背景
}
vector<Point>::const_iterator it;
/*下面4句代码是将选中的4个点用不同的颜色显示出来*/
for( it = bgdPxls.begin(); it != bgdPxls.end(); ++it ) //迭代器可以看成是一个指针
circle( res, *it, radius, BLUE, thickness );
for( it = fgdPxls.begin(); it != fgdPxls.end(); ++it ) //确定的前景用红色表示
circle( res, *it, radius, RED, thickness );
for( it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it )
circle( res, *it, radius, LIGHTBLUE, thickness );
for( it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it )
circle( res, *it, radius, PINK, thickness );
/*画矩形*/
if( rectState == IN_PROCESS || rectState == SET )
rectangle( res, Point( rect.x, rect.y ), Point(rect.x + rect.width, rect.y + rect.height ), GREEN, 2);
imshow( *winName, res );
}
/*该步骤完成后,mask图像中rect内部是3,外面全是0*/
void GCApplication::setRectInMask()
{
assert( !mask.empty() );
mask.setTo( GC_BGD ); //GC_BGD == 0
rect.x = max(0, rect.x);
rect.y = max(0, rect.y);
rect.width = min(rect.width, image->cols-rect.x);
rect.height = min(rect.height, image->rows-rect.y);
(mask(rect)).setTo( Scalar(GC_PR_FGD) ); //GC_PR_FGD == 3,矩形内部,为可能的前景点
}
void GCApplication::setLblsInMask( int flags, Point p, bool isPr )
{
vector<Point> *bpxls, *fpxls;
uchar bvalue, fvalue;
if( !isPr ) //确定的点
{
bpxls = &bgdPxls;
fpxls = &fgdPxls;
bvalue = GC_BGD; //0
fvalue = GC_FGD; //1
}
else //概率点
{
bpxls = &prBgdPxls;
fpxls = &prFgdPxls;
bvalue = GC_PR_BGD; //2
fvalue = GC_PR_FGD; //3
}
if( flags & BGD_KEY )
{
bpxls->push_back(p);
circle( mask, p, radius, bvalue, thickness ); //该点处为2
}
if( flags & FGD_KEY )
{
fpxls->push_back(p);
circle( mask, p, radius, fvalue, thickness ); //该点处为3
}
}
/*鼠标响应函数,参数flags为CV_EVENT_FLAG的组合*/
void GCApplication::mouseClick( int event, int x, int y, int flags, void* )
{
// TODO add bad args check
switch( event )
{
case CV_EVENT_LBUTTONDOWN: // set rect or GC_BGD(GC_FGD) labels
{
bool isb = (flags & BGD_KEY) != 0,
isf = (flags & FGD_KEY) != 0;
if( rectState == NOT_SET && !isb && !isf )//只有左键按下时
{
rectState = IN_PROCESS; //表示正在画矩形
rect = Rect( x, y, 1, 1 );
}
if ( (isb || isf) && rectState == SET ) //按下了alt键或者shift键,且画好了矩形,表示正在画前景背景点
lblsState = IN_PROCESS;
}
break;
case CV_EVENT_RBUTTONDOWN: // set GC_PR_BGD(GC_PR_FGD) labels
{
bool isb = (flags & BGD_KEY) != 0,
isf = (flags & FGD_KEY) != 0;
if ( (isb || isf) && rectState == SET ) //正在画可能的前景背景点
prLblsState = IN_PROCESS;
}
break;
case CV_EVENT_LBUTTONUP:
if( rectState == IN_PROCESS )
{
rect = Rect( Point(rect.x, rect.y), Point(x,y) ); //矩形结束
rectState = SET;
setRectInMask();
assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() );
showImage();
}
if( lblsState == IN_PROCESS ) //已画了前后景点
{
setLblsInMask(flags, Point(x,y), false); //画出前景点
lblsState = SET;
showImage();
}
break;
case CV_EVENT_RBUTTONUP:
if( prLblsState == IN_PROCESS )
{
setLblsInMask(flags, Point(x,y), true); //画出背景点
prLblsState = SET;
showImage();
}
break;
case CV_EVENT_MOUSEMOVE:
if( rectState == IN_PROCESS )
{
rect = Rect( Point(rect.x, rect.y), Point(x,y) );
assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() );
showImage(); //不断的显示图片
}
else if( lblsState == IN_PROCESS )
{
setLblsInMask(flags, Point(x,y), false);
showImage();
}
else if( prLblsState == IN_PROCESS )
{
setLblsInMask(flags, Point(x,y), true);
showImage();
}
break;
}
}
/*该函数进行grabcut算法,并且返回算法运行迭代的次数*/
int GCApplication::nextIter()
{
if( isInitialized )
//使用grab算法进行一次迭代,参数2为mask,里面存的mask位是:矩形内部除掉那些可能是背景或者已经确定是背景后的所有的点,且mask同时也为输出
//保存的是分割后的前景图像
grabCut( *image, mask, rect, bgdModel, fgdModel, 1 );
else
{
if( rectState != SET )
return iterCount;
if( lblsState == SET || prLblsState == SET )
grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK );
else
grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT );
isInitialized = true;
}
iterCount++;
bgdPxls.clear(); fgdPxls.clear();
prBgdPxls.clear(); prFgdPxls.clear();
return iterCount;
}
GCApplication gcapp;
static void on_mouse( int event, int x, int y, int flags, void* param )
{
gcapp.mouseClick( event, x, y, flags, param );
}
int main( int argc, char** argv )
{
string filename;
cout<<" Grabcuts ! \n";
cout<<"input image name: "<<endl;
cin>>filename;
Mat image = imread( filename, 1 );
if( image.empty() )
{
cout << "\n Durn, couldn't read image filename " << filename << endl;
return 1;
}
help();
const string winName = "image";
cvNamedWindow( winName.c_str(), CV_WINDOW_AUTOSIZE );
cvSetMouseCallback( winName.c_str(), on_mouse, 0 );
gcapp.setImageAndWinName( image, winName );
gcapp.showImage();
for(;;)
{
int c = cvWaitKey(0);
switch( (char) c )
{
case '\x1b':
cout << "Exiting ..." << endl;
goto exit_main;
case 'r':
cout << endl;
gcapp.reset();
gcapp.showImage();
break;
case 'n':
ComputeTime ct ;
ct.Begin();
int iterCount = gcapp.getIterCount();
cout << "<" << iterCount << "... ";
int newIterCount = gcapp.nextIter();
if( newIterCount > iterCount )
{
gcapp.showImage();
cout << iterCount << ">" << endl;
cout<<"运行时间: "<<ct.End()<<endl;
}
else
cout << "rect must be determined>" << endl;
break;
}
}
exit_main:
cvDestroyWindow( winName.c_str() );
return 0;
}
lazy snapping代码实现:
// LazySnapping.cpp : 定义控制台应用程序的入口点。
//
/* author: zhijie Lee
* home page: lzhj.me
* 2012-02-06
*/
#include "stdafx.h"
#include <cv.h>
#include <highgui.h>
#include "graph.h"
#include <vector>
#include <iostream>
#include <cmath>
#include <string>
using namespace std;
typedef Graph<float,float,float> GraphType;
class LasySnapping
{
public :
LasySnapping();
~LasySnapping()
{
if(graph)
{
delete graph;
}
};
private :
vector<CvPoint> forePts;
vector<CvPoint> backPts;
IplImage* image;
// average color of foreground points
unsigned char avgForeColor[3];
// average color of background points
unsigned char avgBackColor[3];
public :
void setImage(IplImage* image)
{
this->image = image;
graph = new GraphType(image->width*image->height,image->width*image->height*2);
}
// include-pen locus
void setForegroundPoints(vector<CvPoint> pts)
{
forePts.clear();
for(int i =0; i< pts.size(); i++)
{
if(!isPtInVector(pts[i],forePts))
{
forePts.push_back(pts[i]);
}
}
if(forePts.size() == 0)
{
return;
}
int sum[3] = {0};
for(int i =0; i < forePts.size(); i++)
{
unsigned char* p = (unsigned char*)image->imageData + forePts[i].x * 3
+ forePts[i].y*image->widthStep;
sum[0] += p[0];
sum[1] += p[1];
sum[2] += p[2];
}
cout<<sum[0]<<" " <<forePts.size()<<endl;
avgForeColor[0] = sum[0]/forePts.size();
avgForeColor[1] = sum[1]/forePts.size();
avgForeColor[2] = sum[2]/forePts.size();
}
// exclude-pen locus
void setBackgroundPoints(vector<CvPoint> pts)
{
backPts.clear();
for(int i =0; i< pts.size(); i++)
{
if(!isPtInVector(pts[i],backPts))
{
backPts.push_back(pts[i]);
}
}
if(backPts.size() == 0)
{
return;
}
int sum[3] = {0};
for(int i =0; i < backPts.size(); i++)
{
unsigned char* p = (unsigned char*)image->imageData + backPts[i].x * 3 +
backPts[i].y*image->widthStep;
sum[0] += p[0];
sum[1] += p[1];
sum[2] += p[2];
}
avgBackColor[0] = sum[0]/backPts.size();
avgBackColor[1] = sum[1]/backPts.size();
avgBackColor[2] = sum[2]/backPts.size();
}
// return maxflow of graph
int runMaxflow();
// get result, a grayscale mast image indicating forground by 255 and background by 0
IplImage* getImageMask();
private :
float colorDistance(unsigned char* color1, unsigned char* color2);
float minDistance(unsigned char* color, vector<CvPoint> points);
bool isPtInVector(CvPoint pt, vector<CvPoint> points);
void getE1(unsigned char* color,float* energy);
float getE2(unsigned char* color1,unsigned char* color2);
GraphType *graph;
};
LasySnapping::LasySnapping()
{
graph = NULL;
avgForeColor[0] = 0;
avgForeColor[1] = 0;
avgForeColor[2] = 0;
avgBackColor[0] = 0;
avgBackColor[1] = 0;
avgBackColor[2] = 0;
}
float LasySnapping::colorDistance(unsigned char* color1, unsigned char* color2)
{
return sqrt(((float)color1[0]-(float)color2[0])*((float)color1[0]-(float)color2[0])+
((float)color1[1]-(float)color2[1])*((float)color1[1]-(float)color2[1])+
((float)color1[2]-(float)color2[2])*((float)color1[2]-(float)color2[2]));
}
float LasySnapping::minDistance(unsigned char* color, vector<CvPoint> points)
{
float distance = -1;
for(int i =0 ; i < points.size(); i++)
{
unsigned char* p = (unsigned char*)image->imageData + points[i].y * image->widthStep +
points[i].x * image->nChannels;
float d = colorDistance(p,color);
if(distance < 0 )
{
distance = d;
}
else
{
if(distance > d)
{
distance = d;
}
}
}
return distance;
}
bool LasySnapping::isPtInVector(CvPoint pt, vector<CvPoint> points)
{
for(int i =0 ; i < points.size(); i++)
{
if(pt.x == points[i].x && pt.y == points[i].y)
{
return true;
}
}
return false;
}
void LasySnapping::getE1(unsigned char* color,float* energy)
{
// average distance
float df = colorDistance(color,avgForeColor);
float db = colorDistance(color,avgBackColor);
// min distance from background points and forground points
// float df = minDistance(color,forePts);
// float db = minDistance(color,backPts);
energy[0] = df/(db+df);
energy[1] = db/(db+df);
}
float LasySnapping::getE2(unsigned char* color1,unsigned char* color2)
{
const float EPSILON = 0.01;
float lambda = 100;
return lambda/(EPSILON+
(color1[0]-color2[0])*(color1[0]-color2[0])+
(color1[1]-color2[1])*(color1[1]-color2[1])+
(color1[2]-color2[2])*(color1[2]-color2[2]));
}
int LasySnapping::runMaxflow()
{
const float INFINNITE_MAX = 1e10;
int indexPt = 0;
for(int h = 0; h < image->height; h ++)
{
unsigned char* p = (unsigned char*)image->imageData + h *image->widthStep;
for(int w = 0; w < image->width; w ++)
{
// calculate energe E1
float e1[2]={0};
if(isPtInVector(cvPoint(w,h),forePts))
{
e1[0] =0;
e1[1] = INFINNITE_MAX;
}
else if
(isPtInVector(cvPoint(w,h),backPts))
{
e1[0] = INFINNITE_MAX;
e1[1] = 0;
}
else
{
getE1(p,e1);
}
// add node
graph->add_node();
graph->add_tweights(indexPt, e1[0],e1[1]);
// add edge, 4-connect
if(h > 0 && w > 0)
{
float e2 = getE2(p,p-3);
graph->add_edge(indexPt,indexPt-1,e2,e2);
e2 = getE2(p,p-image->widthStep);
graph->add_edge(indexPt,indexPt-image->width,e2,e2);
}
p+= 3;
indexPt ++;
}
}
return graph->maxflow();
}
IplImage* LasySnapping::getImageMask()
{
IplImage* gray = cvCreateImage(cvGetSize(image),8,1);
int indexPt =0;
for(int h =0; h < image->height; h++)
{
unsigned char* p = (unsigned char*)gray->imageData + h*gray->widthStep;
for(int w =0 ;w <image->width; w++)
{
if (graph->what_segment(indexPt) == GraphType::SOURCE)
{
*p = 0;
}
else
{
*p = 255;
}
p++;
indexPt ++;
}
}
return gray;
}
// global
vector<CvPoint> forePts;
vector<CvPoint> backPts;
int currentMode = 0;// indicate foreground or background, foreground as default
CvScalar paintColor[2] = {CV_RGB(0,0,255),CV_RGB(255,0,0)};
IplImage* image = NULL;
char* winName = "lazySnapping";
IplImage* imageDraw = NULL;
const int SCALE = 4;
void on_mouse( int event, int x, int y, int flags, void* )
{
if( event == CV_EVENT_LBUTTONUP )
{
if(backPts.size() == 0 && forePts.size() == 0)
{
return;
}
LasySnapping ls;
IplImage* imageLS = cvCreateImage(cvSize(image->width/SCALE,image->height/SCALE),
8,3);
cvResize(image,imageLS);
ls.setImage(imageLS);
ls.setBackgroundPoints(backPts);
ls.setForegroundPoints(forePts);
ls.runMaxflow();
IplImage* mask = ls.getImageMask();
IplImage* gray = cvCreateImage(cvGetSize(image),8,1);
cvResize(mask,gray);
// edge
cvCanny(gray,gray,50,150,3);
IplImage* showImg = cvCloneImage(imageDraw);
for(int h =0; h < image->height; h ++)
{
unsigned char* pgray = (unsigned char*)gray->imageData + gray->widthStep*h;
unsigned char* pimage = (unsigned char*)showImg->imageData + showImg->widthStep*h;
for(int width =0; width < image->width; width++)
{
if(*pgray++ != 0 )
{
pimage[0] = 0;
pimage[1] = 255;
pimage[2] = 0;
}
pimage+=3;
}
}
cvSaveImage("t.bmp",showImg);
cvShowImage(winName,showImg);
cvReleaseImage(&imageLS);
cvReleaseImage(&mask);
cvReleaseImage(&showImg);
cvReleaseImage(&gray);
}
else if( event == CV_EVENT_LBUTTONDOWN )
{
}
else if( event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))
{
CvPoint pt = cvPoint(x,y);
if(currentMode == 0)
{//foreground
forePts.push_back(cvPoint(x/SCALE,y/SCALE));
}
else
{//background
backPts.push_back(cvPoint(x/SCALE,y/SCALE));
}
cvCircle(imageDraw,pt,2,paintColor[currentMode]);
cvShowImage(winName,imageDraw);
}
}
int main(int argc, char** argv)
{
//if(argc != 2)
//{
// cout<<"command : lazysnapping inputImage"<<endl;
// return 0;
// }
string image_name;
cout<<"input image name: "<<endl;
cin>>image_name;
cvNamedWindow(winName,1);
cvSetMouseCallback( winName, on_mouse, 0);
image = cvLoadImage(image_name.c_str(),CV_LOAD_IMAGE_COLOR);
imageDraw = cvCloneImage(image);
cvShowImage(winName, image);
for(;;)
{
int c = cvWaitKey(0);
c = (char)c;
if(c == 27)
{//exit
break;
}
else if(c == 'r')
{//reset
image = cvLoadImage(image_name.c_str(),CV_LOAD_IMAGE_COLOR);
imageDraw = cvCloneImage(image);
forePts.clear();
backPts.clear();
currentMode = 0;
cvShowImage(winName, image);
}
else if(c == 'b')
{//change to background selection
currentMode = 1;
}else if(c == 'f')
{//change to foreground selection
currentMode = 0;
}
}
cvReleaseImage(&image);
cvReleaseImage(&imageDraw);
return 0;
}
=======
OpenCV由汉字生成图片(透明)
今天听说很多同志们写毕业论文重复率过高的问题,大牛说用图片代替字就行了,我就想用OpenCV实现一下看看能不能搞,果不其然还是可以的!!!主要的难点在于普通格式的图片背景不透明,需要使用背景透明的png格式图片就行。
主要思想和步骤:
1.首先配置好FreeType与OpenCV,添加编译好的lib,与include目录和CvxText.h和CvxText.cpp就行了,参考[1]
2.说一下思路,主要就是OpenCV版本的问题造成有的函数用的IplImage,而函数
//设置原图像文字
text.putText(ImageSrc, msg, cvPoint(1, size_zi), color);
只能接受IplImage格式的参数,所以保存成png,就比较麻烦了。
png格式的图片是4个通道,按照BGRA来放置,alaph就是透明通道。我们的思路就是按照原来直接给图片上叠加文字的办法,新建与文字大小相同的图片,然后二值化,按照二值模版生成新的png文字图片,有字的地方添上颜色,没字的地方设置为透明。
当然二值化算法网上搜了一个自适应阀值的算法效果非常好
3.生成了透明的文字图片,粘贴到论文里面,估计查询重复的系统再牛逼也是无能为力了。后序有空做一些程序界面跟字符分割的东西,可以直接卖钱了。
当然,字体跟大小,上下边距都是可以设置的,后序再往程序里面写。
实现效果:
主要代码:
// AddChinese.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "CvxText.h"
#pragma comment(lib,"freetype255d.lib")
#pragma comment(lib,"opencv_core2410d.lib")
#pragma comment(lib,"opencv_highgui2410d.lib")
#pragma comment(lib,"opencv_imgproc2410d.lib")
using namespace std;
using namespace cv;
#define ROW_BLOCK 2
#define COLUMN_Block 2
// writePng.cpp : 定义控制台应用程序的入口点。
//
int run_test_png(Mat &mat,string image_name)
{
/*采用自己设置的参数来保存图片*/
//Mat mat(480, 640, CV_8UC4);
//createAlphaMat(mat);
vector<int> compression_params;
compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION);
compression_params.push_back(9); //png格式下,默认的参数为3.
try
{
imwrite(image_name, mat, compression_params);
}
catch (runtime_error& ex)
{
fprintf(stderr, "Exception converting image to PNG format: %s\n", ex.what());
return 1;
}
fprintf(stdout, "Saved PNG file with alpha data.\n");
waitKey(0);
return 0;
}
int coloured(Mat &template_src, Mat &mat_png, CvScalar color)
{
for (int i = 0; i < template_src.rows; ++i)
{
for (int j = 0; j < template_src.cols; ++j)
{
Vec4b& bgra = mat_png.at<Vec4b>(i, j);
//int temp = template_src.at<uchar>(i,j);
if (template_src.at<uchar>(i,j)== 0)
{
bgra[0] = color.val[0]; //b通道
bgra[1] = color.val[1]; //g通道
bgra[2] = color.val[2]; //r通道
bgra[3] = 255;//alpha通道全部设置为透明完全透明为0,否则为255
}
else
{
bgra[3] = 0;//alpha通道全部设置为透明完全透明为0,否则为255
}
}
}
return 0;
}
void ImageBinarization(IplImage *src)
{ /*对灰度图像二值化,自适应门限threshold*/
int i,j,width,height,step,chanel,threshold;
/*size是图像尺寸,svg是灰度直方图均值,va是方差*/
float size,avg,va,maxVa,p,a,s;
unsigned char *dataSrc;
float histogram[256];
width = src->width;
height = src->height;
dataSrc = (unsigned char *)src->imageData;
step = src->widthStep/sizeof(char);
chanel = src->nChannels;
/*计算直方图并归一化histogram*/
for(i=0; i<256; i++)
histogram[i] = 0;
for(i=0; i<height; i++)
for(j=0; j<width*chanel; j++)
{
histogram[dataSrc[i*step+j]-'0'+48]++;
}
size = width * height;
for(i=0; i<256; i++)
histogram[i] /=size;
/*计算灰度直方图中值和方差*/
avg = 0;
for(i=0; i<256; i++)
avg += i*histogram[i];
va = 0;
for(i=0; i<256; i++)
va += fabs(i*i*histogram[i]-avg*avg);
/*利用加权最大方差求门限*/
threshold = 20;
maxVa = 0;
p = a = s = 0;
for(i=0; i<256; i++)
{
p += histogram[i];
a += i*histogram[i];
s = (avg*p-a)*(avg*p-a)/p/(1-p);
if(s > maxVa)
{
threshold = i;
maxVa = s;
}
}
/*二值化*/
for(i=0; i<height; i++)
for(j=0; j<width*chanel; j++)
{
if(dataSrc[i*step+j] > threshold)
dataSrc[i*step+j] = 255;
else
dataSrc[i*step+j] = 0;
}
}
Mat binaryzation(Mat &src)
{
Mat des_gray(src.size(),CV_8UC1);
cvtColor(src,des_gray,CV_BGR2GRAY);
//Mat bin_mat();
IplImage temp(des_gray);
ImageBinarization(&temp);
//threshold(des_gray,des_gray,150,255,THRESH_BINARY);
imshow("二值图像",des_gray);
return des_gray;
}
int generate_chinese(const int size_zi, const char *msg ,int number,CvScalar color)
{
//int size_zi = 50;//字体大小
CvSize czSize; //目标图像尺寸
float p = 0.5;
CvScalar fsize;
//读取TTF字体文件
CvxText text("simhei.ttf");
//设置字体属性 字体大小/空白比例/间隔比例/旋转角度
fsize = cvScalar(size_zi, 1, 0.1, 0);
text.setFont(NULL, &fsize, NULL, &p);
czSize.width = size_zi*number;
czSize.height = size_zi;
//加载原图像
IplImage* ImageSrc = cvCreateImage(czSize,IPL_DEPTH_8U,3);//cvLoadImage(Imagename, CV_LOAD_IMAGE_UNCHANGED);
//Mat image(ImageSrc);
//createAlphaMat(image);
//ImageSrc = ℑ
//IplImage temp(image);
//ImageSrc = &temp;
//设置原图像文字
text.putText(ImageSrc, msg, cvPoint(1, size_zi), color);
//显示原图像
cvShowImage("原图", ImageSrc);
string hanzi = msg;
hanzi = hanzi + ".png";
Mat chinese(ImageSrc,true);
Mat gray = binaryzation(chinese);
imwrite("chinese_gray.jpg",gray);
Mat mat_png(chinese.size(),CV_8UC4);
coloured(gray,mat_png,color);
run_test_png(mat_png,hanzi);
//
////cvSaveImage("hanzi.jpg",reDstImage);
//run_test_png(chinese,hanzi);
//等待按键事件
cvWaitKey();
return 0;
}
int main()
{
CvScalar color = CV_RGB(0,0,0);
int size = 200;
const char* msg = "你好a";//暂时一行字不要太长
int number = 3;//字符个数
generate_chinese(size,msg,number,color);
return 0;
}
完整工程下载:
http://download.csdn.net/detail/wangyaninglm/8486521
参考文献:
http://blog.csdn.net/fengbingchun/article/details/8029337
http://www.oschina.net/code/snippet_1447359_36028
http://blog.csdn.net/hustspy1990/article/details/6301592
========