日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

opencv图像处理总结

發(fā)布時間:2025/4/14 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 opencv图像处理总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

opencv圖像處理基本操作



1. 矩陣數據類型
通用矩陣數據類型:
CV_<bit_depth>(S|U|F)C<number_of_channels>
其中,S表示帶符號整數;
U表示無符號整數;
F表示浮點數;
例如:CV_8UC1 表示8位無符號單通道矩陣;
   CV_32FC2 表示32位浮點數雙通道矩陣;


2. 圖像數據類型
通用圖像數據類型為:
   IPL_DEPTH_<bit_depth>(S|U|F)
如:IPL_DEPTH_8U 表示8位無符號整數圖像;
  IPL_DEPTH_32F 表示32位浮點數圖像;


3. 分配和釋放圖像
3.1 分配一幅圖像
  IpIImage * cvCreateImage(cvSize size, int depth, int channels);
其中size可以用cvSize(width, height)得到。
depth為像素的單位,包括:
IPL_DEPTH_8U
IPL_DEPTH_8S
IPL_DEPTH_16U
IPL_DEPTH_16S
IPL_DEPTH_32S
IPL_DEPTH_32F
IPL_DEPTH_64F
channels為每個像素的通道數,可以是1,2,3或4。通道是交叉排列的,一幅彩色
圖像的通常的排列順序是:
b0 g0 r0 b1 g1 r1 ...


例如:分配一個單通道單字節(jié)圖像的語句是:
IpIImage* img1 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1);


分配一個三通道浮點數圖像語句是:
IpIImage* img2 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_32F, 3);


3.2 釋放圖像
void cvReleaseImage(IpIImage **);


3.3 復制一幅圖像
IpIImage* cvCloneImage(IpIImage *);
如:
IpIImage* img1 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1);
IpIImage* img2;
img2 = cvCloneImage(img1);


3.4 設置或得到感興趣區(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);


默認為讀取三通道圖像。如果改變設置則采用如下的方式:
img = cvLoadImage(filename, flag);
當flag > 0時,表示載入圖像為3通道彩色圖像;
當flag = 0時,表示載入圖像為單通道灰色圖像;
當flag < 0時,表示載入圖像由文件中的圖像通道數決定。


5. 圖像轉換
5.1 將灰度圖像轉換為彩色圖像
cvConvertImage(src, dst, flags = 0);
其中,src表示浮點(單字節(jié))灰度(彩色)圖像;
dst表示單字節(jié)灰度(彩色)圖像;
flags表示
+--- CV_CVTIMG_FLIP, 垂直翻轉
flags = |
+--- CV_CVTIMG_SWAP_RB, 交換R和B通道


5.2 將彩色圖像轉換為灰度圖像
cvCvtColor(cimg, gimg, CV_RGB2GRAY);


5.3 彩色空間的轉換
cvCvtColor(src, dst, code);
其中code為:CV_<X>2<Y>,而<X>,<Y> = RGB, BGR, GRAY, HSV, YCrCb, XYZ, Lab, Luv, HLS。


6. 繪制命令
繪圖語句為:
cvRectangle, cvCircle, cvLine, cvPolyLine, cvFillPoly, cvInitFont, cvPutText。


先把需要縮放的部分用cvcopy出來,cvresize,然后再cvcopy回去


cvSetImageROI(img, roi1);IPLImage tempimg= cvCreateImage(//size must be the resized image size//);cvResizeImage(img,rempimg....);cvSetImageROI(img,newroi);cvCopy(tempimg,img);


其實他把這個問題復雜化了,對指定部分縮放,首先要說明自己對哪個部分


感興趣cvSetImageROI,通過這個函數,圖像就僅僅剩下了ROI部分,然后


通過cvResize()把這個ROI區(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入門-圖像處理基本函數



1、圖像的內存分配與釋放
(1) 分配內存給一幅新圖像:
IplImage* cvCreateImage(CvSize size, int depth, int channels);
?
size: cvSize(width,height);
depth: 像素深度: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U,
? ?IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F, IPL_DEPTH_64F
channels: 像素通道數. Can be 1, 2, 3 or 4.
? ? ? ? ? ? ? ? ?各通道是交錯排列的. 一幅彩色圖像的數據排列格式如下:
? ? ? ? ? ? ? ? ?b0 g0 r0 b1 g1 r1 ...
?
示例:
// Allocate a 1-channel byte image
IplImage* img1=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
?
// Allocate a 3-channel float image
IplImage* img2=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);
?
?
(2) 釋放圖像:
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
cvReleaseImage(&img);
?
(3) 復制圖像:
IplImage* img1=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
IplImage* img2;
img2=cvCloneImage(img1); ?// 注意通過cvCloneImage得到的圖像
? ? ? ? ? ? ? ? ? ? ? // 也要用 cvReleaseImage 釋放,否則容易產生內存泄漏
?
(4) 設置/獲取感興趣區(qū)域ROI: ? ? ? ? (ROI:Region Of Interest) ? ? ?
void ?cvSetImageROI(IplImage* image, CvRect rect);
void ?cvResetImageROI(IplImage* image);
CvRect cvGetImageROI(const IplImage* image);
大多數OpenCV函數都支持 ROI.
?
(5) 設置/獲取感興趣通道COI: ? ? ? ? (COI:channel of interest)
void cvSetImageCOI(IplImage* image, int coi); // 0=all
int cvGetImageCOI(const IplImage* image);
大多數OpenCV函數不支持 COI.
?
2、圖像讀寫
(1) 從文件中讀入圖像:
IplImage* img=0;
? img=cvLoadImage(fileName);
? if(!img) printf("Could not load image file: %s\n",fileName);
?支持的圖像格式: BMP, DIB, JPEG, JPG, JPE, PNG, PBM, PGM, PPM,
? ? ? ? ? ? ? ? ? ? ? ? ? SR, RAS, TIFF, TIF
OpenCV默認將讀入的圖像強制轉換為一幅三通道彩色圖像. 不過可以按以下方法修改讀入方式:
img=cvLoadImage(fileName,flag);
?flag: >0 將讀入的圖像強制轉換為一幅三通道彩色圖像
? ? ? ?=0 將讀入的圖像強制轉換為一幅單通道灰度圖像
? ? ? ?<0 讀入的圖像通道數與所讀入的文件相同.
?
(2) 保存圖像:
if(!cvSaveImage(outFileName,img)) printf("Could not save: %s\n", outFileName);
保存的圖像格式由 outFileName 中的擴展名確定.
?
3、訪問圖像像素
(1) 假設你要訪問第k通道、第i行、第j列的像素。
(2) 間接訪問: (通用,但效率低,可訪問任意格式的圖像)
對于單通道字節(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中坐標參數的順序與其它opencv函數坐標參數順序恰好相反.本函數中i代表y軸,即height;j代表x軸,即weight.
printf("intensity=%f\n",s.val[0]);
s.val[0]=111;
cvSet2D(img,i,j,s); ? // set the (j,i) pixel value
?
對于多通道字節(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即數組首指針,i為行數,img->widthStep每行所占字節(jié)數)
? ? ? ?對于多通道字節(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;
?
? ? ? ?對于多通道浮點型圖像(假設圖像數據采用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++封裝沒怎么學,要用再仔細學)
? ? ? ?首先定義一個 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中圖像處理函數 ?



1。濾波 Filtering
filter2D() 用核函數對圖像做卷積


sepFilter2D() 用分解的核函數對圖像做卷積。首先,圖像的每一行與一維的核kernelX做卷積;然后,運算結果的每一列與一維的核kernelY做卷積


boxFilter() 就是滑動窗口平均濾波的二維版。
GaussianBlur() 高斯平均,也就是高斯模糊。
medianBlur() 中值濾波,個人最愛的濾波函數。
bilateralFilter() 雙線性濾波。
前面這四個函數是原來OpenCV里的cvSmooth()取不同參數的應用。
Sobel() 使用擴展 Sobel 算子計算一階、二階、三階或混合圖像差分。
Scharr() 計算一階導,x方向或y方向,以前這個方法是放在cvSobel里的。
Laplacian() 拉普拉斯變換。
erode(), dilate() 腐蝕、膨脹。


示例:
filter2D(image, image, image.depth(), (Mat<float>(3,3)<<-1, -1, -1, -1, 9, -1, -1, -1, -1), Point(1,1), 128);
構造了一個如下所示的核對圖像做卷積:
-1 -1 -1
-1 9 -1
-1 -1 -1
核的錨點在(1,1)位置,卷積之后每個像素加上128.
2。幾何變換 Geometrical Transformations
resize() 改變圖像尺寸,可以指定x方向和y方向上的縮放比例,可以指定插值方法。
getRectSubPix() 以亞像素精度從圖像中提取矩形。 dst(x,y)=src(x+center.x-(dst.cols-1)*0.5,y+center.y-(dst.rows-1)*0.5) 其中非整數象素點坐標采用雙線性插值提取。
warpAffine() 仿射變換。
warpPerspective() 透射變換。
remap() 幾何變換。
convertMaps() 將圖像從一種類型,轉換成另一種類型。


示例:
Mat dst;
resize(src, dst, Size(), 1./sqrt(2), 1./sqrt(2)); // 把圖像縮小到原來的根號二分之一。
3。 圖像變換 Various Image Transformations
cvtColor()色彩空間轉換。這個函數可以用于把CCD的raw格式轉換為RGB,請參考,但是不能用于把灰度圖轉成偽彩圖,請參考。
threshold() ?二值化,常用操作,一般應用時建議用大津算法,即使用THRESH_OTSU參數。
adaptivethreshold() ?自適應閾值的二值化。
floodFill() ?填充連通域。
integral() ?計算積分圖像,一次或者二次。
distanceTransform() ?距離變換,對原圖像的每一個像素計算到最近非零像素的距離。
watershed() ?分水嶺圖像分割。
grabCut()
一種彩色圖像分割算法,效果可以參考這里。See the samples watershed.cpp and grabcut.cpp.
4。 直方圖 Histograms
calcHist() ?計算直方圖。
calcBackProject() ?計算反向投影。
equalizeHist() ?灰度圖像的直方圖均衡化,常用操作。
compareHist() ?比較兩個直方圖。
例子:計算圖像的色調-飽和度直方圖。
Mat hsv, H;
cvtColor(image, hsv, CVBGR2HSV);
int planes[]=f0, 1g, hsize[] = f32, 32g;
calcHist(&hsv, 1, planes, Mat(), H, 2, hsize, 0);
========

形態(tài)學圖像處理 膨脹與腐蝕

http://blog.csdn.net/poem_qianmo/article/details/23710721


寫作當前博文時配套使用的OpenCV版本: 2.4.8


一、理論與概念講解——從現象到本質


1.1 ?形態(tài)學概述


形態(tài)學(morphology)一詞通常表示生物學的一個分支,該分支主要研究動植物的形態(tài)和結構。而我們圖像處理中指的形態(tài)學,往往表示的是數學形態(tài)學。下面一起來了解數學形態(tài)學的概念。


數學形態(tài)學(Mathematical morphology) 是一門建立在格論和拓撲學基礎之上的圖像分析學科,是數學形態(tài)學圖像處理的基本理論。其基本的運算包括:二值腐蝕和膨脹、二值開閉運算、骨架抽取、極限腐蝕、擊中擊不中變換、形態(tài)學梯度、Top-hat變換、顆粒分析、


流域變換、灰值腐蝕和膨脹、灰值開閉運算、灰值形態(tài)學梯度等。


簡單來講,形態(tài)學操作就是基于形狀的一系列圖像處理操作。OpenCV為進行圖像的形態(tài)學變換提供了快捷、方便的函數。最基本的形態(tài)學操作有二種,他們是:膨脹與腐蝕(Dilation與Erosion)。


膨脹與腐蝕能實現多種多樣的功能,主要如下:


消除噪聲
分割(isolate)出獨立的圖像元素,在圖像中連接(join)相鄰的元素。
尋找圖像中的明顯的極大值區(qū)域或極小值區(qū)域
求出圖像的梯度
我們在這里給出下文會用到的,用于對比膨脹與腐蝕運算的“淺墨”字樣毛筆字原圖:


在進行腐蝕和膨脹的講解之前,首先需要注意,腐蝕和膨脹是對白色部分(高亮部分)而言的,不是黑色部分。膨脹就是圖像中的高亮部分進行膨脹,“領域擴張”,效果圖擁有比原圖更大的高亮區(qū)域。腐蝕就是原圖中的高亮部分被腐蝕,“領域被蠶食”,效果圖擁有


比原圖更小的高亮區(qū)域。


1.2 膨脹


其實,膨脹就是求局部最大值的操作。


按數學方面來說,膨脹或者腐蝕操作就是將圖像(或圖像的一部分區(qū)域,我們稱之為A)與核(我們稱之為B)進行卷積。


核可以是任何的形狀和大小,它擁有一個單獨定義出來的參考點,我們稱其為錨點(anchorpoint)。多數情況下,核是一個小的中間帶有參考點和實心正方形或者圓盤,其實,我們可以把核視為模板或者掩碼。


而膨脹就是求局部最大值的操作,核B與圖形卷積,即計算核B覆蓋的區(qū)域的像素點的最大值,并把這個最大值賦值給參考點指定的像素。這樣就會使圖像中的高亮區(qū)域逐漸增長。如下圖所示,這就是膨脹操作的初衷。


膨脹的數學表達式:


膨脹效果圖(毛筆字):
??
照片膨脹效果圖:


1.3 腐蝕


再來看一下腐蝕,大家應該知道,膨脹和腐蝕是一對好基友,是相反的一對操作,所以腐蝕就是求局部最小值的操作。


我們一般都會把腐蝕和膨脹對應起來理解和學習。下文就可以看到,兩者的函數原型也是基本上一樣的。


原理圖:


腐蝕的數學表達式:


腐蝕效果圖(毛筆字):


照片腐蝕效果圖:


二、深入——OpenCV源碼分析溯源


直接上源碼吧,在 …\opencv\sources\modules\imgproc\src\ morph.cpp路徑中 的第 1353行開始就為 erode(腐蝕)函數的源碼, 1361行為 dilate(膨脹)函數的源碼。


//-----------------------------------【erode()函數中文注釋版源代碼】----------------------------?
// ? ?說明:以下代碼為來自于計算機開源視覺庫OpenCV的官方源代碼?
// ? ?OpenCV源代碼版本:2.4.8?
// ? ?源碼路徑:…\opencv\sources\modules\imgproc\src\ morph.cpp?
// ? ?源文件中如下代碼的起始行數:1353行?
// ? ?中文注釋by淺墨?
//-------------------------------------------------------------------------------------------------------- ?
void cv::erode( InputArray src, OutputArraydst, InputArray kernel,
? ? ? ? ? ? ? ? Point anchor, int iterations,
? ? ? ? ? ? ? ? int borderType, constScalar& borderValue )
{
//調用morphOp函數,并設定標識符為MORPH_ERODE
? ?morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType,borderValue );
}
//-----------------------------------【dilate()函數中文注釋版源代碼】----------------------------?
// ? ?說明:以下代碼為來自于計算機開源視覺庫OpenCV的官方源代碼?
// ? ?OpenCV源代碼版本:2.4.8?
// ? ?源碼路徑:…\opencv\sources\modules\imgproc\src\ morph.cpp?
// ? ?源文件中如下代碼的起始行數:1361行?
// ? ?中文注釋by淺墨?
//--------------------------------------------------------------------------------------------------------?
void cv::dilate( InputArray src,OutputArray dst, InputArray kernel,
? ? ? ? ? ? ? ? ?Point anchor, int iterations,
? ? ? ? ? ? ? ? ?int borderType, constScalar& borderValue )
{
//調用morphOp函數,并設定標識符為MORPH_DILATE
? ?morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType,borderValue );
}
可以發(fā)現erode和dilate這兩個函數內部就是調用了一下morphOp,只是他們調用morphOp時,第一個參數標識符不同,一個為MORPH_ERODE(腐蝕),一個為MORPH_DILATE(膨脹)。


morphOp函數的源碼在…\opencv\sources\modules\imgproc\src\morph.cpp中的第1286行,有興趣的朋友們可以研究研究,這里就不費時費力花篇幅展開分析了。


三、淺出——API函數快速上手


3.1 ? 形態(tài)學膨脹——dilate函數


erode函數,使用像素鄰域內的局部極大運算符來膨脹一張圖片,從src輸入,由dst輸出。支持就地(in-place)操作。


函數原型:


C++: void dilate(
? InputArray src,
? OutputArray dst,
? InputArray kernel,
? Point anchor=Point(-1,-1),
? int iterations=1,
? int borderType=BORDER_CONSTANT,
? const Scalar& borderValue=morphologyDefaultBorderValue()?
);


參數詳解:


第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。圖像通道的數量可以是任意的,但圖像深度應為CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
第二個參數,OutputArray類型的dst,即目標圖像,需要和源圖片有一樣的尺寸和類型。
第三個參數,InputArray類型的kernel,膨脹操作的核。若為NULL時,表示的是使用參考點位于中心3x3的核。
我們一般使用函數 getStructuringElement配合這個參數的使用。getStructuringElement函數會返回指定形狀和尺寸的結構元素(內核矩陣)。


其中,getStructuringElement函數的第一個參數表示內核的形狀,我們可以選擇如下三種形狀之一:


矩形: MORPH_RECT
交叉形: MORPH_CROSS
橢圓形: MORPH_ELLIPSE
而getStructuringElement函數的第二和第三個參數分別是內核的尺寸以及錨點的位置。


我們一般在調用erode以及dilate函數之前,先定義一個Mat類型的變量來獲得getStructuringElement函數的返回值。對于錨點的位置,有默認值Point(-1,-1),表示錨點位于中心。且需要注意,十字形的element形狀唯一依賴于錨點的位置。而在其他情況下,錨點只是


影響了形態(tài)學運算結果的偏移。


getStructuringElement函數相關的調用示例代碼如下:?


int g_nStructElementSize = 3; //結構元素(內核矩陣)的尺寸
?
? //獲取自定義核
Mat element = getStructuringElement(MORPH_RECT,
? Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),
? Point( g_nStructElementSize, g_nStructElementSize ));
調用這樣之后,我們便可以在接下來調用erode或dilate函數時,第三個參數填保存了getStructuringElement返回值的Mat類型變量。對應于我們上面的示例,就是填element變量。


第四個參數,Point類型的anchor,錨的位置,其有默認值(-1,-1),表示錨位于中心。
第五個參數,int類型的iterations,迭代使用erode()函數的次數,默認值為1。
第六個參數,int類型的borderType,用于推斷圖像外部像素的某種邊界模式。注意它有默認值BORDER_DEFAULT。
第七個參數,const Scalar&類型的borderValue,當邊界為常數時的邊界值,有默認值morphologyDefaultBorderValue(),一般我們不用去管他。需要用到它時,可以看官方文檔中的createMorphologyFilter()函數得到更詳細的解釋。
?
使用erode函數,一般我們只需要填前面的三個參數,后面的四個參數都有默認值。而且往往結合getStructuringElement一起使用。


調用范例:


//載入原圖?
? ? Mat image = imread("1.jpg");
? ? //獲取自定義核
? ? Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
? ? Mat out;
? ? //進行膨脹操作
? ? dilate(image, out, element);
用上面核心代碼架起來的完整程序代碼:


//-----------------------------------【頭文件包含部分】---------------------------------------
// ? ? 描述:包含程序所依賴的頭文件
//----------------------------------------------------------------------------------------------
#include <opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include <iostream>
?
//-----------------------------------【命名空間聲明部分】---------------------------------------
// ? ? 描述:包含程序所使用的命名空間
//-----------------------------------------------------------------------------------------------?
using namespace std;
using namespace cv;
?
//-----------------------------------【main( )函數】--------------------------------------------
// ? ? 描述:控制臺應用程序的入口函數,我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( ?)
{
?
? //載入原圖?
? Mat image = imread("1.jpg");
?
? //創(chuàng)建窗口?
? namedWindow("【原圖】膨脹操作");
? namedWindow("【效果圖】膨脹操作");
?
? //顯示原圖
? imshow("【原圖】膨脹操作", image);
?
? //獲取自定義核
? Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
? Mat out;
? //進行膨脹操作
? dilate(image,out, element);
?
? //顯示效果圖
? imshow("【效果圖】膨脹操作", out);
?
? waitKey(0);
?
? return 0;
}
?運行截圖:


3.2 ?形態(tài)學腐蝕——erode函數


erode函數,使用像素鄰域內的局部極小運算符來腐蝕一張圖片,從src輸入,由dst輸出。支持就地(in-place)操作。


看一下函數原型:


C++: void erode(
? InputArray src,
? OutputArray dst,
? InputArray kernel,
? Point anchor=Point(-1,-1),
? int iterations=1,
? int borderType=BORDER_CONSTANT,
? const Scalar& borderValue=morphologyDefaultBorderValue()
?);
參數詳解:


第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。圖像通道的數量可以是任意的,但圖像深度應為CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
第二個參數,OutputArray類型的dst,即目標圖像,需要和源圖片有一樣的尺寸和類型。
第三個參數,InputArray類型的kernel,腐蝕操作的內核。若為NULL時,表示的是使用參考點位于中心3x3的核。我們一般使用函數 getStructuringElement配合這個參數的使用。getStructuringElement函數會返回指定形狀和尺寸的結構元素(內核矩陣)。(具體看上


文中淺出部分dilate函數的第三個參數講解部分)
第四個參數,Point類型的anchor,錨的位置,其有默認值(-1,-1),表示錨位于單位(element)的中心,我們一般不用管它。
第五個參數,int類型的iterations,迭代使用erode()函數的次數,默認值為1。
第六個參數,int類型的borderType,用于推斷圖像外部像素的某種邊界模式。注意它有默認值BORDER_DEFAULT。
第七個參數,const Scalar&類型的borderValue,當邊界為常數時的邊界值,有默認值morphologyDefaultBorderValue(),一般我們不用去管他。需要用到它時,可以看官方文檔中的createMorphologyFilter()函數得到更詳細的解釋。
同樣的,使用erode函數,一般我們只需要填前面的三個參數,后面的四個參數都有默認值。而且往往結合getStructuringElement一起使用。


調用范例:


//載入原圖?
? ? Mat image = imread("1.jpg");
? ? //獲取自定義核
? ? Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
? ? Mat out;
? ? //進行腐蝕操作
? ? erode(image,out, element);
用上面核心代碼架起來的完整程序代碼:


