实在是太喜欢Richard Szeliski的这本书了。每一章节(after chapter3)都详述了该研究方向比較新的成果。还有很多很多的reference,假设你感兴趣。全然能够看那些參考论文
Point operators(点算子)
Pixel transform(像素变换)
g(x) = af (x) +b a和b有时被觉得用来控制对照度和亮度,在我的opencv栏目有个样例是简单的对照度和亮度变换,用的就是这个公式
g(x) = a(x)f (x) + b(x) a,b不一定是常数。能够是空间上的函数
g(x) = (1 − α)f0(x) + αf1(x) α from0→1 能够实现两幅图像的淡入淡出
在OpenCV里有addWeighted( src1, alpha, src2, beta, 0.0, dst);这个函数,就是实现这个式子的
g(x) = [f(x)]1/γ 这是伽马校正属于幂变换。通经常使用于图像预处理阶段。对于大多数数字摄像机来说γ≈2.2
除了伽马校正。幂变换在控制对照度也非常实用,能够取不同的γ试一试
g(x) = L -1 -f(x) 灰度级属于[0,L-1] 这是图像反转 可用于增强嵌入与图像暗色区域的白色或灰色细节
g(x) = clog(1+f(x)) 对数变换
Color transform(彩色变换)
好像没有讲什么 = =Compositing and matting(合成与抠图)
C=(1−α)B+αF. (覆盖算子)
Histogram equalization(直方图均衡化)
public int[][] Histogram_Equalization(int[][] oldmat)
{
int[][] new_mat = new int[height][width];
int[] tmp = new int[256];
for(int i = 0;i < width;i++){
for(int j = 0;j < height;j++){
//System.out.println(oldmat[j][i]);
int index = oldmat[j][i];
tmp[index]++;
}
}
float[] C = new float[256];
int total = width*height;
//计算累积函数
for(int i = 0;i < 256 ; i++){
if(i == 0)
C[i] = 1.0f * tmp[i] / total;
else
C[i] = C[i-1] + 1.0f * tmp[i] / total;
}
for(int i = 0;i < width;i++){
for(int j = 0;j < height;j++){
new_mat[j][i] = (int)(C[oldmat[j][i]] * 255);
new_mat[j][i] = new_mat[j][i] + (new_mat[j][i] << 8) + (new_mat[j][i] << 16);
//System.out.println(new_mat[j][i]);
}
}
return new_mat;
}
![](https://i-blog.csdnimg.cn/blog_migrate/14a2eada6f754f1b75de25fff678e55f.png)
以下是我用Java写的CLAHE.
/*
* CLAHE
* 自适应直方图均衡化
*/
public int[][] AHE(int[][] oldmat,int pblock)
{
int block = pblock;
//将图像均匀分成等矩形大小,8行8列64个块是经常使用的选择
int width_block = width/block;
int height_block = height/block;
//存储各个直方图
int[][] tmp = new int[block*block][256];
//存储累积函数
float[][] C = new float[block*block][256];
//计算累积函数
for(int i = 0 ; i < block ; i ++)
{
for(int j = 0 ; j < block ; j++)
{
int start_x = i * width_block;
int end_x = start_x + width_block;
int start_y = j * height_block;
int end_y = start_y + height_block;
int num = i+block*j;
int total = width_block * height_block;
for(int ii = start_x ; ii < end_x ; ii++)
{
for(int jj = start_y ; jj < end_y ; jj++)
{
int index = oldmat[jj][ii];
tmp[num][index]++;
}
}
//裁剪操作
int average = width_block * height_block / 255;
int LIMIT = 4 * average;
int steal = 0;
for(int k = 0 ; k < 256 ; k++)
{
if(tmp[num][k] > LIMIT){
steal += tmp[num][k] - LIMIT;
tmp[num][k] = LIMIT;
}
}
int bonus = steal/256;
//hand out the steals averagely
for(int k = 0 ; k < 256 ; k++)
{
tmp[num][k] += bonus;
}
//计算累积分布直方图
for(int k = 0 ; k < 256 ; k++)
{
if( k == 0)
C[num][k] = 1.0f * tmp[num][k] / total;
else
C[num][k] = C[num][k-1] + 1.0f * tmp[num][k] / total;
}
}
}
int[][] new_mat = new int[height][width];
//计算变换后的像素值
//依据像素点的位置,选择不同的计算方法
for(int i = 0 ; i < width; i++)
{
for(int j = 0 ; j < height; j++)
{
//four coners
if(i <= width_block/2 && j <= height_block/2)
{
int num = 0;
new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);
}else if(i <= width_block/2 && j >= ((block-1)*height_block + height_block/2)){
int num = block*(block-1);
new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);
}else if(i >= ((block-1)*width_block+width_block/2) && j <= height_block/2){
int num = block-1;
new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);
}else if(i >= ((block-1)*width_block+width_block/2) && j >= ((block-1)*height_block + height_block/2)){
int num = block*block-1;
new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);
}
//four edges except coners
else if( i <= width_block/2 )
{
//线性插值
int num_i = 0;
int num_j = (j - height_block/2)/height_block;
int num1 = num_j*block + num_i;
int num2 = num1 + block;
float p = (j - (num_j*height_block+height_block/2))/(1.0f*height_block);
float q = 1-p;
new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);
}else if( i >= ((block-1)*width_block+width_block/2)){
//线性插值
int num_i = block-1;
int num_j = (j - height_block/2)/height_block;
int num1 = num_j*block + num_i;
int num2 = num1 + block;
float p = (j - (num_j*height_block+height_block/2))/(1.0f*height_block);
float q = 1-p;
new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);
}else if( j <= height_block/2 ){
//线性插值
int num_i = (i - width_block/2)/width_block;
int num_j = 0;
int num1 = num_j*block + num_i;
int num2 = num1 + 1;
float p = (i - (num_i*width_block+width_block/2))/(1.0f*width_block);
float q = 1-p;
new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);
}else if( j >= ((block-1)*height_block + height_block/2) ){
//线性插值
int num_i = (i - width_block/2)/width_block;
int num_j = block-1;
int num1 = num_j*block + num_i;
int num2 = num1 + 1;
float p = (i - (num_i*width_block+width_block/2))/(1.0f*width_block);
float q = 1-p;
new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);
}
//inner area
else{
int num_i = (i - width_block/2)/width_block;
int num_j = (j - height_block/2)/height_block;
int num1 = num_j*block + num_i;
int num2 = num1 + 1;
int num3 = num1 + block;
int num4 = num2 + block;
float u = (i - (num_i*width_block+width_block/2))/(1.0f*width_block);
float v = (j - (num_j*height_block+height_block/2))/(1.0f*height_block);
new_mat[j][i] = (int)((u*v*C[num4][oldmat[j][i]] +
(1-v)*(1-u)*C[num1][oldmat[j][i]] +
u*(1-v)*C[num2][oldmat[j][i]] +
v*(1-u)*C[num3][oldmat[j][i]]) * 255);
}
new_mat[j][i] = new_mat[j][i] + (new_mat[j][i] << 8) + (new_mat[j][i] << 16);
}
}
return new_mat;
}
Application:Tonal adjustment(色调调整)
Write a simple application to change the color balance of an imageby multiplying each color value by a different user-specified constant. If you want to getfancy, you can make this application interactive, with sliders.
我仅仅是非常easy地将颜色乘以系数,有slider,比較方便~~
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
int alpha = 50;
Mat image,new_image;
static void change_color(int, void*)
{
for( int y = 0; y < image.rows; y++ )
for( int x = 0; x < image.cols; x++ )
for( int c = 0; c < 3; c++ )
new_image.at<Vec3b>(y,x)[c] = saturate_cast<uchar>( alpha/50.0 *( image.at<Vec3b>(y,x)[c] ));
imshow("Image", new_image);
}
int main( int, char** argv )
{
image = imread( argv[1] );
new_image = Mat::zeros( image.size(), image.type() );
namedWindow("Image", 1);
createTrackbar( "pick:", "Image", &alpha, 100, change_color);
change_color(0, 0);
waitKey();
return 0;
}
Linear filtering(线性滤波)
![](https://i-blog.csdnimg.cn/blog_migrate/e1ffc737e6dd2e5f4906bcd81b022540.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f6a72e7737b65092b1de87d256789d21.png)
- 0填充,非常easy的处理方式
- 常数填充
- 夹取填塞(clamp),不断地复制边缘像素的值
- 重叠填塞(wrap)。以环状形态围绕图像进行循环
- 镜像填塞(mirror),像素围绕图像边界进行镜像反射
- 延长(extend),通过在边缘像素值中减去镜像信号的方式延长信号
Separable filtering(可分离的滤波)
K =vhT
![](https://i-blog.csdnimg.cn/blog_migrate/ff7911974d142d0fb364d6ceb5d6823b.png)
用分解的核函数对图像做卷积。
首先,图像的每一行与一维的核 kernelX 做卷积。然后,运算结果的每一列与一维的核 kernelY 做卷积。
Examples of linear filtering(线性滤波演示样例)
- box,非常easy,就是平均
- bilinear,双线性核
- Gaussian,非常有名,高斯核
- Sobel算子,有效突出水平边缘(拉普拉斯算子也用于边缘提取,canny检測是边缘提取经常使用的算法)
- corner,简单的角点检測器。同一时候寻找水平和垂直方向的二阶导数,这种算子不仅对正方形的角点有响应。并且对沿对角线方向的边缘也有响应
1 | 1 | 1 |
1 | -8 | 1 |
1 | 1 | 1 |
0 | 1 | 0 |
1 | -4 | 1 |
0 | 1 | 0 |
在Opencv里,都有各自相应的函数
就是滑动窗体平均滤波的二维版。
GaussianBlur()
高斯平均。也就是高斯模糊。
medianBlur()
中值滤波,有效去除椒盐噪声。
bilateralFilter()
双线性滤波。
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
using namespace cv;
int main( int, char** argv )
{
Mat src, src_gray;
Mat grad;
const char* window_name = "Sobel Demo - Simple Edge Detector";
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
/// Load an image
src = imread( argv[1] );
if( !src.data )
{ return -1; }
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
/// Convert it to gray
cvtColor( src, src_gray, CV_RGB2GRAY );
/// Create window
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
/// Generate grad_x and grad_y
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
/// Gradient X
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_x, abs_grad_x );
/// Gradient Y
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_y, abs_grad_y );
/// Total Gradient (approximate)
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
imshow( window_name, grad );
waitKey(0);
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/bc6838acef23672f63e0c585c3c996d8.png)
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
using namespace cv;
int main( int, char** argv )
{
Mat src, src_gray, dst;
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
const char* window_name = "Laplace Demo";
/// Load an image
src = imread( argv[1] );
if( !src.data )
{ return -1; }
/// Remove noise by blurring with a Gaussian filter
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
/// Convert the image to grayscale
cvtColor( src, src_gray, CV_RGB2GRAY );
/// Create window
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
/// Apply Laplace function
Mat abs_dst;
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
convertScaleAbs( dst, abs_dst );
/// Show what you got
imshow( window_name, abs_dst );
waitKey(0);
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/a8f97d16ad45d6db8888042150fb38ed.png)
构造了一个例如以下所看到的的核对图像做卷积:
-1 -1 -1
-1 9 -1
-1 -1 -1
核的锚点在 (1,1) 位置。卷积之后每一个像素加上 128.
锚点应该处于核内部。缺省值 (-1,-1) 表示锚点在核中心。
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
using namespace cv;
int main ( int, char** argv )
{
/// Declare variables
Mat src, dst;
Mat kernel;
Point anchor;
double delta;
int ddepth;
int kernel_size;
const char* window_name = "filter2D Demo";
int c;
/// Load an image
src = imread( argv[1] );
if( !src.data )
{ return -1; }
/// Create window
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
/// Initialize arguments for the filter
anchor = Point( -1, -1 );
delta = 0;
ddepth = -1;
/// Loop - Will filter the image with different kernel sizes each 0.5 seconds
int ind = 0;
for(;;)
{
c = waitKey(500);
/// Press 'ESC' to exit the program
if( (char)c == 27 )
{ break; }
/// Update kernel size for a normalized box filter
kernel_size = 3 + 2*( ind%5 );
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
/// Apply filter
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
imshow( window_name, dst );
ind++;
}
return 0;
}
Band-pass and steerable filters(带通和导向滤波器)
![](https://i-blog.csdnimg.cn/blog_migrate/eb0ebe8be5af5904c54a9885ffcac898.png)
非常easy发现有 s(i, j) = s(i−1, j) +s(i, j−1)−s(i−1, j−1) +f(i, j).
- 人脸检測利用积分图像来计算简单的多尺度上的底层特征
- 立体视觉和运动算法中差分平方和(SSD)的求和计算
- 可分离的移动平均滤波器
OpenCV有自带的计算积分图的函数integral 提供了很多其它选项,sum是和,sqsum是平方和图像。tilted是旋转45度的和
-
sum: the sum summation integral image
-
sqsum: the square sum integral image
-
tilted: image is rotated by 45 degrees and then its integral is calculated
More neighborhood operators(很多其它的领域算子)
Non linear filtering(非线性滤波)
![](https://i-blog.csdnimg.cn/blog_migrate/01e1669086a6383d8d930726f3cbf4cc.png)
双边滤波器顾名思义比
高斯滤波 多了一个高斯方差sigma-d,它是基于空间分布的高斯滤波 函数 ,所以在边缘附近,离的较远的像素不会太多影响到边缘上的像素值。这样就保证了边缘附近像素值的保存。可是由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净的滤掉,仅仅能够对于低频信息进行较好的滤波![](https://i-blog.csdnimg.cn/blog_migrate/f0b30e978729162d06b4b4821c7a1e4f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9b725fb5f70ebf7a1c89ef3e33e2b155.png)
//OpenCV双边滤波 //src:输入图像 //dst:输入图像 //滤波模板半径 //颜色空间标准差 //坐标空间标准差 bilateralFilter(src,dst,5,10.0,2.0);
//关于滤波,还能够參考这里
Iterated adaptive smoothing and anisotropic diffusion(迭代自适应平滑和各向异性扩散)
Morphology(形态学)
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
using namespace cv;
/// Global variables
Mat src, erosion_dst, dilation_dst;
int erosion_elem = 0;
int erosion_size = 0;
int dilation_elem = 0;
int dilation_size = 0;
int const max_elem = 2;
int const max_kernel_size = 21;
/** Function Headers */
void Erosion( int, void* );
void Dilation( int, void* );
/**
* @function main
*/
int main( int, char** argv )
{
/// Load an image
src = imread( argv[1] );
if( !src.data )
{ return -1; }
/// Create windows
namedWindow( "Erosion Demo", CV_WINDOW_AUTOSIZE );
namedWindow( "Dilation Demo", CV_WINDOW_AUTOSIZE );
cvMoveWindow( "Dilation Demo", src.cols, 0 );
/// Create Erosion Trackbar
createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Erosion Demo",
&erosion_elem, max_elem,
Erosion );
createTrackbar( "Kernel size:\n 2n +1", "Erosion Demo",
&erosion_size, max_kernel_size,
Erosion );
/// Create Dilation Trackbar
createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Dilation Demo",
&dilation_elem, max_elem,
Dilation );
createTrackbar( "Kernel size:\n 2n +1", "Dilation Demo",
&dilation_size, max_kernel_size,
Dilation );
/// Default start
Erosion( 0, 0 );
Dilation( 0, 0 );
waitKey(0);
return 0;
}
/**
* @function Erosion
*/
void Erosion( int, void* )
{
int erosion_type = 0;
if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }
else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }
else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }
Mat element = getStructuringElement( erosion_type,
Size( 2*erosion_size + 1, 2*erosion_size+1 ),
Point( erosion_size, erosion_size ) );
/// Apply the erosion operation
erode( src, erosion_dst, element );
imshow( "Erosion Demo", erosion_dst );
}
/**
* @function Dilation
*/
void Dilation( int, void* )
{
int dilation_type = 0;
if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }
else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }
else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }
Mat element = getStructuringElement( dilation_type,
Size( 2*dilation_size + 1, 2*dilation_size+1 ),
Point( dilation_size, dilation_size ) );
/// Apply the dilation operation
dilate( src, dilation_dst, element );
imshow( "Dilation Demo", dilation_dst );
}
开运算
dst=open(src,element)=dilate(erode(src,element),element)
闭运算
dst=close(src,element)=erode(dilate(src,element),element)
形态梯度
dst=morph_grad(src,element)=dilate(src,element)-erode(src,element)
"顶帽"
dst=tophat(src,element)=src-open(src,element)
"黑帽"
dst=blackhat(src,element)=close(src,element)-src
暂时图像 temp 在形态梯度以及对“顶帽”和“黑帽”操作时的 in-place 模式下须要。
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
using namespace cv;
/// Global variables
Mat src, dst;
int morph_elem = 0;
int morph_size = 0;
int morph_operator = 0;
int const max_operator = 4;
int const max_elem = 2;
int const max_kernel_size = 21;
const char* window_name = "Morphology Transformations Demo";
/** Function Headers */
void Morphology_Operations( int, void* );
/**
* @function main
*/
int main( int, char** argv )
{
/// Load an image
src = imread( argv[1] );
if( !src.data )
{ return -1; }
/// Create window
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
/// Create Trackbar to select Morphology operation
createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );
/// Create Trackbar to select kernel type
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
&morph_elem, max_elem,
Morphology_Operations );
/// Create Trackbar to choose kernel size
createTrackbar( "Kernel size:\n 2n +1", window_name,
&morph_size, max_kernel_size,
Morphology_Operations );
/// Default start
Morphology_Operations( 0, 0 );
waitKey(0);
return 0;
}
/**
* @function Morphology_Operations
*/
void Morphology_Operations( int, void* )
{
// Since MORPH_X : 2,3,4,5 and 6
int operation = morph_operator + 2;
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
/// Apply the specified morphology operation
morphologyEx( src, dst, operation, element );
imshow( window_name, dst );
}
Reference
- Richard Szeliski 《Computer Vision: Algorithms and Applications》
- http://blog.csdn.net/xiaowei_cqu/article/details/7785365
- 《The OpenCV Reference Manual 》 Release 2.4.7
我近期刚開始看Computer Vision: Algorithms and Applications这本书,发现这本书内容非常丰富。这里的文章我也会不断更新,假设你也非常感兴趣,谢谢与我一起讨论^^