目录
我是看着这个学习的,在此推荐一下:
在此记录一下自己的学习过程
1.项目代码
图像处理与机器学习课件和代码地址:传送门
含完整代码的项目可在github下载:
https://github.com/chenshunpeng/Impro_MLearning
ps.
- 项目第一次运行可能会卡,第二次就好了
- 如果项目需要加载.dll的符号,直接取消即可(即禁用后序符号加载)
2.第一章 基本概念
2.1.直方图(code)
数字图像:以空间位置 ( x , y ) (x,y) (x,y) 为自变量的二维函数 f ( x , y ) f(x,y) f(x,y)
直方图定义:
- 不同灰度级分布构成不同图像
- 统计灰度级出现的次数(概率)
- 直方图表征了图像中灰度级分布特性
灰度直方图反映了图像灰度的分布(统计)特征
- 灰度级的函数
- 具有该灰度级的像素个数
计算公式:
h
(
r
k
)
=
n
k
h(r_{k})=n_{k}
h(rk)=nk
r
k
为灰度级
,
n
为该灰度级的像素个数
r_{k}为灰度级,n为该灰度级的像素个数
rk为灰度级,n为该灰度级的像素个数
且有灰度直方图累加: ∑ k = 0 L − 1 h ( r k ) = ∑ k = 0 L − 1 n k = N ( 图像中像素的总个数 ) \sum ^{L-1}_{k=0}h\left( r_{k}\right) =\sum ^{L-1}_{k=0}n_{k}=N\left( 图像中像素的总个数\right) k=0∑L−1h(rk)=k=0∑L−1nk=N(图像中像素的总个数)
之后我们来写这部分的代码:
首先有初始化:
// 计算图像直方图
int histFlag;
int hist[256]; //存储图像直方图,256灰度级
void histCompute(BYTE*, int, int);//计算图像直方图函数
添加好事件处理函数,调用求取直方图函数,更新窗口,显示直方图
//计算图像直方图
void CMFCApplication1View::HistCompute(){
// TODO: 完成histCompute函数设计
//求取直方图的函数
histCompute(image, width, height);
histFlag = 1;
//更新窗口,显示直方图的部分
OnInitialUpdate();
CRect ClientRect;
GetClientRect(&ClientRect);
InvalidateRect(&ClientRect);
}
计算直方图部分:
void CMFCApplication1View::histCompute(BYTE*image, int width, int height){
//计算直方图
int n;
for (n = 0; n < 256; n++) {
hist[n] = 0;
}
int i, j;
BYTE gray;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
gray = image[i * width + j];
//根据定义来求就好
hist[gray]++;
}
}
}
如果想方便切换图片,可以修改路径,之后用键盘右上角的page down
和page up
键即可实现切换图片(当然也可以不改,用鼠标切换也很方便):
最后成功显示出结果:
核心代码位置:
3.第二章 图像增强
首先进行灰度变换:
D
B
=
f
(
D
A
)
D_{B}=f(D_{A})
DB=f(DA)
H
A
(
D
A
)
H_{A}\left( D_{A}\right)
HA(DA):变换之前图像的直方图
H
B
(
D
B
)
H_{B}\left( D_{B}\right)
HB(DB):变换之后图像的直方图
有如下结论:
H
B
(
D
B
)
=
H
A
(
D
A
)
f
′
(
D
A
)
H_{B}\left( D_{B}\right)=\dfrac{H_{A}\left( D_{A}\right)}{f'\left( D_{A}\right) }
HB(DB)=f′(DA)HA(DA)
即灰度变换后图像直方图是变换前直方图与变换函数导数之比
3.1.直方图均衡(code)
- 直方图均衡化处理之后,原来比较少像素的灰度会被分配到别的灰度去,像素相对集中, 处理后灰度范围变大,对比度变大,清晰度变大,所以能有效增强图像
- 这种方法通常用来增加许多图像的局部对比度,尤其是当图像的有用数据的对比度相当接近的时候,通过这种方法,亮度可以更好地在直方图上分布
- 可以用于增强局部的对比度而不影响整体的对比度,直方图均衡化通过有效地扩展常用的亮度来实现这种功能
改变后使每个灰度级拥有相同的像素个数,即出现
H
B
(
D
B
)
=
H
A
(
D
A
)
f
′
(
D
A
)
=
常数
=
A
0
D
m
H_{B}\left( D_{B}\right)=\dfrac{H_{A}\left( D_{A}\right)}{f'\left( D_{A}\right) }=常数=\dfrac{A_{0}}{D_{m}}
HB(DB)=f′(DA)HA(DA)=常数=DmA0
其中, D m D_{m} Dm代表灰度级, A 0 A_{0} A0 代表 D m D_{m} Dm这个灰度级上的 图像像素总数
通过积分最终得到:
D
B
=
D
m
A
0
⋅
∑
0
D
A
H
A
(
D
A
)
D_{B}=\dfrac{D_{m}}{A_{0}}\cdot \sum ^{D_{A}}_{0}H_{A}\left( D_{A}\right)
DB=A0Dm⋅0∑DAHA(DA)
这种技术可应用于人脸识别中
我们来研究一下代码,共有3步:
- 计算输入图像直方图
- 计算像素新的灰度级
- 新灰度级替换原灰度级
void CMFCApplication1View::hisEqualiz(BYTE* image, int w, int h, BYTE* outImg){
//直方图均衡
//计算输入图像直方图
int his[256];
int n,i,j;
for (n = 0; n < 256; n++){
his[n] = 0;
}
for (i = 0; i < h; i++)
for (j = 0; j < w; j++)
his[image[i * w + j]]++;
//利用公式,计算像素新的灰度级
for (n = 1; n < 256; n++)
his[n] += his[n - 1];
BYTE gray[256];
float cons;
cons = 255.0 / his[255];
for (n = 0; n < 256; n++)
gray[n] = (BYTE)(cons * his[n]);
//新灰度级替换原灰度级
for (i = 0; i < h; i++)
for (j = 0; j < w; j++)
outImg[i * w + j]=gray[image[i * w + j]];
}
其中注意一下BYTE* image
的数据结构定义(在minwindef.h
中):
成功显示出结果:
核心代码位置:
3.2.卷积运算
这部分是看着这个学习的,在此推荐一下:
首先了解最基本的名词:
之后需要了解单位冲激函数和单位阶跃函数(一些奇异信号):
关系如下:
之后引入卷积:
从 τ = − ∞ \tau =-\infty τ=−∞到 ∞ \infty ∞, e ( t ) e(t) e(t)可表示为许多窄脉冲的叠加
将任意信号表示为时移单位冲激信号的加权叠加:
利用卷积求系统的零状态响应:
卷积的物理意义:
总结一下,公式即:
f
(
t
)
=
f
1
(
t
)
∗
f
2
(
t
)
=
∫
−
∞
∞
f
1
(
τ
)
f
2
(
t
−
τ
)
d
τ
f\left( t\right) =f_{1}\left( t\right) \ast f_{2}\left( t\right) =\int _{-\infty }^{\infty }f_{1}\left( \tau\right) f_{2}\left( t-\tau \right) d\tau
f(t)=f1(t)∗f2(t)=∫−∞∞f1(τ)f2(t−τ)dτ
3.3.低通滤波
低通滤波的显著作用:实现图像平滑
代码部分:
我们用到的卷积公式:
y
(
j
,
i
)
=
h
(
j
,
i
)
∗
j
(
j
,
i
)
=
∑
m
∑
n
h
(
m
,
n
)
x
(
j
+
m
,
i
+
n
)
y(j,i)=h(j,i)\ast j(j,i)=\sum _{m}\sum _{n}h(m,n)x(j+m,i+n)
y(j,i)=h(j,i)∗j(j,i)=m∑n∑h(m,n)x(j+m,i+n)
点对点相乘的代码:
int CMFCApplication1View::convolution(int* operatr, BYTE* block){
int value;
int i, j;
value = 0;
//卷积运算
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
value += operatr[i * 3 + j] * block[i * 3 + j];
return value;
}
3.3.1.均值滤波(code)
y ( j , i ) = ∑ m ∑ n h ( m , n ) x ( j + m , i + n ) = 1 9 ∑ m = − 1 1 ∑ n = − 1 1 x ( j + m , i + n ) \begin{aligned}y(j,i)=\sum _{m}\sum _{n}h(m,n)x(j+m,i+n)\\ =\dfrac{1}{9}\sum ^{1}_{m=-1}\sum ^{1}_{n=-1}x(j+m,i+n)\end{aligned} y(j,i)=m∑n∑h(m,n)x(j+m,i+n)=91m=−1∑1n=−1∑1x(j+m,i+n)
注意 b l o c k block block 的大小设为9就可以了,省空间
void CMFCApplication1View::meanFilter(BYTE* image, int width, int heigth, BYTE* outImg){
//均值滤波
int smth[9];
int i, j, m, n;
BYTE block[9];
int value;
for (i = 0; i < 9; i++)
smth[i] = 1;
for (i = 0; i < height; i++){
for (j = 0; j < width; j++){
//最外面这一圈不做卷积运算了,即外圈灰度级直接置0
if (i == 0 || j == 0 || i == height - 1 || j == width - 1)
outImg[i * width + j] = 0;
else{
for (m = -1; m < 2; m++)
for (n = -1; n < 2; n++)
block[(m + 1) * 3 + n + 1] = image[(i + m) * width + j + n];
value = convolution(smth, block);
//归一化的步骤
outImg[i * width + j] = BYTE(value / 9.);
}
}
}
}
Additive_Noised_Image.raw
Pulse_Noised_Image.raw
3.3.2.高斯滤波(code)
高斯滤波的归一化因子是16,因此除以16就行
void CMFCApplication1View::gaussian(BYTE* image, int width, int heigth, BYTE* outImg){
//高斯滤波
int smth[9];
int i, j, m, n;
BYTE block[9];
int value;
smth[0] = 1; smth[4] = 4;
smth[1] = 2; smth[5] = 2;
smth[2] = 1; smth[6] = 1;
smth[3] = 2; smth[7] = 2;
smth[8] = 1;
for (i = 0; i < height; i++){
for (j = 0; j < width; j++){
if (i == 0 || j == 0 || i == height - 1 || j == width - 1)
outImg[i * width + j] = 0;
else{
for (m = -1; m < 2; m++)
for (n = -1; n < 2; n++)
block[(m + 1) * 3 + n + 1] = image[(i + m) * width + j + n];
value = convolution(smth, block);
outImg[i * width + j] = BYTE(value / 16.);
}
}
}
}
Additive_Noised_Image.raw
Pulse_Noised_Image.raw
附上直方图均衡:
Additive_Noised_Image.raw
Pulse_Noised_Image.raw
3.4.中值滤波(code)
为了在去除噪声的过程中、不模糊边缘,可以采用中值滤波:
代码部分:
中值滤波函数:
void CMFCApplication1View::midFindFiltering(BYTE* image, int width, int heigth, BYTE* outImg){
//中值滤波
int i, j, m, n;
BYTE block[9];
int value;
int blockNum = 9;
for (i = 0; i < height; i++){
for (j = 0; j < width; j++){
if (i == 0 || j == 0 || i == height - 1 || j == width - 1)
outImg[i * width + j] = 0;
else{
for (m = -1; m < 2; m++)
for (n = -1; n < 2; n++)
block[(m + 1) * 3 + n + 1] = image[(i + m) * width + j + n];
value = MidValueFind(blockNum, block);
outImg[i * width + j] = value;
}
}
}
}
O ( n 2 ) O(n^2) O(n2)的排序函数,有兴趣推荐改成 O ( n l o g n ) O(nlogn) O(nlogn):
int CMFCApplication1View::MidValueFind(int num, BYTE* d){
int value;
int i, j;
int temp;
for (i = 0; i < num - 1; i++)
for (j = i + 1; j < num; j++){
if (d[i] < d[j]){
temp = d[i];
d[i] = d[j];
d[j] = temp;
}
}
return d[num / 2];
}
中值滤波:
Additive_Noised_Image.raw
Pulse_Noised_Image.raw
3.5.高通滤波
高通滤波的显著作用:实现图像锐化
4.第三章 数学形态学处理
4.1.腐蚀(code)
- 一种消除边界点,使边界向内部收缩的过程,用来消除小且无意义的物体
- 图形点(gray=255)在其3x3邻域,只要有若干个背景点,则该点设为背景点(0)。
- 我们选择的是2值图像,即像素大小只有0(背景点)和255(图形点)
- 图像最外圈像素点不予处理
代码实现:
void CMFCApplication1View::erosion(BYTE* image, int w, int h, BYTE* outImg){
int rept;
//多次腐蚀,把结果图像image拷贝至outImg
memcpy(outImg, image, sizeof(BYTE) * width * height);
int i, j;
int m, n;
BYTE flag;
for (rept = 0; rept < 3; rept++){//多次腐蚀
for (i = 1; i < h - 1; i++){
for (j = 1; j < w - 1; j++){
if (image[i * w + j] == 255){//找到一个图形点
flag = 0;
for (m = -1; m < 2; m++){
for (n = -1; n < 2; n++){
if (image[(i + m) * w + j + n] == 0){
flag++;//3x3邻域包含多少个背景点
break;
}
}
}
if (flag > 2)
outImg[i * w + j] = 0;//该图形点设为背景点
}
}
}
}
memcpy(image, outImg, sizeof(BYTE) * width * height);
}
腐蚀结果:
4.2.膨胀(code)
将与物体接触所有背景点合并到该物体中,使边界向外部扩张的过程,可以用来填补物体中的空洞
与腐蚀不同的是,我们关注的是图像的背景点,如果背景点周围有一定个数的图形点,我们就将这个背景点设为图形点
代码实现:
void CMFCApplication1View::dilation(BYTE* image, int w, int h, BYTE* outImg){
int rept;
//膨胀
memcpy(outImg, image, sizeof(BYTE) * width * height);
int i, j;
int m, n;
BYTE flag;
for (rept = 0; rept < 3; rept++){//多次膨胀
for (i = 1; i < h - 1; i++){
for (j = 1; j < w - 1; j++){
if (image[i * w + j] == 0){//找到一个背景点(gray=0)
flag = 0;
for (m = -1; m < 2; m++){
for (n = -1; n < 2; n++){
if (image[(i + m) * w + j + n] == 255){
flag++;//3x3邻域包含多少个图形点
}
}
}
if (flag > 1)
outImg[i * w + j] = 255;//该图形点设为图形点
}
}
}
}
memcpy(image, outImg, sizeof(BYTE) * width * height);
}
膨胀:
4.3.开运算
先腐蚀后膨胀,能够消除图像区域 外 的 小白点(噪声)
4.4.闭运算
先膨胀后腐蚀,能够消除图像区域 内 的 小黑点(噪声)
开闭运算可以保持物体原有大小,一个是消除物体外部噪声(开运算)的,另一个是增强物体之间连接点(闭运算)的
5.第四章 图像分割
5.1.基于阈值的方法
5.2.基于边缘的方法
5.3.基于区域的方法
5.4.基于学习的方法
6.总结
数字图像处理需要认真的分析,本文涉及的算法都很简单,而且自己动手调参就可以看到变化,也易于解释(比如在腐蚀运算中,把邻域背景点判断范围从大于2放大为大于1)