//-----------------------------------【頭文件包含部分】---------------------------------------
// ? ? 描述:包含程序所依賴的頭文件
//----------------------------------------------------------------------------------------------
#include <opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include <iostream>
?
//-----------------------------------【命名空間聲明部分】---------------------------------------
// ? ? 描述:包含程序所使用的命名空間
//-----------------------------------------------------------------------------------------------?
using namespace std;
using namespace cv;
?
//-----------------------------------【main( )函數】--------------------------------------------
// ? ? 描述:控制臺應用程序的入口函數,我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( ?)
{
? //載入原圖?
? Matimage = imread("1.jpg");
?
? ?//創(chuàng)建窗口?
? namedWindow("【原圖】腐蝕操作");
? namedWindow("【效果圖】腐蝕操作");
?
? //顯示原圖
? imshow("【原圖】腐蝕操作", image);
?
? ?
? //獲取自定義核
? Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
? Mat out;
?
? //進行腐蝕操作
? erode(image,out, element);
?
? //顯示效果圖
? imshow("【效果圖】腐蝕操作", out);
?
? waitKey(0);
?
? return 0;
}
運行結果:


四、綜合示例——在實戰(zhàn)中熟稔


這個示例程序中的效果圖窗口有兩個滾動條,顧名思義,第一個滾動條“腐蝕/膨脹”用于在腐蝕/膨脹之間進行切換;第二個滾動條”內核尺寸”用于調節(jié)形態(tài)學操作時的內核尺寸,以得到效果不同的圖像,有一定的可玩性。廢話不多說,上代碼吧:


//-----------------------------------【程序說明】----------------------------------------------
// ? ? 程序名稱::《【OpenCV入門教程之十】形態(tài)學圖像處理(一):膨脹與腐蝕 ?》 博文配套源碼
// ? ? 開發(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; //結構元素(內核矩陣)的尺寸
?
?
//-----------------------------------【全局函數聲明部分】--------------------------------------
// ? ? 描述:全局函數聲明
//-----------------------------------------------------------------------------------------------
void Process();//膨脹和腐蝕的處理函數
void on_TrackbarNumChange(int, void *);//回調函數
void on_ElementSizeChange(int, void *);//回調函數
?
?
//-----------------------------------【main( )函數】--------------------------------------------
// ? ? 描述:控制臺應用程序的入口函數,我們的程序從這里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
? //改變console字體顏色
? system("color5E");?
?
? //載入原圖
? g_srcImage= imread("1.jpg");
? if(!g_srcImage.data ) { printf("Oh,no,讀取srcImage錯誤~!\n"); return false; }
? ? ??
? //顯示原始圖
? namedWindow("【原始圖】");
? imshow("【原始圖】", g_srcImage);
? ? ??
? //進行初次腐蝕操作并顯示效果圖
? namedWindow("【效果圖】");
? //獲取自定義核
? Matelement = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));
? erode(g_srcImage,g_dstImage, element);
? imshow("【效果圖】", g_dstImage);
?
? //創(chuàng)建軌跡條
? createTrackbar("腐蝕/膨脹", "【效果圖】", &g_nTrackbarNumer, 1, on_TrackbarNumChange);
? createTrackbar("內核尺寸", "【效果圖】",&g_nStructElementSize, 21, on_ElementSizeChange);
?
? //輸出一些幫助信息
? cout<<endl<<"\t嗯。運行成功,請調整滾動條觀察圖像效果~\n\n"
? ? <<"\t按下“q”鍵時,程序退出~!\n"
? ? <<"\n\n\t\t\t\tby淺墨";
?
? //輪詢獲取按鍵信息,若下q鍵,程序退出
? while(char(waitKey(1))!= 'q') {}
?
? return 0;
}
?
//-----------------------------【Process( )函數】------------------------------------
// ? ? 描述:進行自定義的腐蝕和膨脹操作
//-----------------------------------------------------------------------------------------
void Process()
{
? //獲取自定義核
? Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));
?
? //進行腐蝕或膨脹操作
? if(g_nTrackbarNumer== 0) { ??
? ? erode(g_srcImage,g_dstImage, element);
? }
? else{
? ? dilate(g_srcImage,g_dstImage, element);
? }
?
? //顯示效果圖
? imshow("【效果圖】", g_dstImage);
}
?
?
//-----------------------------【on_TrackbarNumChange( )函數】------------------------------------
// ? ? 描述:腐蝕和膨脹之間切換開關的回調函數
//-----------------------------------------------------------------------------------------------------
void on_TrackbarNumChange(int, void *)
{
? //腐蝕和膨脹之間效果已經切換,回調函數體內需調用一次Process函數,使改變后的效果立即生效并顯示出來
? Process();
}
?
?
//-----------------------------【on_ElementSizeChange( )函數】-------------------------------------
// ? ? 描述:腐蝕和膨脹操作內核改變時的回調函數
//-----------------------------------------------------------------------------------------------------
void on_ElementSizeChange(int, void *)
{
? //內核尺寸已改變,回調函數體內需調用一次Process函數,使改變后的效果立即生效并顯示出來
? Process();
}
放出一些效果圖吧。原始 圖:


膨脹效果圖:


腐蝕效果圖:


腐蝕和膨脹得到的圖,都特有喜感,但千變萬變,還是原圖好看:


========

OpenCV圖像處理篇之腐蝕與膨脹



腐蝕與膨脹


腐蝕和膨脹是圖像的形態(tài)學處理中最基本的操作,之后遇見的開操作和閉操作都是腐蝕和膨脹操作的結合運算。腐蝕和膨脹的應用非常廣泛,而且效果還很好:


腐蝕可以分割(isolate)獨立的圖像元素,膨脹用于連接(join)相鄰的元素,這也是腐蝕和膨脹后圖像最直觀的展現
去噪:通過低尺寸結構元素的腐蝕操作很容易去掉分散的椒鹽噪聲點
圖像輪廓提取:腐蝕操作
圖像分割
等等...(在文后給出一則簡單實用膨脹操作提取車牌數字區(qū)域的例子)
結構元素是形態(tài)學操作中最重要的概念,


erode_show dilate_show


如上圖,B為結構元素。


腐蝕操作描述為:掃描圖像的每一個像素,用結構元素與其覆蓋的二值圖像做“與”操作:如果都為1,結果圖像的該像素為1,否則為0。


膨脹操作描述為:掃描圖像的每一個像素,用結構元素與其覆蓋的二值圖像做“與”操作:如果都為0,結果圖像的該像素為0,否則為1。


以上都是關于二值圖像的形態(tài)學操作,對于灰度圖像:


腐蝕操作


其中,g(x,y)為腐蝕后的灰度圖像,f(x,y)為原灰度圖像,B為結構元素。腐蝕運算是由結構元素確定的鄰域塊中選取圖像值與結構元素值的差的最小值。


膨脹操作


其中,g(x,y)為腐蝕后的灰度圖像,f(x,y)為原灰度圖像,B為結構元素。 膨脹運算是由結構元素確定的鄰域塊中選取圖像值與結構元素值的和的最大值。


在灰度圖的形態(tài)學操作中,一般選擇“平攤”的結構元素,即結構元素B的值為0,則上面對灰度圖的形態(tài)學操作可簡化如下:


好了,這就是基本的形態(tài)學操作——腐蝕和膨脹,下面是使用OpenCV對圖像進行腐蝕和膨脹的程序,還是秉承我們一貫的原則:擱下理論,先直觀地感覺圖像處理算法的效果,實際項目需要時再深入挖掘!


程序分析


/*
?* FileName : eroding_and_dilating.cpp
?* Author ? : xiahouzuoxin @163.com
?* Version ?: v1.0
?* Date ? ? : Fri 19 Sep 2014 07:42:12 PM CST
?* Brief ? ?:?
?*?
?* Copyright (C) MICL,USTB
?*/
#include "cv.h"?
#include "highgui.h"
#include "opencv2/imgproc/imgproc.hpp"


using namespace std;
using namespace cv;


#define TYPE_MORPH_RECT ? ? ?(0)
#define TYPE_MORPH_CROSS ? ? (1)
#define TYPE_MORPH_ELLIPSE ? (2)


#define MAX_ELE_TYPE ? ? ? ? (2)
#define MAX_ELE_SIZE ? ? ? ? (20)


Mat src, erode_dst, dilate_dst;


const char *erode_wn ?= "eroding demo";
const char *dilate_wn = "dilating demo";


int erode_ele_type;
int dilate_ele_type;
int erode_ele_size;
int dilate_ele_size;


static void Erosion(int, void *);
static void Dilation(int, void *);


/*
?* @brief ??
?* @inputs ?
?* @outputs?
?* @retval ?
?*/
int main(int argc, char *argv[])
{
? ? if (argc < 2) {
? ? ? ? cout<<"Usage: ./eroding_and_dilating [file name]"<<endl;
? ? ? ? return -1;
? ? }


? ? src = imread(argv[1]);
? ? if (!src.data) {
? ? ? ? cout<<"Read image failure."<<endl;
? ? ? ? return -1;
? ? }


? ? // Windows
? ? namedWindow(erode_wn, WINDOW_AUTOSIZE);
? ? namedWindow(dilate_wn, WINDOW_AUTOSIZE);


? ? // Track Bar for Erosion
? ? createTrackbar("Element Type\n0:Rect\n1:Cross\n2:Ellipse", erode_wn,?
? ? ? ? ? ? &erode_ele_type, MAX_ELE_TYPE, Erosion); ?// callback @Erosion
? ? createTrackbar("Element Size: 2n+1", erode_wn,?
? ? ? ? ? ? &erode_ele_size, MAX_ELE_SIZE, Erosion);


? ? // Track Bar for Dilation
? ? createTrackbar("Element Type\n0:Rect\n1:Cross\n2:Ellipse", dilate_wn,?
? ? ? ? ? ? &dilate_ele_type, MAX_ELE_TYPE, Dilation); ?// callback @Erosion
? ? createTrackbar("Element Size: 2n+1", dilate_wn,?
? ? ? ? ? ? &dilate_ele_size, MAX_ELE_SIZE, Dilation);


? ? // Default start
? ? Erosion(0, 0);
? ? Dilation(0, 0);


? ? waitKey(0);


? ? return 0;
}


/*
?* @brief ? 腐蝕操作的回調函數
?* @inputs ?
?* @outputs?
?* @retval ?
?*/
static void Erosion(int, void *)
{
? ? int erode_type;


? ? switch (erode_ele_type) {
? ? case TYPE_MORPH_RECT:
? ? ? ?erode_type = MORPH_RECT;?
? ? ? ?break;
? ? case TYPE_MORPH_CROSS:
? ? ? ?erode_type = MORPH_CROSS;
? ? ? ?break;
? ? case TYPE_MORPH_ELLIPSE:
? ? ? ?erode_type = MORPH_ELLIPSE;
? ? ? ?break;
? ? default:
? ? ? ?erode_type = MORPH_RECT;
? ? ? ?break;
? ? }


? ? Mat ele = getStructuringElement(erode_type, Size(2*erode_ele_size+1, 2*erode_ele_size+1),?
? ? ? ? ? ? Point(erode_ele_size, erode_ele_size));


? ? erode(src, erode_dst, ele);


? ? imshow(erode_wn, erode_dst);
}


/*
?* @brief ? 膨脹操作的回調函數
?* @inputs ?
?* @outputs?
?* @retval ?
?*/
static void Dilation(int, void *)
{
? ? int dilate_type;


? ? switch (dilate_ele_type) {
? ? case TYPE_MORPH_RECT:
? ? ? ?dilate_type = MORPH_RECT;?
? ? ? ?break;
? ? case TYPE_MORPH_CROSS:
? ? ? ?dilate_type = MORPH_CROSS;
? ? ? ?break;
? ? case TYPE_MORPH_ELLIPSE:
? ? ? ?dilate_type = MORPH_ELLIPSE;
? ? ? ?break;
? ? default:
? ? ? ?dilate_type = MORPH_RECT;
? ? ? ?break;
? ? }


? ? Mat ele = getStructuringElement(dilate_type, Size(2*dilate_ele_size+1, 2*dilate_ele_size+1),?
? ? ? ? ? ? Point(dilate_ele_size, dilate_ele_size));


? ? dilate(src, dilate_dst, ele);


? ? imshow(dilate_wn, dilate_dst);
}
膨脹和腐蝕操作的函數分別是erode和dilate,傳遞給他們的參數也都依次是原圖像、形態(tài)學操作后的圖像、結構元素ele。本程序中給出了3種結構元素類型,分別是


#define TYPE_MORPH_RECT ? ? ?(0) ?// 矩形
#define TYPE_MORPH_CROSS ? ? (1) ?// 十字交叉型
#define TYPE_MORPH_ELLIPSE ? (2) ?// 橢圓型
再通過OpenCV提供的getStructuringElement函數創(chuàng)建Mat類型的結構元素。


getStructuringElement的參數依次是結構元素類型(OpenCV中提供了宏定義MORPH_RECT、MORPH_CROSS和MORPH_ELLIPSE表示)、結構元素大小。


這里我們首次接觸了createTrackbar函數(聲明在highgui.hpp中),該函數的功能是給窗口添加滑動條。其原型是:


CV_EXPORTS int createTrackbar( const string& trackbarname, const string& winname,
? ? ? ? ? ? ? ? ? ? ? ? ?int* value, int count,
? ? ? ? ? ? ? ? ? ? ? ? ?TrackbarCallback onChange=0,
? ? ? ? ? ? ? ? ? ? ? ? ?void* userdata=0);
trackbarname為滑動條的名稱,將會顯示在滑動條的前面,參見結果中的圖片顯示; winname為窗口名; value為滑動條關聯(lián)的變量,如上面程序中第一個滑動條關聯(lián)到erode_ele_type,表示——當滑動條滑動變化時,erode_ele_type的值發(fā)生響應的變化; count表示


滑動條能滑動到的最大值; TrackbarCallback onChange其實是這個函數的關鍵,是滑動條變化時調用的回調函數。當滑動條滑動時,value值發(fā)生變化,系統(tǒng)立刻調用onChange函數,執(zhí)行相關的操作,回調函數的定義形式是固定的:


void onChange(int, void *)
程序中的回調函數Erosion和Dilation函數的定義都遵循該形式:


static void Erosion(int, void *);
static void Dilation(int, void *);
結果及實際應用


對“黑白小豬”進行膨脹操作的變化(隨著結構元素大小的變化)如下圖:


dilating_demo


對“黑白小豬”進行腐蝕操作的變化(隨著結構元素大小的變化)如下圖:


eroding_demo


膨脹與腐蝕在圖像處理中具有廣泛的用途,比如提取車牌過程中,可以通過膨脹運算確定車牌的區(qū)域。如下圖為通過sobel算子提取邊緣后的車牌,


car_plate


為去掉邊界,確定車牌在圖中的位置,可以通過膨脹操作,結果如下:


car_plate_dilate


上圖中的紅線區(qū)域就是膨脹后能用于確定車牌的連通區(qū)域,再通過對連通區(qū)域的搜索及“車牌的矩形特性”即可確定含有車牌數字在圖片中的位置。


========

OpenCV&Qt學習之三-圖像的初步處理?Qt圖像的縮放顯示



實現圖像縮放的方法很多,在 OpenCV&Qt學習之一——打開圖片文件并顯示 的例程中,label控件是通過


ui->imagelabel->resize(ui->imagelabel->pixmap()->size());
來實現適應圖像顯示的,但是由于窗口固定,在打開的圖像小于控件大小時就會縮在左上角顯示,在打開圖像過大時則顯示不全。因此這個例程中首先實現圖像適合窗口的縮放顯示。


由于是基于OpenCV和Qt的圖像處理,因此圖像的縮放處理在OpenCV和Qt都可以完成,我這里就把OpenCV用作圖像的原始處理,Qt用作顯示處理,因此縮放顯示由Qt完成。


Qt中QImage提供了用于縮放的基本函數,而且功能非常強大,使用Qt自帶的幫助可以檢索到相關信息。


函數原型:


QImage QImage::scaled ( const QSize & size, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation ) const
這是直接獲取大小,還有另一種形式:


QImage QImage::scaled ( int width, int height, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation ) const
函數說明以及參數在文檔中已經說的非常清楚了,文檔摘錄如下:
Returns a copy of the image scaled to a rectangle defined by the given size according to the given aspectRatioMode and transformMode.


image


If aspectRatioMode is Qt::IgnoreAspectRatio, the image is scaled to size.
If aspectRatioMode is Qt::KeepAspectRatio, the image is scaled to a rectangle as large as possible inside size, preserving the aspect ratio.
If aspectRatioMode is Qt::KeepAspectRatioByExpanding, the image is scaled to a rectangle as small as possible outside size, preserving the aspect ratio.


官方文檔中已經說的比較清楚了,代碼實現也比較簡單,代碼如下:


{
? ? QImage imgScaled ;
? ? imgScaled = img.scaled(ui->imagelabel->size(),Qt::KeepAspectRatio);
// ?imgScaled = img.QImage::scaled(ui->imagelabel->width(),ui->imagelabel->height(),Qt::KeepAspectRatio);
? ? ui->imagelabel->setPixmap(QPixmap::fromImage(imgScaled));
}
顯示效果如下:


image


QImage的一點疑問與理解


在查找資料時參考了這篇 ?Qt中圖像的顯示與基本操作 博客,但是存在一些疑點,博客中相關代碼如下:


QImage* imgScaled = new QImage;
*imgScaled=img->scaled(width,height,Qt::KeepAspectRatio);
ui->label->setPixmap(QPixmap::fromImage(*imgScaled));


對于以上代碼通過和我之前的代碼做簡單對比,發(fā)現有幾點不一樣的地方:


圖像的定義方式,這里的定義方式為QImage* imgScale = new QImage
scaled函數的調用方式,一個是imgScaled = img.scaled后者為*imgScaled=img->scaled,我最開始也是將.寫為->一直沒找出錯誤,提示base operand of '->' has non-pointer type 'QImage'
繼續(xù)查找Qt的幫助手冊,發(fā)現QImage的構造函數還真是多:


Public Functions


QImage ()


QImage ( const QSize & size, Format format )


QImage ( int width, int height, Format format )


QImage ( uchar * data, int width, int height, Format format )


QImage ( const uchar * data, int width, int height, Format format )


QImage ( uchar * data, int width, int height, int bytesPerLine, Format format )


QImage ( const uchar * data, int width, int height, int bytesPerLine, Format format )


QImage ( const char * const[] xpm )


QImage ( const QString & fileName, const char * format = 0 )


QImage ( const char * fileName, const char * format = 0 )


QImage ( const QImage & image )


~QImage ()


QImage提供了適用于不同場合的構造方式,在手冊中對他們也有具體的應用,但是我仍然沒找到QImage image;和QImage* image = new QImage這兩種究竟對應的是哪兩種,有什么區(qū)別和不同。 在上一篇博文 ?OpenCV&Qt學習之二——QImage的進一步認識 ?中提到了對于


圖像數據的一點認識,其中提到QImage是對現有數據的一種重新整合,是一種格式,但是數據還是指向原來的。從這里來看還需要根據構造方式具體區(qū)別,并不完全正確。




凌亂查了查資料,網上的資料就那幾個,互相轉來轉去的,而且多數比較老,仍然沒有幫助我想通關于這里面數據結構的一些疑問,Qt 和 OpenCV對C和指針的要求還是比較高的,長時間從單片機類的程序過來那點功底還真不夠,具體的C應用都忘光了。這個問題只能暫


時擱置,在后面的學習中慢慢理解。




基于OpenCV的圖像初步處理


以下兩個例程根據書籍 OpenCV 2 Computer Vision Application Programming Cookbook中的相關例程整理,這是一本比較新也比較基礎的入門書籍。


salt-and-pepper noise


關于圖像數據的基礎知識參見這段介紹:


Fundamentally, an image is a matrix of numerical values. This is why OpenCV 2 manipulates them using the cv::Mat data structure. Each element of the matrix represents one pixel. For a gray-level image (a "black-and-white" image), pixels?


are unsigned 8-bit values where 0 corresponds to black and corresponds 255 to white. For a color image, three such values per pixel are required to represent the usual three primary color channels {Red, Green, Blue}. A matrix element is?


therefore made, in this case, of a triplet of values.


這兒以想圖像中添加saltand-pepper noise為例,來說明如何訪問圖像矩陣中的獨立元素。saltand-pepper noise就是圖片中一些像素點,隨機的被黑色或者白色的像素點所替代,因此添加saltand-pepper noise也比較簡單,只需要隨機的產生行和列,將這些行列值對


應的像素值更改即可,當然通過上面的介紹,需要更改RGB3個通道。程序如下:


void Widget::salt(cv::Mat &image, int n)
{


? ? int i,j;
? ? for (int k=0; k<n; k++)
? ? {


? ? ? ? i= qrand()%image.cols;
? ? ? ? j= qrand()%image.rows;


? ? ? ? if (image.channels() == 1) { // gray-level image


? ? ? ? ? ? image.at<uchar>(j,i)= 255;


? ? ? ? } else if (image.channels() == 3) { // color image


? ? ? ? ? ? image.at<cv::Vec3b>(j,i)[0]= 255;
? ? ? ? ? ? image.at<cv::Vec3b>(j,i)[1]= 255;
? ? ? ? ? ? image.at<cv::Vec3b>(j,i)[2]= 255;
? ? ? ? }
? ? }
}


對Win 7系統(tǒng)中的自帶圖像考拉進行處理后的效果如下圖所示(程序是Ubuntu 12.04下的):
image


減少色彩位數


在很多處理中需要對圖片中的所有像素進行遍歷操作,采用什么方式進行這個操作是需要思考的問題,關于這個問題的論述可以參考下面一段簡介:


Color images are composed of 3-channel pixels. Each of these channels corresponds to the intensity value of one of the three primary colors (red, green, blue). Since each of these values is an 8-bit unsigned char, the total number of?


colors is 256x256x256, which is more than 16 million colors. Consequently, to reduce the complexity of an analysis, it is sometimes useful to reduce the number of colors in an image. One simple way to achieve this goal is to simply?


subdivide the RGB space into cubes of equal sizes. For example, if you reduce the number of colors in each dimension by 8, then you would obtain a total of 32x32x32 colors. Each color in the original image is then assigned a new color?


value in the color-reduced image that corresponds to the value in the center of the cube to which it belongs.


這個例子就是通過操作每一個像素點來減少色彩的位數,基本內容在以上的英文引文中已經有了介紹,代碼的實現也比較直接。在彩色圖像中,3個通道的數據是依次排列的,每一行的像素三個通道的值依次排列,cv::Mat中的通道排列順序為BGR,那么一個圖像需要的地


址塊空間為uchar 寬×高×3.但是需要注意的是,有些處理器針對行數為4或8的圖像處理更有效率,因此為了提高效率就會填充一些額外的像素,這些額外的像素不被顯示和保存,值是忽略的。


