OpenCV于1999年由Intel建立,如今由Willow Garage提供支持。OpenCV是一個基于BSD許可(開源)發(fā)行的跨平臺計算機(jī)視覺庫,可以運(yùn)行在Linux、Windows和Mac OS操作系統(tǒng)上。它輕量級而且高效——由一系列 C 函數(shù)和少量 C++ 類構(gòu)成,同時提供了Python、Ruby、MATLAB等語言的接口,實(shí)現(xiàn)了圖像處理和計算機(jī)視覺方面的很多通用算法。最新版本是3.3 ,2017年8月3日發(fā)布。
OpenCV 擁有包括 500 多個C函數(shù)的跨平臺的中、高層 API。它不依賴于其它的外部庫——盡管也可以使用某些外部庫。
OpenCV 為Intel? Integrated Performance Primitives (IPP) 提供了透明接口。 這意味著如果有為特定處理器優(yōu)化的 IPP 庫, OpenCV 將在運(yùn)行時自動加載這些庫。
?。ㄗⅲ篛penCV 2.0版的代碼已顯著優(yōu)化,無需IPP來提升性能,故2.0版不再提供IPP接口)
二值化是圖像分割的一種方法。在二值化圖象的時候把大于某個臨界灰度值的像素灰度設(shè)為灰度極大值,把小于這個值的像素灰度設(shè)為灰度極小值,從而實(shí)現(xiàn)二值化。
根據(jù)閾值選取的不同,二值化的算法分為固定閾值和自適應(yīng)閾值。 比較常用的二值化方法則有:雙峰法、P參數(shù)法、迭代法和OTSU法等。
以下是opencv 圖像去噪學(xué)習(xí)總結(jié)
OpenCV圖像處理篇之圖像平滑
圖像平滑算法
程序分析及結(jié)果
圖像平滑算法
圖像平滑與圖像模糊是同一概念,主要用于圖像的去噪。平滑要使用濾波器,為不改變圖像的相位信息,一般使用線性濾波器,其統(tǒng)一形式如下:
其中h稱為濾波器的核函數(shù),說白了就是權(quán)值。不同的核函數(shù)代表不同的濾波器,有不同的用途。
在圖像處理中,常見的濾波器包括:
歸一化濾波器(Homogeneous blur)
也是均值濾波器,用輸出像素點(diǎn)核窗口內(nèi)的像素均值代替輸出點(diǎn)像素值。
高斯濾波器(Guassian blur)是實(shí)際中最常用的濾波器,高斯濾波是將輸入數(shù)組的每一個像素點(diǎn)與 高斯內(nèi)核 卷積將卷積和當(dāng)作輸出像素值。高斯核相當(dāng)于對輸出像素的鄰域賦予不同的權(quán)值,輸出像素點(diǎn)所在位置的權(quán)值最大(對應(yīng)高斯函數(shù)的均值位置)。二維高斯函數(shù)為,
中值濾波器(median blur)
中值濾波將圖像的每個像素用鄰域(以當(dāng)前像素為中心的正方形區(qū)域)像素的中值代替。對椒鹽噪聲最有效的濾波器,去除跳變點(diǎn)非常有效。
雙邊濾波器(Bilatrial blur)
為避免濾波器平滑圖像去噪的同時使邊緣也模糊,這種情況下使用雙邊濾波器。
下面的程序?qū)⑾冉o標(biāo)準(zhǔn)Lena圖像添加椒鹽噪聲,分別使用4種不同的濾波器進(jìn)行平滑操作,請注意觀察不
同濾波器對椒鹽噪聲的去噪效果!
程序分析及結(jié)果
/*
* FileName : image_smoothing.cpp
* Author : xiahouzuoxin @163.com
* Version : v1.0
* Date : Wed 17 Sep 2014 08:30:25 PM CST
* Brief :
*
* Copyright (C) MICL,USTB
*/
#include “cv.h”
#include “imgproc/imgproc.hpp”
#include “highgui/highgui.hpp”
using namespace std;
using namespace cv;
const int MAX_KERNEL_LENGTH = 10;
const char *wn_name = “Smoothing”;
static void salt(Mat &I, int n);
static void disp_caption(const char *wn_name, Mat src, const char *caption);
static void disp_image(const char *wn_name, Mat I);
/*
* @brief
* @inputs
* @outputs
* @retval
*/
int main(int argc, char *argv[])
{
if (argc《2) {
cout《《“Usage: 。/image_smoothing [file name]”《《endl;
return -1;
}
Mat I = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);
salt(I, 6000);
imshow(wn_name, I);
waitKey(0);
Mat dst; // Result
/* Homogeneous blur */
disp_caption(wn_name, I, “Homogeneous blur”);
for (int i=1; i《MAX_KERNEL_LENGTH; i+=2) {
blur(I, dst, Size(i, i), Point(-1,-1));
disp_image(wn_name, dst);
}
/* Guassian blur */
disp_caption(wn_name, I, “Gaussian blur”);
for (int i=1; i《MAX_KERNEL_LENGTH; i+=2) {
GaussianBlur(I, dst, Size(i, i), 0, 0);
disp_image(wn_name, dst);
}
/* Median blur */
disp_caption(wn_name, I, “Median blur”);
for (int i=1; i《MAX_KERNEL_LENGTH; i+=2) {
medianBlur(I, dst, i);
disp_image(wn_name, dst);
}
/* Bilatrial blur */
disp_caption(wn_name, I, “Bilatrial blur”);
for (int i=1; i《MAX_KERNEL_LENGTH; i+=2) {
bilateralFilter(I, dst, i, i*2, i/2);
disp_image(wn_name, dst);
}
waitKey(0);
return 0;
}
/*
* @brief 顯示提示文字(濾波方法)
* @inputs
* @outputs
* @retval
*/
static void disp_caption(const char *wn_name, Mat src, const char *caption)
{
Mat dst = Mat::zeros(src.size(), src.type());
putText(dst, caption, Point(src.cols/4, src.rows/2), CV_FONT_HERSHEY_COMPLEX, 1, Scalar
(255,255,255));
imshow(wn_name, dst);
waitKey(0);
}
/*
* @brief 顯示圖像
* @inputs
* @outputs
* @retval
*/
static void disp_image(const char *wn_name, Mat I)
{
imshow(wn_name, I);
waitKey(1000);
}
/*
* @brief 添加椒鹽噪聲
* @inputs
* @outputs
* @retval
*/
static void salt(Mat &I, int n=3000)
{
for (int k=0; k《n; k++) {
int i = rand() % I.cols;
int j = rand() % I.rows;
if (I.channels()) {
I.at《uchar》(j,i) = 255;
} else {
I.at《Vec3b》(j,i)[0] = 255;
I.at《Vec3b》(j,i)[1] = 255;
I.at《Vec3b》(j,i)[2] = 255;
}
}
}
上面程序的邏輯非常清晰:
讀入灰度圖,并添加椒鹽噪聲(6000個噪聲點(diǎn)):
Mat I = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);
salt(I, 6000);
LenaNoise
disp_caption和disp_image函數(shù)分別是用于顯示提示文字和平滑過程中的變化圖像的,平滑過程中圖像的
變化如下圖:
blur
注意觀察上面的圖,中值濾波(Median Blur)對椒鹽噪聲的效果最好!
四種濾波方法分別使用到4個OpenCV函數(shù),這些函數(shù)的聲明都在imgproc.hpp中,這些函數(shù)的前2個參數(shù)都
是原圖像和濾波后圖像。
歸一化濾波器blur的第3個參數(shù)為濾波核窗口的大小,Size(i,i)表示ixi大小的窗口。
高斯濾波器GaussianBlur第3個參數(shù)也是濾波核窗口的大小,第4、第5個參數(shù)分辨表示x方向和y方向的δ。
中值濾波器medianBlur第3個參數(shù)是濾波器的長度,該濾波器的窗口為正方形。
雙邊濾波器的函數(shù)原型如下:
//! smooths the image using bilateral filter
CV_EXPORTS_W void bilateralFilter( InputArray src, OutputArray dst, int d,
double sigmaColor, double sigmaSpace,
int borderType=BORDER_DEFAULT );
本程序使用的Makefile文件為:
TARG=image_smoothing
SRC=image_smoothing.cpp
LIB=-L/usr/local/lib/
INC=-I/usr/local/include/opencv/ -I/usr/local/include/opencv2
CFLAGS=
$(TARG):$(SRC)
g++ -g -o $@ ${CFLAGS} $(LIB) $(INC) \
-lopencv_core -lopencv_highgui -lopencv_imgproc \
$^
.PHONY:clean
clean:
-rm $(TARG) tags -f
========
圖像代數(shù)運(yùn)算:平均值去噪,減去背景
代數(shù)運(yùn)算,就是對兩幅圖像的點(diǎn)之間進(jìn)行加、減、乘、除的運(yùn)算。四種運(yùn)算相應(yīng)的公式為:
代數(shù)運(yùn)算中比較常用的是圖像相加和相減。圖像相加常用來求平均值去除addtive噪聲或者實(shí)現(xiàn)二次曝光(double-exposure)。圖像相減用于減去背景或周期噪聲,污染等。
圖像相加
OpenCV中提供了相加的函數(shù)
void cvAcc(
const CvArr* image,//輸入圖像
CvArr* sum, //累積圖像
const CvArr* mask=NULL//可選的運(yùn)算
?。?
我們還需要用到一個線性變換轉(zhuǎn)換函數(shù)來對相加的結(jié)果求平均
void cvConvertScale(
const CvArr* src, //輸入數(shù)組
CvArr* dst,//輸出數(shù)組
double scale=1,//比例
double shift=0 //縮放比例,可選
?。?
#define cvCvtScale cvConvertScale
#define cvScale cvConvertScale
#define cvConvert( src, dst ) cvConvertScale( (src), (dst), 1, 0 )
實(shí)踐:平均值去噪
我們用NASA的一段幸運(yùn)團(tuán)的視頻做實(shí)驗,截取視頻的某幾個連續(xù)幀求平均值:
int main()
{
CvCapture* capture=cvCaptureFromFile(“media.avi”);
IplImage* frame= NULL;
IplImage * imgsum =NULL;
int start=301;
int end=304;
cvSetCaptureProperty(capture, CV_CAP_PROP_POS_FRAMES, start);
int count = start;
while( cvGrabFrame(capture) && count 《= end )
{
frame = cvRetrieveFrame(capture);// 獲取當(dāng)前幀
if(imgsum==NULL){
imgsum=cvCreateImage(cvGetSize(frame),IPL_DEPTH_32F,3);
cvZero(imgsum);
}
cvAcc(frame,imgsum);
char testname[100];
sprintf(testname,“%s%d%s”,“image”,count,“.jpg”);
cvShowImage(testname,frame);
cvSaveImage(testname,frame);
count++;
}
IplImage * imgavg = cvCreateImage(cvGetSize(frame),IPL_DEPTH_8U,3);
cvConvertScale(imgsum,imgavg,1.0/4.0);
cvShowImage(“imageavg”,imgavg);
cvSaveImage(“imageavg_4.jpg”,imgavg);
cvWaitKey(0);
cvReleaseCapture(&capture);
return 0;
}
以下從左到右分別是連續(xù)兩幀、四幀、八幀、十六幀求均值的結(jié)果:
實(shí)踐:圖像二次曝光
曝光和去噪是一樣的,也是對幾幅圖像求平均
//通過求平均二次曝光
int main()
{
IplImage* image1= cvLoadImage(“psu3.jpg”);
IplImage* image2= cvLoadImage(“psu4.jpg”);
IplImage * imgsum =cvCreateImage(cvGetSize(image1),IPL_DEPTH_32F,3);
cvZero(imgsum);
cvAcc(image1,imgsum);
cvAcc(image2,imgsum);
IplImage * imgavg = cvCreateImage(cvGetSize(image1),IPL_DEPTH_8U,3);
cvConvertScale(imgsum,imgavg,1.0/2.0);
cvShowImage(“imageavg”,imgavg);
cvSaveImage(“avg.jpg”,imgavg);
cvWaitKey(0);
cvReleaseImage(&image1);
cvReleaseImage(&image2);
cvReleaseImage(&imgsum);
cvReleaseImage(&imgavg);
return 0;
}
下圖是對同學(xué)街舞截圖的“二次曝光”效果:
圖像相減
OpenCV中用cvAbsDiff函數(shù)計算兩數(shù)組的差的絕對值
void cvAbsDiff(
const CvArr* src1,//第一個輸入數(shù)組
const CvArr* src2,//第二個輸入數(shù)組
CvArr* dst//輸出數(shù)組
?。?
實(shí)踐:減去背景
減去背景是通過兩幅圖像代數(shù)相減,可以判斷出前景區(qū)域和運(yùn)動區(qū)域,這是最簡單(很多時候也是效果很
好的)運(yùn)動檢測方法。
//減去背景
int main()
{
IplImage* pFrame = NULL;
IplImage* pFrImg = NULL;
IplImage* pBkImg = NULL;
CvMat* pFrameMat = NULL;
CvMat* pFrMat = NULL;
CvMat* pBkMat = NULL;
CvCapture* pCapture = NULL;
int nFrmNum = 0;
//創(chuàng)建窗口
cvNamedWindow(“video”, 1);
cvNamedWindow(“background”,1);
cvNamedWindow(“foreground”,1);
pCapture = cvCaptureFromFile(“media.avi”);
while(pFrame = cvQueryFrame( pCapture ))
{
nFrmNum++;
//如果是第一幀,需要申請內(nèi)存,并初始化
if(nFrmNum == 1)
{
pBkImg = cvCreateImage(cvSize(pFrame-》width, pFrame-》height), IPL_DEPTH_8U,1);
pFrImg = cvCreateImage(cvSize(pFrame-》width, pFrame-》height), IPL_DEPTH_8U,1);
pBkMat = cvCreateMat(pFrame-》height, pFrame-》width, CV_32FC1);
pFrMat = cvCreateMat(pFrame-》height, pFrame-》width, CV_32FC1);
pFrameMat = cvCreateMat(pFrame-》height, pFrame-》width, CV_32FC1);
//轉(zhuǎn)化成單通道圖像再處理
cvCvtColor(pFrame, pBkImg, CV_BGR2GRAY);
cvCvtColor(pFrame, pFrImg, CV_BGR2GRAY);
cvConvert(pFrImg, pFrameMat);
cvConvert(pFrImg, pFrMat);
cvConvert(pFrImg, pBkMat);
}
else
{
cvCvtColor(pFrame, pFrImg, CV_BGR2GRAY);
cvConvert(pFrImg, pFrameMat);
//當(dāng)前幀跟背景圖相減
cvAbsDiff(pFrameMat, pBkMat, pFrMat);
//二值化前景圖
cvThreshold(pFrMat, pFrImg, 60, 255.0, CV_THRESH_BINARY);
//更新背景
cvRunningAvg(pFrameMat, pBkMat, 0.003, 0);
//將背景轉(zhuǎn)化為圖像格式,用以顯示
cvConvert(pBkMat, pBkImg);
cvShowImage(“video”, pFrame);
cvShowImage(“background”, pBkImg);
cvShowImage(“foreground”, pFrImg);
if( cvWaitKey(2) 》= 0 )
break;
}
}
cvDestroyWindow(“video”);
cvDestroyWindow(“background”);
cvDestroyWindow(“foreground”);
cvReleaseImage(&pFrImg);
cvReleaseImage(&pBkImg);
cvReleaseMat(&pFrameMat);
cvReleaseMat(&pFrMat);
cvReleaseMat(&pBkMat);
cvReleaseCapture(&pCapture);
return 0;
}
效果圖:
========
opencv連通域去噪
//Find_Connected_Component參數(shù)說明:
/*mask———一副灰度圖
polygon1_hull0———用多邊形擬合選1,用凸包擬合選0
scale———設(shè)置不被刪除的連通輪廓大小
num————連通輪廓的最大數(shù)目
bbs——指向連通輪廓的外接矩形
center——指向連通輪廓的中心*/
#define CVCONTOUR_APPROX_LEVEL 2//數(shù)越大連通區(qū)域的邊界越簡單
#define CVCLOSE_ITR 1 //圖像形態(tài)學(xué)運(yùn)算的次數(shù)
void Find_Connected_Component(IplImage *mask,int polygon1_hull0=1,float scale=4
,int *num=0,CvRect *bbs=0,CvPoint
*centers=0)//連通域去噪聲
{
static CvMemStorage* mem_storage=0;
static CvSeq* contours=0;
//先開操作,后閉操作
cvMorphologyEx(mask,mask,0,0,CV_MOP_OPEN,CVCLOSE_ITR);
cvMorphologyEx(mask,mask,0,0,CV_MOP_CLOSE,CVCLOSE_ITR);
//找到所有輪廓
if(!mem_storage)
{
mem_storage=cvCreateMemStorage(0);
}
else{
cvClearMemStorage(mem_storage);
}
CvContourScanner scanner=cvStartFindContours(mask,mem_storage,sizeof
?。–vContour),CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE);
//丟棄太小的輪廓,用多邊形或凸包擬合剩下的輪廓
CvSeq* c;
int numCont=0;
while(c=cvFindNextContour(scanner))
{
double len=cvContourPerimeter(c);//連通域周長
double q=(mask-》height+mask-》width)/scale;
//cout《《len《《“ ”;
if(len《q){cvSubstituteContour(scanner,NULL);}
else{
CvSeq* c_new;
if(polygon1_hull0)
{
//多邊形擬合
c_new=cvApproxPoly(c,sizeof
?。–vContour),mem_storage,CV_POLY_APPROX_DP,CVCONTOUR_APPROX_LEVEL,0);
}
else{
//凸包擬合
c_new=cvConvexHull2(c,mem_storage,CV_CLOCKWISE,1);
}
cvSubstituteContour(scanner,c_new);
numCont++;
}
}
contours=cvEndFindContours(&scanner);
const CvScalar CVX_WHITE=CV_RGB(0xff,0xff,0xff);//白色
const CvScalar CVX_BLACK=CV_RGB(0x00,0x00,0x00); //黑色
//重繪連通區(qū)域
cvZero(mask);
IplImage *maskTemp;
int numFilled=0;
if(num!=NULL)
{
int N=*num,i=0;
CvMoments moments;
double M00,M01,M10;
maskTemp=cvCloneImage(mask);
for(i=0,c=contours;c!=NULL;c=c-》h_next,i++)
{
if(i《N)
{
cvDrawContours(maskTemp,c,CVX_WHITE,CVX_WHITE,-1,CV_FILLED,8);
//找中心
if(centers){
cvMoments(maskTemp,&moments,1);
M00=cvGetSpatialMoment(&moments,0,0);
M10=cvGetSpatialMoment(&moments,1,0);
M01=cvGetSpatialMoment(&moments,0,1);
centers[i].x=(int)(M10/M00);
centers[i].y=(int)(M01/M00);
}
if(bbs!=NULL){
bbs[i]=cvBoundingRect(c);
}
cvZero(maskTemp);
numFilled++;
}
//畫區(qū)域
cvDrawContours(mask,c,CVX_WHITE,CVX_WHITE,-1,CV_FILLED,8);
}
//*num=numFilled;
cvReleaseImage(&maskTemp);
}
else
{
for(c=contours;c!=NULL;c=c-》h_next)
cvDrawContours(mask,c,CVX_WHITE,CVX_WHITE,-1,CV_FILLED,8);
}
*num=numCont;
}
========
OpenCV單kinect多幀靜止場景的深度圖像去噪
老板kinect去噪的任務(wù)下達(dá)已經(jīng)有半個多月了,前期除了看了幾天文獻(xiàn)之外就打醬油了,好像每天都很忙,可是就是不知道在忙什么。這幾天為了交差,就胡亂湊了幾段代碼,得到一個結(jié)果,也知道不行,先應(yīng)付一下,再圖打算。程序思想很簡單,先對靜止的場景連續(xù)采樣若干幀,然后對所有點(diǎn)在時間域取中值,對取完中值之后的無效點(diǎn)在空間域取最近鄰,勉強(qiáng)將黑窟窿填上了。由于代碼較長,現(xiàn)在奉上關(guān)鍵的幾個片段:
#include《cv.h》
#include《highgui.h》
#include《iostream》
using namespace std;
#ifndef _DENOISE
#define _DENOISE
const int nFrames = 9; // number of consecutive frames
const int width = 640; // frame width
const int height = 480; // frame height
class kinectDenoising
{
private:
IplImage* denoisedImage;
IplImage* frameSet[nFrames];
unsigned int numOfFrames;
CvRect imageROI;
public:
kinectDenoising();
~kinectDenoising();
void addFrame(IplImage* img);
void setImageROI(bool isUpdate = true);
void medianFiltering();
void nearestFiltering();
void updateFrameSet(IplImage* img);
void showDenoiedImage(const char* window);
void showCurrentImage(const char* window);
};
void insertSort(unsigned short* data,int& len,unsigned short newData);
#endif
這是定義的頭文件,裝模作樣的寫了一個類,在構(gòu)造函數(shù)里面,除了對denoisedImage分配內(nèi)存之外其他都置0,析構(gòu)函數(shù)需要釋放denoisedImage和frameSet數(shù)組的內(nèi)存。numOfFrames本來設(shè)計為frameSet中的圖像的幀數(shù),結(jié)果由于偷懶就用了一個定長的數(shù)組。
void kinectDenoising::setImageROI(bool isUpdate)
{
if(!isUpdate)
{
imageROI = cvRect(22,44,591,434);
}
else
{
IplImage* image8u = cvCreateImage(cvSize(width,height),IPL_DEPTH_8U,1);
IplImage* bitImage = cvCreateImage(cvSize(width,height),IPL_DEPTH_8U,1);
// cvThreshold can only handle images of 8UC1 or 32FC1
cvConvertScale(frameSet[0],image8u,255.0/4096.0);
cvThreshold(image8u,bitImage,0,1,CV_THRESH_BINARY);
// the two mats rowReduced and colReduced have to be CV_32SC1 type
// for function cvReduce() seems not to suitable for 16U type and
// 8U type doesn‘t have enough room for the result.
CvMat* rowReduced = cvCreateMat(1,bitImage-》width,CV_32FC1);
// bitImage-》width represents number of cols, while bitImage-》height stands for
rows
CvMat* colReduced = cvCreateMat(bitImage-》height,1,CV_32FC1);
cvReduce(bitImage,rowReduced,0,CV_REDUCE_SUM);
cvReduce(bitImage,colReduced,1,CV_REDUCE_SUM);
// compute imageROI.x
for(int i=0;i《rowReduced-》cols;i++)
{
float temp = CV_MAT_ELEM(*rowReduced,float,0,i);
if(temp》bitImage-》height/3)
{
imageROI.x = i;
break;
}
}
// computer imageROI.width
for(int i=rowReduced-》cols;i》0;i--)
{
float temp = CV_MAT_ELEM(*rowReduced,float,0,i-1);
if(temp》bitImage-》height/3)
{
imageROI.width = i-imageROI.x;
break;
}
}
// compute imageROI.y
for(int i=0;i《colReduced-》rows;i++)
{
float temp = CV_MAT_ELEM(*colReduced,float,i,0);
if(temp》bitImage-》height/3)
{
imageROI.y = i;
break;
}
}
// compute imageROI.height
for(int i=colReduced-》rows;i》0;i--)
{
float temp = CV_MAT_ELEM(*colReduced,float,i-1,0);
if(temp》bitImage-》height/3)
{
imageROI.height = i-imageROI.y;
break;
}
}
// set memory free
cvReleaseImage(&bitImage);
cvReleaseImage(&image8u);
cvReleaseMat(&rowReduced);
cvReleaseMat(&colReduced);
}
}
這是計算深度圖像的濾波范圍。由于深度圖像和彩色圖像的視點(diǎn)不一致,導(dǎo)致了將深度圖像映射到彩色圖像上時有效像素會縮小,典型的現(xiàn)象就是在深度圖像的四周會出現(xiàn)黑色的區(qū)域。這個函數(shù)就是用來將四周的黑色框框去掉。用OpenCV的投影的方法。由于cvReduce()函數(shù)要進(jìn)行累積和的計算,為了不使數(shù)據(jù)溢出,目標(biāo)數(shù)組應(yīng)該用32位的浮點(diǎn)型(此函數(shù)只支持8位unsigned char型和32位float型)。
void kinectDenoising::medianFiltering()
{
// set result image zero
cvSetZero(denoisedImage);
unsigned short data[nFrames];
int total;
for(int i=imageROI.y;i《imageROI.y+imageROI.height;i++)
{
unsigned short* denoisedImageData = (unsigned short*)(denoisedImage-
》imageData+denoisedImage-》widthStep*i);
for(int j=imageROI.x;j《imageROI.x+imageROI.width;j++)
{
total = 0;
for(int k=0;k《nFrames;k++)
{
insertSort(data,total,CV_IMAGE_ELEM(frameSet[k],unsigned
short,i,j));
}
if(total != 0)
{
denoisedImageData[j] = data[total/2];
}
}
}
}
中值濾波,統(tǒng)計有效點(diǎn)并排序,然后取中值。insertSort()函數(shù)用來將值按從小到大的順序進(jìn)行插入,鑒于篇幅的關(guān)系,就不貼出來了。
void kinectDenoising::nearestFiltering()
{
CvPoint topLeft,downRight;
IplImage* tempImage = cvCloneImage(denoisedImage);
for(int i=imageROI.y;i《imageROI.y+imageROI.height;i++)
{
unsigned short* data = (unsigned short*)(denoisedImage-》imageData
+denoisedImage-》widthStep*i);
for(int j=imageROI.x;j《imageROI.x+imageROI.width;j++)
{
for(int k=1;data[j]==0;k++)
{
topLeft = cvPoint(j-k,i-k); // j為行數(shù) i為列數(shù)
downRight = cvPoint(j+k,i+k);
for(int m=topLeft.x;(m《=downRight.x) && (data[j]==0);m++)
{
if(m《0) continue;
if(m》=width) break;
if(topLeft.y》=0)
{
unsigned short temp = CV_IMAGE_ELEM
(tempImage,unsigned short,topLeft.y,m);
if(temp 》 0)
{
data[j] = temp;
break;
}
}
if(downRight.y 《 height)
{
unsigned short temp = CV_IMAGE_ELEM
?。╰empImage,unsigned short,downRight.y,m);
if(temp 》 0)
{
data[j] = temp;
break;
}
}
}
for(int m=topLeft.y;(m《downRight.y) && (data[j]==0);m++)
{
if(m《0) continue;
if(m》=height) break;
if(topLeft.x》0)
{
unsigned short temp = CV_IMAGE_ELEM
?。╰empImage,unsigned short,m,topLeft.x);
if(temp 》 0)
{
data[j] = temp;
break;
}
}
if(downRight.x《width)
{
unsigned short temp = CV_IMAGE_ELEM
?。╰empImage,unsigned short,m,downRight.x);
if(temp 》 0)
{
data[j] = temp;
break;
}
}
}
}
}
}
cvReleaseImage(&tempImage);
}
最后是中值濾波,從最內(nèi)層開始,一層層往外擴(kuò),直到找到有效值為止。
運(yùn)行結(jié)果:
源圖像:
結(jié)果圖像:
附注:本來這個程序是在8位圖像上進(jìn)行的。先取得16位的unsigned short型深度圖像,然后通過cvConvertScale()函數(shù)將其轉(zhuǎn)化為8位的unsigned char型,結(jié)果在進(jìn)行去噪的時候怎么都不對,將unsigned char型的數(shù)據(jù)放到matlab中一看,發(fā)現(xiàn)在unsigned short型數(shù)據(jù)中為0值的像素莫名其妙的在unsigned char型里有了一個很小的值(比如說1, 2, 3, 4, 5什么的,就是不為0)。很奇怪,不知道OpenCV中是怎么搞的??磥磉€是源數(shù)據(jù)靠譜,于是將其改為16位的unsigned short型,結(jié)果形勢一片大好。
========
視頻圖像去模糊常用處理方法
隨著“平安城市”的廣泛建設(shè),各大城市已經(jīng)建有大量的視頻監(jiān)控系統(tǒng),雖然監(jiān)控系統(tǒng)己經(jīng)廣泛地存在于銀行、商場、車站和交通路口等公共場所,但是在公安工作中,由于設(shè)備或者其他條件的限制,案情發(fā)生后的圖像回放都存在圖像不清晰,數(shù)據(jù)不完整的問題,無法為案件的及時偵破提供有效線索。經(jīng)常出現(xiàn)嫌疑人面部特征不清晰,難以辨認(rèn),嫌疑車輛車牌模糊無法辨認(rèn)等問題。這給公安部門破案、法院的取證都帶來了極大的麻煩。隨著平安城市的推廣、各地各類監(jiān)控系統(tǒng)建設(shè)的進(jìn)一步推進(jìn),此類問題會越來越突出。
一.模糊圖像產(chǎn)生的原因
1. 系統(tǒng)自身因素
?。?)鏡頭聚焦不當(dāng)、攝像機(jī)故障等。
?。?)傳輸太遠(yuǎn)、視頻線老化
?。?)光學(xué)鏡頭的極限分辨率和攝像機(jī)不匹配導(dǎo)致的模糊;
(4)相機(jī)分辨率低,欠采樣成像。
2. 自然環(huán)境
?。?)攝像機(jī)罩或鏡頭受臟污、受遮擋等。
?。?)大霧,沙塵、雨雪等環(huán)境影響等。
3. 人為環(huán)境
?。?)環(huán)境電磁干擾;
(2)視頻壓縮算法、傳輸帶寬導(dǎo)致的模糊。
(3)運(yùn)動目標(biāo)高速運(yùn)動導(dǎo)致的運(yùn)動模糊等;
二。 模糊圖像常用的處理方法
對于模糊圖像處理技術(shù),國內(nèi)大學(xué)和科研機(jī)構(gòu)在多年以前就在研究這些理論和應(yīng)用,相關(guān)文獻(xiàn)也發(fā)布了不少,已經(jīng)取得了一些很好的應(yīng)用。當(dāng)前有很多軟件已經(jīng)有了相當(dāng)成熟的一套模糊圖像恢復(fù)方法,在美國FBI及其他執(zhí)法機(jī)構(gòu)中已有多年實(shí)際應(yīng)用,其恢復(fù)出的圖像可以直接當(dāng)作法庭證據(jù)使用,可見模糊圖像處理技術(shù)已經(jīng)取得了相當(dāng)?shù)膶?shí)際應(yīng)用。從技術(shù)方面來向,模糊圖像處理方法主要分為三大類,分別是圖像增強(qiáng)、圖像復(fù)原和超分辨率重構(gòu)。
2.1 圖像增強(qiáng)
增強(qiáng)圖象中的有用信息,它可以是一個失真的過程,其目的是要改善圖像的視覺效果,針對給定圖像的應(yīng)用場合,有目的地強(qiáng)調(diào)圖像的整體或局部特性,將原來不清晰的圖像變得清晰或強(qiáng)調(diào)某些感興趣的特征,擴(kuò)大圖像中不同物體特征之間的差別,抑制不感興趣的特征,使之改善圖像質(zhì)量、豐富信息量,加強(qiáng)圖像判讀和識別效果,滿足某些特殊分析的需要。圖像增強(qiáng)技術(shù)根據(jù)增強(qiáng)處理過程所在的空間不同,可分為基于空域的算法和基于頻域的算法兩大類。
前者把圖像看成一種二維信號,對其進(jìn)行基于二維傅里葉變換的信號增強(qiáng)。采用低通濾波(即只讓低頻信號通過)法,可去掉圖中的噪聲;采用高通濾波法,則可增強(qiáng)邊緣等高頻信號,使模糊的圖片變得清晰。具有代表性的空間域算法有局部求平均值法和中值濾波(取局部鄰域中的中間像素值)法等,它們可用于去除或減弱噪聲。
基于空域的算法分為點(diǎn)運(yùn)算算法和鄰域去噪算法。點(diǎn)運(yùn)算算法即灰度級校正、灰度變換和直方圖修正等,目的或使圖像成像均勻,或擴(kuò)大圖像動態(tài)范圍,擴(kuò)展對比度。鄰域增強(qiáng)算法分為圖像平滑和銳化兩種。平滑一般用于消除圖像噪聲,但是也容易引起邊緣的模糊。常用算法有均值濾波、中值濾波。銳化的目的在于突出物體的邊緣輪廓,便于目標(biāo)識別。常用算法有梯度法、算子、高通濾波、掩模匹配法、統(tǒng)計差值法等。
2.1.1 圖像增強(qiáng)的幾個方面及方法
1.對比度變換:線性變換、非線性變換
2.空間濾波:圖像卷積運(yùn)算、平滑、銳化
3.彩色變換:單波段彩色變換、多波段彩色運(yùn)算、HIS
4.多光譜變換:K-L變換、K-T變換
5.圖像運(yùn)算:插值運(yùn)算、比值運(yùn)算
2.1.2 圖像增強(qiáng)的應(yīng)用概況
圖像增強(qiáng)的方法分為空域法和頻域法兩種,空域法是對圖像中的像素點(diǎn)進(jìn)行操作,用公式描述
如下:
g(x,y)=f(x,y)*h(x,y)
其中是f(x,y)原圖像;h(x,y)為空間轉(zhuǎn)換函數(shù);g(x,y)表示進(jìn)行處理后的圖像。頻域法是間接的處理方法,是先在圖像的頻域中對圖像的變換值進(jìn)行操作,然后變回空域。例如,先對圖像進(jìn)行傅里葉變化到頻域,再對圖像的頻譜進(jìn)行某種濾波修正,最后將修正后的圖像進(jìn)行傅里葉反變化到空域,以此增強(qiáng)圖像。
很多傳統(tǒng)圖像算法都可以減輕圖像的模糊程度, 比如圖像濾波、幾何變換、對比度拉伸、直方圖均衡、空間域銳化、亮度均勻化、形態(tài)學(xué)、顏色處理等。單個來講,這些算法比較成熟,相對簡單。但是對于一個具體的模糊圖像,往往需要上面的一種或者多種算法組合,配合不同的參數(shù)才能達(dá)到理想的效果。
這些算法和參數(shù)的組合進(jìn)一步發(fā)展為具體的增強(qiáng)算法,比如“圖像去霧”算法(可參考何愷明經(jīng)典去霧算法)、“圖像去噪”算法、“圖像銳化”算法、“圖像暗細(xì)節(jié)增強(qiáng)”算法等。
2.2 圖像復(fù)原
2.2.1 圖像復(fù)原概述
在圖像的獲取、傳輸以及保存過程中,由于各種因素,如大氣的湍流效應(yīng)、攝像設(shè)備中光學(xué)系統(tǒng)的衍射、傳感器特性的非線性、光學(xué)系統(tǒng)的像差、成像設(shè)備與物體之間的相對運(yùn)動、感光膠卷的非線性及膠片顆粒噪聲以及電視攝像掃描的非線性等所引起的幾何失真,都難免會造成圖像的畸變和失真。通常,稱由于這些因素引起的質(zhì)量下降為圖像退化。早期的圖像復(fù)原是利用光學(xué)的方法對失真的觀測圖像進(jìn)行校正,而數(shù)字圖像復(fù)原技術(shù)最早則是從對天文觀測圖像的后期處理中逐步發(fā)展起來的。其中一個成功例子是NASA的噴氣推進(jìn)實(shí)驗室在1964年用計算機(jī)處理有關(guān)月球的照片。照片是在空間飛行器上用電視攝像機(jī)拍攝的,圖像的復(fù)原包括消除干擾和噪聲,校正幾何失真和對比度損失以及反卷積。另一個典型的例子是對肯尼迪遇刺事件現(xiàn)場照片的處理。
? ? ? ?由于事發(fā)突然,照片是在相機(jī)移動過程中拍攝的,圖像復(fù)原的主要目的就是消除移動造成的失真。早期的復(fù)原方法有:非鄰域濾波法,最近鄰域濾波法以及效果較好的維納濾波和最小二乘濾波等。目前國內(nèi)外圖像復(fù)原技術(shù)的研究和應(yīng)用主要集中于諸如空間探索、天文觀測、物質(zhì)研究、遙感遙測、軍事科學(xué)、生物科學(xué)、醫(yī)學(xué)影象、交通監(jiān)控、刑事偵察等領(lǐng)域。如生物方面,主要是用于生物活體細(xì)胞內(nèi)部組織的三維再現(xiàn)和重構(gòu),通過復(fù)原熒光顯微鏡所采集的細(xì)胞內(nèi)部逐層切片圖,來重現(xiàn)細(xì)胞內(nèi)部構(gòu)成;醫(yī)學(xué)方面,如對腫瘤周圍組織進(jìn)行顯微觀察,以獲取腫瘤安全切緣與癌腫原發(fā)部位之間關(guān)系的定量數(shù)據(jù);天文方面,如采用迭代盲反卷積進(jìn)行氣動光學(xué)效應(yīng)圖像復(fù)原研究等。
2.2.2 圖像退化模型
圖像復(fù)原問題的有效性關(guān)鍵之一取決于描述圖像退化過程模型的精確性。要建立圖像的退化模型,則首先必須了解、分析圖像退化的機(jī)理并用數(shù)學(xué)模型表現(xiàn)出來。在實(shí)際的圖像處理過程中,圖像均需以數(shù)字離散函數(shù)表示,所以必須將退化模型離散化。
2.2.3 幾種較經(jīng)典的復(fù)原方法介紹
圖像復(fù)原算法有線性和非線性兩類。線性算法通過對圖像進(jìn)行逆濾波來實(shí)現(xiàn)反卷積,這類方法方便快捷,無需循環(huán)或迭代,直接可以得到反卷積結(jié)果,然而,它有一些局限性,比如無法保證圖像的非負(fù)性。而非線性方法通過連續(xù)的迭代過程不斷提高復(fù)原質(zhì)量,直到滿足預(yù)先設(shè)定的終止條件,結(jié)果往往令人滿意。但是迭代程序?qū)е掠嬎懔亢艽?,圖像復(fù)原時耗較長,有時甚至需要幾個小時。所以實(shí)際應(yīng)用中還需要對兩種處理方法綜合考慮,進(jìn)行選擇。
1)維納濾波法
維納濾波法是由Wiener首先提出的,應(yīng)用于一維信號處理,取得了很好的效果。之后,維納濾波法被用于二維信號處理,也取得了不錯的效果,尤其在圖像復(fù)原領(lǐng)域,由于維納濾波計算量小,復(fù)原效果好,從而得到了廣泛的應(yīng)用和發(fā)展。
2)正則濾波法
另一個容易實(shí)現(xiàn)線性復(fù)原的方法稱為約束的最小二乘方濾波,在IPT中稱為正則濾波,并且通過函數(shù)deconvreg來實(shí)現(xiàn)。
3)Lucy-Richardson算法
L-R算法是一種迭代非線性復(fù)原算法,它是從最大似然公式印出來的,圖像用泊松分布加以模型化的。當(dāng)?shù)諗繒r模型的最大似然函數(shù)就可以得到一個令人滿意的方程。
4)盲去卷積
在圖像復(fù)原過程中,最困難的問題之一是,如何獲得PSF的恰當(dāng)估計。那些不以PSF為基礎(chǔ)的圖像復(fù)原方法統(tǒng)稱為盲區(qū)卷積。它以MLE為基礎(chǔ)的,即一種用被隨機(jī)噪聲所干擾的量進(jìn)行估計的最優(yōu)化策略。工具箱通過函數(shù)deconvblind來執(zhí)行盲區(qū)卷積。
2.2.4 圖像復(fù)原與圖像增強(qiáng)
圖像復(fù)原與圖像增強(qiáng)技術(shù)一樣,也是一種改善圖像質(zhì)量的技術(shù)。圖像復(fù)原是根據(jù)圖像退化的先驗知識建立一個退化模型,以此模型為基礎(chǔ),采用各種逆退化處理方法進(jìn)行恢復(fù),改善圖像質(zhì)量。圖像復(fù)原和圖像增強(qiáng)是有區(qū)別的,二者的目的都是為了改善圖像的質(zhì)量。但圖像增強(qiáng)不考慮圖像是如何退化的,只有通過試探各種技術(shù)來增強(qiáng)圖像的視覺效果,而圖像復(fù)原就完全不同,需知道圖像退化過程的先驗知識,據(jù)此找出一種相應(yīng)的逆過程方法,從而得到復(fù)原的圖像。圖像復(fù)原主要取決于對圖像退化過程的先驗知識所掌握的精確程度。對由于離焦、運(yùn)動、大氣湍流等原因引起的圖像模糊,圖像復(fù)原的方法效果較好,常用的算法包括維納濾波算法、小波算法、基于訓(xùn)練的方法等。在知道退化模型的情況下,相對圖像增強(qiáng)來說,圖像復(fù)原可以取得更好的效果。
2.3 圖像超分辨率重構(gòu)
現(xiàn)有的監(jiān)控系統(tǒng)主要目標(biāo)為宏觀場景的監(jiān)視,一個攝像機(jī),覆蓋很大的一個范圍,導(dǎo)致畫面中目標(biāo)太小,人眼很難直接辨認(rèn)。這類由于欠采樣導(dǎo)致的模糊占很大比例,對于由欠采樣導(dǎo)致的模糊需要使用超分辨率重構(gòu)的方法。超分辨率復(fù)原是通過信號處理的方法,在提高圖像的分辨率的同時改善采集圖像質(zhì)量。其核心思想是通過對成像系統(tǒng)截止頻率之外的信號高頻成分估計來提高圖像的分辨率。超分辨率復(fù)原技術(shù)最初只對單幅圖像進(jìn)行處理,這種方法由于可利用的信息只有單幅圖像,圖像復(fù)原效果有著固有的局限。序列圖像的超分辨率復(fù)原技術(shù)旨在采用信號處理方法通過對序列低分辨率退化圖像的處理來獲得一幅或者多幅高分辨率復(fù)原圖像。由于序列圖像復(fù)原可利用幀間的額外信息,比單幅復(fù)原效果更好,是當(dāng)前的研究熱點(diǎn)。
序列圖像的超分辨率復(fù)原主要分為頻域法和空域法兩大類,頻域方法的優(yōu)點(diǎn)是:理論簡單,運(yùn)算復(fù)雜度低,缺點(diǎn)是:只局限于全局平移運(yùn)動和線性空間不變降質(zhì)模型,包含空域先驗知識的能理有限.空域方法所采用的觀測模型涉及全局和局部運(yùn)動、空間可變模糊點(diǎn)擴(kuò)散函數(shù)、非理想亞采樣等,而且具有很強(qiáng)的包含空域先驗約束的能力。常用的空域法有非均勻插值法、迭代反投影方法(IBP)、凸集投影法(POCS)、最大后驗估計法(MAP)、最大似然估計法(ML)、濾波器法等,其中,MAP和POCS法研究較多,發(fā)展空間很大。
三:模糊圖像處理的關(guān)鍵和不足
雖然很多模糊圖像的處理方法在實(shí)際應(yīng)用中取得了很好的效果,但是當(dāng)前仍然有一些因素制約著模糊圖像處理的進(jìn)一步發(fā)展,主要如下:
1、 算法的高度針對性;
絕大部分的模糊圖像處理算法只適用于特定圖像,而算法本身無法智能決定某個算法模塊的開啟還是關(guān)閉。舉例來說,對于有霧的圖像,“去霧算法”可以取得很好的處理效果,但是作用于正常圖像,反而導(dǎo)致圖像效果下降,“去霧算法”模塊的打開或者關(guān)閉需要人工介入。
2、 算法參數(shù)復(fù)雜性;
模糊圖像處理里面所有的算法都會包含大量的參數(shù),這些參數(shù)的選擇需要和實(shí)際的圖像表現(xiàn)相結(jié)合,直接決定最終的處理效果。目前算法還沒有辦法智能選擇這些最優(yōu)參數(shù)。
3、 算法流程的經(jīng)驗性;
由于實(shí)際圖像很復(fù)雜,需要處理多種情況,這就需要一個算法處理流程,對于一個具體的模糊視頻,采用什么樣的處理流程很難做到自動選擇,需要人工選擇一個合適的方法,只能靠人的經(jīng)驗。
四:實(shí)踐和總結(jié)
由于環(huán)境、線路、鏡頭、攝像機(jī)等影響,監(jiān)控系統(tǒng)建成運(yùn)營一段時間后,都會出現(xiàn)一部分的視頻模糊不清的問題。前面提到了針對模糊圖像的各種處理算法,雖然這些算法都取得了一些較好的處理效果,但是再好的算法都是一種后期的補(bǔ)救措施。如果能及時發(fā)現(xiàn)監(jiān)控系統(tǒng)中圖像的各種問題,并及時維修,必然會起到事半功倍的效果。利用先進(jìn)的視頻診斷技術(shù),開發(fā)出適用于各種需求場景的視頻質(zhì)量診斷系統(tǒng)。它能夠?qū)σ曨l圖像出現(xiàn)的模糊、噪聲、亮度異常和視頻丟失等低質(zhì)視頻以及常見攝像機(jī)故障問題進(jìn)行診斷,有效預(yù)防因硬件問題導(dǎo)致的圖像質(zhì)量低下所帶來的損失。從幾路視頻到幾百上千、上萬路視頻,均可高效的進(jìn)行檢測,自動生成檢測報告,提供及時且精準(zhǔn)的維護(hù)信息,第一時間從根源上解決圖像模糊的問題。
? ? ? ? 總體來說,對于不同種類的模糊問題,要區(qū)別對待。對于由鏡頭離焦、灰塵遮擋、線路老化、攝像機(jī)故障等造成的模糊或者圖像質(zhì)量下降,在視頻診斷系統(tǒng)的幫助下,一定要及時維修,從源頭上解決問題。對于低光照等優(yōu)先選擇日夜兩用型高感光度攝像機(jī),對于雨霧、運(yùn)動和前采樣等造成的圖像質(zhì)量下降,可以借助于“視頻增強(qiáng)服務(wù)器”包含的各種模糊圖像處理算法,提升圖像質(zhì)量。
后記
Single-Image Super-Resolution for anime/fan-art using Deep Convolutional NeuralNetworks.waifu2x是采用了最新銳的人工智能技術(shù)“Deep Convolutional Neural Networks”開發(fā)的網(wǎng)絡(luò)服務(wù)。名字來源于海外的動畫粉絲們將喜歡的角色稱作“waifu(即‘我老婆’)”。把縮小的鋸齒狀圖傳到waifu2x的話,“現(xiàn)在給你的圖是某張圖縮小一半的圖。求縮小前的圖哦”,人工智能就會將噪點(diǎn)和鋸齒的部分進(jìn)行補(bǔ)充,生成新的圖。于是“擴(kuò)大時的圖”將不存在了,小的圖變成了擴(kuò)大了的圖,同時還可以去除噪點(diǎn)。
========
opencv 背景差分法 改進(jìn)OTSU閾值去噪
/*
*1)頭文件cvaux.h的庫文件需要鏈接到linker. cvaux210d.lib, cvaux210.lib分別是debug和release版
本。
* 否則有些函數(shù)會出現(xiàn)error:: LINK 2101
*2)cvAdaptiveThreshold, cvThreshold的src和dst圖像必須是同類型的單通道圖像
*3)重要: 函數(shù)退出之后,函數(shù)中的動態(tài)變量會隨著棧的退出全部清空。
* 要保存上次操作的結(jié)果,則在函數(shù)內(nèi)聲明為靜態(tài)變量?;蛘咴谝{(diào)用的函數(shù)里先聲明
*4)cvAdaptiveThreshold()的處理結(jié)果不好,此函數(shù)試圖找出所有的物體輪廓。用在背景差分中,會找到不想要的物體
*5)當(dāng)沒有前景物體時,OTSU算法會把路面顯示出來。因為閾值是自動調(diào)整的。解決辦法,做一個閾值篩選
*6)VedioControl() 未實(shí)現(xiàn)。
* 將cvWaitKey()封裝到VedioControl()中, 如果不觸發(fā)按鍵,VedioControl()不會退出。造成無法自動播放
*
*Date: 2012/4/6
*Author: Rocky Chen
*/
#include 《stdio.h》
#include “stdafx.h”
#include 《cv.h》
#include 《cxcore.h》
#include 《highgui.h》
#include 《iostream》
#include “cvaux.h”
#include “cxmisc.h”
using namespace std;
void BackgroundDiff(IplImage* SrcImg, IplImage* FroundImg, IplImage* BackgroundImg, int
nFrmNum, int threshold_method);
void cvOtsu(IplImage *src, int *thresholdValue);
void PrintVedioInfo(CvCapture* pCapture, IplImage* img);
void VedioControl(); //未實(shí)現(xiàn)
//視頻控制全局變量,
// ‘s’ 畫面stop
// ‘q’ 退出播放
// ‘p’ 打印OTSU算法中找到的閾值
char ctrl = NULL;
int main( int argc, char** argv )
{
//聲明IplImage指針
IplImage* pFrame = NULL;
IplImage* pFroundImg = NULL;
IplImage* pBackgroundImg = NULL;
IplImage* pFroundImg_c = NULL;
IplImage* pBackgroundImg_c = NULL;
//大門背景建模良好 best
// CvCapture* pCapture = cvCreateFileCapture(“D:\\C++ Projects\\OpenCV_project\\test_video
\\gate_11ms_00-30s.avi”);
// CvCapture* pCapture = cvCreateFileCapture(“D:\\C++ Projects\\OpenCV_project\\img_video\
\video.short.mjpg.avi”);
CvCapture* pCapture = cvCreateFileCapture(“D:\\C++ Projects\\OpenCV_project\\img_video\
\video.long.mjpg.avi”);
int nFrmNum = 0;
//創(chuàng)建窗口
cvNamedWindow(“video”, 1);
cvNamedWindow(“background”,1);
cvNamedWindow(“OTSU foreground”,1);
cvNamedWindow(“改進(jìn)的OTSU foreground”,1);
//使窗口有序排列
cvMoveWindow(“video”, 30, 0);
cvMoveWindow(“background”, 360, 0);
cvMoveWindow(“OTSU foreground”, 690, 0);
cvMoveWindow(“改進(jìn)的OTSU foreground”, 690, 320);
//逐幀讀取視頻
while(pFrame = cvQueryFrame( pCapture ))
{
nFrmNum++;
//視頻控制
if( (ctrl = cvWaitKey(1000/180)) ==‘s’ ) cvWaitKey();
else if(ctrl == ‘p’) cout 《《 “Current Frame = ” 《《 nFrmNum 《《 endl;
else if( ctrl ==‘q’ )
break;
if(nFrmNum ==1)
{
pBackgroundImg = cvCreateImage(cvGetSize(pFrame), 8,1);
pFroundImg = cvCreateImage(cvGetSize(pFrame), 8,1);
pBackgroundImg_c = cvCreateImage(cvGetSize(pFrame), 8,1); //對比算法的圖像
pFroundImg_c = cvCreateImage(cvGetSize(pFrame), 8,1);
}
BackgroundDiff(pFrame,pFroundImg,pBackgroundImg, nFrmNum, CV_THRESH_OTSU); //普通OTSU
BackgroundDiff(pFrame,pFroundImg_c,pBackgroundImg_c, nFrmNum, CV_THRESH_BINARY); //閾值篩選
后的OTSU
//打印視頻信息,畫面控制
PrintVedioInfo(pCapture, pFroundImg);
//顯示圖像
cvShowImage(“video”, pFrame);
cvShowImage(“background”, pBackgroundImg);
cvShowImage(“OTSU foreground”, pFroundImg);
cvShowImage(“改進(jìn)的OTSU foreground”, pFroundImg_c);
} //while
//銷毀窗口
cvDestroyAllWindows();
//釋放圖像和矩陣
cvReleaseImage(&pFroundImg);
cvReleaseImage(&pBackgroundImg);
cvReleaseCapture(&pCapture);
return 0;
}
void VedioControl()
{
}
/*
*輸出文字到圖像
*/
void PrintVedioInfo(CvCapture* pCapture, IplImage* img)
{
assert( pCapture != NULL);
double frames = cvGetCaptureProperty(pCapture, CV_CAP_PROP_POS_FRAMES); //視頻當(dāng)前幀數(shù)
double fps = cvGetCaptureProperty(pCapture,CV_CAP_PROP_FPS); //獲得視頻每秒幀數(shù)
char str[255];
sprintf(str,“%4.2f FPS %4.2f frames”,fps,frames); // 將浮點(diǎn)數(shù)轉(zhuǎn)化為字符串
CvPoint location = cvPoint(20,20); // 建立字符串打印的位置
CvScalar color = cvScalar(255,255,255);
CvFont font; //建立字體變量
cvInitFont(&font, CV_FONT_HERSHEY_PLAIN, 1.0,1.0); //字體設(shè)置
cvPutText(img, str, location, &font,color); //打印文本到圖像
}
/********
*背景差分函數(shù),求前景目標(biāo)
*重要: 函數(shù)退出之后,函數(shù)中的動態(tài)變量會隨著棧的退出全部清空。
*要保存上次操作的結(jié)果,則在函數(shù)內(nèi)聲明為靜態(tài)變量。或者在要調(diào)用的函數(shù)里先聲明
*
********/
void BackgroundDiff(IplImage* SrcImg, IplImage* FroundImg, IplImage* BackgroundImg, int
nFrmNum, int threshold_method = CV_THRESH_OTSU)
{
static IplImage* SrcImg_gray = NULL;//源圖像的灰度圖像
static IplImage* SrcImg_grayf =NULL; //單通道浮點(diǎn)圖像用于背景建模
static IplImage* FroundImgf = NULL;
static IplImage* BackgroundImgf = NULL;
static IplImage* FroundImg_temp = NULL;
if(nFrmNum == 1)
{
SrcImg_gray = cvCreateImage(cvGetSize(SrcImg), 8,1);
FroundImg_temp = cvCreateImage(cvGetSize(SrcImg), 8,1);
BackgroundImgf = cvCreateImage(cvGetSize(SrcImg), 32,1); //浮點(diǎn)圖像
FroundImgf = cvCreateImage(cvGetSize(SrcImg), 32,1);
SrcImg_grayf = cvCreateImage(cvGetSize(SrcImg), 32,1);
//RGB圖像先轉(zhuǎn)化成8位單通道圖像,再轉(zhuǎn)化為浮點(diǎn)。
cvCvtColor(SrcImg, BackgroundImg, CV_BGR2GRAY);
cvCvtColor(SrcImg, FroundImg, CV_BGR2GRAY);
cvConvert(BackgroundImg,BackgroundImgf);
cvConvert(FroundImg,F(xiàn)roundImgf);
}
else
{
cvCvtColor(SrcImg, SrcImg_gray, CV_BGR2GRAY); //SrcImg_gray在上次函數(shù)退出的時候被程序棧回收
cvConvert(SrcImg_gray,SrcImg_grayf);
//當(dāng)前幀跟背景圖相減
cvAbsDiff(SrcImg_grayf, BackgroundImgf, FroundImgf);
cvConvert(FroundImgf,F(xiàn)roundImg_temp); //浮點(diǎn)轉(zhuǎn)化為整點(diǎn)
//二值化前景圖
int threshold_otsu =0;
cvOtsu(FroundImg_temp, &threshold_otsu);
if(threshold_method == CV_THRESH_OTSU)
{
cvThreshold(FroundImg_temp, FroundImg, 0, 255.0, CV_THRESH_OTSU); //對比自適應(yīng)閾值化
// cvAdaptiveThreshold(FroundImg_temp, FroundImg, 255.0, 0, 0, 51); //src和dst必須同時是
8bit或浮點(diǎn)圖像
}
else
{
cvThreshold(FroundImg_temp, FroundImg, threshold_otsu, 255.0, CV_THRESH_BINARY);
}
cvSegmentFGMask( FroundImg ); //對前景做連通域分割
//更新背景
cvRunningAvg(SrcImg_grayf, BackgroundImgf, 0.003, 0); //必須是浮點(diǎn)圖像,因為會有小數(shù)出現(xiàn)
cvConvert(BackgroundImgf,BackgroundImg);
}
}
/********
*OTSU大津法
* thresholdValue 為使類間方差最大的閾值
* 當(dāng)找到的閾值小于一個修正閾值,返回此修正閾值。防止沒有前景物體時,將背景找出來
********/
void cvOtsu(IplImage *src, int *thresholdValue)
{
int deltaT = 0; //光照調(diào)節(jié)參數(shù)
uchar grayflag =1;
IplImage* gray = NULL;
if(src-》nChannels != 1) //檢查源圖像是否為灰度圖像
{
gray = cvCreateImage(cvGetSize(src), 8, 1);
cvCvtColor(src, gray, CV_BGR2GRAY);
grayflag = 0;
}
else gray = src;
uchar* ImgData=(uchar*)(gray-》imageData);
int thresholdValue_temp = 1;
int ihist[256]; //圖像直方圖,256個點(diǎn)
int i, imgsize; //循環(huán)變量,圖像尺寸
int n, n1, n2; //n 非零像素個數(shù), n1 前景像素個數(shù), n2 背景像素個數(shù)
double m1, m2, sum, csum, fmax, sb;//m1前景灰度均值,m2背景灰度均值
//對直方圖置零
memset(ihist, 0, sizeof(ihist));
//生成直方圖
imgsize = (gray-》widthStep)*(gray-》height);//圖像數(shù)據(jù)總數(shù)
for (i=0; i《imgsize;i++)
{
ihist[((int)(*ImgData))&255]++;//灰度統(tǒng)計 ‘&255’防止指針溢出
ImgData++;//像素遍歷
}
// set up everything
sum=csum=0.0;
n=0;
for (i=0; i《255; i++)
{
sum+=(double)i*(double)ihist[i]; // x*f(x)質(zhì)量矩
n+= ihist[i]; //f(x)質(zhì)量 像素總數(shù)
}
deltaT = (int)(sum/imgsize); //像素平均灰度
deltaT = deltaT》》1; //與之矯正,delatT = v*n; v=0.5
if (!n)
{//圖像全黑,輸出警告
fprintf (stderr, “NOT NORMAL thresholdValue=160\n”);
}
// OTSU算法
fmax=-1.0;
n1=0;
for (i=0; i《255; i++)
{
n1+= ihist[i];
if (n1==0) {continue;}
n2=n-n1;
if (n2==0) {break;}
csum += (double)i *ihist[i];
m1=csum/n1;
m2=(sum-csum)/n2;
sb=(double)n1*(double)n2*(m1-m2)*(m1-m2); //計算類間方差, 公式已簡化
if (sb》fmax)
{
fmax=sb;
thresholdValue_temp=i; //找到使類間方差最大的灰度值i
}
}
if(thresholdValue_temp 《 20)
*thresholdValue = 20; //閾值篩選
else *thresholdValue = thresholdValue_temp;
if( ctrl == ‘p’) //ctrl = cvWaitKey(100),且是全局變量
{
cout 《《 “OTSU thresholdValue = ” 《《 thresholdValue_temp《《
“, Returned thresholdValue = ” 《《 *thresholdValue《《‘\n’《《endl;
}
if(!grayflag) cvReleaseImage(&gray);
}
/***********
*輪廓提取
************/
void Labeling(IplImage *src, IplImage *dst)
{
CvMemStorage* storage = 0;
storage = cvCreateMemStorage(0); //開辟默認(rèn)大小的空間
CvSeq* contour=0;
cvCopy(src,dst,0);
cvFindContours( dst, storage, &contour, sizeof(CvContour),
CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE ); //外邊緣
int num=0;
for( ;contour!=0; contour=contour-》h_next)
{
CvRect rect;
rect = cvBoundingRect(contour,0);//得到目標(biāo)外接矩形
num++;
if((rect.height + rect.width) 》= 16)
cvRectangle(src,cvPoint(rect.x,rect.y),cvPoint(rect.x+rect.width,rect.y
+rect.height),
CV_RGB(255, 255,255),1,8);//繪制目標(biāo)外接矩形
// cvRectangle(dst,cvPoint(rect.x,rect.y),cvPoint(rect.x+rect.width,rect.y+rect.height),
// CV_RGB(255, 255,255),1,8);//繪制目標(biāo)外接矩形
}
}
========
線性濾波專場:方框濾波、均值濾波與高斯濾波
一、理論與概念講解
1.關(guān)于平滑處理
“平滑處理“(smoothing)也稱“模糊處理”(bluring),是一項簡單且使用頻率很高的圖像處理方法。平滑處理的用途有很多,最常見的是用來減少圖像上的噪點(diǎn)或者失真。在涉及到降低圖像分辨率時,平滑處理是非常好用的方法。
2.濾波與濾波器
首先看一下濾波的概念,濾波是將信號中特定波段頻率濾除的操作,是抑制和防止干擾的一項重要措施。而濾波器就是建立的一個數(shù)學(xué)模型,通過這個模型來將圖像數(shù)據(jù)進(jìn)行能量轉(zhuǎn)化,能量低的就排除掉,噪聲就是屬于低能量部分。一種形象的比喻法是:我們可以把濾波器想象成一個包含加權(quán)系數(shù)的窗口,當(dāng)使用這個濾波器平滑處理圖像時,就把這個窗口放到圖像之上,透過這個窗口來看我們得到的圖像。
? 濾波器的種類有很多, 在新版本的OpenCV中,提供了如下五種常用的圖像平滑處理操作方法,且他們分別被封裝在單獨(dú)的函數(shù)中,使用起來非常方便:
方框濾波——boxblur函數(shù)
均值濾波——blur函數(shù)
高斯濾波——GaussianBlur函數(shù)
中值濾波——medianBlur函數(shù)
雙邊濾波——bilateralFilter函數(shù)
今天我們要講解的是作為線性濾波的方框濾波,均值濾波和高斯濾波。兩種非線性濾波操作——中值濾波和雙邊濾波,我們留待下次講解。
3.對線性濾波器的簡介
線性濾波器:線性濾波器經(jīng)常用于剔除輸入信號中不想要的頻率或者從許多頻率中選擇一個想要的頻率。
幾種常見的線性濾波器:
允許低頻率通過的 低通濾波器 。
允許高頻率通過的 高通濾波器 。
允許一定范圍頻率通過的 帶通濾波器 。
阻止一定范圍頻率通過并且允許其它頻率通過的 帶阻濾波器 。
允許所有頻率通過、僅僅改變相位關(guān)系的 全通濾波器 。
阻止一個狹窄頻率范圍通過的特殊 帶阻濾波器 , 陷波濾波器 (Band-stop filter)。
4.關(guān)于濾波和模糊
關(guān)于濾波和模糊,大家往往在初次接觸的時候會弄混淆,“一會兒說濾波,一會兒又說模糊,什么玩意兒啊”。我們上文已經(jīng)提到過,濾波是將信號中特定波段頻率濾除的操作,是抑制和防止干擾的一項重要措施。為了方便說明,就拿我們經(jīng)常用的高斯濾波來作例子吧。 我們知道,濾波可分低通濾波和高通濾波兩種。 而高斯濾波是指用高斯函數(shù)作為濾波函數(shù)的濾波操作 ,至于是不是模糊,要看是高斯低通還是高斯高通,低通就是模糊,高通就是銳化。其實(shí)說白了是很簡單的,對吧:高斯濾波是指用高斯函數(shù)作為濾波函數(shù)的濾波操作。高斯模糊就是高斯低通濾波。
5.線性濾波
線性濾波是一種常用的鄰域算子,像素的輸出值取決于輸入像素的加權(quán)和,具體過程如圖。圖注:鄰域濾波(卷積):左邊圖像與中間圖像的卷積產(chǎn)生右邊圖像。目標(biāo)圖像中藍(lán)色標(biāo)記的像素是利用原圖像中紅色標(biāo)記的像素計算得到的。線性濾波處理的輸出像素值 是輸入像素值 的加權(quán)和 :其中的加權(quán)和為 ,我們稱其為“核”,濾波器的加權(quán)系數(shù),即濾波器的“濾波系數(shù)”。
上面的式子可以簡單寫作:
其中f表示輸入像素值,h表示加權(quán)系數(shù)“核“,g表示輸出像素值
在新版本的OpenCV中,提供了如下三種常用的線性濾波操作,他們分別被封裝在單獨(dú)的函數(shù)中,使用起來
非常方便:
方框濾波——boxblur函數(shù)
均值濾波——blur函數(shù)
高斯濾波——GaussianBlur函數(shù)
下面我們來對他們進(jìn)行一一介紹。
6.方框濾波(box Filter)
方框濾波(box Filter)被封裝在一個名為boxblur的函數(shù)中,即boxblur函數(shù)的作用是使用方框濾波器(box filter)來模糊一張圖片,從src輸入,從dst輸出。
函數(shù)原型如下:
C++: void boxFilter(InputArray src,OutputArray dst, int ddepth, Size ksize, Point
anchor=Point(-1,-1), boolnormalize=true, int borderType=BORDER_DEFAULT )
參數(shù)詳解:
第一個參數(shù),InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。該函數(shù)對通道是獨(dú)立處
理的,且可以處理任意通道數(shù)的圖片,但需要注意,待處理的圖片深度應(yīng)該為CV_8U, CV_16U, CV_16S,
CV_32F 以及 CV_64F之一。
第二個參數(shù),OutputArray類型的dst,即目標(biāo)圖像,需要和源圖片有一樣的尺寸和類型。
第三個參數(shù),int類型的ddepth,輸出圖像的深度,-1代表使用原圖深度,即src.depth()。
第四個參數(shù),Size類型(對Size類型稍后有講解)的ksize,內(nèi)核的大小。一般這樣寫Size( w,h )來表示
內(nèi)核的大?。?其中,w 為像素寬度, h為像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表
示5x5的核大小
第五個參數(shù),Point類型的anchor,表示錨點(diǎn)(即被平滑的那個點(diǎn)),注意他有默認(rèn)值Point(-1,-1)。如
果這個點(diǎn)坐標(biāo)是負(fù)值的話,就表示取核的中心為錨點(diǎn),所以默認(rèn)值Point(-1,-1)表示這個錨點(diǎn)在核的中心
。
第六個參數(shù),bool類型的normalize,默認(rèn)值為true,一個標(biāo)識符,表示內(nèi)核是否被其區(qū)域歸一化
?。╪ormalized)了。
第七個參數(shù),int類型的borderType,用于推斷圖像外部像素的某種邊界模式。有默認(rèn)值BORDER_DEFAULT
,我們一般不去管它。
boxFilter()函數(shù)方框濾波所用的核為:
其中:
其中f表示原圖,h表示核,g表示目標(biāo)圖,當(dāng)normalize=true的時候,方框濾波就變成了我們熟悉的均值濾波。也就是說,均值濾波是方框濾波歸一化(normalized)后的特殊情況。其中,歸一化就是把要處理的量都縮放到一個范圍內(nèi),比如(0,1),以便統(tǒng)一處理和直觀量化。而非歸一化(Unnormalized)的方框濾波用于計算每個像素鄰域內(nèi)的積分特性,比如密集光流算法(dense optical flow algorithms)中用到的圖像倒數(shù)的協(xié)方差矩陣(covariance matrices of imagederivatives)如果我們要在可變的窗口中計算像素總和,可以使用integral()函數(shù)。
7.均值濾波
均值濾波,是最簡單的一種濾波操作,輸出圖像的每一個像素是核窗口內(nèi)輸入圖像對應(yīng)像素的像素的平均值( 所有像素加權(quán)系數(shù)相等),其實(shí)說白了它就是歸一化后的方框濾波。
我們在下文進(jìn)行源碼剖析時會發(fā)現(xiàn),blur函數(shù)內(nèi)部中其實(shí)就是調(diào)用了一下boxFilter。
下面開始講均值濾波的內(nèi)容吧。
1)均值濾波的理論簡析
均值濾波是典型的線性濾波算法,主要方法為鄰域平均法,即用一片圖像區(qū)域的各個像素的均值來代替原圖像中的各個像素值。一般需要在圖像上對目標(biāo)像素給出一個模板(內(nèi)核),該模板包括了其周圍的臨近像素(比如以目標(biāo)像素為中心的周圍8(3x3-1)個像素,構(gòu)成一個濾波模板,即去掉目標(biāo)像素本身)。再用模板中的全體像素的平均值來代替原來像素值。即對待處理的當(dāng)前像素點(diǎn)(x,y),選擇一個模板,該模板由其近鄰的若干像素組成,求模板中所有像素的均值,再把該均值賦予當(dāng)前像素點(diǎn)(x,y),作為處理后圖像在該點(diǎn)上的灰度個g(x,y),即個g(x,y)=1/m ∑f(x,y) ,其中m為該模板中包含當(dāng)前像素在內(nèi)的像素總個數(shù)。
2)均值濾波的缺陷
均值濾波本身存在著固有的缺陷,即它不能很好地保護(hù)圖像細(xì)節(jié),在圖像去噪的同時也破壞了圖像的細(xì)節(jié)部分,從而使圖像變得模糊,不能很好地去除噪聲點(diǎn)。
3)在OpenCV中使用均值濾波——blur函數(shù)
blur函數(shù)的作用是,對輸入的圖像src進(jìn)行均值濾波后用dst輸出。
blur函數(shù)文檔中,給出的其核是這樣的:
這個內(nèi)核一看就明了,就是在求均值,即blur函數(shù)封裝的就是均值濾波。
blur 函數(shù)的原型:
C++: void blur(InputArray src, OutputArraydst, Size ksize, Point anchor=Point(-1,-1), int
borderType=BORDER_DEFAULT )
第一個參數(shù),InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。該函數(shù)對通道是獨(dú)立處
理的,且可以處理任意通道數(shù)的圖片,但需要注意,待處理的圖片深度應(yīng)該為CV_8U, CV_16U, CV_16S,
CV_32F 以及 CV_64F之一。
第二個參數(shù),OutputArray類型的dst,即目標(biāo)圖像,需要和源圖片有一樣的尺寸和類型。比如可以用
Mat::Clone,以源圖片為模板,來初始化得到如假包換的目標(biāo)圖。
第三個參數(shù),Size類型(對Size類型稍后有講解)的ksize,內(nèi)核的大小。一般這樣寫Size( w,h )來表示
內(nèi)核的大?。?其中,w 為像素寬度, h為像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表
示5x5的核大小
第四個參數(shù),Point類型的anchor,表示錨點(diǎn)(即被平滑的那個點(diǎn)),注意他有默認(rèn)值Point(-1,-1)。如
果這個點(diǎn)坐標(biāo)是負(fù)值的話,就表示取核的中心為錨點(diǎn),所以默認(rèn)值Point(-1,-1)表示這個錨點(diǎn)在核的中心
。
第五個參數(shù),int類型的borderType,用于推斷圖像外部像素的某種邊界模式。有默認(rèn)值BORDER_DEFAULT
,我們一般不去管它。
8.高斯濾波
1)高斯濾波的理論簡析
高斯濾波是一種線性平滑濾波,適用于消除高斯噪聲,廣泛應(yīng)用于圖像處理的減噪過程。通俗的講,高斯濾波就是對整幅圖像進(jìn)行加權(quán)平均的過程,每一個像素點(diǎn)的值,都由其本身和鄰域內(nèi)的其他像素值經(jīng)過加權(quán)平均后得到。高斯濾波的具體操作是:用一個模板(或稱卷積、掩模)掃描圖像中的每一個像素,用模板確定的鄰域內(nèi)像素的加權(quán)平均灰度值去替代模板中心像素點(diǎn)的值。大家常常說高斯濾波最有用的濾波操作,雖然它用起來,效率往往不是最高的。高斯模糊技術(shù)生成的圖像,其視覺效果就像是經(jīng)過一個半透明屏幕在觀察圖像,這與鏡頭焦外成像效果散景以及普通照明陰影中的效果都明顯不同。高斯平滑也用于計算機(jī)視覺算法中的預(yù)先處理階段,以增強(qiáng)圖像在不同比例大小下的圖像效果(參見尺度空間表示以及尺度空間實(shí)現(xiàn))。從數(shù)學(xué)的角度來看,圖像的高斯模糊過程就是圖像與正態(tài)分布做卷積。由于正態(tài)分布又叫作高斯分布,所以這項技術(shù)就叫作高斯模糊。
? ? ? ? 圖像與圓形方框模糊做卷積將會生成更加精確的焦外成像效果。由于高斯函數(shù)的傅立葉變換是另外一個高斯函數(shù),所以高斯模糊對于圖像來說就是一個低通濾波操作。高斯濾波器是一類根據(jù)高斯函數(shù)的形狀來選擇權(quán)值的線性平滑濾波器。高斯平滑濾波器對于抑制服從正態(tài)分布的噪聲非常有效。一維零均值高斯函數(shù)為:其中,高斯分布參數(shù)Sigma決定了高斯函數(shù)的寬度。對于圖像處理來說,常用二維零均值離散高斯函數(shù)作平滑濾波器。
二維高斯函數(shù)為:
2)在OpenCV中使用高斯濾波——GaussianBlur函數(shù)
GaussianBlur函數(shù)的作用是用高斯濾波器來模糊一張圖片,對輸入的圖像src進(jìn)行高斯濾波后用dst輸出。
它將源圖像和指定的高斯核函數(shù)做卷積運(yùn)算,并且支持就地過濾(In-placefiltering)。
C++: void GaussianBlur(InputArray src,OutputArray dst, Size ksize, double sigmaX, double
sigmaY=0, intborderType=BORDER_DEFAULT )
第一個參數(shù),InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。它可以是單獨(dú)的任意通
道數(shù)的圖片,但需要注意,圖片深度應(yīng)該為CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二個參數(shù),OutputArray類型的dst,即目標(biāo)圖像,需要和源圖片有一樣的尺寸和類型。比如可以用
Mat::Clone,以源圖片為模板,來初始化得到如假包換的目標(biāo)圖。
第三個參數(shù),Size類型的ksize高斯內(nèi)核的大小。其中ksize.width和ksize.height可以不同,但他們都必
須為正數(shù)和奇數(shù)。或者,它們可以是零的,它們都是由sigma計算而來。
第四個參數(shù),double類型的sigmaX,表示高斯核函數(shù)在X方向的的標(biāo)準(zhǔn)偏差。
第五個參數(shù),double類型的sigmaY,表示高斯核函數(shù)在Y方向的的標(biāo)準(zhǔn)偏差。若sigmaY為零,就將它設(shè)為
sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height計算出來。
為了結(jié)果的正確性著想,最好是把第三個參數(shù)Size,第四個參數(shù)sigmaX和第五個參數(shù)sigmaY全部指定到。
第六個參數(shù), int類型的borderType,用于推斷圖像外部像素的某種邊界模式。有默認(rèn)值BORDER_DEFAULT,我們一般不去管它。
嗯,第一部分的理論介紹大概就是這些了,我們接著進(jìn)入第二部分,OpenCV的源碼剖析。
二、深入——OpenCV源碼剖析
《1》OpenCV中boxFilter函數(shù)源碼解析
我們可以在OpenCV的安裝路徑的\sources\modules\imgproc\src下的smooth.cpp源文件的第711行找到
boxFilter函數(shù)的源代碼。對應(yīng)于淺墨將OpenCV 2.4.8安裝在D:\Program Files\opencv的路徑下,那么,
smooth.cpp文件就在 D:\ProgramFiles\opencv\sources\modules\imgproc\src路徑下。
//-----------------------------------【boxFilter()函數(shù)中文注釋版源代
碼】----------------------------
// 代碼作用:進(jìn)行box Filter濾波操作的函數(shù)
// 說明:以下代碼為來自于計算機(jī)開源視覺庫OpenCV的官方源代碼
// OpenCV源代碼版本:2.4.8
// 源碼路徑:…\opencv\sources\modules\imgproc\src\smooth.cpp
// 源文件中如下代碼的起始行數(shù):711行
// 中文注釋by淺墨
//------------------------------------------------------------------------------------------
--------------
void cv::boxFilter( InputArray _src,OutputArray _dst, int ddepth,
Size ksize, Point anchor,
bool normalize, int borderType)
{
Mat src = _src.getMat();//拷貝源圖的形參Mat數(shù)據(jù)到臨時變量,用于稍后的操作
int sdepth =src.depth(), cn = src.channels();//定義int型臨時變量,代表源圖深度的sdepth,源圖
通道的引用cn
//處理ddepth小于零的情況
if( ddepth 《 0 )
ddepth = sdepth;
_dst.create( src.size(), CV_MAKETYPE(ddepth, cn) );//初始化目標(biāo)圖
Mat dst =_dst.getMat();//拷貝目標(biāo)圖的形參Mat數(shù)據(jù)到臨時變量,用于稍后的操作
//處理 borderType不為 BORDER_CONSTANT 且normalize為真的情況
if( borderType != BORDER_CONSTANT && normalize ){
if( src.rows == 1 )
ksize.height = 1;
if( src.cols == 1 )
ksize.width = 1;
}
//若之前有過HAVE_TEGRA_OPTIMIZATION優(yōu)化選項的定義,則執(zhí)行宏體中的tegra優(yōu)化版函數(shù)并返回
#ifdef HAVE_TEGRA_OPTIMIZATION
if ( tegra::box(src, dst, ksize, anchor, normalize, borderType) )
return;
#endif
//調(diào)用FilterEngine濾波引擎,正式開始濾波操作
Ptr《FilterEngine》 f = createBoxFilter( src.type(), dst.type(),
ksize, anchor,normalize, borderType );
f-》apply( src, dst );
}
其中的Ptr是用來動態(tài)分配的對象的智能指針模板類。可以發(fā)現(xiàn),函數(shù)的內(nèi)部代碼思路是很清晰的,先拷
貝源圖的形參Mat數(shù)據(jù)到臨時變量,定義一些臨時變量,在處理ddepth小于零的情況,接著處理
borderType不為 BORDER_CONSTANT 且normalize為真的情況,最終調(diào)用FilterEngine濾波引擎創(chuàng)建一個
BoxFilter,正式開始濾波操作。
這里的FilterEngine是OpenCV圖像濾波功能的核心引擎,我們有必要詳細(xì)剖析看其源代碼。
《2》FilterEngine 類解析——OpenCV圖像濾波核心引擎
FilterEngine類是OpenCV關(guān)于圖像濾波的主力軍類,OpenCV圖像濾波功能的核心引擎。各種濾波函數(shù)比如
blur, GaussianBlur,到頭來其實(shí)是就是在函數(shù)末尾處定義了一個Ptr《FilterEngine》類型的f,然后f-
》apply( src, dst )了一下而已。
這個類可以把幾乎是所有的濾波操作施加到圖像上。它包含了所有必要的中間緩存器。有很多和濾波相關(guān)
的create系函數(shù)的返回值直接就是Ptr《FilterEngine》。比如cv::createSeparableLinearFilter(),
cv::createLinearFilter(),cv::createGaussianFilter(), cv::createDerivFilter(),
cv::createBoxFilter() 和cv::createMorphologyFilter()。,這里給出其中一個函數(shù)的原型吧:
Ptr《FilterEngine》createLinearFilter(int srcType, int dstType, InputArray kernel,
Point_anchor=Point(-1,-1), double delta=0, int rowBorderType=BORDER_DEFAULT,
intcolumnBorderType=-1, const Scalar& borderValue=Scalar() )
上面我們提到過了,其中的Ptr是用來動態(tài)分配的對象的智能指針模板類,而上面的尖括號里面的模板參
數(shù)就是FilterEngine。
使用FilterEngine類可以分塊處理大量的圖像,構(gòu)建復(fù)雜的管線,其中就包含一些進(jìn)行濾波階段。如果我
們需要使用預(yù)先定義好的的濾波操作,cv::filter2D(), cv::erode(),以及cv::dilate(),可以選擇,他
們不依賴于FilterEngine,自立自強(qiáng),在自己函數(shù)體內(nèi)部就實(shí)現(xiàn)了FilterEngine提供的功能。不像其他的
諸如我們今天講的blur系列函數(shù),依賴于FilterEngine引擎。
我們看下其類聲明經(jīng)過淺墨詳細(xì)注釋的源碼:
//-----------------------------------【FilterEngine類中文注釋版源代
碼】----------------------------
// 代碼作用:FilterEngine類,OpenCV圖像濾波功能的核心引擎
// 說明:以下代碼為來自于計算機(jī)開源視覺庫OpenCV的官方源代碼
// OpenCV源代碼版本:2.4.8
// 源碼路徑:…\opencv\sources\modules\imgproc\include\opencv2\imgproc\imgproc.hpp
// 源文件中如下代碼的起始行數(shù):222行
// 中文注釋by淺墨
//------------------------------------------------------------------------------------------
--------------
class CV_EXPORTS FilterEngine
{
public:
//默認(rèn)構(gòu)造函數(shù)
FilterEngine();
//完整的構(gòu)造函數(shù)。 _filter2D 、_rowFilter 和 _columnFilter之一,必須為非空
FilterEngine(const Ptr《BaseFilter》& _filter2D,
constPtr《BaseRowFilter》& _rowFilter,
constPtr《BaseColumnFilter》& _columnFilter,
int srcType, int dstType, intbufType,
int_rowBorderType=BORDER_REPLICATE,
int _columnBorderType=-1,
const Scalar&_borderValue=Scalar());
//默認(rèn)析構(gòu)函數(shù)
virtual ~FilterEngine();
//重新初始化引擎。釋放之前濾波器申請的內(nèi)存。
void init(const Ptr《BaseFilter》& _filter2D,
constPtr《BaseRowFilter》& _rowFilter,
constPtr《BaseColumnFilter》& _columnFilter,
int srcType, int dstType, intbufType,
int_rowBorderType=BORDER_REPLICATE, int _columnBorderType=-1,
const Scalar&_borderValue=Scalar());
//開始對指定了ROI區(qū)域和尺寸的圖片進(jìn)行濾波操作
virtual int start(Size wholeSize, Rect roi, int maxBufRows=-1);
//開始對指定了ROI區(qū)域的圖片進(jìn)行濾波操作
virtual int start(const Mat& src, const Rect&srcRoi=Rect(0,0,-1,-1),
bool isolated=false, intmaxBufRows=-1);
//處理圖像的下一個srcCount行(函數(shù)的第三個參數(shù))
virtual int proceed(const uchar* src, int srcStep, int srcCount,
uchar* dst, intdstStep);
//對圖像指定的ROI區(qū)域進(jìn)行濾波操作,若srcRoi=(0,0,-1,-1),則對整個圖像進(jìn)行濾波操作
virtual void apply( const Mat& src, Mat& dst,
const Rect&srcRoi=Rect(0,0,-1,-1),
Point dstOfs=Point(0,0),
bool isolated=false);
//如果濾波器可分離,則返回true
boolisSeparable() const { return (const BaseFilter*)filter2D == 0; }
//返回輸入和輸出行數(shù)
int remainingInputRows() const;
intremainingOutputRows() const;
//一些成員參數(shù)定義
int srcType, dstType, bufType;
Size ksize;
Point anchor;
int maxWidth;
Size wholeSize;
Rect roi;
int dx1, dx2;
int rowBorderType, columnBorderType;
vector《int》 borderTab;
int borderElemSize;
vector《uchar》 ringBuf;
vector《uchar》 srcRow;
vector《uchar》 constBorderValue;
vector《uchar》 constBorderRow;
int bufStep, startY, startY0, endY, rowCount, dstY;
vector《uchar*》 rows;
Ptr《BaseFilter》 filter2D;
Ptr《BaseRowFilter》 rowFilter;
Ptr《BaseColumnFilter》 columnFilter;
};
《3》OpenCV中size類型剖析
size類型我們也講一下, 通過轉(zhuǎn)到定義,我們可以在……\opencv\sources\modules\core\include
\opencv2\core\core.hpp路徑下,對應(yīng)于淺墨的OpenCV安裝路徑,就是在
D:\ProgramFiles\opencv\sources\modules\core\include\opencv2\core\core.hpp下,找到其原型聲明
?。?/p>
typedef Size_《int》 Size2i;
typedef Size2i Size;
Size_ 是個模板類,在這里Size_《int》表示其類體內(nèi)部的模板所代表的類型為int。
那這兩句的意思,就是首先給已知的數(shù)據(jù)類型Size_《int》起個新名字,叫Size2i。
然后又給已知的數(shù)據(jù)類型Size2i起個新名字,叫Size。
所以,連起來就是,Size_《int》、Size2i、Size這三個類型名等價。
然后我們追根溯源,找到Size_模板類的定義:
//-----------------------------------【Size_類中文注釋版源代碼】----------------------------
// 代碼作用:作為尺寸相關(guān)數(shù)據(jù)結(jié)構(gòu)的Size_ 模板類
// 說明:以下代碼為來自于計算機(jī)開源視覺庫OpenCV的官方源代碼
// OpenCV源代碼版本:2.4.8
// 源碼路徑:…\opencv\sources\modules\core\include\opencv2\core\core.hpp
// 源文件中如下代碼的起始行數(shù):816行
// 中文注釋by淺墨
//------------------------------------------------------------------------------------------
--------------
template《typename _Tp》 class Size_
{
public:
typedef _Tp value_type;
//不同的構(gòu)造函數(shù)定義
Size_();
Size_(_Tp _width, _Tp _height);
Size_(const Size_& sz);
Size_(const CvSize& sz);
Size_(const CvSize2D32f& sz);
Size_(const Point_《_Tp》& pt);
Size_& operator = (const Size_& sz);
//區(qū)域(width*height)
_Tp area() const;
//轉(zhuǎn)化另一種數(shù)據(jù)類型。
template《typename_Tp2》 operator Size_《_Tp2》() const;
//轉(zhuǎn)換為舊式的OpenCV類型。
operator CvSize() const;
operator CvSize2D32f() const;
_Tp width, height; //寬度和高度,常用屬性
};
可以看到Size_模板類的內(nèi)部又是重載了一些構(gòu)造函數(shù)以滿足我們的需要,其中,我們用得最多的是如下
這個構(gòu)造函數(shù):
Size_(_Tp _width, _Tp _height);
另外,代碼末尾定義了模板類型的寬度和高度:
_Tp width, height; //寬度和高度
于是我們可以用XXX. width和XXX.height來分別表示其寬度和高度。
給大家一個示例,方便理解:
Size(5, 5);//構(gòu)造出的Size寬度和高度都為5,即XXX.width和XXX.height都為5
《4》OpenCV中blur函數(shù)源碼剖析
我們可以在OpenCV的安裝路徑的\sources\modules\imgproc\src下的smooth.cpp源文件中找到blur的源代
碼。對應(yīng)于淺墨將OpenCV 2.4.8安裝在D:\Program Files\opencv下,那么,smooth.cpp文件就在 D:
\ProgramFiles\opencv\sources\modules\imgproc\src路徑下。
一起來看一下OpenCV中blur函數(shù)定義的真面目:
//-----------------------------------【blur()函數(shù)中文注釋版源代碼】----------------------------
// 代碼作用:進(jìn)行blur均值濾波操作的函數(shù)
// 說明:以下代碼為來自于計算機(jī)開源視覺庫OpenCV的官方源代碼
// OpenCV源代碼版本:2.4.8
// 源碼路徑:…\opencv\sources\modules\imgproc\src\smooth.cpp
// 源文件中如下代碼的起始行數(shù):738行
// 中文注釋by淺墨
//------------------------------------------------------------------------------------------
--------------
void cv::blur(InputArray src, OutputArray dst,
Size ksize, Point anchor, int borderType )
{
//調(diào)用boxFilter函數(shù)進(jìn)行處理
boxFilter( src, dst, -1, ksize, anchor, true, borderType );
}
可以看到在blur函數(shù)內(nèi)部就是調(diào)用了一個boxFilter函數(shù),且第六個參數(shù)為true,即我們上文所說的
normalize=true,即均值濾波是均一化后的方框濾波。
《5》OpenCV中GaussianBlur函數(shù)源碼剖析
最后,我們看一下OpenCV中GaussianBlur函數(shù)源代碼:
//-----------------------------------【GaussianBlur()函數(shù)中文注釋版源代碼】-----------------------
// 代碼作用:封裝高斯濾波的GaussianBlur()函數(shù)
// 說明:以下代碼為來自于計算機(jī)開源視覺庫OpenCV的官方源代碼
// OpenCV源代碼版本:2.4.8
// 源碼路徑:…\opencv\sources\modules\imgproc\src\smooth.cpp
// 源文件中如下代碼的起始行數(shù):832行
// 中文注釋by淺墨
//------------------------------------------------------------------------------------------
--------------
void cv::GaussianBlur( InputArray _src,OutputArray _dst, Size ksize,
double sigma1, doublesigma2,
int borderType )
{
//拷貝形參Mat數(shù)據(jù)到臨時變量,用于稍后的操作
Mat src = _src.getMat();
_dst.create( src.size(), src.type() );
Mat dst =_dst.getMat();
//處理邊界選項不為BORDER_CONSTANT時的情況
if( borderType != BORDER_CONSTANT )
{
if( src.rows == 1 )
ksize.height = 1;
if( src.cols == 1 )
ksize.width = 1;
}
//若ksize長寬都為1,將源圖拷貝給目標(biāo)圖
if( ksize.width == 1 && ksize.height == 1 )
{
src.copyTo(dst);
return;
}
//若之前有過HAVE_TEGRA_OPTIMIZATION優(yōu)化選項的定義,則執(zhí)行宏體中的tegra優(yōu)化版函數(shù)并返回
#ifdef HAVE_TEGRA_OPTIMIZATION
if(sigma1 == 0 && sigma2 == 0 && tegra::gaussian(src,dst, ksize, borderType))
return;
#endif
//如果HAVE_IPP&& (IPP_VERSION_MAJOR 》= 7為真,則執(zhí)行宏體中語句
#if defined HAVE_IPP &&(IPP_VERSION_MAJOR 》= 7)
if(src.type() == CV_32FC1 && sigma1 == sigma2 &&ksize.width == ksize.height && sigma1 !=
0.0 )
{
IppiSize roi = {src.cols, src.rows};
int bufSize = 0;
ippiFilterGaussGetBufferSize_32f_C1R(roi, ksize.width, &bufSize);
AutoBuffer《uchar》 buf(bufSize+128);
if( ippiFilterGaussBorder_32f_C1R((const Ipp32f *)src.data,(int)src.step,
?。↖pp32f *)dst.data, (int)dst.step,
roi,ksize.width, (Ipp32f)sigma1,
(IppiBorderType)borderType, 0.0,
alignPtr(&buf[0],32)) 》= 0 )
return;
}
#endif
//調(diào)動濾波引擎,正式進(jìn)行高斯濾波操作
Ptr《FilterEngine》 f = createGaussianFilter( src.type(), ksize,sigma1, sigma2, borderType
);
f-》apply( src, dst );
}
嗯,今天的源碼解析就到這里吧,原理部分學(xué)完,深入OpenCV源碼部分也學(xué)完,相信大家應(yīng)該對OpenCV中的線性濾波有了比較詳細(xì)的認(rèn)識,已經(jīng)躍躍欲試想看這個幾個函數(shù)用起來可以得出什么效果了。
三、淺出——線性濾波函數(shù)快速上手攻略
這一部分的內(nèi)容就是為了大家能快速上手 boxFilter、blur和GaussianBlur 這三個函數(shù)所準(zhǔn)備的。還等什么呢,開始吧。
《1》boxFilter函數(shù)——方框濾波
boxFilter的函數(shù)作用是使用方框濾波(box filter)來模糊一張圖片,由src輸入,dst輸出。
函數(shù)原型如下:
C++: void boxFilter(InputArray src,OutputArray dst, int ddepth, Size ksize, Point
anchor=Point(-1,-1), boolnormalize=true, int borderType=BORDER_DEFAULT )
參數(shù)詳解如下:
第一個參數(shù),InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。該函數(shù)對通道是獨(dú)立處
理的,且可以處理任意通道數(shù)的圖片,但需要注意,待處理的圖片深度應(yīng)該為CV_8U, CV_16U, CV_16S,
CV_32F 以及 CV_64F之一。
第二個參數(shù),OutputArray類型的dst,即目標(biāo)圖像,需要和源圖片有一樣的尺寸和類型。
第三個參數(shù),int類型的ddepth,輸出圖像的深度,-1代表使用原圖深度,即src.depth()。
第四個參數(shù),Size類型的ksize,內(nèi)核的大小。一般這樣寫Size( w,h )來表示內(nèi)核的大?。?其中,w 為像
素寬度, h為像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
第五個參數(shù),Point類型的anchor,表示錨點(diǎn)(即被平滑的那個點(diǎn)),注意他有默認(rèn)值Point(-1,-1)。如
果這個點(diǎn)坐標(biāo)是負(fù)值的話,就表示取核的中心為錨點(diǎn),所以默認(rèn)值Point(-1,-1)表示這個錨點(diǎn)在核的中心
。
第六個參數(shù),bool類型的normalize,默認(rèn)值為true,一個標(biāo)識符,表示內(nèi)核是否被其區(qū)域歸一化
?。╪ormalized)了。
第七個參數(shù),int類型的borderType,用于推斷圖像外部像素的某種邊界模式。有默認(rèn)值BORDER_DEFAULT
,我們一般不去管它。
調(diào)用代碼示范如下:
//載入原圖
Matimage=imread(“2.jpg”);
//進(jìn)行均值濾波操作
Matout;
boxFilter(image, out, -1,Size(5, 5));
用上面三句核心代碼架起來的完整程序代碼:
//-----------------------------------【頭文件包含部
分】---------------------------------------
// 描述:包含程序所依賴的頭文件
//------------------------------------------------------------------------------------------
----
#include “opencv2/core/core.hpp”
#include“opencv2/highgui/highgui.hpp”
#include“opencv2/imgproc/imgproc.hpp”
//-----------------------------------【命名空間聲明部
分】---------------------------------------
// 描述:包含程序所使用的命名空間
//------------------------------------------------------------------------------------------
-----
using namespace cv;
//-----------------------------------【main( )函
數(shù)】--------------------------------------------
// 描述:控制臺應(yīng)用程序的入口函數(shù),我們的程序從這里開始
//------------------------------------------------------------------------------------------
-----
int main( )
{
//載入原圖
Matimage=imread(“2.jpg”);
//創(chuàng)建窗口
namedWindow(“均值濾波【原圖】” );
namedWindow(“均值濾波【效果圖】”);
//顯示原圖
imshow(“均值濾波【原圖】”, image );
//進(jìn)行濾波操作
Matout;
boxFilter(image, out, -1,Size(5, 5));
//顯示效果圖
imshow(“均值濾波【效果圖】” ,out );
waitKey(0 );
}
運(yùn)行效果圖(內(nèi)核大小Size(5, 5)):
《2》blur函數(shù)——均值濾波
blur的作用是對輸入的圖像src進(jìn)行均值濾波后用dst輸出。
函數(shù)原型如下:
C++: void blur(InputArray src, OutputArraydst, Size ksize, Point anchor=Point(-1,-1), int
borderType=BORDER_DEFAULT )
參數(shù)詳解如下:
第一個參數(shù),InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。該函數(shù)對通道是獨(dú)立處
理的,且可以處理任意通道數(shù)的圖片,但需要注意,待處理的圖片深度應(yīng)該為CV_8U, CV_16U, CV_16S,
CV_32F 以及 CV_64F之一。
第二個參數(shù),OutputArray類型的dst,即目標(biāo)圖像,需要和源圖片有一樣的尺寸和類型。比如可以用
Mat::Clone,以源圖片為模板,來初始化得到如假包換的目標(biāo)圖。
第三個參數(shù),Size類型(對Size類型稍后有講解)的ksize,內(nèi)核的大小。一般這樣寫Size( w,h )來表示
內(nèi)核的大?。?其中,w 為像素寬度, h為像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表
示5x5的核大小
第四個參數(shù),Point類型的anchor,表示錨點(diǎn)(即被平滑的那個點(diǎn)),注意他有默認(rèn)值Point(-1,-1)。如果這個點(diǎn)坐標(biāo)是負(fù)值的話,就表示取核的中心為錨點(diǎn),所以默認(rèn)值Point(-1,-1)表示這個錨點(diǎn)在核的中心。
第五個參數(shù),int類型的borderType,用于推斷圖像外部像素的某種邊界模式。有默認(rèn)值BORDER_DEFAULT
,我們一般不去管它。
調(diào)用代碼示范:
//載入原圖
Matimage=imread(“1.jpg”);
//進(jìn)行均值濾波操作
Matout;
blur(image, out, Size(7, 7));
為了大家的理解和應(yīng)用方便,還是給出用上面三句核心代碼架起來完整程序的代碼:
//-----------------------------------【頭文件包含部分】---------------------------------------
// 描述:包含程序所依賴的頭文件
//------------------------------------------------------------------------------------------
----
#include “opencv2/core/core.hpp”
#include“opencv2/highgui/highgui.hpp”
#include“opencv2/imgproc/imgproc.hpp”
#include 《stdio.h》
//-----------------------------------【命名空間聲明部分】---------------------------------------
// 描述:包含程序所使用的命名空間
//------------------------------------------------------------------------------------------
-----
using namespace cv;
//-----------------------------------【main( )函數(shù)】--------------------------------------------
// 描述:控制臺應(yīng)用程序的入口函數(shù),我們的程序從這里開始
//------------------------------------------------------------------------------------------
-----
int main( )
{
//載入原圖
Matimage=imread(“1.jpg”);
//創(chuàng)建窗口
namedWindow(“均值濾波【原圖】” );
namedWindow(“均值濾波【效果圖】”);
//顯示原圖
imshow(“均值濾波【原圖】”, image );
//進(jìn)行濾波操作
Matout;
blur(image, out, Size(7, 7));
//顯示效果圖
imshow(“均值濾波【效果圖】” ,out );
waitKey(0 );
}
運(yùn)行效果圖(內(nèi)核大小Size(7, 7)):
《3》GaussianBlur函數(shù)——高斯濾波
GaussianBlur函數(shù)的作用是用高斯濾波器來模糊一張圖片,對輸入的圖像src進(jìn)行高斯濾波后用dst輸出。
函數(shù)原型如下:
C++: void GaussianBlur(InputArray src,OutputArray dst, Size ksize, double sigmaX, double
sigmaY=0, intborderType=BORDER_DEFAULT )
參數(shù)詳解如下:
第一個參數(shù),InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。它可以是單獨(dú)的任意通
道數(shù)的圖片,但需要注意,圖片深度應(yīng)該為CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二個參數(shù),OutputArray類型的dst,即目標(biāo)圖像,需要和源圖片有一樣的尺寸和類型。比如可以用
Mat::Clone,以源圖片為模板,來初始化得到如假包換的目標(biāo)圖。
第三個參數(shù),Size類型的ksize高斯內(nèi)核的大小。其中ksize.width和ksize.height可以不同,但他們都必
須為正數(shù)和奇數(shù)?;蛘?,它們可以是零的,它們都是由sigma計算而來。
第四個參數(shù),double類型的sigmaX,表示高斯核函數(shù)在X方向的的標(biāo)準(zhǔn)偏差。
第五個參數(shù),double類型的sigmaY,表示高斯核函數(shù)在Y方向的的標(biāo)準(zhǔn)偏差。若sigmaY為零,就將它設(shè)為
sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height計算出來。
為了結(jié)果的正確性著想,最好是把第三個參數(shù)Size,第四個參數(shù)sigmaX和第五個參數(shù)sigmaY全部指定到。
第六個參數(shù),int類型的borderType,用于推斷圖像外部像素的某種邊界模式。注意它有默認(rèn)值
BORDER_DEFAULT。
調(diào)用示例:
//載入原圖
Matimage=imread(“1.jpg”);
//進(jìn)行濾波操作
Matout;
blur(image, out, Size(5, 5));
用上面三句核心代碼架起來的完整程序代碼:
//-----------------------------------【頭文件包含部分】---------------------------------------
// 描述:包含程序所依賴的頭文件
//------------------------------------------------------------------------------------------
----
#include “opencv2/core/core.hpp”
#include“opencv2/highgui/highgui.hpp”
#include“opencv2/imgproc/imgproc.hpp”
#include 《stdio.h》
//-----------------------------------【命名空間聲明部分】---------------------------------------
// 描述:包含程序所使用的命名空間
//------------------------------------------------------------------------------------------
-----
using namespace cv;
//-----------------------------------【main( )函數(shù)】--------------------------------------------
// 描述:控制臺應(yīng)用程序的入口函數(shù),我們的程序從這里開始
//------------------------------------------------------------------------------------------
-----
int main( )
{
//載入原圖
Matimage=imread(“1.jpg”);
//創(chuàng)建窗口
namedWindow(“均值濾波【原圖】” );
namedWindow(“均值濾波【效果圖】”);
//顯示原圖
imshow(“均值濾波【原圖】”, image );
//進(jìn)行均值濾波操作
Matout;
GaussianBlur(image, out, Size( 3, 3 ), 0, 0 );
//顯示效果圖
imshow(“均值濾波【效果圖】” ,out );
waitKey(0 );
}
運(yùn)行效果圖(內(nèi)核大小Size(5, 5)):
四、圖像線性濾波綜合示例 程序
依然是每篇文章都會配給大家的一個詳細(xì)注釋的博文配套示例程序,把這篇文章中介紹的知識點(diǎn)以代碼為載體,展現(xiàn)給大家。
這個示例程序中可以用軌跡條來控制三種線性濾波的核參數(shù)值,通過滑動滾動條,就可以控制圖像在三種線性濾波下的模糊度,有一定的可玩性。廢話不多說,上代碼吧:
//-----------------------------------【程序說明】----------------------------------------------
// 程序名稱::【OpenCV入門教程之八】線性濾波專場:方框濾波、均值濾波與高斯濾波 配
套源碼
// 開發(fā)所用OpenCV版本:2.4.8
// 2014年3月31 日 Create by 淺墨
//------------------------------------------------------------------------------------------
------
//-----------------------------------【頭文件包含部分】---------------------------------------
// 描述:包含程序所依賴的頭文件
//------------------------------------------------------------------------------------------
----
#include 《opencv2/core/core.hpp》
#include《opencv2/highgui/highgui.hpp》
#include 《opencv2/imgproc/imgproc.hpp》
#include 《iostream》
//-----------------------------------【命名空間聲明部分】---------------------------------------
// 描述:包含程序所使用的命名空間
//------------------------------------------------------------------------------------------
-----
using namespace std;
using namespace cv;
//-----------------------------------【全局變量聲明部分】--------------------------------------
// 描述:全局變量聲明
//------------------------------------------------------------------------------------------
-----
Matg_srcImage,g_dstImage1,g_dstImage2,g_dstImage3;//存儲圖片的Mat類型
int g_nBoxFilterValue=3; //方框濾波參數(shù)值
int g_nMeanBlurValue=3; //均值濾波參數(shù)值
int g_nGaussianBlurValue=3; //高斯濾波參數(shù)值
//-----------------------------------【全局函數(shù)聲明部分】--------------------------------------
// 描述:全局函數(shù)聲明
//------------------------------------------------------------------------------------------
-----
//四個軌跡條的回調(diào)函數(shù)
static void on_BoxFilter(int, void *); //均值濾波
static void on_MeanBlur(int, void *); //均值濾波
static void on_GaussianBlur(int, void *); //高斯濾波
//-----------------------------------【main( )函數(shù)】--------------------------------------------
// 描述:控制臺應(yīng)用程序的入口函數(shù),我們的程序從這里開始
//------------------------------------------------------------------------------------------
-----
int main( )
{
//改變console字體顏色
system(“color5E”);
//載入原圖
g_srcImage= imread( “1.jpg”, 1 );
if(!g_srcImage.data ) { printf(“Oh,no,讀取srcImage錯誤~!\n”); return false; }
//克隆原圖到三個Mat類型中
g_dstImage1= g_srcImage.clone( );
g_dstImage2= g_srcImage.clone( );
g_dstImage3= g_srcImage.clone( );
//顯示原圖
namedWindow(“【《0》原圖窗口】”, 1);
imshow(“【《0》原圖窗口】”,g_srcImage);
//=================【《1》方框濾波】==================
//創(chuàng)建窗口
namedWindow(“【《1》方框濾波】”, 1);
//創(chuàng)建軌跡條
createTrackbar(“內(nèi)核值:”, “【《1》方框濾波】”,&g_nBoxFilterValue, 40,on_BoxFilter );
on_MeanBlur(g_nBoxFilterValue,0);
imshow(“【《1》方框濾波】”, g_dstImage1);
//================================================
//=================【《2》均值濾波】==================
//創(chuàng)建窗口
namedWindow(“【《2》均值濾波】”, 1);
//創(chuàng)建軌跡條
createTrackbar(“內(nèi)核值:”, “【《2》均值濾波】”,&g_nMeanBlurValue, 40,on_MeanBlur );
on_MeanBlur(g_nMeanBlurValue,0);
//================================================
//=================【《3》高斯濾波】=====================
//創(chuàng)建窗口
namedWindow(“【《3》高斯濾波】”, 1);
//創(chuàng)建軌跡條
createTrackbar(“內(nèi)核值:”, “【《3》高斯濾波】”,&g_nGaussianBlurValue, 40,on_GaussianBlur );
on_GaussianBlur(g_nGaussianBlurValue,0);
//================================================
//輸出一些幫助信息
cout《《endl《《“\t嗯。好了,請調(diào)整滾動條觀察圖像效果~\n\n”
《《“\t按下“q”鍵時,程序退出~!\n”
《《“\n\n\t\t\t\tby淺墨”;
//按下“q”鍵時,程序退出
while(char(waitKey(1))!= ‘q’) {}
return0;
}
//-----------------------------【on_BoxFilter( )函數(shù)】------------------------------------
// 描述:方框濾波操作的回調(diào)函數(shù)
//------------------------------------------------------------------------------------------
-----
static void on_BoxFilter(int, void *)
{
//方框濾波操作
boxFilter(g_srcImage, g_dstImage1, -1,Size( g_nBoxFilterValue+1, g_nBoxFilterValue+1));
//顯示窗口
imshow(“【《1》方框濾波】”, g_dstImage1);
}
//-----------------------------【on_MeanBlur( )函數(shù)】------------------------------------
// 描述:均值濾波操作的回調(diào)函數(shù)
//------------------------------------------------------------------------------------------
-----
static void on_MeanBlur(int, void *)
{
//均值濾波操作
blur(g_srcImage, g_dstImage2, Size( g_nMeanBlurValue+1, g_nMeanBlurValue+1),Point(-1,-1));
//顯示窗口
imshow(“【《2》均值濾波】”, g_dstImage2);
}
//-----------------------------【ContrastAndBright( )函
數(shù)】------------------------------------
// 描述:高斯濾波操作的回調(diào)函數(shù)
//------------------------------------------------------------------------------------------
-----
static void on_GaussianBlur(int, void *)
{
//高斯濾波操作
GaussianBlur(g_srcImage, g_dstImage3, Size(
g_nGaussianBlurValue*2+1,g_nGaussianBlurValue*2+1 ), 0, 0);
//顯示窗口
imshow(“【《3》高斯濾波】”, g_dstImage3);
}
最后是一些運(yùn)行截圖,原圖:
方框濾波:
均值濾波:
高斯濾波:
========
OpenCV基礎(chǔ)篇之圖像的DFT頻域變換
程序及分析
/*
* FileName : fft2.cpp
* Author : xiahouzuoxin @163.com
* Version : v1.0
* Date : Wed 30 Jul 2014 09:42:12 PM CST
* Brief :
*
* Copyright (C) MICL,USTB
*/
#include 《iostream》
#include 《cv.h》
#include 《highgui.h》
#include “imgproc/imgproc.hpp”
using namespace std;
using namespace cv;
int main(int argc, char *argv[])
{
if (argc 《 2) {
cout《《“Usage:。/fft2 [image name]”《《endl;
return -1;
}
// Read as grayscale image
Mat image = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);
if (!image.data) {
cout 《《 “Read image error”《《endl;
return -1;
}
Mat padded;
int m = getOptimalDFTSize(image.rows); // Return size of 2^x that suite for FFT
int n = getOptimalDFTSize(image.cols);
// Padding 0, result is @padded
copyMakeBorder(image, padded, 0, m-image.rows, 0, n-image.cols, BORDER_CONSTANT,
Scalar::all(0));
// Create planes to storage REAL part and IMAGE part, IMAGE part init are 0
Mat planes[] = {Mat_《float》(padded), Mat::zeros(padded.size(), CV_32F) };
Mat complexI;
merge(planes, 2, complexI);
dft(complexI, complexI);
// compute the magnitude and switch to logarithmic scale
split(complexI, planes);
magnitude(planes[0], planes[0], planes[1]);
Mat magI = planes[0];
// =》 log(1+sqrt(Re(DFT(I))^2+Im(DFT(I))^2))
magI += Scalar::all(1);
log(magI, magI);
// crop the spectrum
magI = magI(Rect(0, 0, magI.cols & (-2), magI.rows & (-2)));
Mat _magI = magI.clone();
normalize(_magI, _magI, 0, 1, CV_MINMAX);
// rearrange the quadrants of Fourier image so that the origin is at the image center
int cx = magI.cols/2;
int cy = magI.rows/2;
Mat q0(magI, Rect(0,0,cx,cy)); // Top-Left
Mat q1(magI, Rect(cx,0,cx,cy)); // Top-Right
Mat q2(magI, Rect(0,cy,cx,cy)); // Bottom-Left
Mat q3(magI, Rect(cx,cy,cx,cy)); // Bottom-Right
// exchange Top-Left and Bottom-Right
Mat tmp;
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
// exchange Top-Right and Bottom-Left
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
normalize(magI, magI, 0, 1, CV_MINMAX);
imshow(“Input image”, image);
imshow(“Spectrum magnitude before shift frequency”, _magI);
imshow(“Spectrum magnitude after shift frequency”, magI);
waitKey();
return 0;
}
本程序的作用是:將圖像從空間域轉(zhuǎn)換到頻率域,并繪制頻域圖像。二維圖像的DFT(離散傅里葉變換),圖像的頻域表示的是什么含義呢?又有什么用途呢?圖像的頻率是表征圖像中灰度變化劇烈程度的指標(biāo),是灰度在平面空間上的梯度。圖像的邊緣部分是突變部分,變化較快,因此反應(yīng)在頻域上是高頻分量;圖像的噪聲大部分情況下是高頻部分;圖像大部分平緩的灰度變化部分則為低頻分量。也就是說,傅立葉變換提供另外一個角度來觀察圖像,可以將圖像從灰度分布轉(zhuǎn)化到頻率分布上來觀察圖像的特征。頻域在圖像處理中,就我所知的用途主要在兩方面:圖像壓縮和圖像去噪。關(guān)于這兩點(diǎn)將在下面給出圖片
DFT的變換結(jié)果后說明。有關(guān)DFT的更多性質(zhì)請參考胡廣書教授的《數(shù)字信號處理》教材。
請注意讀圖片的函數(shù)與之前有所不同:
Mat image = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);
CV_LOAD_IMAGE_GRAYSCALE參數(shù)表示將原圖像轉(zhuǎn)換為灰度圖后讀入,這是因為后面的DFT變換都是基于二維信號的,而彩色圖像是三維信號。當(dāng)然,也可以對RGB每一通道都進(jìn)行DFT運(yùn)算。DFT算法的原理要求輸入信號的長度最好為2^n,這樣可以使用快速傅里葉變換算法(FFT算法)進(jìn)行加速。所以程序中使用
copyMakeBorder(image, padded, 0, m-image.rows, 0, n-image.cols, BORDER_CONSTANT,
Scalar::all(0));
填充0使橫縱長度都為2^n。
對于一維信號,原DFT直接運(yùn)算的復(fù)雜度是O(N^2),而快速傅里葉變換的復(fù)雜度降低到O(Nlog2(N)),假設(shè)N為512,足足提高了512/9≈57倍。
由DFT的性質(zhì)知,輸入為實(shí)信號(圖像)的時候,頻域輸出為復(fù)數(shù),因此將頻域信息分為幅值和相位。頻域的幅值高的代表高頻分量,幅值低的地方代表低頻分量,因此程序中使用
// =》 log(1+sqrt(Re(DFT(I))^2+Im(DFT(I))^2))
magI += Scalar::all(1);
log(magI, magI);
// crop the spectrum
magI = magI(Rect(0, 0, magI.cols & (-2), magI.rows & (-2)));
Mat _magI = magI.clone();
normalize(_magI, _magI, 0, 1, CV_MINMAX);
進(jìn)行l(wèi)og幅值計算及歸一化幅值(歸一化目的主要是方便將頻域通過圖像的形式進(jìn)行顯示)。
關(guān)于頻域中心平移:將圖像的高頻分量平移到圖像的中心,便于觀測。
int cx = magI.cols/2;
int cy = magI.rows/2;
Mat q0(magI, Rect(0,0,cx,cy)); // Top-Left
Mat q1(magI, Rect(cx,0,cx,cy)); // Top-Right
Mat q2(magI, Rect(0,cy,cx,cy)); // Bottom-Left
Mat q3(magI, Rect(cx,cy,cx,cy)); // Bottom-Right
// exchange Top-Left and Bottom-Right
Mat tmp;
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
// exchange Top-Right and Bottom-Left
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
其原理就是將左上角的頻域和右下角的互換,右上角和左下角互換。
請注意:頻域點(diǎn)和空域點(diǎn)的坐標(biāo)沒有一一對應(yīng)的關(guān)系,兩者的關(guān)系只是上面的DFT公式所見到的。本程序因為使用到圖像處理相關(guān)的函數(shù),所以包含了頭文件imgproc/imgproc.hpp,該文件位于opencv安裝目錄的include/opencv2/目錄下,在編寫Makefile時也要增加相關(guān)的頭文件路徑和庫,本程序使用的Makefile如下:
TARG=fft2
SRC=fft2.cpp
LIB=-L/usr/local/lib/
INC=-I/usr/local/include/opencv/ -I/usr/local/include/opencv2
CFLAGS=
$(TARG):$(SRC)
g++ -g -o $@ ${CFLAGS} $(LIB) $(INC) \
-lopencv_core -lopencv_highgui -lopencv_imgproc \
$^
.PHONY:clean
clean:
-rm $(TARG) tags -f
其中Makefile中的\表示換行(反斜杠后不能再有任何字符,包括空格),如上庫增加了-lopencv_imgproc,頭文件路徑增加了-I/usr/local/include/opencv2。效果
? ?提到圖像頻域變換的用途:壓縮和去噪。壓縮的原理就是在頻域中,大部分頻域的值為0(或接近0,可以進(jìn)行有損壓縮,如jpeg圖像),只要壓縮頻域中的少數(shù)非0值即可達(dá)到圖片壓縮的目的。去噪則是通過頻域的濾波實(shí)現(xiàn),因為噪聲大部分情況下體現(xiàn)為高頻信號,使用低通濾波器即可濾除高頻噪聲(當(dāng)然,也會帶來損失,那就是邊緣會變得模糊(之前說過,邊緣也是高頻信號))。
========
評論
查看更多