OpenCV图像去噪
时间:2023-07-22 12:07:00
OpenCV图像处理部分图像平滑
图像平滑算法
程序分析及结果
图像平滑算法
图像平滑与图像模糊是同一概念,主要用于图像的去噪。平滑要使用滤波器,为不改变图像的相位信息,
线性滤波器的统一形式如下:
h称为滤波器的核函数,说白了就是权值。不同的核函数代表不同的滤波器,有不同的用途。
在图像处理中,常见的滤波器包括:
归一化滤波器(Homogeneous blur)
也是平均过滤器,用输出像素点核窗口中的平均像素代替输出点像素值。
高斯滤波器(Guassian blur)
它是实际上最常用的滤波器。高斯滤波器是输入数组的每个像素点和 高斯内核 卷积将卷积和当作输出像
素值。高斯核相当于给输出像素的邻域不同的权值,输出像素点的权值最大(对应高斯函数
均值位置)。二维高斯函数为,
中值滤波器(median blur)
中值滤波用邻域(以当前像素为中心的正方形区域)像素的中值代替图像的每个像素。椒盐噪声最有效
去除跳变点的滤波器非常有效。
双边滤波器(Bilatrial blur)
在这种情况下,使用双边滤波器,以避免滤波器平滑图像去噪,模糊边缘。双边滤波器的解释参考
见http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Filtering.html
以下程序将首先给出标准Lena在图像中添加胡椒盐噪声,并使用四种不同的滤波器进行平滑操作。请注意不要观察
同滤波器对胡椒盐噪声的去噪效果!
程序分析及结果
/*
* 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]"<
}
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
disp_image(wn_name, dst);
}
/* Guassian blur */
disp_caption(wn_name, I, "Gaussian blur");
for (int i=1; i
disp_image(wn_name, dst);
}
/* Median blur */
disp_caption(wn_name, I, "Median blur");
for (int i=1; i
disp_image(wn_name, dst);
}
/* Bilatrial blur */
disp_caption(wn_name, I, "Bilatrial blur");
for (int i=1; i
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);
}
br> /*
* @brief 添加椒盐噪声
* @inputs
* @outputs
* @retval
*/
static void salt(Mat &I, int n=3000)
{
for (int k=0; k
int j = rand() % I.rows;
if (I.channels()) {
I.at
} else {
I.at
I.at
I.at
}
}
}
上面程序的逻辑非常清晰:
读入灰度图,并添加椒盐噪声(6000个噪声点):
Mat I = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);
salt(I, 6000);
LenaNoise
disp_caption和disp_image函数分别是用于显示提示文字和平滑过程中的变化图像的,平滑过程中图像的
变化如下图:
blur
注意观察上面的图,中值滤波(Median Blur)对椒盐噪声的效果最好!
四种滤波方法分别使用到4个OpenCV函数,这些函数的声明都在imgproc.hpp中,这些函数的前2个参数都
是原图像和滤波后图像。
归一化滤波器blur的第3个参数为滤波核窗口的大小,Size(i,i)表示ixi大小的窗口。
高斯滤波器GaussianBlur第3个参数也是滤波核窗口的大小,第4、第5个参数分辨表示x方向和y方向的δ
。
中值滤波器medianBlur第3个参数是滤波器的长度,该滤波器的窗口为正方形。
双边滤波器的函数原型如下:
//! 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
========
图像代数运算:平均值去噪,减去背景
代数运算,就是对两幅图像的点之间进行加、减、乘、除的运算。四种运算相应的公式为:
代数运算中比较常用的是图像相加和相减。图像相加常用来求平均值去除addtive噪声或者实现二次曝光
(double-exposure)。图像相减用于减去背景或周期噪声,污染等。
图像相加
OpenCV中提供了相加的函数
void cvAcc(
const CvArr* image,//输入图像
CvArr* sum, //累积图像
const CvArr* mask=NULL//可选的运算
);
我们还需要用到一个线性变换转换函数来对相加的结果求平均
void cvConvertScale(
const CvArr* src, //输入数组
CvArr* dst,//输出数组
double scale=1,//比例
double shift=0 //缩放比例,可选
);
#define cvCvtScale cvConvertScale
#define cvScale cvConvertScale
#define cvConvert( src, dst ) cvConvertScale( (src), (dst), 1, 0 )
实践:平均值去噪
我们用NASA的一段幸运团的视频做实验,截取视频的某几个连续帧求平均值:
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);// 获取当前帧
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;
}
以下从左到右分别是连续两帧、四帧、八帧、十六帧求均值的结果:
实践:图像二次曝光
曝光和去噪是一样的,也是对几幅图像求平均
//通过求平均二次曝光
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;
}
下图是对同学街舞截图的“二次曝光”效果:
图像相减
OpenCV中用cvAbsDiff函数计算两数组的差的绝对值
void cvAbsDiff(
const CvArr* src1,//第一个输入数组
const CvArr* src2,//第二个输入数组
CvArr* dst//输出数组
);
实践:减去背景
减去背景是通过两幅图像代数相减,可以判断出前景区域和运动区域,这是最简单(很多时候也是效果很
好的)运动检测方法。
//减去背景
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;
//创建窗口
cvNamedWindow("video", 1);
cvNamedWindow("background",1);
cvNamedWindow("foreground",1);
pCapture = cvCaptureFromFile("media.avi");
while(pFrame = cvQueryFrame( pCapture ))
{
nFrmNum++;
//如果是第一帧,需要申请内存,并初始化
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);
//转化成单通道图像再处理
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);
//当前帧跟背景图相减
cvAbsDiff(pFrameMat, pBkMat, pFrMat);
//二值化前景图
cvThreshold(pFrMat, pFrImg, 60, 255.0, CV_THRESH_BINARY);
//更新背景
cvRunningAvg(pFrameMat, pBkMat, 0.003, 0);
//将背景转化为图像格式,用以显示
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参数说明:
/*mask———一副灰度图
polygon1_hull0———用多边形拟合选1,用凸包拟合选0
scale———设置不被删除的连通轮廓大小
num————连通轮廓的最大数目
bbs——指向连通轮廓的外接矩形
center——指向连通轮廓的中心*/
#define CVCONTOUR_APPROX_LEVEL 2//数越大连通区域的边界越简单
#define CVCLOSE_ITR 1 //图像形态学运算的次数
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
(CvContour),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<
if(len
else{
CvSeq* c_new;
if(polygon1_hull0)
{
//多边形拟合
c_new=cvApproxPoly(c,sizeof
(CvContour),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); //黑色
//重绘连通区域
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
{
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++;
}
//画区域
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去噪的任务下达已经有半个多月了,前期除了看了几天文献之外就打酱油了,好像每天都很忙
,可是就是不知道在忙什么。这几天为了交差,就胡乱凑了几段代码,得到一个结果,也知道不行,先应
付一下,再图打算。
程序思想很简单,先对静止的场景连续采样若干帧,然后对所有点在时间域取中值,对取完中值之后的无
效点在空间域取最近邻,勉强将黑窟窿填上了。由于代码较长,现在奉上关键的几个片段:
#include
#include
#include
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
这是定义的头文件,装模作样的写了一个类,在构造函数里面,除了对denoisedImage分配内存之外其他
都置0,析构函数需要释放denoisedImage和frameSet数组的内存。numOfFrames本来设计为frameSet中的
图像的帧数,结果由于偷懒就用了一个定长的数组。
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
{
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
{
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);
}
}
这是计算深度图像的滤波范围。由于深度图像和彩色图像的视点不一致,导致了将深度图像映射到彩色图
像上时有效像素会缩小,典型的现象就是在深度图像的四周会出现黑色的区域。这个函数就是用来将四周
的黑色框框去掉。用OpenCV的投影的方法。由于cvReduce()函数要进行累积和的计算,为了不使数据溢出
,目标数组应该用32位的浮点型(此函数只支持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
unsigned short* denoisedImageData = (unsigned short*)(denoisedImage-
>imageData+denoisedImage->widthStep*i);
for(int j=imageROI.x;j
total = 0;
for(int k=0;k
insertSort(data,total,CV_IMAGE_ELEM(frameSet[k],unsigned
short,i,j));
}
if(total != 0)
{
denoisedImageData[j] = data[total/2];
}
}
}
}
中值滤波,统计有效点并排序,然后取中值。insertSort()函数用来将值按从小到大的顺序进行插入,鉴
于篇幅的关系,就不贴出来了。
void kinectDenoising::nearestFiltering()
{
CvPoint topLeft,downRight;
IplImage* tempImage = cvCloneImage(denoisedImage);
for(int i=imageROI.y;i
unsigned short* data = (unsigned short*)(denoisedImage->imageData
+denoisedImage->widthStep*i);
for(int j=imageROI.x;j
for(int k=1;data[j]==0;k++)
{
topLeft = cvPoint(j-k,i-k); // j为行数 i为列数
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
(tempImage,unsigned short,downRight.y,m);
if(temp > 0)
{
data[j] = temp;
break;
}
}
}
for(int m=topLeft.y;(m
if(m<0) continue;
if(m>=height) break;
if(topLeft.x>0)
{
unsigned short temp = CV_IMAGE_ELEM
(tempImage,unsigned short,m,topLeft.x);
if(temp > 0)
{
data[j] = temp;
break;
}
}
if(downRight.x
unsigned short temp = CV_IMAGE_ELEM
(tempImage,unsigned short,m,downRight.x);
if(temp > 0)
{
data[j] = temp;
break;
}
}
}
}
}
}
cvReleaseImage(&tempImage);
}
最后是中值滤波,从最内层开始,一层层往外扩,直到找到有效值为止。
运行结果:
源图像:
结果图像:
附注:本来这个程序是在8位图像上进行的。先取得16位的unsigned short型深度图像,然后通过
cvConvertScale()函数将其转化为8位的unsigned char型,结果在进行去噪的时候怎么都不对,将
unsigned char型的数据放到matlab中一看,发现在unsigned short型数据中为0值的像素莫名其妙的在
unsigned char型里有了一个很小的值(比如说1, 2, 3, 4, 5什么的,就是不为0)。很奇怪,不知道
OpenCV中是怎么搞的。看来还是源数据靠谱,于是将其改为16位的unsigned short型,结果形势一片大好
。
http://blog.csdn.net/chenli2010/article/details/7006573
========
视频图像去模糊常用处理方法
随着“平安城市”的广泛建设,各大城市已经建有大量的视频监控系统,虽然监控系统己经广泛地存在于
银行、商场、车站和交通路口等公共场所,但是在公安工作中,由于设备或者其他条件的限制,案情发生
后的图像回放都存在图像不清晰,数据不完整的问题,无法为案件的及时侦破提供有效线索。经常出现嫌
疑人面部特征不清晰,难以辨认,嫌疑车辆车牌模糊无法辨认等问题。这给公安部门破案、法院的取证都
带来了极大的麻烦。随着平安城市的推广、各地各类监控系统建设的进一步推进,此类问题会越来越突出
。
一.模糊图像产生的原因
1. 系统自身因素
(1)镜头聚焦不当、摄像机故障等。
(2)传输太远、视频线老化
(3)光学镜头的极限分辨率和摄像机不匹配导致的模糊;
(4)相机分辨率低,欠采样成像。
2. 自然环境
(1)摄像机罩或镜头受脏污、受遮挡等。
(2)大雾,沙尘、雨雪等环境影响等。
3. 人为环境
(1)环境电磁干扰;
(2)视频压缩算法、传输带宽导致的模糊。
(3)运动目标高速运动导致的运动模糊等;
二. 模糊图像常用的处理方法
对于模糊图像处理技术,国内大学和科研机构在多年以前就在研究这些理论和应用,相关文献
也发布了不少,已经取得了一些很好的应用。当前有很多软件已经有了相当成熟的一套模糊图像恢复方法
,在美国FBI及其他执法机构中已有多年实际应用,其恢复出的图像可以直接当作法庭证据使用,可见模
糊图像处理技术已经取得了相当的实际应用。
从技术方面来向,模糊图像处理方法主要分为三大类,分别是图像增强、图像复原和超分辨率
重构。
2.1 图像增强
增强图象中的有用信息,它可以是一个失真的过程,其目的是要改善图像的视觉效果,针对给
定图像的应用场合,有目的地强调图像的整体或局部特性,将原来不清晰的图像变得清晰或强调某些感兴
趣的特征,扩大图像中不同物体特征之间的差别,抑制不感兴趣的特征,使之改善图像质量、丰富信息量
,加强图像判读和识别效果,满足某些特殊分析的需要。
图像增强技术根据增强处理过程所在的空间不同,可分为基于空域的算法和基于频域的算法
两大类。
前者把图像看成一种二维信号,对其进行基于二维傅里叶变换的信号增强。采用低通滤波(即
只让低频信号通过)法,可去掉图中的噪声;采用高通滤波法,则可增强边缘等高频信号,使模糊的图片
变得清晰。具有代表性的空间域算法有局部求平均值法和中值滤波(取局部邻域中的中间像素值)法等,
它们可用于去除或减弱噪声。
基于空域的算法分为点运算算法和邻域去噪算法。点运算算法即灰度级校正、灰度变换和直方
图修正等,目的或使图像成像均匀,或扩大图像动态范围,扩展对比度。邻域增强算法分为图像平滑和锐
化两种。平滑一般用于消除图像噪声,但是也容易引起边缘的模糊。常用算法有均值滤波、中值滤波。锐
化的目的在于突出物体的边缘轮廓,便于目标识别。常用算法有梯度法、算子、高通滤波、掩模匹配法、
统计差值法等。
2.1.1 图像增强的几个方面及方法
1.对比度变换:线性变换、非线性变换
2.空间滤波:图像卷积运算、平滑、锐化
3.彩色变换:单波段彩色变换、多波段彩色运算、HIS
2.1.2 图像增强的应用概况
图像增强的方法分为空域法和频域法两种,空域法是对图像中的像素点进行操作,用公式描述
如下:
g(x,y)=f(x,y)*h(x,y)
其中是f(x,y)原图像;h(x,y)为空间转换函数;g(x,y)表示进行处理后的图像。
频域法是间接的处理方法,是先在图像的频域中对图像的变换值进行操作,然后变回空域。例
如,先对图像进行傅里叶变化到频域,再对图像的频谱进行某种滤波修正,最后将修正后的图像进行傅里
叶反变化到空域,以此增强图像。
很多传统图像算法都可以减轻图像的模糊程度, 比如图像滤波、几何变换、对比度拉伸、直
方图均衡、空间域锐化、亮度均匀化、形态学、颜色处理等。单个来讲,这些算法比较成熟,相对简单。
但是对于一个具体的模糊图像,往往需要上面的一种或者多种算法组合,配合不同的参数才能达到理想的
效果。
这些算法和参数的组合进一步发展为具体的增强算法,比如“图像去雾”算法(可参考何恺明
经典去雾算法)、“图像去噪”算法、“图像锐化”算法、“图像暗细节增强”算法等。
2.2 图像复原
2.2.1 图像复原概述
在图像的获取、传输以及保存过程中,由于各种因素,如大气的湍流效应、摄像设备中光学系
统的衍射、传感器特性的非线性、光学系统的像差、成像设备与物体之间的相对运动、感光胶卷的非线性
及胶片颗粒噪声以及电视摄像扫描的非线性等所引起的几何失真,都难免会造成图像的畸变和失真。通常
,称由于这些因素引起的质量下降为图像退化。
早期的图像复原是利用光学的方法对失真的观测图像进行校正,而数字图像复原技术最早则
是从对天文观测图像的后期处理中逐步发展起来的。其中一个成功例子是NASA的喷气推进实验室在1964年
用计算机处理有关月球的照片。照片是在空间飞行器上用电视摄像机拍摄的,图像的复原包括消除干扰和
噪声,校正几何失真和对比度损失以及反卷积。另一个典型的例子是对肯尼迪遇刺事件现场照片的处理。
由于事发突然,照片是在相机移动过程中拍摄的,图像复原的主要目的就是消除移动造成的失真。
早期的复原方法有:非邻域滤波法,最近邻域滤波法以及效果较好的维纳滤波和最小二乘滤波
等。目前国内外图像复原技术的研究和应用主要集中于诸如空间探索、天文观测、物质研究、遥感遥测、
军事科学、生物科学、医学影象、交通监控、刑事侦察等领域。如生物方面,主要是用于生物活体细胞内
部组织的三维再现和重构,通过复原荧光显微镜所采集的细胞内部逐层切片图,来重现细胞内部构成;医
学方面,如对肿瘤周围组织进行显微观察,以获取肿瘤安全切缘与癌肿原发部位之间关系的定量数据;天
文方面,如采用迭代盲反卷积进行气动光学效应图像复原研究等。
2.2.2 图像退化模型
图像复原问题的有效性关键之一取决于描述图像退化过程模型的精确性。要建立图像的退化模
型,则首先必须了解、分析图像退化的机理并用数学模型表现出来。在实际的图像处理过程中,图像均需
以数字离散函数表示,所以必须将退化模型离散化。
2.2.3 几种较经典的复原方法介绍
图像复原算法有线性和非线性两类。线性算法通过对图像进行逆滤波来实现反卷积,这类方法
方便快捷,无需循环或迭代,直接可以得到反卷积结果,然而,它有一些局限性,比如无法保证图像的非
负性。而非线性方法通过连续的迭代过程不断提高复原质量,直到满足预先设定的终止条件,结果往往令
人满意。但是迭代程序导致计算量很大,图像复原时耗较长,有时甚至需要几个小时。所以实际应用中还
需要对两种处理方法综合考虑,进行选择。
1)维纳滤波法
维纳滤波法是由Wiener首先提出的,应用于一维信号处理,取得了很好的效果。之后,维纳滤
波法被用于二维信号处理,也取得了不错的效果,尤其在图像复原领域,由于维纳滤波计算量小,复原效
果好,从而得到了广泛的应用和发展。
2)正则滤波法
另一个容易实现线性复原的方法称为约束的最小二乘方滤波,在IPT中称为正则滤波,并且通
过函数deconvreg来实现。
3)Lucy-Richardson算法
L-R算法是一种迭代非线性复原算法,它是从最大似然公式印出来的,图像用泊松分布加以模
型化的。当迭代收敛时模型的最大似然函数就可以得到一个令人满意的方程。
4)盲去卷积
在图像复原过程中,最困难的问题之一是,如何获得PSF的恰当估计。那些不以PSF为基础的图
像复原方法统称为盲区卷积。它以MLE为基础的,即一种用被随机噪声所干扰的量进行估计的最优化策略
。工具箱通过函数deconvblind来执行盲区卷积。
2.2.4 图像复原与图像增强
图像复原与图像增强技术一样,也是一种改善图像质量的技术。图像复原是根据图像退化的
先验知识建立一个退化模型,以此模型为基础,采用各种逆退化处理方法进行恢复,改善图像质量。
图像复原和图像增强是有区别的,二者的目的都是为了改善图像的质量。但图像增强不考虑
图像是如何退化的,只有通过试探各种技术来增强图像的视觉效果,而图像复原就完全不同,需知道图像
退化过程的先验知识,据此找出一种相应的逆过程方法,从而得到复原的图像。图像复原主要取决于对图
像退化过程的先验知识所掌握的精确程度。
对由于离焦、运动、大气湍流等原因引起的图像模糊,图像复原的方法效果较好,常用的算法
包括维纳滤波算法、小波算法、基于训练的方法等。在知道退化模型的情况下,相对图像增强来说,图像
复原可以取得更好的效果。
2.3 图像超分辨率重构
现有的监控系统主要目标为宏观场景的监视,一个摄像机,覆盖很大的一个范围,导致画面中
目标太小,人眼很难直接辨认。这类由于欠采样导致的模糊占很大比例,对于由欠采样导致的模糊需要使
用超分辨率重构的方法。
超分辨率复原是通过信号处理的方法,在提高图像的分辨率的同时改善采集图像质量。其核心
思想是通过对成像系统截止频率之外的信号高频成分估计来提高图像的分辨率。超分辨率复原技术最初只
对单幅图像进行处理,这种方法由于可利用的信息只有单幅图像,图像复原效果有着固有的局限。序列图
像的超分辨率复原技术旨在采用信号处理方法通过对序列低分辨率退化图像的处理来获得一幅或者多幅高
分辨率复原图像。由于序列图像复原可利用帧间的额外信息,比单幅复原效果更好,是当前的研究热点。
序列图像的超分辨率复原主要分为频域法和空域法两大类,频域方法的优点是:理论简单,运算
复杂度低,缺点是:只局限于全局平移运动和线性空间不变降质模型,包含空域先验知识的能理有限。
空域方法所采用的观测模型涉及全局和局部运动、空间可变模糊点扩散函数、非理想亚采样等
,而且具有很强的包含空域先验约束的能力。常用的空域法有非均匀插值法、迭代反投影方法(IBP)、凸集
投影法(POCS)、最大后验估计法(MAP)、最大似然估计法(ML)、滤波器法等,其中,MAP和POCS法研究较多
,发展空间很大。
三:模糊图像处理的关键和不足
虽然很多模糊图像的处理方法在实际应用中取得了很好的效果,但是当前仍然有一些因素制约
着模糊图像处理的进一步发展,主要如下:
1、 算法的高度针对性;
绝大部分的模糊图像处理算法只适用于特定图像,而算法本身无法智能决定某个算法模块的开
启还是关闭。举例来说,对于有雾的图像,“去雾算法”可以取得很好的处理效果,但是作用于正常图像
,反而导致图像效果下降,“去雾算法”模块的打开或者关闭需要人工介入。
2、 算法参数复杂性;
模糊图像处理里面所有的算法都会包含大量的参数,这些参数的选择需要和实际的图像表现相
结合,直接决定最终的处理效果。目前算法还没有办法智能选择这些最优参数。
3、 算法流程的经验性;
由于实际图像很复杂,需要处理多种情况,这就需要一个算法处理流程,对于一个具体的模糊
视频,采用什么样的处理流程很难做到自动选择,需要人工选择一个合适的方法,只能靠人的经验。
四:实践和总结
由于环境、线路、镜头、摄像机等影响,监控系统建成运营一段时间后,都会出现一部分的视
频模糊不清的问题。前面提到了针对模糊图像的各种处理算法,虽然这些算法都取得了一些较好的处理效
果,但是再好的算法都是一种后期的补救措施。如果能及时发现监控系统中图像的各种问题,并及时维修
,必然会起到事半功倍的效果。
利用先进的视频诊断技术,开发出适用于各种需求场景的视频质量诊断系统。它能够对视频图
像出现的模糊、噪声、亮度异常和视频丢失等低质视频以及常见摄像机故障问题进行诊断,有效预防因硬
件问题导致的图像质量低下所带来的损失。从几路视频到几百上千、上万路视频,均可高效的进行检测,
自动生成检测报告,提供及时且精准的维护信息,第一时间从根源上解决图像模糊的问题。
总体来说,对于不同种类的模糊问题,要区别对待。对于由镜头离焦、灰尘遮挡、线路老化、
摄像机故障等造成的模糊或者图像质量下降,在视频诊断系统的帮助下,一定要及时维修,从源头上解决
问题。对于低光照等优先选择日夜两用型高感光度摄像机,对于雨雾、运动和前采样等造成的图像质量下
降,可以借助于“视频增强服务器”包含的各种模糊图像处理算法,提升图像质量。
后记
Single-Image Super-Resolution for anime/fan-art using Deep Convolutional Neural
Networks.
waifu2x是采用了最新锐的人工智能技术“Deep Convolutional Neural Networks”开发的网络服务。
名字来源于海外的动画粉丝们将喜欢的角色称作“waifu(即‘我老婆’)”。把缩小的锯齿状图
传到waifu2x的话,“现在给你的图是某张图缩小一半的图。求缩小前的图哦”,人工智能就会将噪点和
锯齿的部分进行补充,生成新的图。于是“扩大时的图”将不存在了,小的图变成了扩大了的图,同时还
可以去除噪点。
========
opencv 背景差分法 改进OTSU阈值去噪
/*
*1)头文件cvaux.h的库文件需要链接到linker. cvaux210d.lib, cvaux210.lib分别是debug和release版
本.
* 否则有些函数会出现error:: LINK 2101
*2)cvAdaptiveThreshold, cvThreshold的src和dst图像必须是同类型的单通道图像
*3)重要: 函数退出之后,函数中的动态变量会随着栈的退出全部清空.
* 要保存上次操作的结果,则在函数内声明为静态变量.或者在要调用的函数里先声明
*4)cvAdaptiveThreshold()的处理结果不好,此函数试图找出所有的物体轮廓.用在背景差分中,会找到不
想要的物体
*5)当没有前景物体时,OTSU算法会把路面显示出来.因为阈值是自动调整的.解决办法,做一个阈值筛选
*6)VedioControl() 未实现.
* 将cvWaitKey()封装到VedioControl()中, 如果不触发按键,VedioControl()不会退出.造成无法自
动播放
*
*Date: 2012/4/6
*Author: Rocky Chen
*/
#include
#include "stdafx.h"
#include
#include