實現這個功能的代碼如下:


// using .ptr and []
void Widget::colorReduce0(cv::Mat &image, int div)
{


? ? ? int nl= image.rows; // number of lines
? ? ? int nc= image.cols * image.channels(); // total number of elements per line


? ? ? for (int j=0; j<nl; j++)
? ? ? {


? ? ? ? ? uchar* data= image.ptr<uchar>(j);


? ? ? ? ? for (int i=0; i<nc; i++)
? ? ? ? ? {


? ? ? ? ? ? // process each pixel ---------------------
? ? ? ? ? ? ? ? data[i]= data[i]/div*div+div/2;


? ? ? ? ? ? // end of pixel processing ----------------


? ? ? ? ? } // end of line
? ? ? }
}


data[i]= data[i]/div*div+div/2; 通過整除的方式,就像素位數進行減少,這里沒明白的是為啥后面還要加上div/2。


效果如下:


image


程序源代碼:


#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent) :
? ? QWidget(parent),
? ? ui(new Ui::Widget)
{
? ? ui->setupUi(this);


}


Widget::~Widget()
{
? ? delete ui;
}


void Widget::on_openButton_clicked()
{
? ? QString fileName = QFileDialog::getOpenFileName(this,tr("Open Image"),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ".",tr("Image Files (*.png *.jpg *.bmp)"));
? ? qDebug()<<"filenames:"<<fileName;
? ? image = cv::imread(fileName.toAscii().data());
? ? ui->imgfilelabel->setText(fileName);
? ? //here use 2 ways to make a copy
// ? ?image.copyTo(originalimg); ? ? ? ? ?//make a copy
? ? originalimg = image.clone(); ? ? ? ?//clone the img
? ? qimg = Widget::Mat2QImage(image);
? ? display(qimg); ? ? ? ? ? ? ? ? ? ? ?//display by the label
? ? if(image.data)
? ? {
? ? ? ? ui->saltButton->setEnabled(true);
? ? ? ? ui->originalButton->setEnabled(true);
? ? ? ? ui->reduceButton->setEnabled(true);
? ? }
}


QImage Widget::Mat2QImage(const cv::Mat &mat)
{
? ? QImage img;
? ? if(mat.channels()==3)
? ? {
? ? ? ? //cvt Mat BGR 2 QImage RGB
? ? ? ? cvtColor(mat,rgb,CV_BGR2RGB);
? ? ? ? img =QImage((const unsigned char*)(rgb.data),
? ? ? ? ? ? ? ? ? ? rgb.cols,rgb.rows,
? ? ? ? ? ? ? ? ? ? rgb.cols*rgb.channels(),
? ? ? ? ? ? ? ? ? ? QImage::Format_RGB888);
? ? }
? ? else
? ? {
? ? ? ? img =QImage((const unsigned char*)(mat.data),
? ? ? ? ? ? ? ? ? ? mat.cols,mat.rows,
? ? ? ? ? ? ? ? ? ? mat.cols*mat.channels(),
? ? ? ? ? ? ? ? ? ? QImage::Format_RGB888);
? ? }
? ? return img;
}


void Widget::display(QImage img)
{
? ? QImage imgScaled;
? ? imgScaled = img.scaled(ui->imagelabel->size(),Qt::KeepAspectRatio);
// ?imgScaled = img.QImage::scaled(ui->imagelabel->width(),ui->imagelabel->height(),Qt::KeepAspectRatio);
? ? ui->imagelabel->setPixmap(QPixmap::fromImage(imgScaled));
}


void Widget::on_originalButton_clicked()
{
? ? qimg = Widget::Mat2QImage(originalimg);
? ? display(qimg);
}


void Widget::on_saltButton_clicked()
{
? ? salt(image,3000);
? ? qimg = Widget::Mat2QImage(image);
? ? display(qimg);
}
void Widget::on_reduceButton_clicked()
{
? ? colorReduce0(image,64);
? ? qimg = Widget::Mat2QImage(image);
? ? display(qimg);
}
void Widget::salt(cv::Mat &image, int n)
{


? ? int i,j;
? ? for (int k=0; k<n; k++)
? ? {


? ? ? ? i= qrand()%image.cols;
? ? ? ? j= qrand()%image.rows;


? ? ? ? if (image.channels() == 1)
? ? ? ? { // gray-level image


? ? ? ? ? ? image.at<uchar>(j,i)= 255;


? ? ? ? }
? ? ? ? else if (image.channels() == 3)
? ? ? ? { // color image


? ? ? ? ? ? image.at<cv::Vec3b>(j,i)[0]= 255;
? ? ? ? ? ? image.at<cv::Vec3b>(j,i)[1]= 255;
? ? ? ? ? ? image.at<cv::Vec3b>(j,i)[2]= 255;
? ? ? ? }
? ? }
}


// using .ptr and []
void Widget::colorReduce0(cv::Mat &image, int div)
{


? ? ? int nl= image.rows; // number of lines
? ? ? int nc= image.cols * image.channels(); // total number of elements per line


? ? ? for (int j=0; j<nl; j++)
? ? ? {


? ? ? ? ? uchar* data= image.ptr<uchar>(j);


? ? ? ? ? for (int i=0; i<nc; i++)
? ? ? ? ? {


? ? ? ? ? ? // process each pixel ---------------------
? ? ? ? ? ? ? ? data[i]= data[i]/div*div+div/2;


? ? ? ? ? ? // end of pixel processing ----------------


? ? ? ? ? } // end of line
? ? ? }
}


#ifndef WIDGET_H
#define WIDGET_H


#include <QWidget>
#include <QImage>
#include <QFileDialog>
#include <QTimer>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>


using namespace cv;


namespace Ui {
class Widget;
}


class Widget : public QWidget
{
? ? Q_OBJECT
? ??
public:
? ? explicit Widget(QWidget *parent = 0);
? ? ~Widget();


private slots:
? ? void on_openButton_clicked();
? ? QImage Mat2QImage(const cv::Mat &mat);
? ? void display(QImage image);
? ? void salt(cv::Mat &image, int n);




? ? void on_saltButton_clicked();
? ? void on_reduceButton_clicked();
? ? void colorReduce0(cv::Mat &image, int div);


? ? void on_originalButton_clicked();


private:
? ? Ui::Widget *ui;
? ? cv::Mat image;
? ? cv::Mat originalimg; //store the original img
? ? QImage qimg;
? ? QImage imgScaled;
? ? cv::Mat rgb;
};


#endif // WIDGET_H


書中還給了其他十余種操作的方法:


#include <iostream>


#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>


// using .ptr and []
void colorReduce0(cv::Mat &image, int div=64) {


? ? ? int nl= image.rows; // number of lines
? ? ? int nc= image.cols * image.channels(); // total number of elements per line
? ? ? ? ? ? ??
? ? ? for (int j=0; j<nl; j++) {


? ? ? ? ? uchar* data= image.ptr<uchar>(j);


? ? ? ? ? for (int i=0; i<nc; i++) {
?
? ? ? ? ? ? // process each pixel ---------------------
? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? data[i]= data[i]/div*div + div/2;
?
? ? ? ? ? ? // end of pixel processing ----------------
?
? ? ? ? ? ? } // end of line ? ? ? ? ? ? ? ? ??
? ? ? }
}


// using .ptr and * ++?
void colorReduce1(cv::Mat &image, int div=64) {


? ? ? int nl= image.rows; // number of lines
? ? ? int nc= image.cols * image.channels(); // total number of elements per line
? ? ? ? ? ? ??
? ? ? for (int j=0; j<nl; j++) {


? ? ? ? ? uchar* data= image.ptr<uchar>(j);


? ? ? ? ? for (int i=0; i<nc; i++) {
?
? ? ? ? ? ? // process each pixel ---------------------
? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ?*data++= *data/div*div + div/2;
?
? ? ? ? ? ? // end of pixel processing ----------------
?
? ? ? ? ? ? } // end of line ? ? ? ? ? ? ? ? ??
? ? ? }
}


// using .ptr and * ++ and modulo
void colorReduce2(cv::Mat &image, int div=64) {


? ? ? int nl= image.rows; // number of lines
? ? ? int nc= image.cols * image.channels(); // total number of elements per line
? ? ? ? ? ? ??
? ? ? for (int j=0; j<nl; j++) {


? ? ? ? ? uchar* data= image.ptr<uchar>(j);


? ? ? ? ? for (int i=0; i<nc; i++) {
?
? ? ? ? ? ? // process each pixel ---------------------
? ? ? ?
? ? ? ? ? ? ? ? ? int v= *data;
? ? ? ? ? ? ? ? ? *data++= v - v%div + div/2;
?
? ? ? ? ? ? // end of pixel processing ----------------
?
? ? ? ? ? ? } // end of line ? ? ? ? ? ? ? ? ??
? ? ? }
}


// using .ptr and * ++ and bitwise
void colorReduce3(cv::Mat &image, int div=64) {


? ? ? int nl= image.rows; // number of lines
? ? ? int nc= image.cols * image.channels(); // total number of elements per line
? ? ? int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
? ? ? // mask used to round the pixel value
? ? ? uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
? ? ? ? ? ? ??
? ? ? for (int j=0; j<nl; j++) {


? ? ? ? ? uchar* data= image.ptr<uchar>(j);


? ? ? ? ? for (int i=0; i<nc; i++) {
?
? ? ? ? ? ? // process each pixel ---------------------
? ? ? ? ? ? ? ? ?
? ? ? ? ? ? *data++= *data&mask + div/2;
?
? ? ? ? ? ? // end of pixel processing ----------------
?
? ? ? ? ? ? } // end of line ? ? ? ? ? ? ? ? ??
? ? ? }
}




// direct pointer arithmetic
void colorReduce4(cv::Mat &image, int div=64) {


? ? ? int nl= image.rows; // number of lines
? ? ? int nc= image.cols * image.channels(); // total number of elements per line
? ? ? int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
? ? ? int step= image.step; // effective width
? ? ? // mask used to round the pixel value
? ? ? uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
? ? ? ? ? ? ??
? ? ? // get the pointer to the image buffer
? ? ? uchar *data= image.data;


? ? ? for (int j=0; j<nl; j++) {


? ? ? ? ? for (int i=0; i<nc; i++) {
?
? ? ? ? ? ? // process each pixel ---------------------
? ? ? ? ? ? ? ? ?
? ? ? ? ? ? *(data+i)= *data&mask + div/2;
?
? ? ? ? ? ? // end of pixel processing ----------------
?
? ? ? ? ? ? } // end of line ? ? ? ? ? ? ? ? ??


? ? ? ? ? ? data+= step; ?// next line
? ? ? }
}


// using .ptr and * ++ and bitwise with image.cols * image.channels()
void colorReduce5(cv::Mat &image, int div=64) {


? ? ? int nl= image.rows; // number of lines
? ? ? int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
? ? ? // mask used to round the pixel value
? ? ? uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
? ? ? ? ? ? ??
? ? ? for (int j=0; j<nl; j++) {


? ? ? ? ? uchar* data= image.ptr<uchar>(j);


? ? ? ? ? for (int i=0; i<image.cols * image.channels(); i++) {
?
? ? ? ? ? ? // process each pixel ---------------------
? ? ? ? ? ? ? ? ?
? ? ? ? ? ? *data++= *data&mask + div/2;
?
? ? ? ? ? ? // end of pixel processing ----------------
?
? ? ? ? ? ? } // end of line ? ? ? ? ? ? ? ? ??
? ? ? }
}


// using .ptr and * ++ and bitwise (continuous)
void colorReduce6(cv::Mat &image, int div=64) {


? ? ? int nl= image.rows; // number of lines
? ? ? int nc= image.cols * image.channels(); // total number of elements per line


? ? ? if (image.isContinuous()) ?{
? ? ? ? ? // then no padded pixels
? ? ? ? ? nc= nc*nl;?
? ? ? ? ? nl= 1; ?// it is now a 1D array
? ? ? ?}


? ? ? int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
? ? ? // mask used to round the pixel value
? ? ? uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
? ? ? ? ? ? ??
? ? ? for (int j=0; j<nl; j++) {


? ? ? ? ? uchar* data= image.ptr<uchar>(j);


? ? ? ? ? for (int i=0; i<nc; i++) {
?
? ? ? ? ? ? // process each pixel ---------------------
? ? ? ? ? ? ? ? ?
? ? ? ? ? ? *data++= *data&mask + div/2;
?
? ? ? ? ? ? // end of pixel processing ----------------
?
? ? ? ? ? ? } // end of line ? ? ? ? ? ? ? ? ??
? ? ? }
}


// using .ptr and * ++ and bitwise (continuous+channels)
void colorReduce7(cv::Mat &image, int div=64) {


? ? ? int nl= image.rows; // number of lines
? ? ? int nc= image.cols ; // number of columns


? ? ? if (image.isContinuous()) ?{
? ? ? ? ? // then no padded pixels
? ? ? ? ? nc= nc*nl;?
? ? ? ? ? nl= 1; ?// it is now a 1D array
? ? ? ?}


? ? ? int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
? ? ? // mask used to round the pixel value
? ? ? uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
? ? ? ? ? ? ??
? ? ? for (int j=0; j<nl; j++) {


? ? ? ? ? uchar* data= image.ptr<uchar>(j);


? ? ? ? ? for (int i=0; i<nc; i++) {
?
? ? ? ? ? ? // process each pixel ---------------------
? ? ? ? ? ? ? ? ?
? ? ? ? ? ? *data++= *data&mask + div/2;
? ? ? ? ? ? *data++= *data&mask + div/2;
? ? ? ? ? ? *data++= *data&mask + div/2;
?
? ? ? ? ? ? // end of pixel processing ----------------
?
? ? ? ? ? ? } // end of line ? ? ? ? ? ? ? ? ??
? ? ? }
}




// using Mat_ iterator?
void colorReduce8(cv::Mat &image, int div=64) {


? ? ? // get iterators
? ? ? cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
? ? ? cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::Vec3b>();


? ? ? for ( ; it!= itend; ++it) {
? ? ? ??
? ? ? ? // process each pixel ---------------------


? ? ? ? (*it)[0]= (*it)[0]/div*div + div/2;
? ? ? ? (*it)[1]= (*it)[1]/div*div + div/2;
? ? ? ? (*it)[2]= (*it)[2]/div*div + div/2;


? ? ? ? // end of pixel processing ----------------
? ? ? }
}


// using Mat_ iterator and bitwise
void colorReduce9(cv::Mat &image, int div=64) {


? ? ? // div must be a power of 2
? ? ? int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
? ? ? // mask used to round the pixel value
? ? ? uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0


? ? ? // get iterators
? ? ? cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
? ? ? cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::Vec3b>();


? ? ? // scan all pixels
? ? ? for ( ; it!= itend; ++it) {
? ? ? ??
? ? ? ? // process each pixel ---------------------


? ? ? ? (*it)[0]= (*it)[0]&mask + div/2;
? ? ? ? (*it)[1]= (*it)[1]&mask + div/2;
? ? ? ? (*it)[2]= (*it)[2]&mask + div/2;


? ? ? ? // end of pixel processing ----------------
? ? ? }
}


// using MatIterator_?
void colorReduce10(cv::Mat &image, int div=64) {


? ? ? // get iterators
? ? ? cv::Mat_<cv::Vec3b> cimage= image;
? ? ? cv::Mat_<cv::Vec3b>::iterator it=cimage.begin();
? ? ? cv::Mat_<cv::Vec3b>::iterator itend=cimage.end();


? ? ? for ( ; it!= itend; it++) {?
? ? ? ??
? ? ? ? // process each pixel ---------------------


? ? ? ? (*it)[0]= (*it)[0]/div*div + div/2;
? ? ? ? (*it)[1]= (*it)[1]/div*div + div/2;
? ? ? ? (*it)[2]= (*it)[2]/div*div + div/2;


? ? ? ? // end of pixel processing ----------------
? ? ? }
}




void colorReduce11(cv::Mat &image, int div=64) {


? ? ? int nl= image.rows; // number of lines
? ? ? int nc= image.cols; // number of columns
? ? ? ? ? ? ??
? ? ? for (int j=0; j<nl; j++) {
? ? ? ? ? for (int i=0; i<nc; i++) {
?
? ? ? ? ? ? // process each pixel ---------------------
? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? image.at<cv::Vec3b>(j,i)[0]= ? ? image.at<cv::Vec3b>(j,i)[0]/div*div + div/2;
? ? ? ? ? ? ? ? ? image.at<cv::Vec3b>(j,i)[1]= ? ? image.at<cv::Vec3b>(j,i)[1]/div*div + div/2;
? ? ? ? ? ? ? ? ? image.at<cv::Vec3b>(j,i)[2]= ? ? image.at<cv::Vec3b>(j,i)[2]/div*div + div/2;
?
? ? ? ? ? ? // end of pixel processing ----------------
?
? ? ? ? ? ? } // end of line ? ? ? ? ? ? ? ? ??
? ? ? }
}


// with input/ouput images
void colorReduce12(const cv::Mat &image, // input image?
? ? ? ? ? ? ? ? ?cv::Mat &result, ? ? ?// output image
? ? ? ? ? ? ? ? ?int div=64) {


? ? ? int nl= image.rows; // number of lines
? ? ? int nc= image.cols ; // number of columns


? ? ? // allocate output image if necessary
? ? ? result.create(image.rows,image.cols,image.type());


? ? ? // created images have no padded pixels
? ? ? nc= nc*nl;?
? ? ? nl= 1; ?// it is now a 1D array


? ? ? int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
? ? ? // mask used to round the pixel value
? ? ? uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0
? ? ? ? ? ? ??
? ? ? for (int j=0; j<nl; j++) {


? ? ? ? ? uchar* data= result.ptr<uchar>(j);
? ? ? ? ? const uchar* idata= image.ptr<uchar>(j);


? ? ? ? ? for (int i=0; i<nc; i++) {
?
? ? ? ? ? ? // process each pixel ---------------------
? ? ? ? ? ? ? ? ?
? ? ? ? ? ? *data++= (*idata++)&mask + div/2;
? ? ? ? ? ? *data++= (*idata++)&mask + div/2;
? ? ? ? ? ? *data++= (*idata++)&mask + div/2;
?
? ? ? ? ? ? // end of pixel processing ----------------
?
? ? ? ? ? } // end of line ? ? ? ? ? ? ? ? ??
? ? ? }
}


// using overloaded operators
void colorReduce13(cv::Mat &image, int div=64) {
? ??
? ? ? int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
? ? ? // mask used to round the pixel value
? ? ? uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0


? ? ? // perform color reduction
? ? ? image=(image&cv::Scalar(mask,mask,mask))+cv::Scalar(div/2,div/2,div/2);
}


========

OpenCV圖像處理 空間域圖像增強

(圖像銳化 1 基于拉普拉斯算子) ?




OpenCV
OpenCV 圖像銳化
拉普拉斯算子 ( Laplacian operator )
Quote :


It is indeed a well-known result in image processing that if you subtract its Laplacian from an image, the image edges are amplified giving a sharper image. [From OpenCV 2 Computer Vision Application Programming Cookbook]


對于求一個銳化后的像素點(sharpened_pixel),這個基于拉普拉斯算子的簡單算法主要是遍歷圖像中的像素點,根據領域像素確定其銳化后的值
計算公式:sharpened_pixel = 5 * current – left – right – up – down ; [見Code1]
OpenCV圖像處理 空間域圖像增強(圖像銳化 1 基于拉普拉斯算子) - ___________杰 - __________Ggicci
?
當一個運算是通過領域像素進行的時候,我們通常用一個矩陣來表示這種運算關系,也就是我們經常所說的 核 (Kernel) 。那么上面的 銳化濾波器 (Sharpening Filter) 就可以用這個矩陣表示為它的核:
? -1 ?
-1 5 -1
? -1 ?
因為 濾波 在圖像處理中是一個非常普通且常用的操作,所以OpenCV里面已經定義了一個特殊的函數用來執(zhí)行這個操作。要使用它的話只需要定義一個 核 ,然后作為參數傳遞就行了。[見Code2]
Code 1 :


/*
? ? Author ?: Ggicci
? ? Date ? ?: ?2012.07.19
? ? File ? ?: ?sharp.h
*/
#pragma once
#include <opencv\cv.h>
using namespace cv;
namespace ggicci
{
? ? void sharpen(const Mat& img, Mat& result);
}
/*
? ? Author ?: Ggicci
? ? Date ? ?: ?2012.07.19
? ? File ? ?: ?sharp.cpp
*/
#include "sharp.h"
void ggicci::sharpen(const Mat& img, Mat& result)
{ ? ?
? ? result.create(img.size(), img.type());
? ? //處理邊界內部的像素點, 圖像最外圍的像素點應該額外處理
? ? for (int row = 1; row < img.rows-1; row++)
? ? {
? ? ? ? //前一行像素點
? ? ? ? const uchar* previous = img.ptr<const uchar>(row-1);
? ? ? ? //待處理的當前行
? ? ? ? const uchar* current = img.ptr<const uchar>(row);
? ? ? ? //下一行
? ? ? ? const uchar* next = img.ptr<const uchar>(row+1);
? ? ? ? uchar *output = result.ptr<uchar>(row);
? ? ? ? int ch = img.channels();
? ? ? ? int starts = ch;
? ? ? ? int ends = (img.cols - 1) * ch;
? ? ? ? for (int col = starts; col < ends; col++)
? ? ? ? {
? ? ? ? ? ? //輸出圖像的遍歷指針與當前行的指針同步遞增, 以每行的每一個像素點的每一個通道值為一個遞增量, 因為要考慮到圖像的通道數
? ? ? ? ? ? *output++ = saturate_cast<uchar>(5 * current[col] - current[col-ch] - current[col+ch] - previous[col] - next[col]);
? ? ? ? }
? ? } //end loop
? ? //處理邊界, 外圍像素點設為 0
? ? result.row(0).setTo(Scalar::all(0));
? ? result.row(result.rows-1).setTo(Scalar::all(0));
? ? result.col(0).setTo(Scalar::all(0));
? ? result.col(result.cols-1).setTo(Scalar::all(0));
}
/*
? ? Author ?: ?Ggicci
? ? Date ? ?: ?2012.07.19
? ? File ? ?: ?main.cpp
*/
#include <opencv\highgui.h>
#pragma comment(lib, "opencv_core231d.lib")
#pragma comment(lib, "opencv_highgui231d.lib")
#pragma comment(lib, "opencv_imgproc231d.lib")
using namespace cv;
?
#include "sharp.h"
?
int main()
{ ? ?
? ? Mat lena = imread("lena.jpg");
? ? Mat sharpenedLena;
? ? ggicci::sharpen(lena, sharpenedLena);
?
? ? imshow("lena", lena);
? ? imshow("sharpened lena", sharpenedLena);
? ? cvWaitKey();
? ? return 0;
}
Output 1 :




