opencv图像处理总结
生活随笔
收集整理的這篇文章主要介紹了
opencv图像处理总结
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
opencv圖像處理基本操作
1. 矩陣數(shù)據(jù)類型
通用矩陣數(shù)據(jù)類型:
CV_<bit_depth>(S|U|F)C<number_of_channels>
其中,S表示帶符號整數(shù);
U表示無符號整數(shù);
F表示浮點數(shù);
例如:CV_8UC1 表示8位無符號單通道矩陣;
CV_32FC2 表示32位浮點數(shù)雙通道矩陣;
2. 圖像數(shù)據(jù)類型
通用圖像數(shù)據(jù)類型為:
IPL_DEPTH_<bit_depth>(S|U|F)
如:IPL_DEPTH_8U 表示8位無符號整數(shù)圖像;
IPL_DEPTH_32F 表示32位浮點數(shù)圖像;
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為每個像素的通道數(shù),可以是1,2,3或4。通道是交叉排列的,一幅彩色
圖像的通常的排列順序是:
b0 g0 r0 b1 g1 r1 ...
例如:分配一個單通道單字節(jié)圖像的語句是:
IpIImage* img1 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1);
分配一個三通道浮點數(shù)圖像語句是:
IpIImage* img2 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_32F, 3);
3.2 釋放圖像
void cvReleaseImage(IpIImage **);
3.3 復(fù)制一幅圖像
IpIImage* cvCloneImage(IpIImage *);
如:
IpIImage* img1 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1);
IpIImage* img2;
img2 = cvCloneImage(img1);
3.4 設(shè)置或得到感興趣區(qū)域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);
默認(rèn)為讀取三通道圖像。如果改變設(shè)置則采用如下的方式:
img = cvLoadImage(filename, flag);
當(dāng)flag > 0時,表示載入圖像為3通道彩色圖像;
當(dāng)flag = 0時,表示載入圖像為單通道灰色圖像;
當(dāng)flag < 0時,表示載入圖像由文件中的圖像通道數(shù)決定。
5. 圖像轉(zhuǎn)換
5.1 將灰度圖像轉(zhuǎn)換為彩色圖像
cvConvertImage(src, dst, flags = 0);
其中,src表示浮點(單字節(jié))灰度(彩色)圖像;
dst表示單字節(jié)灰度(彩色)圖像;
flags表示
+--- CV_CVTIMG_FLIP, 垂直翻轉(zhuǎn)
flags = |
+--- CV_CVTIMG_SWAP_RB, 交換R和B通道
5.2 將彩色圖像轉(zhuǎn)換為灰度圖像
cvCvtColor(cimg, gimg, CV_RGB2GRAY);
5.3 彩色空間的轉(zhuǎn)換
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);
其實他把這個問題復(fù)雜化了,對指定部分縮放,首先要說明自己對哪個部分
感興趣cvSetImageROI,通過這個函數(shù),圖像就僅僅剩下了ROI部分,然后
通過cvResize()把這個ROI區(qū)域按照自己的意愿放大縮小,我自己編程如下:
::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);
感興趣區(qū)域為中間的區(qū)域,大小為原來的1/2,重新劃分后感興趣區(qū)域為原來
大小,搞定。
========
OpenCv入門-圖像處理基本函數(shù)
1、圖像的內(nèi)存分配與釋放
(1) 分配內(nèi)存給一幅新圖像:
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: 像素通道數(shù). Can be 1, 2, 3 or 4.
? ? ? ? ? ? ? ? ?各通道是交錯排列的. 一幅彩色圖像的數(shù)據(jù)排列格式如下:
? ? ? ? ? ? ? ? ?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) 復(fù)制圖像:
IplImage* img1=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
IplImage* img2;
img2=cvCloneImage(img1); ?// 注意通過cvCloneImage得到的圖像
? ? ? ? ? ? ? ? ? ? ? // 也要用 cvReleaseImage 釋放,否則容易產(chǎn)生內(nèi)存泄漏
?
(4) 設(shè)置/獲取感興趣區(qū)域ROI: ? ? ? ? (ROI:Region Of Interest) ? ? ?
void ?cvSetImageROI(IplImage* image, CvRect rect);
void ?cvResetImageROI(IplImage* image);
CvRect cvGetImageROI(const IplImage* image);
大多數(shù)OpenCV函數(shù)都支持 ROI.
?
(5) 設(shè)置/獲取感興趣通道COI: ? ? ? ? (COI:channel of interest)
void cvSetImageCOI(IplImage* image, int coi); // 0=all
int cvGetImageCOI(const IplImage* image);
大多數(shù)OpenCV函數(shù)不支持 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默認(rèn)將讀入的圖像強(qiáng)制轉(zhuǎn)換為一幅三通道彩色圖像. 不過可以按以下方法修改讀入方式:
img=cvLoadImage(fileName,flag);
?flag: >0 將讀入的圖像強(qiáng)制轉(zhuǎn)換為一幅三通道彩色圖像
? ? ? ?=0 將讀入的圖像強(qiáng)制轉(zhuǎn)換為一幅單通道灰度圖像
? ? ? ?<0 讀入的圖像通道數(shù)與所讀入的文件相同.
?
(2) 保存圖像:
if(!cvSaveImage(outFileName,img)) printf("Could not save: %s\n", outFileName);
保存的圖像格式由 outFileName 中的擴(kuò)展名確定.
?
3、訪問圖像像素
(1) 假設(shè)你要訪問第k通道、第i行、第j列的像素。
(2) 間接訪問: (通用,但效率低,可訪問任意格式的圖像)
對于單通道字節(jié)型圖像
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中坐標(biāo)參數(shù)的順序與其它opencv函數(shù)坐標(biāo)參數(shù)順序恰好相反.本函數(shù)中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
?
對于多通道字節(jié)型/浮點型圖像:
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) 直接訪問: (效率高,但容易出錯)
? ? ? ?對于單通道字節(jié)型圖像:
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
((uchar *)(img->imageData + i*img->widthStep))[j]=111; ? ? ? ? ? ? ? ? ? ? ? (img->imageData即數(shù)組首指針,i為行數(shù),img->widthStep每行所占字節(jié)數(shù))
? ? ? ?對于多通道字節(jié)型圖像:
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) 基于指針的直接訪問: (簡單高效)
? ? ? ?對于單通道字節(jié)型圖像:
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;
?
? ? ? ?對于多通道字節(jié)型圖像:
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;
?
? ? ? ?對于多通道浮點型圖像(假設(shè)圖像數(shù)據(jù)采用4字節(jié)(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++封裝沒怎么學(xué),要用再仔細(xì)學(xué))
? ? ? ?首先定義一個 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;
?
? ? ? ?對于單通道字節(jié)型圖像:
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
BwImage imgA(img);
imgA[i][j] = 111;
?
? ? ? ?對于多通道字節(jié)型圖像:
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中圖像處理函數(shù) ?
1。濾波 Filtering
filter2D() 用核函數(shù)對圖像做卷積
sepFilter2D() 用分解的核函數(shù)對圖像做卷積。首先,圖像的每一行與一維的核kernelX做卷積;然后,運算結(jié)果的每一列與一維的核kernelY做卷積
boxFilter() 就是滑動窗口平均濾波的二維版。
GaussianBlur() 高斯平均,也就是高斯模糊。
medianBlur() 中值濾波,個人最愛的濾波函數(shù)。
bilateralFilter() 雙線性濾波。
前面這四個函數(shù)是原來OpenCV里的cvSmooth()取不同參數(shù)的應(yīng)用。
Sobel() 使用擴(kuò)展 Sobel 算子計算一階、二階、三階或混合圖像差分。
Scharr() 計算一階導(dǎo),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);
構(gòu)造了一個如下所示的核對圖像做卷積:
-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) 其中非整數(shù)象素點坐標(biāo)采用雙線性插值提取。
warpAffine() 仿射變換。
warpPerspective() 透射變換。
remap() 幾何變換。
convertMaps() 將圖像從一種類型,轉(zhuǎn)換成另一種類型。
示例:
Mat dst;
resize(src, dst, Size(), 1./sqrt(2), 1./sqrt(2)); // 把圖像縮小到原來的根號二分之一。
3。 圖像變換 Various Image Transformations
cvtColor()色彩空間轉(zhuǎn)換。這個函數(shù)可以用于把CCD的raw格式轉(zhuǎn)換為RGB,請參考,但是不能用于把灰度圖轉(zhuǎn)成偽彩圖,請參考。
threshold() ?二值化,常用操作,一般應(yīng)用時建議用大津算法,即使用THRESH_OTSU參數(shù)。
adaptivethreshold() ?自適應(yīng)閾值的二值化。
floodFill() ?填充連通域。
integral() ?計算積分圖像,一次或者二次。
distanceTransform() ?距離變換,對原圖像的每一個像素計算到最近非零像素的距離。
watershed() ?分水嶺圖像分割。
grabCut()
一種彩色圖像分割算法,效果可以參考這里。See the samples watershed.cpp and grabcut.cpp.
4。 直方圖 Histograms
calcHist() ?計算直方圖。
calcBackProject() ?計算反向投影。
equalizeHist() ?灰度圖像的直方圖均衡化,常用操作。
compareHist() ?比較兩個直方圖。
例子:計算圖像的色調(diào)-飽和度直方圖。
Mat hsv, H;
cvtColor(image, hsv, CVBGR2HSV);
int planes[]=f0, 1g, hsize[] = f32, 32g;
calcHist(&hsv, 1, planes, Mat(), H, 2, hsize, 0);
========
形態(tài)學(xué)圖像處理 膨脹與腐蝕
http://blog.csdn.net/poem_qianmo/article/details/23710721寫作當(dāng)前博文時配套使用的OpenCV版本: 2.4.8
一、理論與概念講解——從現(xiàn)象到本質(zhì)
1.1 ?形態(tài)學(xué)概述
形態(tài)學(xué)(morphology)一詞通常表示生物學(xué)的一個分支,該分支主要研究動植物的形態(tài)和結(jié)構(gòu)。而我們圖像處理中指的形態(tài)學(xué),往往表示的是數(shù)學(xué)形態(tài)學(xué)。下面一起來了解數(shù)學(xué)形態(tài)學(xué)的概念。
數(shù)學(xué)形態(tài)學(xué)(Mathematical morphology) 是一門建立在格論和拓?fù)鋵W(xué)基礎(chǔ)之上的圖像分析學(xué)科,是數(shù)學(xué)形態(tài)學(xué)圖像處理的基本理論。其基本的運算包括:二值腐蝕和膨脹、二值開閉運算、骨架抽取、極限腐蝕、擊中擊不中變換、形態(tài)學(xué)梯度、Top-hat變換、顆粒分析、
流域變換、灰值腐蝕和膨脹、灰值開閉運算、灰值形態(tài)學(xué)梯度等。
簡單來講,形態(tài)學(xué)操作就是基于形狀的一系列圖像處理操作。OpenCV為進(jìn)行圖像的形態(tài)學(xué)變換提供了快捷、方便的函數(shù)。最基本的形態(tài)學(xué)操作有二種,他們是:膨脹與腐蝕(Dilation與Erosion)。
膨脹與腐蝕能實現(xiàn)多種多樣的功能,主要如下:
消除噪聲
分割(isolate)出獨立的圖像元素,在圖像中連接(join)相鄰的元素。
尋找圖像中的明顯的極大值區(qū)域或極小值區(qū)域
求出圖像的梯度
我們在這里給出下文會用到的,用于對比膨脹與腐蝕運算的“淺墨”字樣毛筆字原圖:
在進(jìn)行腐蝕和膨脹的講解之前,首先需要注意,腐蝕和膨脹是對白色部分(高亮部分)而言的,不是黑色部分。膨脹就是圖像中的高亮部分進(jìn)行膨脹,“領(lǐng)域擴(kuò)張”,效果圖擁有比原圖更大的高亮區(qū)域。腐蝕就是原圖中的高亮部分被腐蝕,“領(lǐng)域被蠶食”,效果圖擁有
比原圖更小的高亮區(qū)域。
1.2 膨脹
其實,膨脹就是求局部最大值的操作。
按數(shù)學(xué)方面來說,膨脹或者腐蝕操作就是將圖像(或圖像的一部分區(qū)域,我們稱之為A)與核(我們稱之為B)進(jìn)行卷積。
核可以是任何的形狀和大小,它擁有一個單獨定義出來的參考點,我們稱其為錨點(anchorpoint)。多數(shù)情況下,核是一個小的中間帶有參考點和實心正方形或者圓盤,其實,我們可以把核視為模板或者掩碼。
而膨脹就是求局部最大值的操作,核B與圖形卷積,即計算核B覆蓋的區(qū)域的像素點的最大值,并把這個最大值賦值給參考點指定的像素。這樣就會使圖像中的高亮區(qū)域逐漸增長。如下圖所示,這就是膨脹操作的初衷。
膨脹的數(shù)學(xué)表達(dá)式:
膨脹效果圖(毛筆字):
??
照片膨脹效果圖:
1.3 腐蝕
再來看一下腐蝕,大家應(yīng)該知道,膨脹和腐蝕是一對好基友,是相反的一對操作,所以腐蝕就是求局部最小值的操作。
我們一般都會把腐蝕和膨脹對應(yīng)起來理解和學(xué)習(xí)。下文就可以看到,兩者的函數(shù)原型也是基本上一樣的。
原理圖:
腐蝕的數(shù)學(xué)表達(dá)式:
腐蝕效果圖(毛筆字):
照片腐蝕效果圖:
二、深入——OpenCV源碼分析溯源
直接上源碼吧,在 …\opencv\sources\modules\imgproc\src\ morph.cpp路徑中 的第 1353行開始就為 erode(腐蝕)函數(shù)的源碼, 1361行為 dilate(膨脹)函數(shù)的源碼。
//-----------------------------------【erode()函數(shù)中文注釋版源代碼】----------------------------?
// ? ?說明:以下代碼為來自于計算機(jī)開源視覺庫OpenCV的官方源代碼?
// ? ?OpenCV源代碼版本:2.4.8?
// ? ?源碼路徑:…\opencv\sources\modules\imgproc\src\ morph.cpp?
// ? ?源文件中如下代碼的起始行數(shù):1353行?
// ? ?中文注釋by淺墨?
//-------------------------------------------------------------------------------------------------------- ?
void cv::erode( InputArray src, OutputArraydst, InputArray kernel,
? ? ? ? ? ? ? ? Point anchor, int iterations,
? ? ? ? ? ? ? ? int borderType, constScalar& borderValue )
{
//調(diào)用morphOp函數(shù),并設(shè)定標(biāo)識符為MORPH_ERODE
? ?morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType,borderValue );
}
//-----------------------------------【dilate()函數(shù)中文注釋版源代碼】----------------------------?
// ? ?說明:以下代碼為來自于計算機(jī)開源視覺庫OpenCV的官方源代碼?
// ? ?OpenCV源代碼版本:2.4.8?
// ? ?源碼路徑:…\opencv\sources\modules\imgproc\src\ morph.cpp?
// ? ?源文件中如下代碼的起始行數(shù):1361行?
// ? ?中文注釋by淺墨?
//--------------------------------------------------------------------------------------------------------?
void cv::dilate( InputArray src,OutputArray dst, InputArray kernel,
? ? ? ? ? ? ? ? ?Point anchor, int iterations,
? ? ? ? ? ? ? ? ?int borderType, constScalar& borderValue )
{
//調(diào)用morphOp函數(shù),并設(shè)定標(biāo)識符為MORPH_DILATE
? ?morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType,borderValue );
}
可以發(fā)現(xiàn)erode和dilate這兩個函數(shù)內(nèi)部就是調(diào)用了一下morphOp,只是他們調(diào)用morphOp時,第一個參數(shù)標(biāo)識符不同,一個為MORPH_ERODE(腐蝕),一個為MORPH_DILATE(膨脹)。
morphOp函數(shù)的源碼在…\opencv\sources\modules\imgproc\src\morph.cpp中的第1286行,有興趣的朋友們可以研究研究,這里就不費時費力花篇幅展開分析了。
三、淺出——API函數(shù)快速上手
3.1 ? 形態(tài)學(xué)膨脹——dilate函數(shù)
erode函數(shù),使用像素鄰域內(nèi)的局部極大運算符來膨脹一張圖片,從src輸入,由dst輸出。支持就地(in-place)操作。
函數(shù)原型:
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()?
);
參數(shù)詳解:
第一個參數(shù),InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。圖像通道的數(shù)量可以是任意的,但圖像深度應(yīng)為CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
第二個參數(shù),OutputArray類型的dst,即目標(biāo)圖像,需要和源圖片有一樣的尺寸和類型。
第三個參數(shù),InputArray類型的kernel,膨脹操作的核。若為NULL時,表示的是使用參考點位于中心3x3的核。
我們一般使用函數(shù) getStructuringElement配合這個參數(shù)的使用。getStructuringElement函數(shù)會返回指定形狀和尺寸的結(jié)構(gòu)元素(內(nèi)核矩陣)。
其中,getStructuringElement函數(shù)的第一個參數(shù)表示內(nèi)核的形狀,我們可以選擇如下三種形狀之一:
矩形: MORPH_RECT
交叉形: MORPH_CROSS
橢圓形: MORPH_ELLIPSE
而getStructuringElement函數(shù)的第二和第三個參數(shù)分別是內(nèi)核的尺寸以及錨點的位置。
我們一般在調(diào)用erode以及dilate函數(shù)之前,先定義一個Mat類型的變量來獲得getStructuringElement函數(shù)的返回值。對于錨點的位置,有默認(rèn)值Point(-1,-1),表示錨點位于中心。且需要注意,十字形的element形狀唯一依賴于錨點的位置。而在其他情況下,錨點只是
影響了形態(tài)學(xué)運算結(jié)果的偏移。
getStructuringElement函數(shù)相關(guān)的調(diào)用示例代碼如下:?
int g_nStructElementSize = 3; //結(jié)構(gòu)元素(內(nèi)核矩陣)的尺寸
?
? //獲取自定義核
Mat element = getStructuringElement(MORPH_RECT,
? Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),
? Point( g_nStructElementSize, g_nStructElementSize ));
調(diào)用這樣之后,我們便可以在接下來調(diào)用erode或dilate函數(shù)時,第三個參數(shù)填保存了getStructuringElement返回值的Mat類型變量。對應(yīng)于我們上面的示例,就是填element變量。
第四個參數(shù),Point類型的anchor,錨的位置,其有默認(rèn)值(-1,-1),表示錨位于中心。
第五個參數(shù),int類型的iterations,迭代使用erode()函數(shù)的次數(shù),默認(rèn)值為1。
第六個參數(shù),int類型的borderType,用于推斷圖像外部像素的某種邊界模式。注意它有默認(rèn)值BORDER_DEFAULT。
第七個參數(shù),const Scalar&類型的borderValue,當(dāng)邊界為常數(shù)時的邊界值,有默認(rèn)值morphologyDefaultBorderValue(),一般我們不用去管他。需要用到它時,可以看官方文檔中的createMorphologyFilter()函數(shù)得到更詳細(xì)的解釋。
?
使用erode函數(shù),一般我們只需要填前面的三個參數(shù),后面的四個參數(shù)都有默認(rèn)值。而且往往結(jié)合getStructuringElement一起使用。
調(diào)用范例:
//載入原圖?
? ? Mat image = imread("1.jpg");
? ? //獲取自定義核
? ? Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
? ? Mat out;
? ? //進(jìn)行膨脹操作
? ? 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( )函數(shù)】--------------------------------------------
// ? ? 描述:控制臺應(yīng)用程序的入口函數(shù),我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( ?)
{
?
? //載入原圖?
? Mat image = imread("1.jpg");
?
? //創(chuàng)建窗口?
? namedWindow("【原圖】膨脹操作");
? namedWindow("【效果圖】膨脹操作");
?
? //顯示原圖
? imshow("【原圖】膨脹操作", image);
?
? //獲取自定義核
? Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
? Mat out;
? //進(jìn)行膨脹操作
? dilate(image,out, element);
?
? //顯示效果圖
? imshow("【效果圖】膨脹操作", out);
?
? waitKey(0);
?
? return 0;
}
?運行截圖:
3.2 ?形態(tài)學(xué)腐蝕——erode函數(shù)
erode函數(shù),使用像素鄰域內(nèi)的局部極小運算符來腐蝕一張圖片,從src輸入,由dst輸出。支持就地(in-place)操作。
看一下函數(shù)原型:
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()
?);
參數(shù)詳解:
第一個參數(shù),InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。圖像通道的數(shù)量可以是任意的,但圖像深度應(yīng)為CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
第二個參數(shù),OutputArray類型的dst,即目標(biāo)圖像,需要和源圖片有一樣的尺寸和類型。
第三個參數(shù),InputArray類型的kernel,腐蝕操作的內(nèi)核。若為NULL時,表示的是使用參考點位于中心3x3的核。我們一般使用函數(shù) getStructuringElement配合這個參數(shù)的使用。getStructuringElement函數(shù)會返回指定形狀和尺寸的結(jié)構(gòu)元素(內(nèi)核矩陣)。(具體看上
文中淺出部分dilate函數(shù)的第三個參數(shù)講解部分)
第四個參數(shù),Point類型的anchor,錨的位置,其有默認(rèn)值(-1,-1),表示錨位于單位(element)的中心,我們一般不用管它。
第五個參數(shù),int類型的iterations,迭代使用erode()函數(shù)的次數(shù),默認(rèn)值為1。
第六個參數(shù),int類型的borderType,用于推斷圖像外部像素的某種邊界模式。注意它有默認(rèn)值BORDER_DEFAULT。
第七個參數(shù),const Scalar&類型的borderValue,當(dāng)邊界為常數(shù)時的邊界值,有默認(rèn)值morphologyDefaultBorderValue(),一般我們不用去管他。需要用到它時,可以看官方文檔中的createMorphologyFilter()函數(shù)得到更詳細(xì)的解釋。
同樣的,使用erode函數(shù),一般我們只需要填前面的三個參數(shù),后面的四個參數(shù)都有默認(rèn)值。而且往往結(jié)合getStructuringElement一起使用。
調(diào)用范例:
//載入原圖?
? ? Mat image = imread("1.jpg");
? ? //獲取自定義核
? ? Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
? ? Mat out;
? ? //進(jìn)行腐蝕操作
? ? 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( )函數(shù)】--------------------------------------------
// ? ? 描述:控制臺應(yīng)用程序的入口函數(shù),我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( ?)
{
? //載入原圖?
? Matimage = imread("1.jpg");
?
? ?//創(chuàng)建窗口?
? namedWindow("【原圖】腐蝕操作");
? namedWindow("【效果圖】腐蝕操作");
?
? //顯示原圖
? imshow("【原圖】腐蝕操作", image);
?
? ?
? //獲取自定義核
? Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
? Mat out;
?
? //進(jìn)行腐蝕操作
? erode(image,out, element);
?
? //顯示效果圖
? imshow("【效果圖】腐蝕操作", out);
?
? waitKey(0);
?
? return 0;
}
運行結(jié)果:
四、綜合示例——在實戰(zhàn)中熟稔
這個示例程序中的效果圖窗口有兩個滾動條,顧名思義,第一個滾動條“腐蝕/膨脹”用于在腐蝕/膨脹之間進(jìn)行切換;第二個滾動條”內(nèi)核尺寸”用于調(diào)節(jié)形態(tài)學(xué)操作時的內(nèi)核尺寸,以得到效果不同的圖像,有一定的可玩性。廢話不多說,上代碼吧:
//-----------------------------------【程序說明】----------------------------------------------
// ? ? 程序名稱::《【OpenCV入門教程之十】形態(tài)學(xué)圖像處理(一):膨脹與腐蝕 ?》 博文配套源碼
// ? ? 開發(fā)所用IDE版本:Visual Studio 2010
// ? 開發(fā)所用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; //結(jié)構(gòu)元素(內(nèi)核矩陣)的尺寸
?
?
//-----------------------------------【全局函數(shù)聲明部分】--------------------------------------
// ? ? 描述:全局函數(shù)聲明
//-----------------------------------------------------------------------------------------------
void Process();//膨脹和腐蝕的處理函數(shù)
void on_TrackbarNumChange(int, void *);//回調(diào)函數(shù)
void on_ElementSizeChange(int, void *);//回調(diào)函數(shù)
?
?
//-----------------------------------【main( )函數(shù)】--------------------------------------------
// ? ? 描述:控制臺應(yīng)用程序的入口函數(shù),我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
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);
? ? ??
? //進(jìn)行初次腐蝕操作并顯示效果圖
? 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);
?
? //創(chuàng)建軌跡條
? createTrackbar("腐蝕/膨脹", "【效果圖】", &g_nTrackbarNumer, 1, on_TrackbarNumChange);
? createTrackbar("內(nèi)核尺寸", "【效果圖】",&g_nStructElementSize, 21, on_ElementSizeChange);
?
? //輸出一些幫助信息
? cout<<endl<<"\t嗯。運行成功,請調(diào)整滾動條觀察圖像效果~\n\n"
? ? <<"\t按下“q”鍵時,程序退出~!\n"
? ? <<"\n\n\t\t\t\tby淺墨";
?
? //輪詢獲取按鍵信息,若下q鍵,程序退出
? while(char(waitKey(1))!= 'q') {}
?
? return 0;
}
?
//-----------------------------【Process( )函數(shù)】------------------------------------
// ? ? 描述:進(jìn)行自定義的腐蝕和膨脹操作
//-----------------------------------------------------------------------------------------
void Process()
{
? //獲取自定義核
? Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));
?
? //進(jìn)行腐蝕或膨脹操作
? if(g_nTrackbarNumer== 0) { ??
? ? erode(g_srcImage,g_dstImage, element);
? }
? else{
? ? dilate(g_srcImage,g_dstImage, element);
? }
?
? //顯示效果圖
? imshow("【效果圖】", g_dstImage);
}
?
?
//-----------------------------【on_TrackbarNumChange( )函數(shù)】------------------------------------
// ? ? 描述:腐蝕和膨脹之間切換開關(guān)的回調(diào)函數(shù)
//-----------------------------------------------------------------------------------------------------
void on_TrackbarNumChange(int, void *)
{
? //腐蝕和膨脹之間效果已經(jīng)切換,回調(diào)函數(shù)體內(nèi)需調(diào)用一次Process函數(shù),使改變后的效果立即生效并顯示出來
? Process();
}
?
?
//-----------------------------【on_ElementSizeChange( )函數(shù)】-------------------------------------
// ? ? 描述:腐蝕和膨脹操作內(nèi)核改變時的回調(diào)函數(shù)
//-----------------------------------------------------------------------------------------------------
void on_ElementSizeChange(int, void *)
{
? //內(nèi)核尺寸已改變,回調(diào)函數(shù)體內(nèi)需調(diào)用一次Process函數(shù),使改變后的效果立即生效并顯示出來
? Process();
}
放出一些效果圖吧。原始 圖:
膨脹效果圖:
腐蝕效果圖:
腐蝕和膨脹得到的圖,都特有喜感,但千變?nèi)f變,還是原圖好看:
========
OpenCV圖像處理篇之腐蝕與膨脹
腐蝕與膨脹
腐蝕和膨脹是圖像的形態(tài)學(xué)處理中最基本的操作,之后遇見的開操作和閉操作都是腐蝕和膨脹操作的結(jié)合運算。腐蝕和膨脹的應(yīng)用非常廣泛,而且效果還很好:
腐蝕可以分割(isolate)獨立的圖像元素,膨脹用于連接(join)相鄰的元素,這也是腐蝕和膨脹后圖像最直觀的展現(xiàn)
去噪:通過低尺寸結(jié)構(gòu)元素的腐蝕操作很容易去掉分散的椒鹽噪聲點
圖像輪廓提取:腐蝕操作
圖像分割
等等...(在文后給出一則簡單實用膨脹操作提取車牌數(shù)字區(qū)域的例子)
結(jié)構(gòu)元素是形態(tài)學(xué)操作中最重要的概念,
erode_show dilate_show
如上圖,B為結(jié)構(gòu)元素。
腐蝕操作描述為:掃描圖像的每一個像素,用結(jié)構(gòu)元素與其覆蓋的二值圖像做“與”操作:如果都為1,結(jié)果圖像的該像素為1,否則為0。
膨脹操作描述為:掃描圖像的每一個像素,用結(jié)構(gòu)元素與其覆蓋的二值圖像做“與”操作:如果都為0,結(jié)果圖像的該像素為0,否則為1。
以上都是關(guān)于二值圖像的形態(tài)學(xué)操作,對于灰度圖像:
腐蝕操作
其中,g(x,y)為腐蝕后的灰度圖像,f(x,y)為原灰度圖像,B為結(jié)構(gòu)元素。腐蝕運算是由結(jié)構(gòu)元素確定的鄰域塊中選取圖像值與結(jié)構(gòu)元素值的差的最小值。
膨脹操作
其中,g(x,y)為腐蝕后的灰度圖像,f(x,y)為原灰度圖像,B為結(jié)構(gòu)元素。 膨脹運算是由結(jié)構(gòu)元素確定的鄰域塊中選取圖像值與結(jié)構(gòu)元素值的和的最大值。
在灰度圖的形態(tài)學(xué)操作中,一般選擇“平攤”的結(jié)構(gòu)元素,即結(jié)構(gòu)元素B的值為0,則上面對灰度圖的形態(tài)學(xué)操作可簡化如下:
好了,這就是基本的形態(tài)學(xué)操作——腐蝕和膨脹,下面是使用OpenCV對圖像進(jìn)行腐蝕和膨脹的程序,還是秉承我們一貫的原則:擱下理論,先直觀地感覺圖像處理算法的效果,實際項目需要時再深入挖掘!
程序分析
/*
?* 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 ? 腐蝕操作的回調(diào)函數(shù)
?* @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 ? 膨脹操作的回調(diào)函數(shù)
?* @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);
}
膨脹和腐蝕操作的函數(shù)分別是erode和dilate,傳遞給他們的參數(shù)也都依次是原圖像、形態(tài)學(xué)操作后的圖像、結(jié)構(gòu)元素ele。本程序中給出了3種結(jié)構(gòu)元素類型,分別是
#define TYPE_MORPH_RECT ? ? ?(0) ?// 矩形
#define TYPE_MORPH_CROSS ? ? (1) ?// 十字交叉型
#define TYPE_MORPH_ELLIPSE ? (2) ?// 橢圓型
再通過OpenCV提供的getStructuringElement函數(shù)創(chuàng)建Mat類型的結(jié)構(gòu)元素。
getStructuringElement的參數(shù)依次是結(jié)構(gòu)元素類型(OpenCV中提供了宏定義MORPH_RECT、MORPH_CROSS和MORPH_ELLIPSE表示)、結(jié)構(gòu)元素大小。
這里我們首次接觸了createTrackbar函數(shù)(聲明在highgui.hpp中),該函數(shù)的功能是給窗口添加滑動條。其原型是:
CV_EXPORTS int createTrackbar( const string& trackbarname, const string& winname,
? ? ? ? ? ? ? ? ? ? ? ? ?int* value, int count,
? ? ? ? ? ? ? ? ? ? ? ? ?TrackbarCallback onChange=0,
? ? ? ? ? ? ? ? ? ? ? ? ?void* userdata=0);
trackbarname為滑動條的名稱,將會顯示在滑動條的前面,參見結(jié)果中的圖片顯示; winname為窗口名; value為滑動條關(guān)聯(lián)的變量,如上面程序中第一個滑動條關(guān)聯(lián)到erode_ele_type,表示——當(dāng)滑動條滑動變化時,erode_ele_type的值發(fā)生響應(yīng)的變化; count表示
滑動條能滑動到的最大值; TrackbarCallback onChange其實是這個函數(shù)的關(guān)鍵,是滑動條變化時調(diào)用的回調(diào)函數(shù)。當(dāng)滑動條滑動時,value值發(fā)生變化,系統(tǒng)立刻調(diào)用onChange函數(shù),執(zhí)行相關(guān)的操作,回調(diào)函數(shù)的定義形式是固定的:
void onChange(int, void *)
程序中的回調(diào)函數(shù)Erosion和Dilation函數(shù)的定義都遵循該形式:
static void Erosion(int, void *);
static void Dilation(int, void *);
結(jié)果及實際應(yīng)用
對“黑白小豬”進(jìn)行膨脹操作的變化(隨著結(jié)構(gòu)元素大小的變化)如下圖:
dilating_demo
對“黑白小豬”進(jìn)行腐蝕操作的變化(隨著結(jié)構(gòu)元素大小的變化)如下圖:
eroding_demo
膨脹與腐蝕在圖像處理中具有廣泛的用途,比如提取車牌過程中,可以通過膨脹運算確定車牌的區(qū)域。如下圖為通過sobel算子提取邊緣后的車牌,
car_plate
為去掉邊界,確定車牌在圖中的位置,可以通過膨脹操作,結(jié)果如下:
car_plate_dilate
上圖中的紅線區(qū)域就是膨脹后能用于確定車牌的連通區(qū)域,再通過對連通區(qū)域的搜索及“車牌的矩形特性”即可確定含有車牌數(shù)字在圖片中的位置。
========
OpenCV&Qt學(xué)習(xí)之三-圖像的初步處理?Qt圖像的縮放顯示
實現(xiàn)圖像縮放的方法很多,在 OpenCV&Qt學(xué)習(xí)之一——打開圖片文件并顯示 的例程中,label控件是通過
ui->imagelabel->resize(ui->imagelabel->pixmap()->size());
來實現(xiàn)適應(yīng)圖像顯示的,但是由于窗口固定,在打開的圖像小于控件大小時就會縮在左上角顯示,在打開圖像過大時則顯示不全。因此這個例程中首先實現(xiàn)圖像適合窗口的縮放顯示。
由于是基于OpenCV和Qt的圖像處理,因此圖像的縮放處理在OpenCV和Qt都可以完成,我這里就把OpenCV用作圖像的原始處理,Qt用作顯示處理,因此縮放顯示由Qt完成。
Qt中QImage提供了用于縮放的基本函數(shù),而且功能非常強(qiáng)大,使用Qt自帶的幫助可以檢索到相關(guān)信息。
函數(shù)原型:
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
函數(shù)說明以及參數(shù)在文檔中已經(jīng)說的非常清楚了,文檔摘錄如下:
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.
官方文檔中已經(jīng)說的比較清楚了,代碼實現(xiàn)也比較簡單,代碼如下:
{
? ? 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中圖像的顯示與基本操作 博客,但是存在一些疑點,博客中相關(guān)代碼如下:
QImage* imgScaled = new QImage;
*imgScaled=img->scaled(width,height,Qt::KeepAspectRatio);
ui->label->setPixmap(QPixmap::fromImage(*imgScaled));
對于以上代碼通過和我之前的代碼做簡單對比,發(fā)現(xiàn)有幾點不一樣的地方:
圖像的定義方式,這里的定義方式為QImage* imgScale = new QImage
scaled函數(shù)的調(diào)用方式,一個是imgScaled = img.scaled后者為*imgScaled=img->scaled,我最開始也是將.寫為->一直沒找出錯誤,提示base operand of '->' has non-pointer type 'QImage'
繼續(xù)查找Qt的幫助手冊,發(fā)現(xiàn)QImage的構(gòu)造函數(shù)還真是多:
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提供了適用于不同場合的構(gòu)造方式,在手冊中對他們也有具體的應(yīng)用,但是我仍然沒找到QImage image;和QImage* image = new QImage這兩種究竟對應(yīng)的是哪兩種,有什么區(qū)別和不同。 在上一篇博文 ?OpenCV&Qt學(xué)習(xí)之二——QImage的進(jìn)一步認(rèn)識 ?中提到了對于
圖像數(shù)據(jù)的一點認(rèn)識,其中提到QImage是對現(xiàn)有數(shù)據(jù)的一種重新整合,是一種格式,但是數(shù)據(jù)還是指向原來的。從這里來看還需要根據(jù)構(gòu)造方式具體區(qū)別,并不完全正確。
凌亂查了查資料,網(wǎng)上的資料就那幾個,互相轉(zhuǎn)來轉(zhuǎn)去的,而且多數(shù)比較老,仍然沒有幫助我想通關(guān)于這里面數(shù)據(jù)結(jié)構(gòu)的一些疑問,Qt 和 OpenCV對C和指針的要求還是比較高的,長時間從單片機(jī)類的程序過來那點功底還真不夠,具體的C應(yīng)用都忘光了。這個問題只能暫
時擱置,在后面的學(xué)習(xí)中慢慢理解。
基于OpenCV的圖像初步處理
以下兩個例程根據(jù)書籍 OpenCV 2 Computer Vision Application Programming Cookbook中的相關(guān)例程整理,這是一本比較新也比較基礎(chǔ)的入門書籍。
salt-and-pepper noise
關(guān)于圖像數(shù)據(jù)的基礎(chǔ)知識參見這段介紹:
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就是圖片中一些像素點,隨機(jī)的被黑色或者白色的像素點所替代,因此添加saltand-pepper noise也比較簡單,只需要隨機(jī)的產(chǎn)生行和列,將這些行列值對
應(yīng)的像素值更改即可,當(dāng)然通過上面的介紹,需要更改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系統(tǒng)中的自帶圖像考拉進(jìn)行處理后的效果如下圖所示(程序是Ubuntu 12.04下的):
image
減少色彩位數(shù)
在很多處理中需要對圖片中的所有像素進(jìn)行遍歷操作,采用什么方式進(jìn)行這個操作是需要思考的問題,關(guān)于這個問題的論述可以參考下面一段簡介:
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.
這個例子就是通過操作每一個像素點來減少色彩的位數(shù),基本內(nèi)容在以上的英文引文中已經(jīng)有了介紹,代碼的實現(xiàn)也比較直接。在彩色圖像中,3個通道的數(shù)據(jù)是依次排列的,每一行的像素三個通道的值依次排列,cv::Mat中的通道排列順序為BGR,那么一個圖像需要的地
址塊空間為uchar 寬×高×3.但是需要注意的是,有些處理器針對行數(shù)為4或8的圖像處理更有效率,因此為了提高效率就會填充一些額外的像素,這些額外的像素不被顯示和保存,值是忽略的。
實現(xiàn)這個功能的代碼如下:
// 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; 通過整除的方式,就像素位數(shù)進(jìn)行減少,這里沒明白的是為啥后面還要加上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圖像處理 空間域圖像增強(qiáng)
(圖像銳化 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),這個基于拉普拉斯算子的簡單算法主要是遍歷圖像中的像素點,根據(jù)領(lǐng)域像素確定其銳化后的值
計算公式:sharpened_pixel = 5 * current – left – right – up – down ; [見Code1]
OpenCV圖像處理 空間域圖像增強(qiáng)(圖像銳化 1 基于拉普拉斯算子) - ___________杰 - __________Ggicci
?
當(dāng)一個運算是通過領(lǐng)域像素進(jìn)行的時候,我們通常用一個矩陣來表示這種運算關(guān)系,也就是我們經(jīng)常所說的 核 (Kernel) 。那么上面的 銳化濾波器 (Sharpening Filter) 就可以用這個矩陣表示為它的核:
? -1 ?
-1 5 -1
? -1 ?
因為 濾波 在圖像處理中是一個非常普通且常用的操作,所以O(shè)penCV里面已經(jīng)定義了一個特殊的函數(shù)用來執(zhí)行這個操作。要使用它的話只需要定義一個 核 ,然后作為參數(shù)傳遞就行了。[見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());
? ? //處理邊界內(nèi)部的像素點, 圖像最外圍的像素點應(yīng)該額外處理
? ? for (int row = 1; row < img.rows-1; row++)
? ? {
? ? ? ? //前一行像素點
? ? ? ? const uchar* previous = img.ptr<const uchar>(row-1);
? ? ? ? //待處理的當(dāng)前行
? ? ? ? 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++)
? ? ? ? {
? ? ? ? ? ? //輸出圖像的遍歷指針與當(dāng)前行的指針同步遞增, 以每行的每一個像素點的每一個通道值為一個遞增量, 因為要考慮到圖像的通道數(shù)
? ? ? ? ? ? *output++ = saturate_cast<uchar>(5 * current[col] - current[col-ch] - current[col+ch] - previous[col] - next[col]);
? ? ? ? }
? ? } //end loop
? ? //處理邊界, 外圍像素點設(shè)為 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圖像處理 空間域圖像增強(qiáng)(圖像銳化 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圖像處理 空間域圖像增強(qiáng)(圖像銳化 1 基于拉普拉斯算子) - ___________杰 - __________Ggicci
?
End :
Author : Ggicci
========
OpenCV圖像處理 圖像的點運算??灰度直方圖?
OpenCV灰度直方圖
Theory :
從圖形上看,灰度直方圖是一個二維圖:
gray_hist
圖像的灰度直方圖是一個離散函數(shù),它表示圖像每一灰度級與該灰度級出現(xiàn)頻率的對應(yīng)關(guān)系。假設(shè)一幅圖像的像素總數(shù)為 N,灰度級總數(shù)為 L,其中灰度級為 g 的像素總數(shù)為 Ng,則這幅數(shù)字圖像的灰度直方圖橫坐標(biāo)即為灰度 g ( 0 ≤ g ≤ L-1 ),縱坐標(biāo)則為灰度值
出現(xiàn)的次數(shù) Ng。實際上,用 N 去除各個灰度值出現(xiàn)的次數(shù) Ng 即可得到各個灰度級出現(xiàn)的概率 Pg = Ng / N = Ng / ∑Ng ,從而得到歸一化的灰度直方圖,其縱坐標(biāo)為概率 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 函數(shù) :
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 );
這個函數(shù)用于計算直方圖是很強(qiáng)大的,在這里就實現(xiàn)一個最簡單的灰度圖像的直方圖計算。
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 };
? ? ? ?//調(diào)用 calcHist 計算直方圖, 結(jié)果存放在 hist 中
? ? ? ?calcHist(arrays, narrays, channels, mask, hist, dims, histSize, ranges);
? ? ? ?
? ? ? ?//調(diào)用一個我自己寫的簡單的函數(shù)用于獲取一張顯示直方圖數(shù)據(jù)的圖片,
? ? ? ?//輸入?yún)?shù)為直方圖數(shù)據(jù) 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 條紅線標(biāo)明區(qū)域
? ? ? ?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) 通過筆記一的學(xué)習(xí),我們已經(jīng)能夠下載、安裝OpenCV并新建VS2010項目進(jìn)行相關(guān)的配置,筆記一也已完成第一個程序HelloCV的演示。本文首先通過詳細(xì)介紹OpenCV中如何從硬盤加載/讀取一幅圖像,并在窗口中進(jìn)行顯示來對筆記一中的演示程序做詳解
。其次,本文實現(xiàn)了簡單的圖像變換,將一幅RGB顏色的圖片lena.jpg轉(zhuǎn)化成灰度圖像,以達(dá)到修改的目的,另外,在此變換中,我們還對如何將圖片保存到硬盤上進(jìn)行講解。實驗結(jié)果表明,通過筆記二的學(xué)習(xí),不但能夠增強(qiáng)對OpenCV的學(xué)習(xí)興趣,還能有初體驗OpenCV的
成就感,吃嘛嘛香,為后續(xù)的學(xué)習(xí)打下堅實的基礎(chǔ)。
1、加載并顯示圖像(Load and Display an Image)
?
1.1 學(xué)習(xí)目標(biāo)
?
在本節(jié)中,我們預(yù)期達(dá)到以下學(xué)習(xí)目標(biāo):1) 加載一幅圖像(采用imread方法);2)創(chuàng)建一個指定的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 = “l(fā)ena.jpg”;
?
? ? ? ? ?//讀入圖像
?
? ? ? ? ?Mat img = imread(imageName, CV_LOAD_IMAGE_COLOR);
?
?
?
? ? ? ? ?//如果讀入圖像失敗
?
? ? ? ? ?if (img.empty())
?
? ? ? ? ?{
?
? ? ? ? ? ? ? ? ? ?cout<<”Could not open or find the image!”<<endl;
?
? ? ? ? ? ? ? ? ? ?return -1;
?
? ? ? ? ?}
?
?
?
? ? ? ? ?//創(chuàng)建窗口
?
? ? ? ? ?namedWindow(“l(fā)ena”, CV_WINDOW_AUTOSIZE);
?
?
?
? ? ? ? ?//顯示圖像
?
? ? ? ? ?imshow(“l(fā)ena”, img);
?
?
?
? ? ? ? ?//等待按鍵,按鍵盤任意鍵返回
?
? ? ? ? ?waitKey();
?
?
?
? ? ? ? ?return 0;
?
}
?
1.3 源碼詳解
?
1.3.1 頭文件
?
OpenCV有許多不同的模塊,每個模塊關(guān)心圖像處理中不同的領(lǐng)域及方法(參見:OpenCV學(xué)習(xí)筆記(基于OpenCV 2.4)一:哈嘍CV),在使用之前我們首先需要對相應(yīng)的頭文件進(jìn)行包含,一般情況下我們都會用到的兩個模塊:
?
1)core section. 這里定義了OpenCV的一些基本的塊(Blocks);
?
2)highgui module. 該模塊包含了一些圖像的輸入輸出操作(UI)。
?
另外,為了能夠在控制臺做輸入輸出,我們會包含iostream,而string是用于字符串的處理。接下來,為了防止OpenCV的數(shù)據(jù)結(jié)構(gòu)或命名與其它庫函數(shù)比如STL有沖突,我們引入命名空間cv,在有沖突的情況下可以用前綴cv::來指定具體使用哪個庫(關(guān)于命名空間,我們
會在下一講做詳細(xì)介紹)。
?
1.3.2 主函數(shù)
?
--------------------------------------------------------------------------------
Mat img = imread(imageName, CV_LOAD_IMAGE_COLOR);
?
--------------------------------------------------------------------------------
imread的函數(shù)原型是:Mat imread( const string& filename, int flags=1 );
?
Mat是OpenCV里的一個數(shù)據(jù)結(jié)構(gòu),在這里我們定義一個Mat類型的變量img,用于保存讀入的圖像,在本文開始有寫到,我們用imread函數(shù)來讀取圖像,第一個字段標(biāo)識圖像的文件名(包括擴(kuò)展名),第二個字段用于指定讀入圖像的顏色和深度,它的取值可以有以下幾種:
?
1) CV_LOAD_IMAGE_UNCHANGED (<0),以原始圖像讀取(包括alpha通道),
?
2) CV_LOAD_IMAGE_GRAYSCALE ( 0),以灰度圖像讀取
?
3) CV_LOAD_IMAGE_COLOR (>0),以RGB格式讀取
?
這三點是在OpenCV的官方教程(opencv_tutorials.pdf)里摘錄并翻譯過來的,但是網(wǎng)上還有關(guān)于CV_LOAD_IMAGE_ANYDEPTH和CV_LOAD_IMAGE_ANYCOLOR的傳說,而且查看OpenCV的源碼可以發(fā)現(xiàn),這些取值放在一個枚舉(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
?
};
?
關(guān)于該枚舉類型的詳細(xì)信息,官方教程的寫法跟官方發(fā)布的正式版代碼描述的不相同,可能是我理解不夠深入,或者兩者是等價的,這點以后再找時間研究,但這并不影響我們對這一章節(jié)的學(xué)習(xí)。
?
Note:OpenCV提供了多種格式圖像的支持,包括Windows bitmap(bmp),portable image formats (pbm, pgm,ppm) 以及 Sun raster (sr, ras)。關(guān)于其它的格式,有JPEG (jpeg, jpg, jpe),JPEG 2000,TIFF 文件 (tiff, tif),portable network graphics (png),
還有openEXR格式,如果OpenCV是自己打包的話,讀取這些格式需要有插件支持,如果是官方提供的庫,則不需再添加插件。
?
?
?
在檢查圖像是否讀取成功之后,我們需要顯示讀入的圖像,因此,我們采用namedWindow函數(shù)來創(chuàng)建一個OpenCV窗口,該函數(shù)的定義如下:
?
CV_EXPORTS_W void namedWindow(const string& winname, int flags = WINDOW_AUTOSIZE);
?
為此,我們需要指定該窗口的名稱(窗口標(biāo)識符, window identifier)以及如何處理窗口大小。①窗口標(biāo)識符需要唯一指定,如果已經(jīng)存在一個該名字的窗口,則此函數(shù)不進(jìn)行任何處理;②flags參數(shù)目前只支持CV_WINDOW_AUTOSIZE,在highgui_c.h中,OpenCV定義了
CV_WINDOW_AUTOSIZE= 0×00000001,如果設(shè)置該參數(shù),則表示顯示的時候窗口自適應(yīng)于需要顯示的圖像,而且不能修改窗口大小,如果不設(shè)置(省略此參數(shù)),可以通過代碼進(jìn)行修改;③如果將OpenCV用于Qt后端開發(fā),該參數(shù)還支持其它值,具體可查看OpenCV開發(fā)文檔
,這里不再贅述。
?
--------------------------------------------------------------------------------
imshow(“l(fā)ena”, img);
?
--------------------------------------------------------------------------------
imshow用于在我們創(chuàng)建的窗口中顯示需要顯示的圖像,其函數(shù)原型為:
?
void imshow(const string& winname, InputArray mat);
?
第一個參數(shù)winname是指窗口的名稱,也就是窗口標(biāo)識符,第二個參數(shù)mat就是我們要顯示的圖像了。正如我們在namedWindow函數(shù)中所描述的那樣,如果namedWindow指定了參數(shù)CV_WINDOW_AUTOSIZE,則圖像會按原始大小顯示,否則圖像會根據(jù)窗口大小進(jìn)行縮放。
--------------------------------------------------------------------------------
waitKey();
?
--------------------------------------------------------------------------------
這條語句表示等待用戶鍵盤操作,waitKey函數(shù)的函數(shù)原型如下:
?
int waitKey(int delay = 0);
?
我們可以看到,該函數(shù)可包含一個整形參數(shù),不設(shè)置參數(shù)的情況下,默認(rèn)為0,也就是無限制等待。該整數(shù)表示需要等待用戶操作的毫秒數(shù)。
?
2 加載、修改并保存圖像(Load, Modify, and Save an Image)
?
2.1 學(xué)習(xí)目標(biāo)
?
在這一章,我們將學(xué)習(xí):1)加載一副圖像(采用imread函數(shù),同第一章);2)將一副圖像從RGB格式轉(zhuǎn)換成灰度圖(grayscale,采用cvtColor函數(shù));3)保存轉(zhuǎn)換后的圖像到磁盤上(采用imwrite函數(shù))。
?
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 = “l(fā)ena.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 = “l(fā)ena_gray”;
?
?
?
? ? ? ? ?cvtColor(image,gray_image,CV_RGB2GRAY);//將RGB圖像轉(zhuǎn)換成灰度圖像
?
? ? ? ? ?imwrite(“../../lena_gray.jpg”,gray_image);//保存圖像
?
?
?
? ? ? ? ?namedWindow(imageName, CV_WINDOW_AUTOSIZE);//創(chuàng)建用于顯示元圖像窗口
?
? ? ? ? ?namedWindow(grayImageName,CV_WINDOW_AUTOSIZE);//創(chuàng)建用于顯示轉(zhuǎn)換后圖像窗口
?
?
?
? ? ? ? ?imshow(imageName,image);
?
? ? ? ? ?imshow(“grayImageName”, gray_image);
?
?
?
? ? ? ? ?waitKey(0);
?
? ? ? ? ?return 0;
?
}
?
</pre>
?
2.3 源碼詳解
?
有了第一章的基礎(chǔ)后,再來理解本章代碼相對就很容易,在這一節(jié),關(guān)于imread函數(shù)的使用則不再贅述,下面給cvtColor和imwrite來一個特寫。
?
--------------------------------------------------------------------------------
cvtColor(image,gray_image,CV_RGB2GRAY);// 將RGB圖像轉(zhuǎn)換成灰度圖像
?
--------------------------------------------------------------------------------
cvtColor函數(shù)用于將圖像從一個顏色空間轉(zhuǎn)換到另一個顏色空間,其函數(shù)原型為:
?
void cvtColor( InputArray src, OutputArray dst, int code, int dstCn=0 );
?
參數(shù)src:是指需要轉(zhuǎn)化的圖像,可以是8位或16位等的無符號型或者是單精度浮點型(Single-Precision Floating-Point);
?
參數(shù)dst:與原始圖像具有相同大小和深度的目標(biāo)圖像;
?
參數(shù)code:顏色空間轉(zhuǎn)換代碼;
?
參數(shù)dstCn:目標(biāo)圖像的通道數(shù),如果該參數(shù)為0,則通道數(shù)可由src和code自動獲得;
?
對于一個原圖像或目標(biāo)圖像是RGB的轉(zhuǎn)換,我們需要詳細(xì)地指定通道的順序(RGB or BGR)。我們注意到,OpenCV默認(rèn)情況下的顏色格式一般是指RGB,但實際上卻進(jìn)行了一個反轉(zhuǎn)變成BGR,因此對一個標(biāo)準(zhǔn)的24位圖像來說,其第一個字節(jié)為8位的藍(lán)色部分(Blue?
Component),其次是綠色,接著是紅色,再然后就是第二個像素,同樣以BGR的通道順序排列。
?
常規(guī)的RGB通道的值的范圍如下:
?
對于8位無符號精度圖像(CV_8U Images),其范圍是0~255
?
對于16位無符號精度圖像(CV_16U Images),其范圍是0~65535
?
對于32位單精度浮點型圖像(CV_32F Images),其范圍是0~1
?
在線性變換的情況下,我們可以不用考慮其通道的取值范圍,但對于非線性變換(Non-Linear Transformation),一個RGB輸入圖像應(yīng)該先做規(guī)格化處理(Normalized),以便得到一個合適的范圍來獲取正確的結(jié)果。比如對于一個RGB顏色空間到LUV顏色空間的變換,如
果我們需要將一副8位圖像轉(zhuǎn)換到一副32位的浮點型精度圖像而不進(jìn)行任何縮放,也就是說,我們將一個從0~255的范圍替換成0~1的范圍,那么我們首先要將圖像按比例縮小(Scale the Image Down):
?
img *= 1./255;
?
cvtColor(img, img, CV_BGR2Luv);
?
如果我們采用8位的圖像進(jìn)行轉(zhuǎn)換,該過程中可能會有信息的丟失,盡管在一般的應(yīng)用中,這種丟失并不明顯(Noticeable),但我們強(qiáng)烈建議使用一個32位的圖像或者在變換之前先轉(zhuǎn)換成32位。
?
備注:關(guān)于參數(shù)code的跟多取值,可以參見OpenCV 2.4.0官方文檔:cvtColor函數(shù)指南及使用方法
?
--------------------------------------------------------------------------------
?
?
imwrite(“../../lena_gray.jpg”,gray_image);// 保存圖像
?
--------------------------------------------------------------------------------
imwrite函數(shù)用于將圖像保存到指定的文件,其函數(shù)原型為:
?
bool imwrite(const string& filename, InputArray image, const vector<int>& params=vector<int>())
?
參數(shù)filename:指代需要保存文件的名稱
?
參數(shù)image:需要保存的圖像
?
參數(shù)params:保存至指定格式圖像格式時的參數(shù)設(shè)置
?
關(guān)于params參數(shù)的取值如下:
?
對JPEG圖像,它表示圖像質(zhì)量(CV_IMWRITE_JPEG_QUALITY),取值從1~100,值越大質(zhì)量越高,默認(rèn)為95;
?
對PNG圖像,它表示圖像壓縮率(CV_IMWRITE_PNG_COMPRESSION),取值從0~9,值越大壓縮率越大壓縮時間越長,默認(rèn)值為3;
?
對PPM,PGM或PBM,它是一個二進(jìn)制標(biāo)識(CV_IMWRITE_PXM_BINARY),取值為0或1,默認(rèn)為1。
?
有關(guān)于該函數(shù)及參數(shù)params的詳細(xì)信息及應(yīng)用可參見開發(fā)文檔:imwrite函數(shù)指南及使用方法
?
========
圖像縮放--OpenCV cvResize函數(shù)--最近鄰插值---雙線性插值
void cvResize( const CvArr* src, CvArr* dst, int interpolation=CV_INTER_LINEAR ); src輸入圖像.dst輸出圖像.interpolation插值方法:
CV_INTER_NN - 最近鄰插值,
CV_INTER_LINEAR - 雙線性插值 (缺省使用)
CV_INTER_AREA - 使用象素關(guān)系重采樣。當(dāng)圖像縮小時候,該方法可以避免波紋出現(xiàn)。當(dāng)圖像放大時,類似于 CV_INTER_NN 方法..
CV_INTER_CUBIC - 立方插值.
函數(shù) cvResize 將圖像 src 改變尺寸得到與 dst 同樣大小。若設(shè)定 ROI,函數(shù)將按常規(guī)支持 ROI.
最近鄰插值:效果(放大4倍)有馬賽克現(xiàn)象
雙線性插值:效果(放大4倍)比最近鄰插值效果好
最近鄰插值和雙線性插值的基本原理
圖像的縮放很好理解,就是圖像的放大和縮小。傳統(tǒng)的繪畫工具中,有一種叫做“放大尺”的繪畫工具,畫家常用它來放大圖畫。當(dāng)然,在計算機(jī)上,我們不再需要用放大尺去放大或縮小圖像了,把這個工作交給程序來完成就可以了。下面就來講講計算機(jī)怎么來放大縮小
圖象;在本文中,我們所說的圖像都是指點陣圖,也就是用一個像素矩陣來描述圖像的方法,對于另一種圖像:用函數(shù)來描述圖像的矢量圖,不在本文討論之列。
越是簡單的模型越適合用來舉例子,我們就舉個簡單的圖像:3X3 的256級灰度圖,也就是高為3個象素,寬也是3個象素的圖像,每個象素的取值可以是 0-255,代表該像素的亮度,255代表最亮,也就是白色,0代表最暗,即黑色。假如圖像的象素矩陣如下圖所示(這
個原始圖把它叫做源圖,Source):
234 ? 38 ? ?22
67 ? ? 44 ? ?12
89 ? ? 65 ? ?63
這個矩陣中,元素坐標(biāo)(x,y)是這樣確定的,x從左到右,從0開始,y從上到下,也是從零開始,這是圖象處理中最常用的坐標(biāo)系,就是這樣一個坐標(biāo):
? ---------------------->X
? |
? |
? |
? |
? |
∨Y
如果想把這副圖放大為 4X4大小的圖像,那么該怎么做呢?那么第一步肯定想到的是先把4X4的矩陣先畫出來再說,好了矩陣畫出來了,如下所示,當(dāng)然,矩陣的每個像素都是未知數(shù),等待著我們?nèi)ヌ畛?#xff08;這個將要被填充的圖的叫做目標(biāo)圖,Destination):
? ? ? ? ?? ? ? ? ?? ? ? ? ?
? ? ? ? ?? ? ? ? ?? ? ? ? ?
? ? ? ? ?? ? ? ? ?? ? ? ? ?
? ? ? ? ?? ? ? ? ?? ? ? ? ??
? ? ? ? ? ? ? ?
? ? ? ? 然后要往這個空的矩陣?yán)锩嫣钪盗?#xff0c;要填的值從哪里來來呢?是從源圖中來,好,先填寫目標(biāo)圖最左上角的象素,坐標(biāo)為(0,0),那么該坐標(biāo)對應(yīng)源圖中的坐標(biāo)可以由如下公式得出: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
srcX=dstX* (srcWidth/dstWidth) , srcY = dstY * (srcHeight/dstHeight)
好了,套用公式,就可以找到對應(yīng)的原圖的坐標(biāo)了(0*(3/4),0*(3/4))=>(0*0.75,0*0.75)=>(0,0)
,找到了源圖的對應(yīng)坐標(biāo),就可以把源圖中坐標(biāo)為(0,0)處的234象素值填進(jìn)去目標(biāo)圖的(0,0)這個位置了。
接下來,如法炮制,尋找目標(biāo)圖中坐標(biāo)為(1,0)的象素對應(yīng)源圖中的坐標(biāo),套用公式:
(1*0.75,0*0.75)=>(0.75,0)
結(jié)果發(fā)現(xiàn),得到的坐標(biāo)里面竟然有小數(shù),這可怎么辦?計算機(jī)里的圖像可是數(shù)字圖像,象素就是最小單位了,象素的坐標(biāo)都是整數(shù),從來沒有小數(shù)坐標(biāo)。這時候采用的一種策略就是采用四舍五入的方法(也可以采用直接舍掉小數(shù)位的方法),把非整數(shù)坐標(biāo)轉(zhuǎn)換成整數(shù),好,那
么按照四舍五入的方法就得到坐標(biāo)(1,0),完整的運算過程就是這樣的:
(1*0.75,0*0.75)=>(0.75,0)=>(1,0)
那么就可以再填一個象素到目標(biāo)矩陣中了,同樣是把源圖中坐標(biāo)為(1,0)處的像素值38填入目標(biāo)圖中的坐標(biāo)。
? ? ? ? ?
依次填完每個象素,一幅放大后的圖像就誕生了,像素矩陣如下所示:
234 ? ?38 ? ? 22 ? ? 22 ?
67 ? ? ?44 ? ? 12 ? ? 12 ?
89 ? ? ?65 ? ? 63 ? ? 63 ?
89 ? ? ?65 ? ? 63 ? ? 63 ?
這種放大圖像的方法叫做最臨近插值算法,這是一種最基本、最簡單的圖像縮放算法,效果也是最不好的,放大后的圖像有很嚴(yán)重的馬賽克,縮小后的圖像有很嚴(yán)重的失真;效果不好的根源就是其簡單的最臨近插值方法引入了嚴(yán)重的圖像失真,比如,當(dāng)由目標(biāo)圖的坐標(biāo)
反推得到的源圖的的坐標(biāo)是一個浮點數(shù)的時候,采用了四舍五入的方法,直接采用了和這個浮點數(shù)最接近的象素的值,這種方法是很不科學(xué)的,當(dāng)推得坐標(biāo)值為 0.75的時候,不應(yīng)該就簡單的取為1,既然是0.75,比1要小0.25 ,比0要大0.75 ,那么目標(biāo)象素值其實應(yīng)該根
據(jù)這個源圖中虛擬的點四周的四個真實的點來按照一定的規(guī)律計算出來的,這樣才能達(dá)到更好的縮放效果。雙線型內(nèi)插值算法就是一種比較好的圖像縮放算法,它充分的利用了源圖中虛擬點四周的四個真實存在的像素值來共同決定目標(biāo)圖中的一個像素值,因此縮放效果
比簡單的最鄰近插值要好很多。
雙線性內(nèi)插值算法描述如下:
對于一個目的像素,設(shè)置坐標(biāo)通過反向變換得到的浮點坐標(biāo)為(i+u,j+v) (其中i、j均為浮點坐標(biāo)的整數(shù)部分,u、v為浮點坐標(biāo)的小數(shù)部分,是取值[0,1)區(qū)間的浮點數(shù)),則這個像素得值 f(i+u,j+v) 可由原圖像中坐標(biāo)為 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所對
應(yīng)的周圍四個像素的值決定,即:
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)處的的像素值,以此類推。
比如,象剛才的例子,現(xiàn)在假如目標(biāo)圖的象素坐標(biāo)為(1,1),那么反推得到的對應(yīng)于源圖的坐標(biāo)是(0.75 , 0.75), 這其實只是一個概念上的虛擬象素,實際在源圖中并不存在這樣一個象素,那么目標(biāo)圖的象素(1,1)的取值不能夠由這個虛擬象素來決定,而只能由源
圖的這四個象素共同決定:(0,0)(0,1)(1,0)(1,1),而由于(0.75,0.75)離(1,1)要更近一些,那么(1,1)所起的決定作用更大一些,這從公式1中的系數(shù)uv=0.75×0.75就可以體現(xiàn)出來,而(0.75,0.75)離(0,0)最遠(yuǎn),所以(0,0)所起的決定作用
就要小一些,公式中系數(shù)為(1-u)(1-v)=0.25×0.25也體現(xiàn)出了這一特點。
原理參考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 - 使用象素關(guān)系重采樣。當(dāng)圖像縮小時候,該方法可以避免波紋出現(xiàn)。
? ? ? ? ?/*當(dāng)圖像放大時,類似于 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,編譯正常。
從網(wǎng)上下個圖片,做個測試。
看下效果,還不錯。
原圖:
處理后:
========
用opencv將圖片變成水波紋效果
將一幅圖片變成水波紋效果。我在網(wǎng)上找到一份源碼,參考之下,順著思路用opencv2重寫之。
思路如下:
1.將圖片中的坐標(biāo)點(x,y)換成極坐標(biāo),有現(xiàn)成的函數(shù)。
2.極坐標(biāo)下,用三角函數(shù)算出新半徑。
3.在新半徑之下,轉(zhuǎn)換成新的坐標(biāo)(x 0 ,y 0 ),如果新坐標(biāo)是小數(shù),用雙線性插值的方法處理。
關(guān)鍵代碼如下:
其中一些變量聲明如下:
Mat imageInfo;//原圖片
? ? ? ? int imageWidth;
? ? ? ? int imageHeight;
? ? ? ? int imageX;//圖像中心點的橫坐標(biāo)
? ? ? ? int imageY;//圖像中心點的縱坐標(biāo)
? ? ? ? float A;//波紋幅度
? ? ? ? float B;//波紋周期 ? Asin(Bx);
? ? ? ? int imageChannels;//通道數(shù)
? ? ? ? Mat imageWater;//轉(zhuǎn)換后的圖片
? ? ? ? void reCalcAB(int i,int j,float &a,float &b);//坐標(biāo)轉(zhuǎn)換
? ? ? ? uchar BLIP(float a,float b,int k);
? ? ? ? //k為通道數(shù),值為-1為單通道,灰度圖
?void imagetest::imageprocess()
?{
? ? ?imageInfo.copyTo(imageWater);
?
? ? ?float a;
? ? ?float b;//臨時坐標(biāo)
?
? ? ?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)//彩色與灰度圖像要單獨處理,否則
? ? ? ? ? ? ?{ ? ? ? ? ? ? ? ? ? ? ? //會出現(xiàn)橢圓的情況
? ? ? ? ? ? ? ? ?*(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是坐標(biāo)轉(zhuǎn)換函數(shù):
?void imagetest::reCalcAB(int i,int j,float &a,float &b)
?{
? ? ?float y0 = (float)(i-imageY);
? ? ?float x0 = (float)(j-imageX);//(i,j)相對于原點的坐標(biāo)
? ? ?float theta0 = atan2f(y0,x0);//轉(zhuǎn)化成角坐標(biāo)
? ? ?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);//轉(zhuǎn)換后的坐標(biāo)
? ? ?if(a>imageWidth)
? ? ? ? ?a = imageWidth-1;
? ? ?else if(a<0)
? ? ? ? ?a = 0; ? ? ? ? ? ? ? ?//超出邊界的處理
? ? ?if(b>imageHeight)
? ? ? ? ?b = imageHeight-1;
? ? ?else if(b<0)
? ? ? ? ?b = 0;
?}
雙線性插值函數(shù):(這個方法看著很高級,實際很簡單。仔細(xì)看代碼就明白怎么回事情了)
?uchar imagetest::BLIP(float a,float b,int k)
?{
? ? ?uchar newData;//保存結(jié)果
? ? ?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)周圍四個整點坐標(biāo)
? ? ?//取值
? ? ?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)//如果是彩色,轉(zhuǎn)換一下
? ? ?{
? ? ? ? ?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;
?}
這個效果看起來倒是不錯,總感覺不是那么真實。
而且,這個程序有嚴(yán)重的問題。如果我換一張圖片,重新設(shè)置 A和B的參數(shù)
就會出現(xiàn)如下的效果:
中間水平方向出現(xiàn)了明顯的一條橫線。
目前還沒有解決的問題主要就是這條橫線,然后就是怎么樣才能使得水波紋看起來更真實。我想把用一張圖片做成視頻,不知道這個效果最后做出來是個什么樣子。
如果是坐標(biāo)轉(zhuǎn)換出錯了的話,理論上來說應(yīng)該會水平、豎直都應(yīng)該出現(xiàn)一條直線,現(xiàn)在只有水平方向有一條直線。
========
在OpenCV中實現(xiàn)特效之浮雕,雕刻和褶皺
下面代碼的基礎(chǔ)是對圖像像素的訪問。
實現(xiàn)浮雕和雕刻的代碼是統(tǒng)一的,如下
#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; ?
} ?
原圖為
浮雕效果圖如下
雕刻效果圖如下
下面是實現(xiàn)圖像褶皺的代碼,效果不是太好,結(jié)構(gòu)過渡不平滑,以后再改進(jìn)一下。希望能做到波浪化。
#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; ?
} ?
測試圖是標(biāo)準(zhǔn)的lena圖,效果圖如下
========
基于GraphCuts圖割算法的圖像分割----OpenCV代碼與實現(xiàn)
圖切算法是組合圖論的經(jīng)典算法之一。近年來,許多學(xué)者將其應(yīng)用到圖像和視頻分割中,取得了很好的效果。本文簡單介紹了圖切算法和交互式圖像分割技術(shù),以及圖切算法在交互式圖像分割中的應(yīng)用。?
圖像分割指圖像分成各具特性的區(qū)域并提取出感興趣目標(biāo)的技術(shù)和過程,它是由圖像處理到圖像分析的關(guān)鍵步驟,是一種基本的計算機(jī)視覺技術(shù)。只有在圖像分割的基礎(chǔ)上才能對目標(biāo)進(jìn)行特征提取和參數(shù)測量,使得更高層的圖像分析和理解成為可能。因此對圖像分割方
法的研究具有十分重要的意義。
?
圖像分割技術(shù)的研究已有幾十年的歷史,但至今人們并不能找到通用的方法能夠適合于所有類型的圖像。常用的圖像分割技術(shù)可劃分為四類:特征閾值或聚類、邊緣檢測、區(qū)域生長或區(qū)域提取。雖然這些方法分割灰度圖像效果較好,但用于彩色圖像的分割往往達(dá)不到理
想的效果。
?
交互式圖像分割是指,首先由用戶以某種交互手段指定圖像的部分前景與部分背景,然后算法以用戶的輸入作為分割的約束條件自動地計算出滿足約束條件下的最佳分割。典型的交互手段包括用一把畫刷在前景和背景處各畫幾筆(如[1][4]等)以及在前景的周圍畫一個
方框(如[2])等。
?
基于圖切算法的圖像分割技術(shù)是近年來國際上圖像分割領(lǐng)域的一個新的研究熱點。該類方法將圖像映射為賦權(quán)無向圖,把像素視作節(jié)點,利用最小切割得到圖像的最佳分割。
?
?
Graph Cut[1]算法是一種直接基于圖切算法的圖像分割技術(shù)。它僅需要在前景和背景處各畫幾筆作為輸入,算法將建立各個像素點與前景背景相似度的賦權(quán)圖,并通過求解最小切割區(qū)分前景和背景。
?
? ? ? ?Grabcut[2]算法方法的用戶交互量很少,僅僅需要指定一個包含前景的矩形,隨后用基于圖切算法在圖像中提取前景。
?
? ? ? ?Lazy Snapping[4]系統(tǒng)則是對[1]的改進(jìn)。通過預(yù)計算和聚類技術(shù),該方法提供了一個即時反饋的平臺,方便用戶進(jìn)行交互分割。
文檔說明:
http://download.csdn.net/detail/wangyaninglm/8484301
?
代碼實現(xiàn)效果:
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方法是基于顏色統(tǒng)計采樣的方法,因此對前背景相差較大的圖像效果較佳。
同時,比例系數(shù)lambda的調(diào)節(jié)直接影響到最終的分割效果。
?
grabcut代碼:
?
// Grabcut.cpp : 定義控制臺應(yīng)用程序的入口點。 ?
// ?
??
#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的最低位,實際上是只保留確定的或者有可能的前景點當(dāng)做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個點,一個矩形和圖像內(nèi)容,因為后面的步驟很多地方都要用到這個函數(shù),所以單獨拿出來*/ ?
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來復(fù)制,只保留跟前景有關(guān)的圖像,比如說可能的前景,可能的背景 ?
? ? } ?
??
? ? 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內(nèi)部是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,矩形內(nèi)部,為可能的前景點 ?
} ?
??
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 ?
? ? } ?
} ?
??
/*鼠標(biāo)響應(yīng)函數(shù),參數(shù)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) ); ? //矩形結(jié)束 ?
? ? ? ? ? ? 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; ?
? ? } ?
} ?
??
/*該函數(shù)進(jìn)行g(shù)rabcut算法,并且返回算法運行迭代的次數(shù)*/ ?
int GCApplication::nextIter() ?
{ ?
? ? if( isInitialized ) ?
? ? ? ? //使用grab算法進(jìn)行一次迭代,參數(shù)2為mask,里面存的mask位是:矩形內(nèi)部除掉那些可能是背景或者已經(jīng)確定是背景后的所有的點,且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代碼實現(xiàn):
?
// LazySnapping.cpp : 定義控制臺應(yīng)用程序的入口點。 ?
// ?
/* 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由漢字生成圖片(透明)
? ? ? ? 今天聽說很多同志們寫畢業(yè)論文重復(fù)率過高的問題,大牛說用圖片代替字就行了,我就想用OpenCV實現(xiàn)一下看看能不能搞,果不其然還是可以的!!!主要的難點在于普通格式的圖片背景不透明,需要使用背景透明的png格式圖片就行。
?
主要思想和步驟:
?
1.首先配置好FreeType與OpenCV,添加編譯好的lib,與include目錄和CvxText.h和CvxText.cpp就行了,參考[1]
?
2.說一下思路,主要就是OpenCV版本的問題造成有的函數(shù)用的IplImage,而函數(shù)
//設(shè)置原圖像文字
?text.putText(ImageSrc, msg, cvPoint(1, size_zi), color);
只能接受IplImage格式的參數(shù),所以保存成png,就比較麻煩了。
?
png格式的圖片是4個通道,按照BGRA來放置,alaph就是透明通道。我們的思路就是按照原來直接給圖片上疊加文字的辦法,新建與文字大小相同的圖片,然后二值化,按照二值模版生成新的png文字圖片,有字的地方添上顏色,沒字的地方設(shè)置為透明。
?
當(dāng)然二值化算法網(wǎng)上搜了一個自適應(yīng)閥值的算法效果非常好
?
?
3.生成了透明的文字圖片,粘貼到論文里面,估計查詢重復(fù)的系統(tǒng)再牛逼也是無能為力了。后序有空做一些程序界面跟字符分割的東西,可以直接賣錢了。
當(dāng)然,字體跟大小,上下邊距都是可以設(shè)置的,后序再往程序里面寫。
?
?
實現(xiàn)效果:
?
?
主要代碼:
// AddChinese.cpp : 定義控制臺應(yīng)用程序的入口點。 ?
// ?
??
#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 : 定義控制臺應(yīng)用程序的入口點。 ?
// ?
??
??
int run_test_png(Mat &mat,string image_name) ?
{ ?
??
??
? ? /*采用自己設(shè)置的參數(shù)來保存圖片*/ ?
? ? //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格式下,默認(rèn)的參數(shù)為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通道全部設(shè)置為透明完全透明為0,否則為255 ?
? ? ? ? ? ? } ?
? ? ? ? ? ? else ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? bgra[3] = 0;//alpha通道全部設(shè)置為透明完全透明為0,否則為255 ?
? ? ? ? ? ? } ?
? ? ? ? ? ? ??
? ? ? ? ? ? ??
? ? ? ? ? ? ??
? ? ? ? } ?
? ? } ?
??
? ? return 0; ?
} ?
??
void ImageBinarization(IplImage *src) ?
{ ? /*對灰度圖像二值化,自適應(yīng)門限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); ?
? ? ? ? /*利用加權(quán)最大方差求門限*/ ?
? ? ? ? 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; ?//目標(biāo)圖像尺寸 ?
? ? float p = 0.5; ?
? ? CvScalar fsize; ?
??
??
? ? //讀取TTF字體文件 ?
? ? CvxText text("simhei.ttf"); ? ? ??
??
? ? //設(shè)置字體屬性 字體大小/空白比例/間隔比例/旋轉(zhuǎn)角度 ?
? ? 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; ?
??
? ? //設(shè)置原圖像文字 ?
? ? 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;//字符個數(shù) ?
??
? ? generate_chinese(size,msg,number,color); ?
? ? ??
??
? ? return 0; ?
} ?
?
完整工程下載:
http://download.csdn.net/detail/wangyaninglm/8486521
?
參考文獻(xiàn):
?
http://blog.csdn.net/fengbingchun/article/details/8029337
http://www.oschina.net/code/snippet_1447359_36028
http://blog.csdn.net/hustspy1990/article/details/6301592
========
總結(jié)
以上是生活随笔為你收集整理的opencv图像处理总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电力管理信息系统数据库表总结
- 下一篇: Java Servlet 开发常用代码、