OpenCV圖像處理 空間域圖像增強(圖像銳化 1 基于拉普拉斯算子) - ___________杰 - __________Ggicci
?
Code 2 :


? ?1: int main()
? ?2: { ? ?
? ?3: ? ? Mat lena = imread("lena.jpg");
? ?4: ? ? Mat sharpenedLena;
? ?5: ? ? Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
? ?6: ? ? cv::filter2D(lena, sharpenedLena, lena.depth(), kernel);
? ?7: ?
? ?8: ? ? imshow("lena", lena);
? ?9: ? ? imshow("sharpened lena", sharpenedLena);
? 10: ? ? cvWaitKey();
? 11: ? ? return 0;
? 12: }
Output 2 :
OpenCV圖像處理 空間域圖像增強(圖像銳化 1 基于拉普拉斯算子) - ___________杰 - __________Ggicci
?
End :


Author : Ggicci




========

OpenCV圖像處理 圖像的點運算??灰度直方圖?



OpenCV灰度直方圖
Theory :


從圖形上看,灰度直方圖是一個二維圖:


gray_hist


圖像的灰度直方圖是一個離散函數,它表示圖像每一灰度級與該灰度級出現頻率的對應關系。假設一幅圖像的像素總數為 N,灰度級總數為 L,其中灰度級為 g 的像素總數為 Ng,則這幅數字圖像的灰度直方圖橫坐標即為灰度 g ( 0 ≤ g ≤ L-1 ),縱坐標則為灰度值


出現的次數 Ng。實際上,用 N 去除各個灰度值出現的次數 Ng 即可得到各個灰度級出現的概率 Pg = Ng / N = Ng / ∑Ng ,從而得到歸一化的灰度直方圖,其縱坐標為概率 Pg 。


Quote : ( From [OpenCV 2 Computer Vision Application Programming Cookbook (Robert Langaniere, 2011) ], 引用作直方圖的解釋 )


A histogram is a simple table that gives the number of pixels that have a given value in an image (or sometime a set of images). The histogram of a gray-level image will therefore have 256 entries (or bins).
Histograms can also be normalized such that sum of the bins equals 1. In that case, each bin gives the percentage of pixels having this specific value in the image.
Implementation :


利用 OpenCV 提供的 calcHist 函數 :


void calcHist(const Mat* arrays, int narrays, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false );


這個函數用于計算直方圖是很強大的,在這里就實現一個最簡單的灰度圖像的直方圖計算。


Code :


? ?int main()
? ?{ ? ?
? ? ? ?Mat img = imread("lena.jpg", CV_LOAD_IMAGE_GRAYSCALE);
? ?
? ? ? ?Mat* arrays = &img;
? ? ? ?int narrays = 1;
? ? ? ?int channels[] = { 0 };
? ? ? ?InputArray mask = noArray();
? ? ? ?Mat hist;
? ? ? ?int dims = 1;
? ? ? ?int histSize[] = { 256 }; ? ?
? ? ? ?float hranges[] = { 0.0, 255.0 };
? ? ? ?const float *ranges[] = { hranges };
? ? ? ?//調用 calcHist 計算直方圖, 結果存放在 hist 中
? ? ? ?calcHist(arrays, narrays, channels, mask, hist, dims, histSize, ranges);
? ? ? ?
? ? ? ?//調用一個我自己寫的簡單的函數用于獲取一張顯示直方圖數據的圖片,
? ? ? ?//輸入參數為直方圖數據 hist 和期望得到的圖片的尺寸
? ? ? ?Mat histImg = ggicci::getHistogram1DImage(hist, Size(600, 420));
? ? ? ?imshow("lena gray image histogram", histImg);
? ? ? ?waitKey();
? ?}
? ?
? ?Mat ggicci::getHistogram1DImage(const Mat& hist, Size imgSize)
? ?{
? ? ? ?Mat histImg(imgSize, CV_8UC3);
? ? ? ?int Padding = 10;
? ? ? ?int W = imgSize.width - 2 * Padding;
? ? ? ?int H = imgSize.height - 2 * Padding;
? ? ? ?double _max;
? ? ? ?minMaxLoc(hist, NULL, &_max);
? ? ? ?double Per = (double)H / _max;
? ? ? ?const Point Orig(Padding, imgSize.height-Padding);
? ? ? ?int bin = W / (hist.rows + 2);
? ?
? ? ? ?//畫方柱
? ? ? ?for (int i = 1; i <= hist.rows; i++)
? ? ? ?{
? ? ? ? ? ?Point pBottom(Orig.x + i * bin, Orig.y);
? ? ? ? ? ?Point pTop(pBottom.x, pBottom.y - Per * hist.at<float>(i-1));
? ? ? ? ? ?line(histImg, pBottom, pTop, Scalar(255, 0, 0), bin);
? ? ? ?}
? ?
? ? ? ?//畫 3 條紅線標明區(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) 通過筆記一的學習,我們已經能夠下載、安裝OpenCV并新建VS2010項目進行相關的配置,筆記一也已完成第一個程序HelloCV的演示。本文首先通過詳細介紹OpenCV中如何從硬盤加載/讀取一幅圖像,并在窗口中進行顯示來對筆記一中的演示程序做詳解


。其次,本文實現了簡單的圖像變換,將一幅RGB顏色的圖片lena.jpg轉化成灰度圖像,以達到修改的目的,另外,在此變換中,我們還對如何將圖片保存到硬盤上進行講解。實驗結果表明,通過筆記二的學習,不但能夠增強對OpenCV的學習興趣,還能有初體驗OpenCV的


成就感,吃嘛嘛香,為后續(xù)的學習打下堅實的基礎。


1、加載并顯示圖像(Load and Display an Image)
?
1.1 學習目標
?
在本節(jié)中,我們預期達到以下學習目標: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有許多不同的模塊,每個模塊關心圖像處理中不同的領域及方法(參見:OpenCV學習筆記(基于OpenCV 2.4)一:哈嘍CV),在使用之前我們首先需要對相應的頭文件進行包含,一般情況下我們都會用到的兩個模塊:
?
1)core section. 這里定義了OpenCV的一些基本的塊(Blocks);
?
2)highgui module. 該模塊包含了一些圖像的輸入輸出操作(UI)。
?
另外,為了能夠在控制臺做輸入輸出,我們會包含iostream,而string是用于字符串的處理。接下來,為了防止OpenCV的數據結構或命名與其它庫函數比如STL有沖突,我們引入命名空間cv,在有沖突的情況下可以用前綴cv::來指定具體使用哪個庫(關于命名空間,我們


會在下一講做詳細介紹)。
?
1.3.2 主函數
?
--------------------------------------------------------------------------------




Mat img = imread(imageName, CV_LOAD_IMAGE_COLOR);
?


--------------------------------------------------------------------------------


imread的函數原型是:Mat imread( const string& filename, int flags=1 );
?
Mat是OpenCV里的一個數據結構,在這里我們定義一個Mat類型的變量img,用于保存讀入的圖像,在本文開始有寫到,我們用imread函數來讀取圖像,第一個字段標識圖像的文件名(包括擴展名),第二個字段用于指定讀入圖像的顏色和深度,它的取值可以有以下幾種:
?
1) CV_LOAD_IMAGE_UNCHANGED (<0),以原始圖像讀取(包括alpha通道),
?
2) CV_LOAD_IMAGE_GRAYSCALE ( 0),以灰度圖像讀取
?
3) CV_LOAD_IMAGE_COLOR (>0),以RGB格式讀取
?
這三點是在OpenCV的官方教程(opencv_tutorials.pdf)里摘錄并翻譯過來的,但是網上還有關于CV_LOAD_IMAGE_ANYDEPTH和CV_LOAD_IMAGE_ANYCOLOR的傳說,而且查看OpenCV的源碼可以發(fā)現,這些取值放在一個枚舉(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
?
};
?
關于該枚舉類型的詳細信息,官方教程的寫法跟官方發(fā)布的正式版代碼描述的不相同,可能是我理解不夠深入,或者兩者是等價的,這點以后再找時間研究,但這并不影響我們對這一章節(jié)的學習。
?
Note:OpenCV提供了多種格式圖像的支持,包括Windows bitmap(bmp),portable image formats (pbm, pgm,ppm) 以及 Sun raster (sr, ras)。關于其它的格式,有JPEG (jpeg, jpg, jpe),JPEG 2000,TIFF 文件 (tiff, tif),portable network graphics (png),


還有openEXR格式,如果OpenCV是自己打包的話,讀取這些格式需要有插件支持,如果是官方提供的庫,則不需再添加插件。
?
?
?
在檢查圖像是否讀取成功之后,我們需要顯示讀入的圖像,因此,我們采用namedWindow函數來創(chuàng)建一個OpenCV窗口,該函數的定義如下:
?
CV_EXPORTS_W void namedWindow(const string& winname, int flags = WINDOW_AUTOSIZE);
?
為此,我們需要指定該窗口的名稱(窗口標識符, window identifier)以及如何處理窗口大小。①窗口標識符需要唯一指定,如果已經存在一個該名字的窗口,則此函數不進行任何處理;②flags參數目前只支持CV_WINDOW_AUTOSIZE,在highgui_c.h中,OpenCV定義了


CV_WINDOW_AUTOSIZE= 0×00000001,如果設置該參數,則表示顯示的時候窗口自適應于需要顯示的圖像,而且不能修改窗口大小,如果不設置(省略此參數),可以通過代碼進行修改;③如果將OpenCV用于Qt后端開發(fā),該參數還支持其它值,具體可查看OpenCV開發(fā)文檔


,這里不再贅述。
?
--------------------------------------------------------------------------------




imshow(“l(fā)ena”, img);
?
--------------------------------------------------------------------------------


imshow用于在我們創(chuàng)建的窗口中顯示需要顯示的圖像,其函數原型為:
?
void imshow(const string& winname, InputArray mat);
?
第一個參數winname是指窗口的名稱,也就是窗口標識符,第二個參數mat就是我們要顯示的圖像了。正如我們在namedWindow函數中所描述的那樣,如果namedWindow指定了參數CV_WINDOW_AUTOSIZE,則圖像會按原始大小顯示,否則圖像會根據窗口大小進行縮放。




--------------------------------------------------------------------------------




waitKey();
?
--------------------------------------------------------------------------------




這條語句表示等待用戶鍵盤操作,waitKey函數的函數原型如下:
?
int waitKey(int delay = 0);
?
我們可以看到,該函數可包含一個整形參數,不設置參數的情況下,默認為0,也就是無限制等待。該整數表示需要等待用戶操作的毫秒數。
?
2 加載、修改并保存圖像(Load, Modify, and Save an Image)
?
2.1 學習目標
?
在這一章,我們將學習:1)加載一副圖像(采用imread函數,同第一章);2)將一副圖像從RGB格式轉換成灰度圖(grayscale,采用cvtColor函數);3)保存轉換后的圖像到磁盤上(采用imwrite函數)。
?
2.2 源代碼
?
<pre lang=”cpp”>
?
#include “StdAfx.h”
?
#include <cv.h>
?
#include <highgui.h>
?
#include <string>
?
?
?
using namespace cv;
?
using namespace std;
?
?
?
int main()
?
{
?
? ? ? ? ?char* imageName = “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圖像轉換成灰度圖像
?
? ? ? ? ?imwrite(“../../lena_gray.jpg”,gray_image);//保存圖像
?
?
?
? ? ? ? ?namedWindow(imageName, CV_WINDOW_AUTOSIZE);//創(chuàng)建用于顯示元圖像窗口
?
? ? ? ? ?namedWindow(grayImageName,CV_WINDOW_AUTOSIZE);//創(chuàng)建用于顯示轉換后圖像窗口
?
?
?
? ? ? ? ?imshow(imageName,image);
?
? ? ? ? ?imshow(“grayImageName”, gray_image);
?
?
?
? ? ? ? ?waitKey(0);
?
? ? ? ? ?return 0;
?
}
?
</pre>
?
2.3 源碼詳解
?
有了第一章的基礎后,再來理解本章代碼相對就很容易,在這一節(jié),關于imread函數的使用則不再贅述,下面給cvtColor和imwrite來一個特寫。
?


--------------------------------------------------------------------------------




cvtColor(image,gray_image,CV_RGB2GRAY);// 將RGB圖像轉換成灰度圖像
?


--------------------------------------------------------------------------------




cvtColor函數用于將圖像從一個顏色空間轉換到另一個顏色空間,其函數原型為:
?
void cvtColor( InputArray src, OutputArray dst, int code, int dstCn=0 );
?
參數src:是指需要轉化的圖像,可以是8位或16位等的無符號型或者是單精度浮點型(Single-Precision Floating-Point);
?
參數dst:與原始圖像具有相同大小和深度的目標圖像;
?
參數code:顏色空間轉換代碼;
?
參數dstCn:目標圖像的通道數,如果該參數為0,則通道數可由src和code自動獲得;
?
對于一個原圖像或目標圖像是RGB的轉換,我們需要詳細地指定通道的順序(RGB or BGR)。我們注意到,OpenCV默認情況下的顏色格式一般是指RGB,但實際上卻進行了一個反轉變成BGR,因此對一個標準的24位圖像來說,其第一個字節(jié)為8位的藍色部分(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輸入圖像應該先做規(guī)格化處理(Normalized),以便得到一個合適的范圍來獲取正確的結果。比如對于一個RGB顏色空間到LUV顏色空間的變換,如


果我們需要將一副8位圖像轉換到一副32位的浮點型精度圖像而不進行任何縮放,也就是說,我們將一個從0~255的范圍替換成0~1的范圍,那么我們首先要將圖像按比例縮小(Scale the Image Down):
?
img *= 1./255;
?
cvtColor(img, img, CV_BGR2Luv);
?
如果我們采用8位的圖像進行轉換,該過程中可能會有信息的丟失,盡管在一般的應用中,這種丟失并不明顯(Noticeable),但我們強烈建議使用一個32位的圖像或者在變換之前先轉換成32位。
?
備注:關于參數code的跟多取值,可以參見OpenCV 2.4.0官方文檔:cvtColor函數指南及使用方法
?
--------------------------------------------------------------------------------


?
?
imwrite(“../../lena_gray.jpg”,gray_image);// 保存圖像
?


--------------------------------------------------------------------------------




imwrite函數用于將圖像保存到指定的文件,其函數原型為:
?
bool imwrite(const string& filename, InputArray image, const vector<int>& params=vector<int>())
?
參數filename:指代需要保存文件的名稱
?
參數image:需要保存的圖像
?
參數params:保存至指定格式圖像格式時的參數設置
?
關于params參數的取值如下:
?
對JPEG圖像,它表示圖像質量(CV_IMWRITE_JPEG_QUALITY),取值從1~100,值越大質量越高,默認為95;
?
對PNG圖像,它表示圖像壓縮率(CV_IMWRITE_PNG_COMPRESSION),取值從0~9,值越大壓縮率越大壓縮時間越長,默認值為3;
?
對PPM,PGM或PBM,它是一個二進制標識(CV_IMWRITE_PXM_BINARY),取值為0或1,默認為1。
?
有關于該函數及參數params的詳細信息及應用可參見開發(fā)文檔:imwrite函數指南及使用方法
?


========

圖像縮放--OpenCV cvResize函數--最近鄰插值---雙線性插值



void cvResize( const CvArr* src, CvArr* dst, int interpolation=CV_INTER_LINEAR ); src輸入圖像.dst輸出圖像.interpolation插值方法:
CV_INTER_NN - 最近鄰插值,
CV_INTER_LINEAR - 雙線性插值 (缺省使用)
CV_INTER_AREA - 使用象素關系重采樣。當圖像縮小時候,該方法可以避免波紋出現。當圖像放大時,類似于 CV_INTER_NN 方法..
CV_INTER_CUBIC - 立方插值.
函數 cvResize 將圖像 src 改變尺寸得到與 dst 同樣大小。若設定 ROI,函數將按常規(guī)支持 ROI.


最近鄰插值:效果(放大4倍)有馬賽克現象




雙線性插值:效果(放大4倍)比最近鄰插值效果好


最近鄰插值和雙線性插值的基本原理


圖像的縮放很好理解,就是圖像的放大和縮小。傳統(tǒng)的繪畫工具中,有一種叫做“放大尺”的繪畫工具,畫家常用它來放大圖畫。當然,在計算機上,我們不再需要用放大尺去放大或縮小圖像了,把這個工作交給程序來完成就可以了。下面就來講講計算機怎么來放大縮小


圖象;在本文中,我們所說的圖像都是指點陣圖,也就是用一個像素矩陣來描述圖像的方法,對于另一種圖像:用函數來描述圖像的矢量圖,不在本文討論之列。
越是簡單的模型越適合用來舉例子,我們就舉個簡單的圖像:3X3 的256級灰度圖,也就是高為3個象素,寬也是3個象素的圖像,每個象素的取值可以是 0-255,代表該像素的亮度,255代表最亮,也就是白色,0代表最暗,即黑色。假如圖像的象素矩陣如下圖所示(這


個原始圖把它叫做源圖,Source):
234 ? 38 ? ?22
67 ? ? 44 ? ?12
89 ? ? 65 ? ?63
這個矩陣中,元素坐標(x,y)是這樣確定的,x從左到右,從0開始,y從上到下,也是從零開始,這是圖象處理中最常用的坐標系,就是這樣一個坐標:
? ---------------------->X
? |
? |
? |
? |
? |
∨Y
如果想把這副圖放大為 4X4大小的圖像,那么該怎么做呢?那么第一步肯定想到的是先把4X4的矩陣先畫出來再說,好了矩陣畫出來了,如下所示,當然,矩陣的每個像素都是未知數,等待著我們去填充(這個將要被填充的圖的叫做目標圖,Destination):
? ? ? ? ?? ? ? ? ?? ? ? ? ?
? ? ? ? ?? ? ? ? ?? ? ? ? ?
? ? ? ? ?? ? ? ? ?? ? ? ? ?
? ? ? ? ?? ? ? ? ?? ? ? ? ??
? ? ? ? ? ? ? ?
? ? ? ? 然后要往這個空的矩陣里面填值了,要填的值從哪里來來呢?是從源圖中來,好,先填寫目標圖最左上角的象素,坐標為(0,0),那么該坐標對應源圖中的坐標可以由如下公式得出: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
srcX=dstX* (srcWidth/dstWidth) , srcY = dstY * (srcHeight/dstHeight)
好了,套用公式,就可以找到對應的原圖的坐標了(0*(3/4),0*(3/4))=>(0*0.75,0*0.75)=>(0,0)
,找到了源圖的對應坐標,就可以把源圖中坐標為(0,0)處的234象素值填進去目標圖的(0,0)這個位置了。
接下來,如法炮制,尋找目標圖中坐標為(1,0)的象素對應源圖中的坐標,套用公式:
(1*0.75,0*0.75)=>(0.75,0)
結果發(fā)現,得到的坐標里面竟然有小數,這可怎么辦?計算機里的圖像可是數字圖像,象素就是最小單位了,象素的坐標都是整數,從來沒有小數坐標。這時候采用的一種策略就是采用四舍五入的方法(也可以采用直接舍掉小數位的方法),把非整數坐標轉換成整數,好,那


么按照四舍五入的方法就得到坐標(1,0),完整的運算過程就是這樣的:
(1*0.75,0*0.75)=>(0.75,0)=>(1,0)
那么就可以再填一個象素到目標矩陣中了,同樣是把源圖中坐標為(1,0)處的像素值38填入目標圖中的坐標。
? ? ? ? ?
依次填完每個象素,一幅放大后的圖像就誕生了,像素矩陣如下所示:
234 ? ?38 ? ? 22 ? ? 22 ?
67 ? ? ?44 ? ? 12 ? ? 12 ?
89 ? ? ?65 ? ? 63 ? ? 63 ?
89 ? ? ?65 ? ? 63 ? ? 63 ?
這種放大圖像的方法叫做最臨近插值算法,這是一種最基本、最簡單的圖像縮放算法,效果也是最不好的,放大后的圖像有很嚴重的馬賽克,縮小后的圖像有很嚴重的失真;效果不好的根源就是其簡單的最臨近插值方法引入了嚴重的圖像失真,比如,當由目標圖的坐標


反推得到的源圖的的坐標是一個浮點數的時候,采用了四舍五入的方法,直接采用了和這個浮點數最接近的象素的值,這種方法是很不科學的,當推得坐標值為 0.75的時候,不應該就簡單的取為1,既然是0.75,比1要小0.25 ,比0要大0.75 ,那么目標象素值其實應該根


據這個源圖中虛擬的點四周的四個真實的點來按照一定的規(guī)律計算出來的,這樣才能達到更好的縮放效果。雙線型內插值算法就是一種比較好的圖像縮放算法,它充分的利用了源圖中虛擬點四周的四個真實存在的像素值來共同決定目標圖中的一個像素值,因此縮放效果


比簡單的最鄰近插值要好很多。
雙線性內插值算法描述如下:
  對于一個目的像素,設置坐標通過反向變換得到的浮點坐標為(i+u,j+v) (其中i、j均為浮點坐標的整數部分,u、v為浮點坐標的小數部分,是取值[0,1)區(qū)間的浮點數),則這個像素得值 f(i+u,j+v) 可由原圖像中坐標為 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所對


應的周圍四個像素的值決定,即:
  f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1) ? ? ? ? ? ? ? ? ? ? ? ? ?公式1
其中f(i,j)表示源圖像(i,j)處的的像素值,以此類推。
比如,象剛才的例子,現在假如目標圖的象素坐標為(1,1),那么反推得到的對應于源圖的坐標是(0.75 , 0.75), 這其實只是一個概念上的虛擬象素,實際在源圖中并不存在這樣一個象素,那么目標圖的象素(1,1)的取值不能夠由這個虛擬象素來決定,而只能由源


圖的這四個象素共同決定:(0,0)(0,1)(1,0)(1,1),而由于(0.75,0.75)離(1,1)要更近一些,那么(1,1)所起的決定作用更大一些,這從公式1中的系數uv=0.75×0.75就可以體現出來,而(0.75,0.75)離(0,0)最遠,所以(0,0)所起的決定作用


就要小一些,公式中系數為(1-u)(1-v)=0.25×0.25也體現出了這一特點。
原理參考link:http://blog.csdn.net/andrew659/article/details/4818988
OpenCV代碼:scale是放縮比例


點擊(此處)折疊或打開
#include "stdafx.h"
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <cmath>
using namespace std;
using namespace cv;


int main(int argc ,char ** argv)
{
? ? IplImage *scr=0;
? ? IplImage *dst=0;
? ? double scale=4;
? ? CvSize dst_cvsize;
? ? if (argc==2&&(scr=cvLoadImage(argv[1],-1))!=0)
? ? {
? ? ? ? dst_cvsize.width=(int)(scr->width*scale);
? ? ? ? dst_cvsize.height=(int)(scr->height*scale);
? ? ? ? dst=cvCreateImage(dst_cvsize,scr->depth,scr->nChannels);


? ? ? ? cvResize(scr,dst,CV_INTER_NN);//
// ? ? ? ? ? ? CV_INTER_NN - 最近鄰插值,
// ? ? ? ? ? ? CV_INTER_LINEAR - 雙線性插值 (缺省使用)
// ? ? ? ? ? ? CV_INTER_AREA - 使用象素關系重采樣。當圖像縮小時候,該方法可以避免波紋出現。
? ? ? ? ?/*當圖像放大時,類似于 CV_INTER_NN 方法..*/
// ? ? ? ? ? ? CV_INTER_CUBIC - 立方插值.


? ? ? ? cvNamedWindow("scr",CV_WINDOW_AUTOSIZE);
? ? ? ? cvNamedWindow("dst",CV_WINDOW_AUTOSIZE);
? ? ? ? cvShowImage("scr",scr);
? ? ? ? cvShowImage("dst",dst);
? ? ? ? cvWaitKey();
? ? ? ? cvReleaseImage(&scr);
? ? ? ? cvReleaseImage(&dst);
? ? ? ? cvDestroyWindow("scr");
? ? ? ? cvDestroyWindow("dst");
? ? }
? ? return 0;
}
========

opencv初體驗-圖片濾鏡效果



我參考了http://blog.csdn.net/yangtrees/article/details/9116337的代碼,


稍作修改,將其變成一個小工具,可將圖片加“懷舊色”濾鏡保存輸出。


不說廢話,直接上代碼。


#include <opencv/cv.h>
#include <opencv/highgui.h>


using namespace cv;
using namespace std;




int main(int argc, char ** argv)
{
? ? // input args check
? ? if(argc < 3){
? ? ? ? printf("please input args.\n");
? ? ? ? printf("e.g. : ./test infilepath outfilepath \n");
? ? ? ? return 0;
? ? }
? ??
? ? char * input = argv[1];
? ? char * output = argv[2];
? ??
? ? printf("input: %s, output: %s\n", input, output);


? ? Mat src = imread(input, 1);


? ? int width=src.cols;
? ? int heigh=src.rows;
? ? RNG rng;
? ? Mat img(src.size(),CV_8UC3);
? ? for (int y=0; y<heigh; y++)
? ? {
? ? ? ? uchar* P0 = src.ptr<uchar>(y);
? ? ? ? uchar* P1 = img.ptr<uchar>(y);
? ? ? ? for (int x=0; x<width; x++)
? ? ? ? {
? ? ? ? ? ? float B=P0[3*x];
? ? ? ? ? ? float G=P0[3*x+1];
? ? ? ? ? ? float R=P0[3*x+2];
? ? ? ? ? ? float newB=0.272*R+0.534*G+0.131*B;
? ? ? ? ? ? float newG=0.349*R+0.686*G+0.168*B;
? ? ? ? ? ? float newR=0.393*R+0.769*G+0.189*B;
? ? ? ? ? ? if(newB<0)newB=0;
? ? ? ? ? ? if(newB>255)newB=255;
? ? ? ? ? ? if(newG<0)newG=0;
? ? ? ? ? ? if(newG>255)newG=255;
? ? ? ? ? ? if(newR<0)newR=0;
? ? ? ? ? ? if(newR>255)newR=255;
? ? ? ? ? ? P1[3*x] = (uchar)newB;
? ? ? ? ? ? P1[3*x+1] = (uchar)newG;
? ? ? ? ? ? P1[3*x+2] = (uchar)newR;
? ? ? ? }


? ? }
? ? //imshow("out",img);
? ? waitKey();
? ? imwrite(output,img);
}


編譯時需要注意一下,需要加上`pkg-config opencv --libs --cflags opencv`


例如:g++ -o test opencvtest.cpp `pkg-config opencv --libs --cflags opencv`


OK,編譯正常。


從網上下個圖片,做個測試。


看下效果,還不錯。


原圖:


處理后:


========

用opencv將圖片變成水波紋效果



將一幅圖片變成水波紋效果。我在網上找到一份源碼,參考之下,順著思路用opencv2重寫之。


思路如下:


1.將圖片中的坐標點(x,y)換成極坐標,有現成的函數。


2.極坐標下,用三角函數算出新半徑。


3.在新半徑之下,轉換成新的坐標(x 0 ,y 0 ),如果新坐標是小數,用雙線性插值的方法處理。


關鍵代碼如下:


其中一些變量聲明如下:


Mat imageInfo;//原圖片
? ? ? ? int imageWidth;
? ? ? ? int imageHeight;
? ? ? ? int imageX;//圖像中心點的橫坐標
? ? ? ? int imageY;//圖像中心點的縱坐標
? ? ? ? float A;//波紋幅度
? ? ? ? float B;//波紋周期 ? Asin(Bx);
? ? ? ? int imageChannels;//通道數
? ? ? ? Mat imageWater;//轉換后的圖片
? ? ? ? void reCalcAB(int i,int j,float &a,float &b);//坐標轉換
? ? ? ? uchar BLIP(float a,float b,int k);
? ? ? ? //k為通道數,值為-1為單通道,灰度圖
?void imagetest::imageprocess()
?{
? ? ?imageInfo.copyTo(imageWater);
?
? ? ?float a;
? ? ?float b;//臨時坐標
?
? ? ?for(int i=0;i<imageHeight-1;i++)
? ? ?{
? ? ? ? ?uchar *Data = imageWater.ptr<uchar>(i);
? ? ? ? ?for(int j=0;j<imageWidth-1;j++)
? ? ? ? ?{
? ? ? ? ? ? ?reCalcAB(i,j,a,b);
? ? ? ? ? ? ?if(imageChannels == 1)//彩色與灰度圖像要單獨處理,否則
? ? ? ? ? ? ?{ ? ? ? ? ? ? ? ? ? ? ? //會出現橢圓的情況
? ? ? ? ? ? ? ? ?*(Data+j) = BLIP(a,b,-1);//-1指灰度圖
? ? ? ? ? ? ?}
? ? ? ? ? ? ?else if(imageChannels == 3)
? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ?for(int k = 0;k<imageChannels;k++)
? ? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ? ?*(Data+j*imageChannels+k)= BLIP(a,b,k);
? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ?}
? ? ? ? ?}
? ? ?}
?}
reCalcAB是坐標轉換函數:


?void imagetest::reCalcAB(int i,int j,float &a,float &b)
?{
? ? ?float y0 = (float)(i-imageY);
? ? ?float x0 = (float)(j-imageX);//(i,j)相對于原點的坐標
? ? ?float theta0 = atan2f(y0,x0);//轉化成角坐標
? ? ?float r0 = sqrtf(x0*x0+y0*y0);//初始半徑
?
? ? ?float r1 = r0+ A*imageWidth*0.01*sin(B*0.1*r0);//計算新的半徑
? ? ?a = imageX + r1*cos(theta0);
? ? ?b = imageY + r1*sin(theta0);//轉換后的坐標
? ? ?if(a>imageWidth)
? ? ? ? ?a = imageWidth-1;
? ? ?else if(a<0)
? ? ? ? ?a = 0; ? ? ? ? ? ? ? ?//超出邊界的處理
? ? ?if(b>imageHeight)
? ? ? ? ?b = imageHeight-1;
? ? ?else if(b<0)
? ? ? ? ?b = 0;
?}
雙線性插值函數:(這個方法看著很高級,實際很簡單。仔細看代碼就明白怎么回事情了)


?uchar imagetest::BLIP(float a,float b,int k)
?{
? ? ?uchar newData;//保存結果
? ? ?uchar DataTemp1;
? ? ?uchar DataTemp2;//兩個中間變量
? ? ?int x[2];
? ? ?int y[2];//存儲周圍四個點。
?
? ? ?x[0] = (int)a;
? ? ?y[0] = (int)b;
? ? ?x[1] = x[0]+1;
? ? ?y[1] = y[0]+1;//(a,b)周圍四個整點坐標
? ? ?//取值
? ? ?uchar *data1 = imageWater.data + y[0]*imageWater.step + x[0]*imageChannels;
? ? ?uchar *data2 = imageWater.data + y[0]*imageWater.step + x[1]*imageChannels;
? ? ?uchar *data3 = imageWater.data + y[1]*imageWater.step + x[0]*imageChannels;
? ? ?uchar *data4 = imageWater.data + y[1]*imageWater.step + x[1]*imageChannels;
? ? ?if(k!=-1)//如果是彩色,轉換一下
? ? ?{
? ? ? ? ?data1 += k;
? ? ? ? ?data2 += k;
? ? ? ? ?data3 += k;
? ? ? ? ?data4 += k;
? ? ?}
?
? ? ?if((fabsf(a-x[0])<0.00001) && (fabsf(b-y[0])<0.00001))//整點,直接返回
? ? ?{
? ? ? ? ?newData = *data1;
? ? ? ? ?return newData;
? ? ?}
?
? ? ?float dx = fabsf(a-x[0]);//x軸的比例
? ? ?float dy = fabsf(b-y[0]);//y軸的比例
?
? ? ?DataTemp1 = (*data1)*(1.0-dx) + (*data2)*dx;
? ? ?DataTemp2 = (*data3)*(1.0-dx) + (*data4)*dx;
? ? ?newData = DataTemp1*(1.0-dy) + DataTemp2*dy;//核心插值過程
?
? ? ?return newData;
?}


這個效果看起來倒是不錯,總感覺不是那么真實。


而且,這個程序有嚴重的問題。如果我換一張圖片,重新設置 A和B的參數


就會出現如下的效果:




中間水平方向出現了明顯的一條橫線。


目前還沒有解決的問題主要就是這條橫線,然后就是怎么樣才能使得水波紋看起來更真實。我想把用一張圖片做成視頻,不知道這個效果最后做出來是個什么樣子。


如果是坐標轉換出錯了的話,理論上來說應該會水平、豎直都應該出現一條直線,現在只有水平方向有一條直線。


========

在OpenCV中實現特效之浮雕,雕刻和褶皺



下面代碼的基礎是對圖像像素的訪問。


實現浮雕和雕刻的代碼是統(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; ?
} ?
原圖為


浮雕效果圖如下


雕刻效果圖如下


下面是實現圖像褶皺的代碼,效果不是太好,結構過渡不平滑,以后再改進一下。希望能做到波浪化。


#include <cv.h> ?
#include <highgui.h> ?
#pragma comment( lib, "cv.lib" ) ?
#pragma comment( lib, "cxcore.lib" ) ?
#pragma comment( lib, "highgui.lib" ) ?
int main() ?
{ ?
? ? IplImage *org=cvLoadImage("lena.jpg",1); ?
? ? IplImage *image=cvCloneImage(org); ?
? ? int width=image->width; ?
? ? int height=image->height; ?
? ? int step=image->widthStep; ?
? ? int channel=image->nChannels; ?
? ? uchar* data=(uchar *)image->imageData; ?
? ? int sign=-1; ?
? ? for(int i=0;i<height;i++) ?
? ? { ? ??
? ? ? ? int cycle=10; ?
? ? ? ? int margin=(i%cycle); ?
? ? ? ? if((i/cycle)%2==0) ?
? ? ? ? { ?
? ? ? ? ? ? sign=-1; ?
? ? ? ? } ?
? ? ? ? else ?
? ? ? ? { ?
? ? ? ? ? ? sign=1; ?
? ? ? ? } ?
? ? ? ? if(sign==-1) ?
? ? ? ? { ? ??
? ? ? ? ? ? margin=cycle-margin; ?
? ? ? ? ? ? for(int j=0;j<width-margin;j++) ?
? ? ? ? ? ? { ? ? ? ? ? ??
? ? ? ? ? ? ? ? for(int k=0;k<channel;k++) ?
? ? ? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? ? ? data[i*step+j*channel+k]=data[i*step+(j+margin)*channel+k]; ?
? ? ? ? ? ? ? ? } ?
? ? ? ? ? ? } ?
? ? ? ? } ?
? ? ? ? else if(sign==1) ?
? ? ? ? { ? ? ? ??
? ? ? ? ? ? for(int j=0;j<width-margin;j++) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? for(int k=0;k<channel;k++) ?
? ? ? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? ? ? data[i*step+j*channel+k]=data[i*step+(j+margin)*channel+k]; ?
? ? ? ? ? ? ? ? } ?
? ? ? ? ? ? } ?
? ? ? ? } ? ??
? ? } ?
? ? cvNamedWindow("original",1); ?
? ? cvShowImage("original",org); ?
? ? cvNamedWindow("image",1); ?
? ? cvShowImage("image",image); ?
? ? cvSaveImage("image.jpg",image); ?
? ? cvWaitKey(0); ??
? ? cvDestroyAllWindows(); ?
? ? cvReleaseImage(&image); ?
? ? cvReleaseImage(&org); ?
? ? return 0; ?
} ?
測試圖是標準的lena圖,效果圖如下


========

基于GraphCuts圖割算法的圖像分割----OpenCV代碼與實現

圖切算法是組合圖論的經典算法之一。近年來,許多學者將其應用到圖像和視頻分割中,取得了很好的效果。本文簡單介紹了圖切算法和交互式圖像分割技術,以及圖切算法在交互式圖像分割中的應用。
?
圖像分割指圖像分成各具特性的區(qū)域并提取出感興趣目標的技術和過程,它是由圖像處理到圖像分析的關鍵步驟,是一種基本的計算機視覺技術。只有在圖像分割的基礎上才能對目標進行特征提取和參數測量,使得更高層的圖像分析和理解成為可能。因此對圖像分割方


法的研究具有十分重要的意義。
?
圖像分割技術的研究已有幾十年的歷史,但至今人們并不能找到通用的方法能夠適合于所有類型的圖像。常用的圖像分割技術可劃分為四類:特征閾值或聚類、邊緣檢測、區(qū)域生長或區(qū)域提取。雖然這些方法分割灰度圖像效果較好,但用于彩色圖像的分割往往達不到理


想的效果。
?
交互式圖像分割是指,首先由用戶以某種交互手段指定圖像的部分前景與部分背景,然后算法以用戶的輸入作為分割的約束條件自動地計算出滿足約束條件下的最佳分割。典型的交互手段包括用一把畫刷在前景和背景處各畫幾筆(如[1][4]等)以及在前景的周圍畫一個


方框(如[2])等。
?
基于圖切算法的圖像分割技術是近年來國際上圖像分割領域的一個新的研究熱點。該類方法將圖像映射為賦權無向圖,把像素視作節(jié)點,利用最小切割得到圖像的最佳分割。
?
?
Graph Cut[1]算法是一種直接基于圖切算法的圖像分割技術。它僅需要在前景和背景處各畫幾筆作為輸入,算法將建立各個像素點與前景背景相似度的賦權圖,并通過求解最小切割區(qū)分前景和背景。
?
? ? ? ?Grabcut[2]算法方法的用戶交互量很少,僅僅需要指定一個包含前景的矩形,隨后用基于圖切算法在圖像中提取前景。
?
? ? ? ?Lazy Snapping[4]系統(tǒng)則是對[1]的改進。通過預計算和聚類技術,該方法提供了一個即時反饋的平臺,方便用戶進行交互分割。


文檔說明:
http://download.csdn.net/detail/wangyaninglm/8484301
?
代碼實現效果:


graphcuts代碼:
http://download.csdn.net/detail/wangyaninglm/8484243
?
ICCV'2001論文"Interactive graph cuts for optimal boundary and region segmentation of objects in N-D images"。
Graph Cut方法是基于顏色統(tǒng)計采樣的方法,因此對前背景相差較大的圖像效果較佳。
同時,比例系數lambda的調節(jié)直接影響到最終的分割效果。
?
grabcut代碼:
?
// Grabcut.cpp : 定義控制臺應用程序的入口點。 ?
// ?
??
#include "stdafx.h" ?
??
#include "opencv2/highgui/highgui.hpp" ?
#include "opencv2/imgproc/imgproc.hpp" ?
??
#include <iostream> ?
??
#include "ComputeTime.h" ?
#include "windows.h" ?
??
using namespace std; ?
using namespace cv; ?
??
static void help() ?
{ ?
? ? cout << "\nThis program demonstrates GrabCut segmentation -- select an object in a region\n" ?
? ? ? ? "and then grabcut will attempt to segment it out.\n" ?
? ? ? ? "Call:\n" ?
? ? ? ? "./grabcut <image_name>\n" ?
? ? ? ? "\nSelect a rectangular area around the object you want to segment\n" << ?
? ? ? ? "\nHot keys: \n" ?
? ? ? ? "\tESC - quit the program\n" ?
? ? ? ? "\tr - restore the original image\n" ?
? ? ? ? "\tn - next iteration\n" ?
? ? ? ? "\n" ?
? ? ? ? "\tleft mouse button - set rectangle\n" ?
? ? ? ? "\n" ?
? ? ? ? "\tCTRL+left mouse button - set GC_BGD pixels\n" ?
? ? ? ? "\tSHIFT+left mouse button - set CG_FGD pixels\n" ?
? ? ? ? "\n" ?
? ? ? ? "\tCTRL+right mouse button - set GC_PR_BGD pixels\n" ?
? ? ? ? "\tSHIFT+right mouse button - set CG_PR_FGD pixels\n" << endl; ?
} ?
??
const Scalar RED = Scalar(0,0,255); ?
const Scalar PINK = Scalar(230,130,255); ?
const Scalar BLUE = Scalar(255,0,0); ?
const Scalar LIGHTBLUE = Scalar(255,255,160); ?
const Scalar GREEN = Scalar(0,255,0); ?
??
const int BGD_KEY = CV_EVENT_FLAG_CTRLKEY; ?//Ctrl鍵 ?
const int FGD_KEY = CV_EVENT_FLAG_SHIFTKEY; //Shift鍵 ?
??
static void getBinMask( const Mat& comMask, Mat& binMask ) ?
{ ?
? ? if( comMask.empty() || comMask.type()!=CV_8UC1 ) ?
? ? ? ? CV_Error( CV_StsBadArg, "comMask is empty or has incorrect type (not CV_8UC1)" ); ?
? ? if( binMask.empty() || binMask.rows!=comMask.rows || binMask.cols!=comMask.cols ) ?
? ? ? ? binMask.create( comMask.size(), CV_8UC1 ); ?
? ? binMask = comMask & 1; ?//得到mask的最低位,實際上是只保留確定的或者有可能的前景點當做mask ?
} ?
??
class GCApplication ?
{ ?
public: ?
? ? enum{ NOT_SET = 0, IN_PROCESS = 1, SET = 2 }; ?
? ? static const int radius = 2; ?
? ? static const int thickness = -1; ?
??
? ? void reset(); ?
? ? void setImageAndWinName( const Mat& _image, const string& _winName ); ?
? ? void showImage() const; ?
? ? void mouseClick( int event, int x, int y, int flags, void* param ); ?
? ? int nextIter(); ?
? ? int getIterCount() const { return iterCount; } ?
private: ?
? ? void setRectInMask(); ?
? ? void setLblsInMask( int flags, Point p, bool isPr ); ?
??
? ? const string* winName; ?
? ? const Mat* image; ?
? ? Mat mask; ?
? ? Mat bgdModel, fgdModel; ?
??
? ? uchar rectState, lblsState, prLblsState; ?
? ? bool isInitialized; ?
??
? ? Rect rect; ?
? ? vector<Point> fgdPxls, bgdPxls, prFgdPxls, prBgdPxls; ?
? ? int iterCount; ?
}; ?
??
/*給類的變量賦值*/ ?
void GCApplication::reset() ?
{ ?
? ? if( !mask.empty() ) ?
? ? ? ? mask.setTo(Scalar::all(GC_BGD)); ?
? ? bgdPxls.clear(); fgdPxls.clear(); ?
? ? prBgdPxls.clear(); ?prFgdPxls.clear(); ?
??
? ? isInitialized = false; ?
? ? rectState = NOT_SET; ? ?//NOT_SET == 0 ?
? ? lblsState = NOT_SET; ?
? ? prLblsState = NOT_SET; ?
? ? iterCount = 0; ?
} ?
??
/*給類的成員變量賦值而已*/ ?
void GCApplication::setImageAndWinName( const Mat& _image, const string& _winName ?) ?
{ ?
? ? if( _image.empty() || _winName.empty() ) ?
? ? ? ? return; ?
? ? image = &_image; ?
? ? winName = &_winName; ?
? ? mask.create( image->size(), CV_8UC1); ?
? ? reset(); ?
} ?
??
/*顯示4個點,一個矩形和圖像內容,因為后面的步驟很多地方都要用到這個函數,所以單獨拿出來*/ ?
void GCApplication::showImage() const ?
{ ?
? ? if( image->empty() || winName->empty() ) ?
? ? ? ? return; ?
??
? ? Mat res; ?
? ? Mat binMask; ?
? ? if( !isInitialized ) ?
? ? ? ? image->copyTo( res ); ?
? ? else ?
? ? { ?
? ? ? ? getBinMask( mask, binMask ); ?
? ? ? ? image->copyTo( res, binMask ); ?//按照最低位是0還是1來復制,只保留跟前景有關的圖像,比如說可能的前景,可能的背景 ?
? ? } ?
??
? ? vector<Point>::const_iterator it; ?
? ? /*下面4句代碼是將選中的4個點用不同的顏色顯示出來*/ ?
? ? for( it = bgdPxls.begin(); it != bgdPxls.end(); ++it ) ?//迭代器可以看成是一個指針 ?
? ? ? ? circle( res, *it, radius, BLUE, thickness ); ?
? ? for( it = fgdPxls.begin(); it != fgdPxls.end(); ++it ) ?//確定的前景用紅色表示 ?
? ? ? ? circle( res, *it, radius, RED, thickness ); ?
? ? for( it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it ) ?
? ? ? ? circle( res, *it, radius, LIGHTBLUE, thickness ); ?
? ? for( it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it ) ?
? ? ? ? circle( res, *it, radius, PINK, thickness ); ?
??
? ? /*畫矩形*/ ?
? ? if( rectState == IN_PROCESS || rectState == SET ) ?
? ? ? ? rectangle( res, Point( rect.x, rect.y ), Point(rect.x + rect.width, rect.y + rect.height ), GREEN, 2); ?
??
? ? imshow( *winName, res ); ?
} ?
??
/*該步驟完成后,mask圖像中rect內部是3,外面全是0*/ ?
void GCApplication::setRectInMask() ?
{ ?
? ? assert( !mask.empty() ); ?
? ? mask.setTo( GC_BGD ); ? //GC_BGD == 0 ?
? ? rect.x = max(0, rect.x); ?
? ? rect.y = max(0, rect.y); ?
? ? rect.width = min(rect.width, image->cols-rect.x); ?
? ? rect.height = min(rect.height, image->rows-rect.y); ?
? ? (mask(rect)).setTo( Scalar(GC_PR_FGD) ); ? ?//GC_PR_FGD == 3,矩形內部,為可能的前景點 ?
} ?
??
void GCApplication::setLblsInMask( int flags, Point p, bool isPr ) ?
{ ?
? ? vector<Point> *bpxls, *fpxls; ?
? ? uchar bvalue, fvalue; ?
? ? if( !isPr ) //確定的點 ?
? ? { ?
? ? ? ? bpxls = &bgdPxls; ?
? ? ? ? fpxls = &fgdPxls; ?
? ? ? ? bvalue = GC_BGD; ? ?//0 ?
? ? ? ? fvalue = GC_FGD; ? ?//1 ?
? ? } ?
? ? else ? ?//概率點 ?
? ? { ?
? ? ? ? bpxls = &prBgdPxls; ?
? ? ? ? fpxls = &prFgdPxls; ?
? ? ? ? bvalue = GC_PR_BGD; //2 ?
? ? ? ? fvalue = GC_PR_FGD; //3 ?
? ? } ?
? ? if( flags & BGD_KEY ) ?
? ? { ?
? ? ? ? bpxls->push_back(p); ?
? ? ? ? circle( mask, p, radius, bvalue, thickness ); ? //該點處為2 ?
? ? } ?
? ? if( flags & FGD_KEY ) ?
? ? { ?
? ? ? ? fpxls->push_back(p); ?
? ? ? ? circle( mask, p, radius, fvalue, thickness ); ? //該點處為3 ?
? ? } ?
} ?
??
/*鼠標響應函數,參數flags為CV_EVENT_FLAG的組合*/ ?
void GCApplication::mouseClick( int event, int x, int y, int flags, void* ) ?
{ ?
? ? // TODO add bad args check ?
? ? switch( event ) ?
? ? { ?
? ? case CV_EVENT_LBUTTONDOWN: // set rect or GC_BGD(GC_FGD) labels ?
? ? ? ? { ?
? ? ? ? ? ? bool isb = (flags & BGD_KEY) != 0, ?
? ? ? ? ? ? ? ? isf = (flags & FGD_KEY) != 0; ?
? ? ? ? ? ? if( rectState == NOT_SET && !isb && !isf )//只有左鍵按下時 ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? rectState = IN_PROCESS; //表示正在畫矩形 ?
? ? ? ? ? ? ? ? rect = Rect( x, y, 1, 1 ); ?
? ? ? ? ? ? } ?
? ? ? ? ? ? if ( (isb || isf) && rectState == SET ) //按下了alt鍵或者shift鍵,且畫好了矩形,表示正在畫前景背景點 ?
? ? ? ? ? ? ? ? lblsState = IN_PROCESS; ?
? ? ? ? } ?
? ? ? ? break; ?
? ? case CV_EVENT_RBUTTONDOWN: // set GC_PR_BGD(GC_PR_FGD) labels ?
? ? ? ? { ?
? ? ? ? ? ? bool isb = (flags & BGD_KEY) != 0, ?
? ? ? ? ? ? ? ? isf = (flags & FGD_KEY) != 0; ?
? ? ? ? ? ? if ( (isb || isf) && rectState == SET ) //正在畫可能的前景背景點 ?
? ? ? ? ? ? ? ? prLblsState = IN_PROCESS; ?
? ? ? ? } ?
? ? ? ? break; ?
? ? case CV_EVENT_LBUTTONUP: ?
? ? ? ? if( rectState == IN_PROCESS ) ?
? ? ? ? { ?
? ? ? ? ? ? rect = Rect( Point(rect.x, rect.y), Point(x,y) ); ? //矩形結束 ?
? ? ? ? ? ? rectState = SET; ?
? ? ? ? ? ? setRectInMask(); ?
? ? ? ? ? ? assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() ); ?
? ? ? ? ? ? showImage(); ?
? ? ? ? } ?
? ? ? ? if( lblsState == IN_PROCESS ) ? //已畫了前后景點 ?
? ? ? ? { ?
? ? ? ? ? ? setLblsInMask(flags, Point(x,y), false); ? ?//畫出前景點 ?
? ? ? ? ? ? lblsState = SET; ?
? ? ? ? ? ? showImage(); ?
? ? ? ? } ?
? ? ? ? break; ?
? ? case CV_EVENT_RBUTTONUP: ?
? ? ? ? if( prLblsState == IN_PROCESS ) ?
? ? ? ? { ?
? ? ? ? ? ? setLblsInMask(flags, Point(x,y), true); //畫出背景點 ?
? ? ? ? ? ? prLblsState = SET; ?
? ? ? ? ? ? showImage(); ?
? ? ? ? } ?
? ? ? ? break; ?
? ? case CV_EVENT_MOUSEMOVE: ?
? ? ? ? if( rectState == IN_PROCESS ) ?
? ? ? ? { ?
? ? ? ? ? ? rect = Rect( Point(rect.x, rect.y), Point(x,y) ); ?
? ? ? ? ? ? assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() ); ?
? ? ? ? ? ? showImage(); ? ?//不斷的顯示圖片 ?
? ? ? ? } ?
? ? ? ? else if( lblsState == IN_PROCESS ) ?
? ? ? ? { ?
? ? ? ? ? ? setLblsInMask(flags, Point(x,y), false); ?
? ? ? ? ? ? showImage(); ?
? ? ? ? } ?
? ? ? ? else if( prLblsState == IN_PROCESS ) ?
? ? ? ? { ?
? ? ? ? ? ? setLblsInMask(flags, Point(x,y), true); ?
? ? ? ? ? ? showImage(); ?
? ? ? ? } ?
? ? ? ? break; ?
? ? } ?
} ?
??
/*該函數進行grabcut算法,并且返回算法運行迭代的次數*/ ?
int GCApplication::nextIter() ?
{ ?
? ? if( isInitialized ) ?
? ? ? ? //使用grab算法進行一次迭代,參數2為mask,里面存的mask位是:矩形內部除掉那些可能是背景或者已經確定是背景后的所有的點,且mask同時也為輸出 ?
? ? ? ? //保存的是分割后的前景圖像 ?
? ? ? ? grabCut( *image, mask, rect, bgdModel, fgdModel, 1 ); ?
? ? else ?
? ? { ?
? ? ? ? if( rectState != SET ) ?
? ? ? ? ? ? return iterCount; ?
??
? ? ? ? if( lblsState == SET || prLblsState == SET ) ?
? ? ? ? ? ? grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK ); ?
? ? ? ? else ?
? ? ? ? ? ? grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT ); ?
??
? ? ? ? isInitialized = true; ?
? ? } ?
? ? iterCount++; ?
??
? ? bgdPxls.clear(); fgdPxls.clear(); ?
? ? prBgdPxls.clear(); prFgdPxls.clear(); ?
??
? ? return iterCount; ?
} ?
??
GCApplication gcapp; ?
??
static void on_mouse( int event, int x, int y, int flags, void* param ) ?
{ ?
? ? gcapp.mouseClick( event, x, y, flags, param ); ?
} ?
??
int main( int argc, char** argv ) ?
{ ?
? ? string filename; ?
? ? cout<<" Grabcuts ! \n"; ?
? ? cout<<"input image name: ?"<<endl; ?
? ? cin>>filename; ?
??
? ? ??
? ? Mat image = imread( filename, 1 ); ?
? ? if( image.empty() ) ?
? ? { ?
? ? ? ? cout << "\n Durn, couldn't read image filename " << filename << endl; ?
? ? ? ? return 1; ?
? ? } ?
??
? ? help(); ?
??
? ? const string winName = "image"; ?
? ? cvNamedWindow( winName.c_str(), CV_WINDOW_AUTOSIZE ); ?
? ? cvSetMouseCallback( winName.c_str(), on_mouse, 0 ); ?
??
? ? gcapp.setImageAndWinName( image, winName ); ?
? ? gcapp.showImage(); ?
??
? ? for(;;) ?
? ? { ?
? ? ? ? int c = cvWaitKey(0); ?
? ? ? ? switch( (char) c ) ?
? ? ? ? { ?
? ? ? ? case '\x1b': ?
? ? ? ? ? ? cout << "Exiting ..." << endl; ?
? ? ? ? ? ? goto exit_main; ?
? ? ? ? case 'r': ?
? ? ? ? ? ? cout << endl; ?
? ? ? ? ? ? gcapp.reset(); ?
? ? ? ? ? ? gcapp.showImage(); ?
? ? ? ? ? ? break; ?
? ? ? ? case 'n': ?
? ? ? ? ? ? ComputeTime ct ; ?
? ? ? ? ? ? ct.Begin(); ?
? ? ? ? ? ? ??
? ? ? ? ? ? int iterCount = gcapp.getIterCount(); ?
? ? ? ? ? ? cout << "<" << iterCount << "... "; ?
? ? ? ? ? ? int newIterCount = gcapp.nextIter(); ?
? ? ? ? ? ? if( newIterCount > iterCount ) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? gcapp.showImage(); ?
? ? ? ? ? ? ? ? cout << iterCount << ">" << endl; ?
? ? ? ? ? ? ? ? cout<<"運行時間: ?"<<ct.End()<<endl; ?
? ? ? ? ? ? } ?
? ? ? ? ? ? else ?
? ? ? ? ? ? ? ? cout << "rect must be determined>" << endl; ?
? ? ? ? ? ? break; ?
? ? ? ? } ?
? ? } ?
??
exit_main: ?
? ? cvDestroyWindow( winName.c_str() ); ?
? ? return 0; ?
} ?


?lazy snapping代碼實現:
?
// LazySnapping.cpp : 定義控制臺應用程序的入口點。 ?
// ?
/* author: zhijie Lee?
?* home page: lzhj.me?
?* 2012-02-06?
?*/ ?
#include "stdafx.h" ?
#include <cv.h> ?
#include <highgui.h> ?
#include "graph.h" ?
#include <vector> ?
#include <iostream> ?
#include <cmath> ?
#include <string> ?
??
using namespace std; ?
??
typedef Graph<float,float,float> GraphType; ?
??
class LasySnapping ?
{ ?
? ? ??
public : ?
? ? LasySnapping(); ?
??
? ? ~LasySnapping() ?
? ? { ??
? ? ? ? if(graph) ?
? ? ? ? { ?
? ? ? ? ? ? delete graph; ?
? ? ? ? } ?
? ? }; ?
private : ?
? ? vector<CvPoint> forePts; ?
? ? vector<CvPoint> backPts; ?
? ? IplImage* image; ?
? ? // average color of foreground points ?
? ? unsigned char avgForeColor[3]; ?
? ? // average color of background points ?
? ? unsigned char avgBackColor[3]; ?
public : ?
? ? void setImage(IplImage* image) ?
? ? { ?
? ? ? ? this->image = image; ?
? ? ? ? graph = new GraphType(image->width*image->height,image->width*image->height*2); ?
? ? } ?
? ? // include-pen locus ?
? ? void setForegroundPoints(vector<CvPoint> pts) ?
? ? { ?
? ? ? ? forePts.clear(); ?
? ? ? ? for(int i =0; i< pts.size(); i++) ?
? ? ? ? { ?
? ? ? ? ? ? if(!isPtInVector(pts[i],forePts)) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? forePts.push_back(pts[i]); ?
? ? ? ? ? ? } ?
? ? ? ? } ?
? ? ? ? if(forePts.size() == 0) ?
? ? ? ? { ?
? ? ? ? ? ? return; ?
? ? ? ? } ?
? ? ? ? int sum[3] = {0}; ?
? ? ? ? for(int i =0; i < forePts.size(); i++) ?
? ? ? ? { ?
? ? ? ? ? ? unsigned char* p = (unsigned char*)image->imageData + forePts[i].x * 3 ??
? ? ? ? ? ? ? ? + forePts[i].y*image->widthStep; ?
? ? ? ? ? ? sum[0] += p[0]; ?
? ? ? ? ? ? sum[1] += p[1]; ?
? ? ? ? ? ? sum[2] += p[2]; ? ? ? ? ? ? ?
? ? ? ? } ?
? ? ? ? cout<<sum[0]<<" " <<forePts.size()<<endl; ?
? ? ? ? avgForeColor[0] = sum[0]/forePts.size(); ?
? ? ? ? avgForeColor[1] = sum[1]/forePts.size(); ?
? ? ? ? avgForeColor[2] = sum[2]/forePts.size(); ?
? ? } ?
? ? // exclude-pen locus ?
? ? void setBackgroundPoints(vector<CvPoint> pts) ?
? ? { ?
? ? ? ? backPts.clear(); ?
? ? ? ? for(int i =0; i< pts.size(); i++) ?
? ? ? ? { ?
? ? ? ? ? ? if(!isPtInVector(pts[i],backPts)) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? backPts.push_back(pts[i]); ?
? ? ? ? ? ? } ?
? ? ? ? } ?
? ? ? ? if(backPts.size() == 0) ?
? ? ? ? { ?
? ? ? ? ? ? return; ?
? ? ? ? } ?
? ? ? ? int sum[3] = {0}; ?
? ? ? ? for(int i =0; i < backPts.size(); i++) ?
? ? ? ? { ?
? ? ? ? ? ? unsigned char* p = (unsigned char*)image->imageData + backPts[i].x * 3 + ??
? ? ? ? ? ? ? ? backPts[i].y*image->widthStep; ?
? ? ? ? ? ? sum[0] += p[0]; ?
? ? ? ? ? ? sum[1] += p[1]; ?
? ? ? ? ? ? sum[2] += p[2]; ? ? ? ? ? ? ?
? ? ? ? } ?
? ? ? ? avgBackColor[0] = sum[0]/backPts.size(); ?
? ? ? ? avgBackColor[1] = sum[1]/backPts.size(); ?
? ? ? ? avgBackColor[2] = sum[2]/backPts.size(); ?
? ? } ?
??
? ? // return maxflow of graph ?
? ? int runMaxflow(); ?
? ? // get result, a grayscale mast image indicating forground by 255 and background by 0 ?
? ? IplImage* getImageMask(); ?
??
private : ?
??
? ? float colorDistance(unsigned char* color1, unsigned char* color2); ?
? ? float minDistance(unsigned char* color, vector<CvPoint> points); ?
? ? bool isPtInVector(CvPoint pt, vector<CvPoint> points); ?
? ? void getE1(unsigned char* color,float* energy); ?
? ? float getE2(unsigned char* color1,unsigned char* color2); ?
? ? ??
? ? GraphType *graph; ? ? ?
}; ?
??
LasySnapping::LasySnapping() ?
{ ?
? ? graph = NULL; ?
? ? avgForeColor[0] = 0; ?
? ? avgForeColor[1] = 0; ?
? ? avgForeColor[2] = 0; ?
??
? ? avgBackColor[0] = 0; ?
? ? avgBackColor[1] = 0; ?
? ? avgBackColor[2] = 0; ?
??
? ? ??
} ?
?
float LasySnapping::colorDistance(unsigned char* color1, unsigned char* color2) ?
{ ?
? ? ??
? ? return sqrt(((float)color1[0]-(float)color2[0])*((float)color1[0]-(float)color2[0])+ ?
? ? ? ? ((float)color1[1]-(float)color2[1])*((float)color1[1]-(float)color2[1])+ ?
? ? ? ? ((float)color1[2]-(float)color2[2])*((float)color1[2]-(float)color2[2])); ? ? ?
} ?
??
float LasySnapping::minDistance(unsigned char* color, vector<CvPoint> points) ?
{ ?
? ? float distance = -1; ?
? ? for(int i =0 ; i < points.size(); i++) ?
? ? { ?
? ? ? ? unsigned char* p = (unsigned char*)image->imageData + points[i].y * image->widthStep + ??
? ? ? ? ? ? points[i].x * image->nChannels; ?
? ? ? ? float d = colorDistance(p,color); ?
? ? ? ? if(distance < 0 ) ?
? ? ? ? { ?
? ? ? ? ? ? distance = d; ?
? ? ? ? } ?
? ? ? ? else ?
? ? ? ? { ?
? ? ? ? ? ? if(distance > d) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? distance = d; ?
? ? ? ? ? ? } ?
? ? ? ? } ?
? ? } ?
??
? ? return distance; ?
} ?
??
bool LasySnapping::isPtInVector(CvPoint pt, vector<CvPoint> points) ?
{ ?
? ? for(int i =0 ; i < points.size(); i++) ?
? ? { ?
? ? ? ? if(pt.x == points[i].x && pt.y == points[i].y) ?
? ? ? ? { ?
? ? ? ? ? ? return true; ?
? ? ? ? } ?
? ? } ?
? ? return false; ?
} ?
void LasySnapping::getE1(unsigned char* color,float* energy) ?
{ ?
? ? // average distance ?
? ? float df = colorDistance(color,avgForeColor); ?
? ? float db = colorDistance(color,avgBackColor); ?
? ? // min distance from background points and forground points ?
? ? // float df = minDistance(color,forePts); ?
? ? // float db = minDistance(color,backPts); ?
? ? energy[0] = df/(db+df); ?
? ? energy[1] = db/(db+df); ?
} ?
??
float LasySnapping::getE2(unsigned char* color1,unsigned char* color2) ?
{ ?
? ? const float EPSILON = 0.01; ?
? ? float lambda = 100; ?
? ? return lambda/(EPSILON+ ?
? ? ? ? (color1[0]-color2[0])*(color1[0]-color2[0])+ ?
? ? ? ? (color1[1]-color2[1])*(color1[1]-color2[1])+ ?
? ? ? ? (color1[2]-color2[2])*(color1[2]-color2[2])); ?
} ?
??
int LasySnapping::runMaxflow() ?
{ ? ??
? ? const float INFINNITE_MAX = 1e10; ?
? ? int indexPt = 0; ?
? ? for(int h = 0; h < image->height; h ++) ?
? ? { ?
? ? ? ? unsigned char* p = (unsigned char*)image->imageData + h *image->widthStep; ?
? ? ? ? for(int w = 0; w < image->width; w ++) ?
? ? ? ? { ?
? ? ? ? ? ? // calculate energe E1 ?
? ? ? ? ? ? float e1[2]={0}; ?
? ? ? ? ? ? if(isPtInVector(cvPoint(w,h),forePts)) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? e1[0] =0; ?
? ? ? ? ? ? ? ? e1[1] = INFINNITE_MAX; ?
? ? ? ? ? ? } ?
? ? ? ? ? ? else if ?
? ? ? ? ? ? ? ? (isPtInVector(cvPoint(w,h),backPts)) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? e1[0] = INFINNITE_MAX; ?
? ? ? ? ? ? ? ? e1[1] = 0; ?
? ? ? ? ? ? } ?
? ? ? ? ? ? else ??
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? getE1(p,e1); ?
? ? ? ? ? ? } ?
??
? ? ? ? ? ? // add node ?
? ? ? ? ? ? graph->add_node(); ?
? ? ? ? ? ? graph->add_tweights(indexPt, e1[0],e1[1]); ?
??
? ? ? ? ? ? // add edge, 4-connect ?
? ? ? ? ? ? if(h > 0 && w > 0) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? float e2 = getE2(p,p-3); ?
? ? ? ? ? ? ? ? graph->add_edge(indexPt,indexPt-1,e2,e2); ?
? ? ? ? ? ? ? ? e2 = getE2(p,p-image->widthStep); ?
? ? ? ? ? ? ? ? graph->add_edge(indexPt,indexPt-image->width,e2,e2); ?
? ? ? ? ? ? } ?
? ? ? ? ? ? ??
? ? ? ? ? ? p+= 3; ?
? ? ? ? ? ? indexPt ++; ? ? ? ? ? ? ?
? ? ? ? } ?
? ? } ?
? ? ??
? ? return graph->maxflow(); ?
} ?
??
IplImage* LasySnapping::getImageMask() ?
{ ?
? ? IplImage* gray = cvCreateImage(cvGetSize(image),8,1); ??
? ? int indexPt =0; ?
? ? for(int h =0; h < image->height; h++) ?
? ? { ?
? ? ? ? unsigned char* p = (unsigned char*)gray->imageData + h*gray->widthStep; ?
? ? ? ? for(int w =0 ;w <image->width; w++) ?
? ? ? ? { ?
? ? ? ? ? ? if (graph->what_segment(indexPt) == GraphType::SOURCE) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? *p = 0; ?
? ? ? ? ? ? } ?
? ? ? ? ? ? else ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? *p = 255; ?
? ? ? ? ? ? } ?
??
? ? ? ? ? ? p++; ?
? ? ? ? ? ? indexPt ++; ?
? ? ? ? } ?
? ? } ?
? ? return gray; ?
} ?
??
// global ?
vector<CvPoint> forePts; ?
vector<CvPoint> backPts; ?
int currentMode = 0;// indicate foreground or background, foreground as default ?
CvScalar paintColor[2] = {CV_RGB(0,0,255),CV_RGB(255,0,0)}; ?
??
IplImage* image = NULL; ?
char* winName = "lazySnapping"; ?
IplImage* imageDraw = NULL; ?
const int SCALE = 4; ?
??
void on_mouse( int event, int x, int y, int flags, void* ) ?
{ ? ? ?
? ? if( event == CV_EVENT_LBUTTONUP ) ?
? ? { ?
? ? ? ? if(backPts.size() == 0 && forePts.size() == 0) ?
? ? ? ? { ?
? ? ? ? ? ? return; ?
? ? ? ? } ?
? ? ? ? LasySnapping ls; ?
? ? ? ? IplImage* imageLS = cvCreateImage(cvSize(image->width/SCALE,image->height/SCALE), ?
? ? ? ? ? ? 8,3); ?
? ? ? ? cvResize(image,imageLS); ?
? ? ? ? ls.setImage(imageLS); ?
? ? ? ? ls.setBackgroundPoints(backPts); ?
? ? ? ? ls.setForegroundPoints(forePts); ?
? ? ? ? ls.runMaxflow(); ?
? ? ? ? IplImage* mask = ls.getImageMask(); ?
? ? ? ? IplImage* gray = cvCreateImage(cvGetSize(image),8,1); ?
? ? ? ? cvResize(mask,gray); ?
? ? ? ? // edge ?
? ? ? ? cvCanny(gray,gray,50,150,3); ?
? ? ? ? ??
? ? ? ? IplImage* showImg = cvCloneImage(imageDraw); ?
? ? ? ? for(int h =0; h < image->height; h ++) ?
? ? ? ? { ?
? ? ? ? ? ? unsigned char* pgray = (unsigned char*)gray->imageData + gray->widthStep*h; ?
? ? ? ? ? ? unsigned char* pimage = (unsigned char*)showImg->imageData + showImg->widthStep*h; ?
? ? ? ? ? ? for(int width ?=0; width < image->width; width++) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? if(*pgray++ != 0 ) ?
? ? ? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? ? ? pimage[0] = 0; ?
? ? ? ? ? ? ? ? ? ? pimage[1] = 255; ?
? ? ? ? ? ? ? ? ? ? pimage[2] = 0; ?
? ? ? ? ? ? ? ? } ?
? ? ? ? ? ? ? ? pimage+=3; ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? } ?
? ? ? ? } ?
? ? ? ? cvSaveImage("t.bmp",showImg); ?
? ? ? ? cvShowImage(winName,showImg); ?
? ? ? ? cvReleaseImage(&imageLS); ?
? ? ? ? cvReleaseImage(&mask); ?
? ? ? ? cvReleaseImage(&showImg); ?
? ? ? ? cvReleaseImage(&gray); ?
? ? } ?
? ? else if( event == CV_EVENT_LBUTTONDOWN ) ?
? ? { ?
??
? ? } ?
? ? else if( event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON)) ?
? ? { ?
? ? ? ? CvPoint pt = cvPoint(x,y); ?
? ? ? ? if(currentMode == 0) ?
? ? ? ? {//foreground ?
? ? ? ? ? ? forePts.push_back(cvPoint(x/SCALE,y/SCALE)); ?
? ? ? ? } ?
? ? ? ? else ?
? ? ? ? {//background ?
? ? ? ? ? ? backPts.push_back(cvPoint(x/SCALE,y/SCALE)); ?
? ? ? ? } ?
? ? ? ? cvCircle(imageDraw,pt,2,paintColor[currentMode]); ?
? ? ? ? cvShowImage(winName,imageDraw); ?
? ? } ?
} ?
int main(int argc, char** argv) ?
{ ? ??
? ? //if(argc != 2) ?
? ? //{ ?
? ? ?// ? cout<<"command : lazysnapping inputImage"<<endl; ?
? ? ?// ? return 0; ?
? ?// } ?
??
? ? string image_name; ?
? ? cout<<"input image name: "<<endl; ?
? ? cin>>image_name; ?
??
? ? cvNamedWindow(winName,1); ?
? ? cvSetMouseCallback( winName, on_mouse, 0); ?
? ? ??
? ? image = cvLoadImage(image_name.c_str(),CV_LOAD_IMAGE_COLOR); ?
? ? imageDraw = cvCloneImage(image); ?
? ? cvShowImage(winName, image); ?
? ? for(;;) ?
? ? { ?
? ? ? ? int c = cvWaitKey(0); ?
? ? ? ? c = (char)c; ?
? ? ? ? if(c == 27) ?
? ? ? ? {//exit ?
? ? ? ? ? ? break; ?
? ? ? ? } ?
? ? ? ? else if(c == 'r') ?
? ? ? ? {//reset ?
? ? ? ? ? ? image = cvLoadImage(image_name.c_str(),CV_LOAD_IMAGE_COLOR); ?
? ? ? ? ? ? imageDraw = cvCloneImage(image); ?
? ? ? ? ? ? forePts.clear(); ?
? ? ? ? ? ? backPts.clear(); ?
? ? ? ? ? ? currentMode = 0; ?
? ? ? ? ? ? cvShowImage(winName, image); ?
? ? ? ? } ?
? ? ? ? else if(c == 'b') ?
? ? ? ? {//change to background selection ?
? ? ? ? ? ? currentMode = 1; ?
? ? ? ? }else if(c == 'f') ?
? ? ? ? {//change to foreground selection ?
? ? ? ? ? ? currentMode = 0; ?
? ? ? ? } ?
? ? } ?
? ? cvReleaseImage(&image); ?
? ? cvReleaseImage(&imageDraw); ?
? ? return 0; ?
} ?


?
=======

?OpenCV由漢字生成圖片(透明)



? ? ? ? 今天聽說很多同志們寫畢業(yè)論文重復率過高的問題,大牛說用圖片代替字就行了,我就想用OpenCV實現一下看看能不能搞,果不其然還是可以的!!!主要的難點在于普通格式的圖片背景不透明,需要使用背景透明的png格式圖片就行。
?
主要思想和步驟:
?
1.首先配置好FreeType與OpenCV,添加編譯好的lib,與include目錄和CvxText.h和CvxText.cpp就行了,參考[1]
?
2.說一下思路,主要就是OpenCV版本的問題造成有的函數用的IplImage,而函數
//設置原圖像文字
?text.putText(ImageSrc, msg, cvPoint(1, size_zi), color);
只能接受IplImage格式的參數,所以保存成png,就比較麻煩了。
?
png格式的圖片是4個通道,按照BGRA來放置,alaph就是透明通道。我們的思路就是按照原來直接給圖片上疊加文字的辦法,新建與文字大小相同的圖片,然后二值化,按照二值模版生成新的png文字圖片,有字的地方添上顏色,沒字的地方設置為透明。
?
當然二值化算法網上搜了一個自適應閥值的算法效果非常好
?
?
3.生成了透明的文字圖片,粘貼到論文里面,估計查詢重復的系統(tǒng)再牛逼也是無能為力了。后序有空做一些程序界面跟字符分割的東西,可以直接賣錢了。
當然,字體跟大小,上下邊距都是可以設置的,后序再往程序里面寫。
?
?
實現效果:
?
?
主要代碼:


// AddChinese.cpp : 定義控制臺應用程序的入口點。 ?
// ?
??
#include "stdafx.h" ?
??
??
??
#include <opencv2/core/core.hpp> ? ?
#include <opencv2/highgui/highgui.hpp> ?
#include "CvxText.h" ?
??
#pragma comment(lib,"freetype255d.lib") ?
#pragma comment(lib,"opencv_core2410d.lib") ? ? ? ? ? ? ? ? ?
#pragma comment(lib,"opencv_highgui2410d.lib") ? ? ? ? ? ? ? ? ?
#pragma comment(lib,"opencv_imgproc2410d.lib") ? ??
??
using namespace std; ?
using namespace cv; ?
??
#define ROW_BLOCK 2 ?
#define COLUMN_Block 2 ?
??
// writePng.cpp : 定義控制臺應用程序的入口點。 ?
// ?
??
??
int run_test_png(Mat &mat,string image_name) ?
{ ?
??
??
? ? /*采用自己設置的參數來保存圖片*/ ?
? ? //Mat mat(480, 640, CV_8UC4); ?
? ? //createAlphaMat(mat); ?
? ? vector<int> compression_params; ?
? ? compression_params.push_back(CV_IMWRITE_PNG_COMPRESSION); ?
? ? compression_params.push_back(9); ? ?//png格式下,默認的參數為3. ?
? ? try ??
? ? { ?
? ? ? ? imwrite(image_name, mat, compression_params); ?
? ? } ?
? ? catch (runtime_error& ex) ??
? ? { ?
? ? ? ? fprintf(stderr, "Exception converting image to PNG format: %s\n", ex.what()); ?
? ? ? ? return 1; ?
? ? } ?
? ? fprintf(stdout, "Saved PNG file with alpha data.\n"); ?
??
? ? waitKey(0); ?
? ? return 0; ?
} ?
??
int coloured(Mat &template_src, Mat &mat_png, CvScalar color) ?
{ ?
??
? ? for (int i = 0; i < template_src.rows; ++i) ??
? ? { ?
? ? ? ? for (int j = 0; j < template_src.cols; ++j) ??
? ? ? ? { ?
? ? ? ? ? ? Vec4b& bgra = mat_png.at<Vec4b>(i, j); ?
? ? ? ? ? ? //int temp = template_src.at<uchar>(i,j); ?
? ? ? ? ? ? if (template_src.at<uchar>(i,j)== 0) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? bgra[0] = color.val[0]; ? ?//b通道 ?
? ? ? ? ? ? ? ? bgra[1] = color.val[1]; ? ? //g通道 ?
? ? ? ? ? ? ? ? bgra[2] = color.val[2]; ? ? //r通道 ?
? ? ? ? ? ? ? ? bgra[3] = 255;//alpha通道全部設置為透明完全透明為0,否則為255 ?
? ? ? ? ? ? } ?
? ? ? ? ? ? else ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? bgra[3] = 0;//alpha通道全部設置為透明完全透明為0,否則為255 ?
? ? ? ? ? ? } ?
? ? ? ? ? ? ??
? ? ? ? ? ? ??
? ? ? ? ? ? ??
? ? ? ? } ?
? ? } ?
??
? ? return 0; ?
} ?
??
void ImageBinarization(IplImage *src) ?
{ ? /*對灰度圖像二值化,自適應門限threshold*/ ?
? ? int i,j,width,height,step,chanel,threshold; ?
? ? /*size是圖像尺寸,svg是灰度直方圖均值,va是方差*/ ?
? ? float size,avg,va,maxVa,p,a,s; ?
? ? unsigned char *dataSrc; ?
? ? float histogram[256]; ?
??
? ? width = src->width; ?
? ? height = src->height; ?
? ? dataSrc = (unsigned char *)src->imageData; ?
? ? step = src->widthStep/sizeof(char); ?
? ? chanel = src->nChannels; ?
? ? /*計算直方圖并歸一化histogram*/ ?
? ? for(i=0; i<256; i++) ?
? ? ? ? histogram[i] = 0; ?
? ? for(i=0; i<height; i++) ?
? ? ? ? for(j=0; j<width*chanel; j++) ?
? ? ? ? { ?
? ? ? ? ? ? histogram[dataSrc[i*step+j]-'0'+48]++; ?
? ? ? ? } ?
? ? ? ? size = width * height; ?
? ? ? ? for(i=0; i<256; i++) ?
? ? ? ? ? ? histogram[i] /=size; ?
? ? ? ? /*計算灰度直方圖中值和方差*/ ?
? ? ? ? avg = 0; ?
? ? ? ? for(i=0; i<256; i++) ?
? ? ? ? ? ? avg += i*histogram[i]; ?
? ? ? ? va = 0; ?
? ? ? ? for(i=0; i<256; i++) ?
? ? ? ? ? ? va += fabs(i*i*histogram[i]-avg*avg); ?
? ? ? ? /*利用加權最大方差求門限*/ ?
? ? ? ? threshold = 20; ?
? ? ? ? maxVa = 0; ?
? ? ? ? p = a = s = 0; ?
? ? ? ? for(i=0; i<256; i++) ?
? ? ? ? { ?
? ? ? ? ? ? p += histogram[i]; ?
? ? ? ? ? ? a += i*histogram[i]; ?
? ? ? ? ? ? s = (avg*p-a)*(avg*p-a)/p/(1-p); ?
? ? ? ? ? ? if(s > maxVa) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? threshold = i; ?
? ? ? ? ? ? ? ? maxVa = s; ?
? ? ? ? ? ? } ?
? ? ? ? } ?
? ? ? ? /*二值化*/ ?
? ? ? ? for(i=0; i<height; i++) ?
? ? ? ? ? ? for(j=0; j<width*chanel; j++) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? if(dataSrc[i*step+j] > threshold) ?
? ? ? ? ? ? ? ? ? ? dataSrc[i*step+j] = 255; ?
? ? ? ? ? ? ? ? else ?
? ? ? ? ? ? ? ? ? ? dataSrc[i*step+j] = 0; ?
? ? ? ? ? ? } ?
} ?
??
Mat binaryzation(Mat &src) ?
{ ?
? ? Mat des_gray(src.size(),CV_8UC1); ?
??
? ? cvtColor(src,des_gray,CV_BGR2GRAY); ?
? ? ??
? ? //Mat bin_mat(); ?
? ? IplImage temp(des_gray); ?
? ? ImageBinarization(&temp); ?
??
??
? ? //threshold(des_gray,des_gray,150,255,THRESH_BINARY); ?
? ? imshow("二值圖像",des_gray); ?
? ? return des_gray; ?
} ?
??
int generate_chinese(const int size_zi, const char *msg ,int number,CvScalar color) ?
{ ?
? ? //int size_zi = 50;//字體大小 ?
? ? CvSize czSize; ?//目標圖像尺寸 ?
? ? float p = 0.5; ?
? ? CvScalar fsize; ?
??
??
? ? //讀取TTF字體文件 ?
? ? CvxText text("simhei.ttf"); ? ? ??
??
? ? //設置字體屬性 字體大小/空白比例/間隔比例/旋轉角度 ?
? ? fsize = cvScalar(size_zi, 1, 0.1, 0); ?
? ? text.setFont(NULL, &fsize, NULL, &p); ? ? ? ?
??
? ? czSize.width = size_zi*number; ?
? ? czSize.height = size_zi; ?
? ? //加載原圖像 ?
? ? IplImage* ImageSrc = cvCreateImage(czSize,IPL_DEPTH_8U,3);//cvLoadImage(Imagename, CV_LOAD_IMAGE_UNCHANGED); ?
? ? //Mat image(ImageSrc); ?
? ? //createAlphaMat(image); ?
? ? //ImageSrc = ? ?
??
? ? //IplImage temp(image); ??
? ? //ImageSrc = &temp; ?
??
? ? //設置原圖像文字 ?
? ? text.putText(ImageSrc, msg, cvPoint(1, size_zi), color); ??
??
? ? //顯示原圖像 ?
? ? cvShowImage("原圖", ImageSrc); ?
??
??
? ? string hanzi = msg; ?
? ? hanzi = hanzi + ".png"; ?
??
? ? Mat chinese(ImageSrc,true); ?
? ? Mat gray = binaryzation(chinese); ?
??
? ? imwrite("chinese_gray.jpg",gray); ?
??
? ? Mat mat_png(chinese.size(),CV_8UC4); ?
? ? coloured(gray,mat_png,color); ?
? ? run_test_png(mat_png,hanzi); ?
? ? // ?
? ? cvSaveImage("hanzi.jpg",reDstImage); ?
? ? //run_test_png(chinese,hanzi); ?
? ? //等待按鍵事件 ?
? ? cvWaitKey(); ?
? ? return 0; ?
} ?
??
int main() ?
{ ?
? ? CvScalar color = CV_RGB(0,0,0); ?
? ? int size = 200; ?
? ? const char* msg = "你好a";//暫時一行字不要太長 ?
??
? ? int number = 3;//字符個數 ?
??
? ? generate_chinese(size,msg,number,color); ?
? ? ??
??
? ? return 0; ?
} ?


?
完整工程下載:
http://download.csdn.net/detail/wangyaninglm/8486521
?
參考文獻:
?
http://blog.csdn.net/fengbingchun/article/details/8029337
http://www.oschina.net/code/snippet_1447359_36028
http://blog.csdn.net/hustspy1990/article/details/6301592
========

總結

以上是生活随笔為你收集整理的opencv图像处理总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

韩国av一区二区三区在线观看 | 天天综合网 天天综合色 | 欧美高清视频不卡网 | 973理论片235影院9 | 九九在线免费视频 | 久久黄色成人 | 久久夜夜夜 | 国产免费成人av | 国产一区二区高清不卡 | 中文字幕888 | 久久精品二区 | 久久美女免费视频 | 一区中文字幕电影 | 日韩综合精品 | 免费观看一级视频 | 精品女同一区二区三区在线观看 | 99久热在线精品 | 国产视频网站在线观看 | 蜜桃av观看 | 免费福利视频网站 | 日韩三级在线观看 | 国产精品久久久久久吹潮天美传媒 | 国产欧美精品一区二区三区 | 国产精品一区二区三区在线播放 | 在线电影日韩 | 成人毛片一区 | 久久免费99 | 色全色在线资源网 | 99精品视频免费在线观看 | 欧美日韩久久不卡 | 国产精品一区二区在线观看 | 国产一区二区三区高清播放 | 国产一区二区三精品久久久无广告 | 久久精品国产免费看久久精品 | 日韩欧美69| 激情网综合| 九色精品免费永久在线 | 亚洲人成人99网站 | 美女免费视频一区 | 国产又黄又爽无遮挡 | 中文字幕av一区二区三区四区 | av电影在线观看完整版一区二区 | 色黄久久久久久 | 国产午夜三级一二三区 | 国产小视频你懂的在线 | 久久优| 少妇性bbb搡bbb爽爽爽欧美 | 国产成人高清 | 欧美国产精品一区二区 | 天天干天天搞天天射 | 四川妇女搡bbbb搡bbbb搡 | 国产在线观看免费 | 在线之家免费在线观看电影 | 久久夜av | 99c视频高清免费观看 | 亚洲第一中文网 | 久久久久久网址 | 特级西西444www大精品视频免费看 | 91精品视频网站 | 久草在线视频免赞 | 区一区二在线 | 国产一区免费看 | 色五月色开心色婷婷色丁香 | 99久久er热在这里只有精品15 | 国产视频不卡一区 | 九色91av | 久久久久久国产一区二区三区 | 国产精品18久久久久vr手机版特色 | 成人久久精品视频 | 伊人色综合久久天天网 | 日韩二区三区在线观看 | 久久久久久国产精品久久 | 三上悠亚一区二区在线观看 | 国产又黄又硬又爽 | 久久综合九色综合久99 | 精品国产一区二区三区久久 | 午夜精品剧场 | 一区二区三区在线观看中文字幕 | 久久久网址 | 草免费视频 | 五月天中文字幕 | 国产精品激情在线观看 | 久久人人爽人人 | 日本一区二区不卡高清 | 精品少妇一区二区三区在线 | 超碰97人人爱| 黄色三级网站在线观看 | 中文字幕在线免费看线人 | 久久精品99国产精品亚洲最刺激 | 亚洲无吗av| 国产精品久久一区二区三区, | 婷婷久久网站 | 99精品国产在热久久下载 | 福利片视频区 | 美女在线观看网站 | 久久热亚洲 | 一区二区三区手机在线观看 | 手机在线欧美 | 亚洲综合色视频在线观看 | 久久久毛片 | 激情综合一区 | 国产精品18毛片一区二区 | 99精品免费视频 | 中文字幕综合在线 | 国产美女在线观看 | 中文字幕专区高清在线观看 | 99性视频 | 亚洲精品456在线播放 | 国产vs久久 | 婷婷六月网 | 国产一区私人高清影院 | 久久人人做 | 国产精品资源网 | 国产精品国产三级国产 | av资源免费在线观看 | 国产色久 | 免费在线观看黄色网 | 日韩网站一区 | 成人免费xyz网站 | 在线免费三级 | 深夜免费福利在线 | 欧美精品小视频 | 国产69久久精品成人看 | 国产丝袜制服在线 | 午夜视频久久久 | 国产不卡精品 | 国产色一区 | 青青河边草免费直播 | 国产一级久久久 | 欧美精品亚州精品 | 国产成人61精品免费看片 | 色99视频| 免费观看视频黄 | 日韩在线观看你懂的 | 久久视频在线观看中文字幕 | 天天色官网 | 91视频免费看 | 久久国产精品视频免费看 | 久久精品日产第一区二区三区乱码 | 久艹视频在线观看 | 国产色婷婷精品综合在线手机播放 | 欧美视频日韩视频 | 国产精品 中文字幕 亚洲 欧美 | 国产中文 | 国产一级二级三级视频 | 日本中文字幕视频 | 欧美色图狠狠干 | 久久成人精品电影 | 高清免费在线视频 | 在线影院 国内精品 | 在线91观看 | 天天做天天爱天天爽综合网 | 草久热| 99久久99久久精品免费 | 日韩二区三区在线 | 欧美性成人 | 99视频在线免费看 | 青草视频在线 | 中文字幕视频在线播放 | 97国产精品久久 | 超黄视频网站 | 91亚洲激情| 日韩中文在线字幕 | 欧美大jb | 色网站在线 | 久久久福利视频 | 欧美va天堂在线电影 | www好男人 | 99精品乱码国产在线观看 | 国产一区二区在线观看免费 | 精品久久久久久久久亚洲 | 久久精品美女视频网站 | 久久久黄视频 | 久久a级片 | 成人久久久久久久久久 | 天天操夜夜操夜夜操 | 久草影视在线 | 黄色性av | 日韩av一区二区在线播放 | 欧美xxxx性xxxxx高清 | 久久综合亚洲鲁鲁五月久久 | jizz18欧美18| 中文字幕在线观看免费高清电影 | 一级免费看 | 国产精品久久久久毛片大屁完整版 | 婷婷色伊人 | 国产美女在线免费观看 | 国产精品免费久久 | 欧美污污网站 | 国产精品久久久久久久久久东京 | 免费三级a | 亚洲成人国产精品 | 久久毛片高清国产 | 久久这里精品视频 | 欧美美女视频在线观看 | 综合视频在线 | 麻豆影视在线播放 | 成人中文字幕在线观看 | 久久99九九99精品 | av色网站| 天堂av色婷婷一区二区三区 | 欧美精品久久久久久久久久白贞 | 91精品视频免费观看 | 国产一区精品在线观看 | 毛片无卡免费无播放器 | 99色精品视频 | 九九在线高清精品视频 | 欧美精品亚洲精品日韩精品 | 国产一区二区在线播放视频 | 日韩在线观看一区二区 | 日本中文字幕在线播放 | 中文在线字幕免费观看 | 国产在线美女 | 黄色在线观看网站 | 日韩美女久久 | 国产精品自产拍在线观看桃花 | 国产.精品.日韩.另类.中文.在线.播放 | 81精品国产乱码久久久久久 | 亚洲精品国产精品国自产观看浪潮 | 丁香婷婷综合激情五月色 | 一级欧美一级日韩 | 色999五月色 | 四虎最新域名 | 久久观看最新视频 | 天堂av一区二区 | 中文字幕精品在线 | 91成人黄色 | 国产免费资源 | 日韩理论片 | 91九色在线观看 | 人人搞人人搞 | 成人免费xxxxxx视频 | 欧美巨乳网 | 免费在线看成人av | 日本中文乱码卡一卡二新区 | 色国产精品 | 日韩网站免费观看 | 国产精品正在播放 | 99热99re6国产在线播放 | 久久成人国产精品一区二区 | www.国产毛片 | 天天色天天射天天操 | 色狠狠久久av五月综合 | 免费在线黄色av | 亚洲精品视频一二三 | 一区二区三区久久精品 | 国产精品久久99综合免费观看尤物 | 久久99久久99免费视频 | 一区二区三区免费在线 | 精品国产精品国产偷麻豆 | 天天操天天干天天操天天干 | 香蕉影视| 久久九九九九 | 黄色一集片 | 久久96| 天天久久夜夜 | 99精品视频在线观看 | 国产又粗又猛又爽又黄的视频免费 | 亚洲精品永久免费视频 | 亚洲国产成人在线播放 | 500部大龄熟乱视频使用方法 | 444av| 热精品 | 国产精品高清一区二区三区 | 久久久国产99久久国产一 | 99久久日韩精品免费热麻豆美女 | 久久综合导航 | 蜜臀av一区二区 | www.一区二区三区 | 国内精品久久天天躁人人爽 | 96精品高清视频在线观看软件特色 | 亚洲精品国产拍在线 | 日韩性xxxx| 一区二区 不卡 | 91在线91拍拍在线91 | 在线免费观看成人 | 日韩亚洲国产中文字幕 | 中文字幕在线免费 | 成年人免费在线观看网站 | 日韩和的一区二在线 | 国产精品久久一 | 成人在线观看av | 免费看av片网站 | 日韩欧美综合在线视频 | 在线国产日韩 | 制服丝袜欧美 | 久久国产精品区 | 探花在线观看 | 国产尤物在线 | 91麻豆精品国产午夜天堂 | 亚洲欧洲日韩 | 午夜精品久久久久久久久久久 | 国产免费久久久久 | 国产精品 中文在线 | 亚洲日本va午夜在线影院 | 色噜噜在线观看视频 | 国内视频一区二区 | 一区二区三区精品在线视频 | 亚洲天堂网在线观看视频 | 97视频入口免费观看 | 国产一区二区在线免费播放 | 天天视频亚洲 | 色播五月婷婷 | 亚洲精品视频在线播放 | 久久国内精品 | 免费观看一级特黄欧美大片 | 蜜臀aⅴ精品一区二区三区 久久视屏网 | 成人cosplay福利网站 | 亚洲伦理电影在线 | 色综久久| 在线观看91久久久久久 | 色香com.| 91黄色在线视频 | 亚州国产精品久久久 | 黄色一区二区在线观看 | 精品在线视频观看 | 免费成人黄色 | 97超碰精品 | 国产自产高清不卡 | 欧美另类交在线观看 | 不卡精品视频 | 激情影音| 九九免费观看全部免费视频 | 国产亚洲精品久久19p | 97超碰人人模人人人爽人人爱 | 新版资源中文在线观看 | 国产一区二区三区四区在线 | 一区二区精品在线 | 国产精品视频资源 | 国产在线a视频 | 日日狠狠 | 天天操天天舔天天干 | 少妇视频一区 | 欧洲一区二区在线观看 | 国产一级a毛片视频爆浆 | 国产一级久久 | 日批网站在线观看 | 激情综合国产 | 人人射人人射 | 日日操日日操 | 国产视频每日更新 | 婷婷av网 | 国产精品99蜜臀久久不卡二区 | 九九热在线视频 | 欧美男女爱爱视频 | 在线观看黄色免费视频 | 婷婷久操| 黄网av在线 | 天天射射天天 | 国产精品九九热 | 在线观看久久久久久 | 亚洲欧洲国产精品 | 免费视频a | 亚洲精品一区二区三区新线路 | 日韩欧美第二页 | 日本精品一区二区三区在线观看 | 色综合久久久久综合体 | 国产成人精品一区二区在线 | 99久久综合国产精品二区 | 97久久精品午夜一区二区 | 免费污片 | 久久国产美女视频 | 久久久久久久久毛片精品 | 亚洲国产欧洲综合997久久, | 一级黄色大片 | 国产精品久久久久久久久久久久午夜片 | 一级一级一片免费 | 中文字幕91在线 | 日韩中文字幕免费在线播放 | 六月激情网 | 久久综合九色综合欧美狠狠 | 国产精品麻豆三级一区视频 | 黄色在线视频网址 | 美女网站黄在线观看 | 国产精品99久久久久 | 国产精品av免费观看 | 亚洲精品免费在线视频 | 91精品国产自产91精品 | 亚洲最快最全在线视频 | 亚洲免费成人av电影 | 精品国产欧美一区二区 | 西西44人体做爰大胆视频 | 天天干,天天插 | 日韩3区| 日韩在线免费小视频 | av成人资源 | 日韩成人一级大片 | 国内精品视频在线 | 亚洲一区二区高潮无套美女 | 91网站在线视频 | 国产精品99久久久久久人免费 | 国产黄色精品在线观看 | 欧美日本不卡 | 精品中文字幕在线播放 | 精品欧美一区二区三区久久久 | 精品久久影院 | 天天操夜夜操国产精品 | 97成人精品 | 久久免费播放视频 | 日韩在线观看你懂得 | 久草在线最新免费 | 久久婷婷视频 | 精品视频免费播放 | 91片黄在线观 | 麻豆久久久久久久 | 国产激情电影综合在线看 | www.色综合.com | 国产专区日韩专区 | 狠狠干网址 | 欧洲亚洲女同hd | 一区二区三区在线免费播放 | 国产美女在线精品免费观看 | 91精品国产九九九久久久亚洲 | 久久久久久久久久久久久久免费看 | 亚洲国产精品第一区二区 | 人人爽人人澡人人添人人人人 | 青春草视频在线播放 | 亚洲成年片 | 最近中文字幕mv免费高清在线 | 欧美日韩视频一区二区三区 | 亚洲欧洲精品在线 | 日韩成人在线免费观看 | 97超碰人人在线 | 粉嫩一区二区三区粉嫩91 | 美女视频久久久 | av电影免费在线看 | 国外调教视频网站 | 看片网站黄色 | 精品国产一区二区三区久久影院 | 成人观看| 啪一啪在线 | 国产系列 在线观看 | 国模精品一区二区三区 | 国产91免费看 | 亚洲人成免费网站 | 日韩中文字幕亚洲一区二区va在线 | 在线播放日韩 | www.天天成人国产电影 | 97精品在线视频 | 91av在线电影 | 国产男女无遮挡猛进猛出在线观看 | 精品国产乱码久久久久 | 日韩黄视频 | 一级免费黄视频 | 久久精品伊人 | 久久国产精品一区二区三区四区 | 亚洲激精日韩激精欧美精品 | 亚洲精品久 | 亚洲精品美女久久久 | 日本中文字幕系列 | 亚洲视频播放 | 国产精品久久久久久久午夜片 | 精品国精品自拍自在线 | 中文字幕成人在线观看 | 亚洲国产精品一区二区尤物区 | 久久久精品| 色综合久久悠悠 | 毛片在线播放网址 | 亚洲成aⅴ人片久久青草影院 | 国产视频999 | 亚洲视频第一页 | 在线观看av大片 | 国产精品成人在线观看 | 国产亚洲综合性久久久影院 | 99国产精品一区二区 | 久久成年人视频 | 97成人啪啪网 | 久热电影 | 狠狠操天天操 | 国产精品一区二区三区在线看 | 欧美日韩二区在线 | 一区二区三区电影 | 国产精品白丝jk白祙 | 久久免费看av | 成人午夜电影免费在线观看 | 五月天久久| 在线观看久草 | 日韩电影在线一区 | 中文字幕在线看片 | 精品人人人 | av在线专区 | 视频一区久久 | 色综合久久久久网 | 五月婷婷激情综合 | 狠狠的日日 | 久久久亚洲精华液 | 亚洲精品一区二区三区四区高清 | 999久久久久久 | 国产精品午夜在线观看 | 色99导航| 精品一区二区三区久久 | 久久久久一区二区三区 | 97视频免费 | 成人午夜毛片 | 免费一级片观看 | 97人人人| 久久尤物电影视频在线观看 | av福利在线 | 国产精品一区二区白浆 | 91香蕉国产 | 91麻豆精品国产91久久久无限制版 | 最近中文字幕视频网 | 超碰在线9 | 91视频高清完整版 | 黄色av大片 | 国产专区第一页 | 久久久网| 99在线免费观看视频 | 国产精品成人aaaaa网站 | 日韩资源在线播放 | 最近中文字幕完整视频高清1 | 97理论片 | 日本精品一二区 | 日韩视频在线播放 | 国产精品一区二区av影院萌芽 | 最新色视频 | 久久成人高清 | 日日精品 | 美女精品久久久 | 97人人超碰在线 | 日本少妇久久久 | 在线国产视频 | 精品视频专区 | 97操碰| 日韩欧美电影网 | 69亚洲精品| 久久精品视 | 国内精品久久久久影院优 | av在线小说| 亚州精品一二三区 | 91成年人在线观看 | 中文字幕高清av | 欧美一级xxxx | 欧美一级性生活视频 | 久久视频在线观看 | 成人免费大片黄在线播放 | 成人观看| 开心色插 | 精品亚洲免费视频 | 九九视频热| 国产高清免费在线播放 | 在线综合 亚洲 欧美在线视频 | 欧美日韩高清一区二区 | 国产精品一区在线播放 | 欧美狠狠色 | 亚洲成免费 | 亚洲在线日韩 | 麻豆国产在线播放 | 91精品国产九九九久久久亚洲 | 在线成人免费电影 | 久久久久久久久爱 | 亚洲精品乱码久久久久久久久久 | 午夜久久网站 | 国产精品美女视频 | 午夜精品剧场 | 欧美另类色图 | 国产中文字幕一区二区三区 | 国产精品一区二区久久精品爱微奶 | 国产日韩精品一区二区 | 在线电影日韩 | 久久久久在线观看 | 狠狠躁夜夜av | 午夜精品久久久久久久99婷婷 | 久久久久久不卡 | 亚洲高清在线观看视频 | 麻豆国产在线视频 | 欧美一级xxxx | 国产香蕉在线 | 天天透天天插 | 亚洲精品国偷自产在线99热 | 日本99热 | 美女国内精品自产拍在线播放 | 久久精品99国产精品亚洲最刺激 | 在线电影日韩 | 在线视频麻豆 | 免费日韩一级片 | 亚洲专区在线播放 | 国产午夜麻豆影院在线观看 | 久久视频在线观看中文字幕 | 成人在线网站观看 | 欧美色综合天天久久综合精品 | 九九综合九九综合 | 亚洲好视频| av一本久道久久波多野结衣 | 亚洲精品午夜国产va久久成人 | 开心激情五月婷婷 | 天天草天天操 | 欧美日韩精品免费观看 | 超碰在线网 | 国产资源在线播放 | 天天综合日| 久草精品视频在线看网站免费 | 久久综合九色综合网站 | 久久久人人人 | 日韩免费视频线观看 | 99亚洲精品 | 91污在线| 色香蕉在线 | 在线一级片 | 美女网站黄在线观看 | 能在线看的av | 麻豆久久精品 | 五月天丁香 | 亚洲成人资源在线 | 成人黄在线观看 | 国产精品岛国久久久久久久久红粉 | 九草视频在线观看 | 亚洲午夜av | 成人h在线 | 97视频在线观看视频免费视频 | 色诱亚洲精品久久久久久 | 久久九九免费视频 | 在线免费视频你懂的 | 99精品视频免费全部在线 | 国产精品免费观看视频 | 精品九九九九 | 91精品免费看 | 久久久九色精品国产一区二区三区 | 四虎在线免费观看 | 国产精品99久久久久久武松影视 | 久久视了| 蜜臀久久99静品久久久久久 | 国产视频999 | 黄色亚洲免费 | 中文字幕成人在线 | 91精品国产一区二区三区 | 日本在线观看一区 | 国产精品一区二区免费在线观看 | 欧美一区二区在线刺激视频 | 亚洲精色 | 亚洲成年人在线播放 | 蜜臀av性久久久久av蜜臀妖精 | 国产精品私人影院 | 精品久久久久一区二区国产 | 激情婷婷在线 | 久久精品一二三区白丝高潮 | 九色porny真实丨国产18 | 久久伦理网 | 国产精品久久久久一区二区三区共 | 97人人精品 | www.成人sex| 91精品欧美 | 色激情五月 | 伊人影院在线观看 | 国产精品一区二区在线免费观看 | 公与妇乱理三级xxx 在线观看视频在线观看 | 免费网站v | 精品国产一区二区三区久久影院 | 日本久久精品视频 | 在线亚洲成人 | 伊人首页| 日韩免费视频网站 | 美女网站视频色 | 揉bbb玩bbb少妇bbb | 18国产精品白浆在线观看免费 | 精品亚洲成a人在线观看 | 黄污网| 大荫蒂欧美视频另类xxxx | 国产 日韩 中文字幕 | 成人黄色电影免费观看 | 99re国产视频 | 久久久精品 | 国产免费视频一区二区裸体 | 国产黄在线 | 一本一本久久aa综合精品 | www.狠狠插.com | 天天干,狠狠干 | 91九色成人蝌蚪首页 | 91污污视频在线观看 | av软件在线观看 | 欧美资源 | 六月丁香综合网 | 色婷婷激情综合 | av一级在线观看 | 欧美精品xx| 免费在线电影网址大全 | av千婊在线免费观看 | 免费麻豆| 天天操福利视频 | 国产黄在线看 | 伊人成人激情 | 午夜精品一区二区三区在线视频 | 亚洲另类在线视频 | 久久激情小视频 | 国产精品99久久久久久小说 | 狠狠色噜噜狠狠 | 在线一二三区 | 精品国产视频在线 | 国产不卡视频在线播放 | 成人亚洲免费 | av成人在线网站 | 中文字幕中文 | 久久在线视频精品 | 国产精品99久久99久久久二8 | 国产精品igao视频网入口 | 一级淫片在线观看 | 97超视频免费观看 | 最近中文字幕完整视频高清1 | 亚洲综合一区二区精品导航 | 91最新国产 | 国产亚洲视频系列 | 成人午夜片av在线看 | 狠狠狠色| 一区二区三区免费在线 | 99r国产精品 | 黄网站免费大全入口 | 国内视频在线观看 | 国产亚洲aⅴaaaaaa毛片 | 中文字幕国产精品一区二区 | 99热精品国产一区二区在线观看 | 成人v | 国产精品美女毛片真酒店 | 国产黄色理论片 | 午夜精品一区二区三区在线播放 | 91麻豆精品国产91久久久无限制版 | 亚洲精品女| 亚洲精品视频二区 | 国产成人一区二区在线观看 | av免费观看高清 | 一区二区三区播放 | 久久美女高清视频 | 亚洲欧洲精品一区二区精品久久久 | 色婷婷国产| 久久精品视频4 | 色婷婷电影 | 欧美日韩三级 | 69xx视频 | 国产欧美在线一区二区三区 | 日日爱网站 | 三级动态视频在线观看 | 97色在线观看免费视频 | 国产最新网站 | 91在线国内视频 | 亚洲高清色综合 | 亚洲四虎 | 国产破处精品 | 中文字幕国内精品 | 91精品视频免费看 | 日本在线视频一区二区三区 | 综合天堂av久久久久久久 | 不卡视频国产 | 国产精品亚洲精品 | 99国产一区二区三精品乱码 | 成 人 黄 色视频免费播放 | 人人澡人人爽欧一区 | 国产高清视频免费 | 99精品视频在线看 | 日本中文字幕在线播放 | 中文字幕美女免费在线 | 一本色道久久综合亚洲二区三区 | 在线免费观看视频一区二区三区 | 99久久99| 亚洲一区二区三区91 | 麻豆 free xxxx movies hd| 国产一二三区在线观看 | 中文字幕在线观看第一页 | 日本中文字幕在线播放 | 国产一区二区视频在线播放 | 国产精品一区二区三区99 | 操操操日日 | 91传媒91久久久 | 日日夜夜天天综合 | 久久99国产精品久久99 | 色多视频在线观看 | 成人午夜久久 | 久久九九精品 | 国产v在线播放 | 视频一区二区免费 | 国产亚洲高清视频 | 日本久久中文字幕 | 国产精品久久片 | 麻豆视传媒官网免费观看 | 久久久午夜电影 | 91久久偷偷做嫩草影院 | 欧美另类一二三四区 | 国产小视频在线 | 97福利视频 | 国产精品久久久久久久久久久不卡 | 色视频在线 | 人人草在线视频 | 九九久| 一区二区三区动漫 | 97人人网 | 天天干天天草天天爽 | 亚洲aaa毛片 | av中文字幕电影 | 成人在线超碰 | 一级黄色片在线播放 | 免费日韩在线 | 国产 av 日韩 | 国产精品videossex国产高清 | 美女久久 | 亚洲h在线播放在线观看h | www.久久久 | 五月婷丁香网 | www久久国产 | 一区二区中文字幕在线播放 | 天天操天天爱天天干 | 色网站中文字幕 | 国产精品精品久久久 | 日本久久中文 | 国产欧美中文字幕 | 国产不卡在线观看 | 日韩啪啪小视频 | 中文字幕精品一区二区三区电影 | 国产精品久久久久久久久软件 | 成年人国产在线观看 | 亚洲欧洲精品一区二区精品久久久 | 色偷偷88888欧美精品久久 | 日韩性久久| 中文字幕一区二区三区乱码不卡 | 成人av电影免费在线观看 | 亚洲激情六月 | 久久国产影院 | 天天干,天天射,天天操,天天摸 | 久久精品8| 国产视频99 | 国产视频亚洲 | 天天玩天天干天天操 | 99爱精品视频 | 91色亚洲| 亚洲国产精品500在线观看 | 在线视频观看成人 | 国产乱码精品一区二区蜜臀 | 日本久久精品 | 中文字幕91视频 | 亚洲第一伊人 | 深夜男人影院 | 波多野结衣一区二区 | 中文字幕电影在线 | 国产成人亚洲在线观看 | 国产精品一级在线 | 成在线播放 | 国产精品成人自产拍在线观看 | 亚洲黄色成人网 | 国产精品入口66mio女同 | 中文高清av| 人人看黄色 | 成人黄色大片在线免费观看 | 国产精品国产三级国产aⅴ无密码 | 国产99在线 | 美女久久视频 | 日日干日日 | av丝袜在线 | 在线欧美小视频 | 免费观看的av网站 | 国产在线播放一区二区三区 | 成人午夜网| 毛片网站观看 | 亚洲精品免费在线观看视频 | 91视频91色 | 欧美极品久久 | 国产色视频一区二区三区qq号 | 国产精品久久久区三区天天噜 | 麻豆视频大全 | 三级在线国产 | 久久久国产99久久国产一 | 久久久18 | 色婷婷综合久久久中文字幕 | 天天综合操 | 在线免费观看黄色 | 国产91亚洲精品 | 在线性视频日韩欧美 | 欧美成人理伦片 | 日韩二区三区在线 | 国产一级片免费视频 | 韩国av电影网 | 国产精品久久久久久久久久新婚 | 天堂久久电影网 | 美女网站在线播放 | 欧美成人精品在线 | 特级西西444www大胆高清无视频 | 欧美日在线观看 | 成人午夜免费剧场 | 国产精品va在线观看入 | 国产精品a久久久久 | 久久国产福利 | 国产精品视频99 | 国产永久免费高清在线观看视频 | 精品国产一区二区三区在线观看 | 精品久久亚洲 | 有码中文字幕在线观看 | 最新真实国产在线视频 | 久久综合婷婷综合 | 99视| 欧美视频国产视频 | 国产一级电影 | 五月开心激情 | 操操操影院 | 日本特黄一级 | 日狠狠| 成人在线视频你懂的 | av一区在线 | 天天综合网天天 | 日韩电影黄色 | 在线观看深夜福利 | 日韩在线免费 | 中文字幕黄网 | 国产色黄网站 | 2020天天干夜夜爽 | 久久怡红院| 人人澡超碰碰 | va视频在线观看 | 天堂av在线网站 | 国产在线国偷精品产拍免费yy | 99国产精品免费网站 | 国产精品久久久久久高潮 | 亚洲va欧洲va国产va不卡 | 免费在线观看成年人视频 | 午夜在线看片 | 99精彩视频在线观看免费 | 在线观看av大片 | 香蕉视频在线视频 | 日韩av影片在线观看 | 亚洲精品乱码久久久久久9色 | 日韩视频一区二区三区在线播放免费观看 | 久久av中文字幕片 | 色综合天天视频在线观看 | 国产精品一区二区三区免费视频 | 人人爽人人爽人人 | 国产成人久 | 麻豆免费在线视频 | 在线观看亚洲免费视频 | 精品免费一区二区三区 | 国产一级二级三级在线观看 | 91在线国内视频 | 西西大胆啪啪 | 狠狠色丁香久久婷婷综合_中 | 国产精品爽爽久久久久久蜜臀 | 香蕉在线影院 | 精品一区二区三区四区在线 | 国语麻豆 | 国产精品乱码一区二三区 | 激情片av| 日韩免费一级a毛片在线播放一级 | 青青草国产在线 | 久久三级毛片 | 天天做日日爱夜夜爽 | 色婷婷亚洲 | av线上看| 香蕉网在线播放 | 天天色天天射天天操 | 黄色亚洲精品 | 欧美亚洲成人免费 | 在线成人中文字幕 | 欧美日韩国产综合一区二区 | 日韩欧美一区二区在线观看 | 国产99久久久国产精品成人免费 | 久久久免费观看完整版 | 亚洲国产精品传媒在线观看 | 久久精品亚洲综合专区 | 成人va在线观看 | 成人精品一区二区三区中文字幕 | 日韩乱色精品一区二区 | 国产精品18久久久久久不卡孕妇 | 五月激情丁香图片 | 久久久在线视频 | 美女视频a美女大全免费下载蜜臀 | 国产精品男女啪啪 | 成年人三级网站 | 99精品国产高清在线观看 | 黄色软件大全网站 | 在线中文字幕电影 | 久久99热这里只有精品国产 | 国产成人精品亚洲精品 | 天堂av在线网 | 色搞搞 | 国产乱老熟视频网88av | 国产福利91精品一区二区三区 | 97精品超碰一区二区三区 | 免费在线观看一区二区三区 | 手机av在线网站 | 国产成人免费观看久久久 | 最新色视频 | 一本一本久久a久久精品综合妖精 | 亚洲免费在线观看视频 | 午夜在线资源 | 五月天堂色 | 精品主播网红福利资源观看 | 欧美天天干 | 中文字幕在线播放一区 | 人人澡人摸人人添学生av | 中国精品少妇 | 天天摸日日摸人人看 | 麻豆你懂的 | 国产精品久久久久av | 亚洲永久在线 | 久久国产精品一区二区 | 国产一级电影免费观看 | 欧美日韩亚洲第一 | 少妇自拍av| av片在线观看免费 | 久久婷婷色 | 91理论片午午伦夜理片久久 | 丁香资源影视免费观看 | 激情五月亚洲 | 欧美日韩一区二区免费在线观看 | 91九色国产视频 |