数字图像处理学习笔记——基础篇
在原图像上寻找最靠近的像素并把它的灰度值赋给栅格上的新像素。
目录
数字图像处理技术概念
- 图像 图像是人类获取视觉信息的重要手段。 “图”,就是物体投射或反射光的分布;“像”是人的视觉系统接收视觉信息而在人的大脑中形成的印象或认识。 前者是客观存在的,而后者是人的感觉,图像应是两者的结合,即图像是对客观存在的物体的一种相似性的、生动的写真或描述。 图像分为三类: 1)可见图像:全部人眼可见(可接收到)的图像,包含了客观存在的光学图像和人类用各种不同方法人工生成的图像。 2)不可见物理图像:各种可测到(客观存在) 的物理特征量的空间分别所构成的一大类图像。 3)数学图像:抽象的数学图像,即用数学函数表达的图像,包括连续型和离散型。
- 图像处理技术 图像处理技术分为模拟图像处理和数字图像处理。模拟图像处理包括光学处理(利用透镜)和电子处理,如照相、电视信号处理等,其速度快,可并行处理,但精度和灵活性差。 因此产生数字图像处理,它利用数字计算机或其他高速、大规模集成数字硬件,对图像信息转换来的数字电信号进行某些数字运算或处理,以期提高图像质量或达到人们所需要求的某些预期结果。 数字图像处理的主要特点是图像信息量大、图像处理技术综合性强、图像信息理论与通信理论密切相关。 数字图像处理技术的基本应用:图像增强/恢复;艺术级效果;医学可视化;工业检验;法律执行;人机交互。 数字处理图像在生物医学 、遥感 、工业 、军事、通信、公安等领域有着广泛的应用。
- 图像处理的内容 图像信息的获取; 图像信息的存贮; 图像信息的传送; 图像信息处理; 图像信息的输出和显示。
- 图像的数字化 由一幅模拟图像获取一幅满足需求的数字图像,使图像便于计算机处理、分析。
- 图像感知和获取 图像形成模型在特定坐标(x,y)处,通过传感器转换获得的f值为一正的标量。 函数f(x,y)由:①入射到观察场景的光源总量; ②场景中物体反射光的总量 组成。
- 图像取样和量化 获取图像的目标是从感知的数据中产生数字图像,但是传感器的输出是连续的电压波形,因此需要把连续的感知数据转换为数字形式。这一过程由图像的取样与量化来完成。数字化坐标值称为取样;数字化幅度值称为量化。图像的取样率:单位距离的取样数目(在两个空间方向上)。 步骤可以分为:模拟图像信号=>空间采样=>灰度级(强度)量化 坐标的数字化称为采样,幅度值的数字化称为量化。
- 数字图像的表示 由矩阵表示,矩阵的每个元素表示为图像的一个像素。
- 空间和灰度分辨率 空间分辨率是指图像中可分辨的最小细节,主要由采样间隔值决定。采样间隔值越小,空间分辨率越高 灰度分辨率是指灰度级别中可辨别的最小变化,通常也把灰度级L称为灰度分辨率,一般情况L=2k,灰度级为L的图像一般也称为k位图像
- 相邻像素 处于4邻域、对角邻域、8邻域的像素
- 邻接性 像素的相邻仅说明了两个像素在位置上的关系,若再加上取值相同或相近,则称两个像素邻接。 令V是用于定义邻接性的灰度值集合(相似性准则),存在三种类型的邻接性: (1)4邻接:若像素p和q的灰度值均属于V中的元素,且q在N4(p)中,则p和q是4邻接的; (2)8邻接:若像素p和q的灰度值均属于V中的元素,且q在N8(p)集中,则p和q是8邻接的; (3)m邻接(混合邻接):若像素p和q的灰度值均属于V中的元素,{①q在N4(p)中,或者②q在ND(p)中}且{集合N4(p)∩N4(q)没有V值的像素},则具有V值的像素p和q是m邻接的。m邻接可以消除8邻接所带来的(通路)二义性。
- 通路 像素到像素的通路定义为特定的像素序列:和(对于1≤i≤n)是邻接的。n是通路的长度。若,则通路是闭合通路。
- 连通性 若S是图像中的一个象素子集,对任意的p,q∈S,如果存在一条由S中像素组成的从p到q的通路,则称p在图像集S中与q连通。 如果S中仅有一个连通分量,则S叫连通集; 所有属于S的连通到某一点p的像素点的集合被称为连通分量。
- 区域 R是图像中的像素子集,如果R是连通集,则称R为一个区域;两个区域,如果联合(并)为一个区域,则称这两个区域为邻接区域。①定义区域时,必须指明灰度相似性准则V={ };②定义邻接区域时,还必须指明邻接类型;
- 边界 假设一副图像S中有K个不连接区域,且它们都不接触图像边界。前景就是所有连通分量的并集;背景就是图像出去前景的剩余像素;内边界就是一个区域的边缘或轮廓线。(即:该区域中和其背景相邻接的点的集合)
- 距离 像素的距离一共有四种定义方式: 1)欧氏距离 = ; 2)D4(城市街区距离)距离 = ; 3)D8(棋盘距离)距离 = ; 4)Dm距离:用点间最短的通路定义。
数字图像处理基本操作
VisualStudio图片打开与保存
1.打开
CString strFilter;
CSimpleArray<GUID> aguidFileTypes;
HRESULT hResult;
// 获取CImage支持的图像文件的过滤字符串
hResult = m_sImage.GetExporterFilterString(strFilter, aguidFileTypes, _T("All Image Files"));
if (FAILED(hResult))
{
AfxMessageBox(_T("GetExporterFilter函数失败!"));
return;
}
// 生成打开图像文件的对话框
CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter);
if (IDOK != dlg.DoModal())
return;
// 清楚之前打开的图像文件
m_sImage.Destroy();
m_rImage.Destroy();
// 将外部图像文件装载到CImage对象中
hResult = m_sImage.Load(dlg.GetPathName());
if (FAILED(hResult))
{
AfxMessageBox(_T("图像文件加载失败!"));
return;
}
if (m_sImage.GetBPP() != 24)
{
m_sImage.Destroy();
AfxMessageBox(_T("只处理24位色图像!"));
return;
}
bFileIsOpen = true;
// 设置主窗口标题栏内容
CString str;
str.LoadString(AFX_IDS_APP_TITLE);
AfxGetMainWnd()->SetWindowText(str + " - " + dlg.GetFileName());
InvalidateRect(NULL, NULL, FALSE);
2.保存
if (m_rImage.IsNull()) {
AfxMessageBox(_T("你还没有生成一个要保存的图像文件!"));
return;
}
CString strFilter;
strFilter = "位图文件|*.bmp|JPEG 图像文件|*.jpg|GIF 图像文件|*.gif|PNG 图像文件|*.png||";
CFileDialog dlg(FALSE, NULL, NULL, NULL, strFilter);
if (IDOK != dlg.DoModal())
return;
// 如果用户没有指定文件扩展名,则为其添加一个
CString strFileName = dlg.GetPathName();
CString strExtension = dlg.GetFileExt();
if (strExtension.IsEmpty())
{
switch (dlg.m_ofn.nFilterIndex)
{
case 1:
strExtension = "bmp"; break;
case 2:
strExtension = "jpg"; break;
case 3:
strExtension = "gif"; break;
case 4:
strExtension = "png"; break;
default:
break;
}
strFileName = strFileName + _T(".") + strExtension;
}
// 图像保存
HRESULT hResult = m_rImage.Save(strFileName);
if (FAILED(hResult))
{
AfxMessageBox(_T("保存图像文件失败!"));
}
图像文件基本处理
1.图像文件信息读取
// 图像打开判断
if (m_sImage.IsNull())
{
AfxMessageBox(_T("请打开要处理的图像!"));
return;
}
// 图像属性
int x, y;
int nRows = m_sImage.GetHeight(); // 图像像素行数 即图像高度
int nCols = m_sImage.GetWidth(); // 图像像素列数 即图像宽度
int nBPP = m_sImage.GetBPP(); // 图像颜色位数
// 清空并创建目标图像
m_rImage.Destroy();
if (!m_rImage.Create(nCols, nRows, nBPP))
return;
// 读取像素数组(由一维数组储存二维数组)
BYTE *sImageData = (BYTE*)m_sImage.GetBits(); // 原图像的像素数组
BYTE *rImageData = (BYTE*)m_rImage.GetBits(); // 目标图像的像素数组
int nRowBytes = m_sImage.GetPitch(); // 原图像的像素数组每行长度
int nLineBytes = m_rImage.GetPitch(); // 目标图像的像素数组每行长度
2.图像缩小
行、列的删除
// 缩小n倍
if (!m_rImage.Create(nCols/n, nRows/n, nBPP))
return;
3.图像放大
1)最近邻域内插法
在原图像上寻找最靠近的像素并把它的灰度值赋给栅格上的新像素。
void CDigitalImageProcessingDoc::OnNearestInterpolationScaling()
{
// TODO: 在此添加命令处理程序代码
if (m_sImage.IsNull())
{
AfxMessageBox(_T("请打开要处理的图像!"));
return;
}
int x;
int y;
CDialogScalingFactor mdlg;
if (mdlg.DoModal() != IDOK) //输入水平和垂直方向放大系数
{
return;
}
if (mdlg.mHoriScalingFactor < 0 || mdlg.mVeriScalingFactor < 0)
{
AfxMessageBox(_T("缩放参数错误!"));
return;
}
int sWidth = m_sImage.GetWidth(); //原始图像宽度
int sHeight = m_sImage.GetHeight(); //原始图像高度
int rWidth = (int)(sWidth * mdlg.mHoriScalingFactor + 0.5); //目标图像宽度
int rHeight = (int)(sHeight * mdlg.mVeriScalingFactor + 0.5);//目标图像高度
m_rImage.Destroy();
if (!m_rImage.Create(rWidth, rHeight, m_sImage.GetBPP(), 0))
return;
int sRowBytes = m_sImage.GetPitch();
int rRowBytes = m_rImage.GetPitch();
BYTE *sImageData = (BYTE *)m_sImage.GetBits();
BYTE *rImageData = (BYTE *)m_rImage.GetBits();
for (y = 1; y < rHeight - 1; y++)
for (x = 1; x < rWidth - 1; x++)
{
int i = (int)(x / mdlg.mHoriScalingFactor);
int j = (int)(y / mdlg.mVeriScalingFactor);
rImageData[y*rRowBytes + x * 3] = sImageData[j * sRowBytes + i * 3];
rImageData[y*rRowBytes + x * 3 + 1] = sImageData[j * sRowBytes + i * 3 + 1];
rImageData[y*rRowBytes + x * 3 + 2] = sImageData[j * sRowBytes + i * 3 + 2];
}
UpdateAllViews(NULL); // 强制调用OnDraw
return;
}
2)双线性内插法
通过公式计算新像素的灰度值
void CDigitalImageProcessingDoc::OnBilinearInterpolationScaling()
{
// TODO: 在此添加命令处理程序代码
if (m_sImage.IsNull())
{
AfxMessageBox(_T("请打开要处理的图像!"));
return;
}
int x;
int y;
CDialogScalingFactor mdlg;
if (mdlg.DoModal() != IDOK)
{
return;
}
if (mdlg.mHoriScalingFactor < 0 || mdlg.mVeriScalingFactor < 0)
{
AfxMessageBox(_T("缩放参数错误!"));
return;
}
int sWidth = m_sImage.GetWidth();
int sHeight = m_sImage.GetHeight();
int rWidth = (int)(sWidth * mdlg.mHoriScalingFactor + 0.5);
int rHeight = (int)(sHeight * mdlg.mVeriScalingFactor + 0.5);
m_rImage.Destroy();
if (!m_rImage.Create(rWidth, rHeight, m_sImage.GetBPP(), 0))
return;
int sRowBytes = m_sImage.GetPitch();
int rRowBytes = m_rImage.GetPitch();
BYTE *sImageData = (BYTE *)m_sImage.GetBits();
BYTE *rImageData = (BYTE *)m_rImage.GetBits();
for (y = 1; y < rHeight - 1; y++)
for (x = 1; x < rWidth - 1; x++)
{
double u = x / mdlg.mHoriScalingFactor;
double v = y / mdlg.mVeriScalingFactor;
int i = (int)u;
int j = (int)v;
u = u - i;
v = v - j;
if ((i >= sWidth - 1) && (j >= sHeight - 1))
{
rImageData[y*rRowBytes + x * 3] = sImageData[(sHeight - 1) * sRowBytes + (sWidth - 1) * 3];
rImageData[y*rRowBytes + x * 3 + 1] = sImageData[(sHeight - 1) * sRowBytes + (sWidth - 1) * 3 + 1];
rImageData[y*rRowBytes + x * 3 + 2] = sImageData[(sHeight - 1) * sRowBytes + (sWidth - 1) * 3 + 2];
}
else if (j >= sHeight - 1)
{
*(rImageData + y*rRowBytes + x * 3) = sImageData[(sHeight - 1) * sRowBytes + i * 3];
*(rImageData + y*rRowBytes + x * 3 + 1) = sImageData[(sHeight - 1) * sRowBytes + i * 3 + 1];
*(rImageData + y*rRowBytes + x * 3 + 2) = sImageData[(sHeight - 1) * sRowBytes + i * 3 + 2];
}
else if (i >= sWidth - 1)
{
rImageData[y*rRowBytes + x * 3] = sImageData[j * sRowBytes + (sWidth - 1) * 3];
rImageData[y*rRowBytes + x * 3 + 1] = sImageData[j * sRowBytes + (sWidth - 1) * 3 + 1];
rImageData[y*rRowBytes + x * 3 + 2] = sImageData[j * sRowBytes + (sWidth - 1) * 3 + 2];
}
else
{
rImageData[y*rRowBytes + x * 3] = (BYTE)((1 - u)*(1 - v)*(sImageData[j * sRowBytes + i * 3])
+ (1 - u)*v*(sImageData[(j + 1)*sRowBytes + i * 3])
+ u*(1 - v)*(sImageData[j*sRowBytes + (i + 1) * 3])
+ u*v*(sImageData[(j + 1)*sRowBytes + (i + 1) * 3]));
rImageData[y*rRowBytes + x * 3 + 1] = (BYTE)((1 - u)*(1 - v)*(sImageData[j * sRowBytes + i * 3 + 1])
+ (1 - u)*v*(sImageData[(j + 1)*sRowBytes + i * 3 + 1])
+ u*(1 - v)*(sImageData[j*sRowBytes + (i + 1) * 3 + 1])
+ u*v*(sImageData[(j + 1)*sRowBytes + (i + 1) * 3 + 1]));
rImageData[y*rRowBytes + x * 3 + 2] = (BYTE)((1 - u)*(1 - v)*(sImageData[j * sRowBytes + i * 3 + 2])
+ (1 - u)*v*(sImageData[(j + 1)*sRowBytes + i * 3 + 2])
+ u*(1 - v)*(sImageData[j*sRowBytes + (i + 1) * 3 + 2])
+ u*v*(sImageData[(j + 1)*sRowBytes + (i + 1) * 3 + 2]));
}
}
UpdateAllViews(NULL); // 强制调用OnDraw
return;
}
数字图像基本操作
1.算数操作
算术运算只涉及一个空间位置(象素)的运算; 算术运算在相同大小的图像之间进行;
1)加
作用:去除“叠加性”噪音;图像叠加。 定义:g(x,y) = f(x,y) + h(x,y)
void CDigitalImageProcessingDoc::OnImageAdd()
{
// TODO: 在此添加命令处理程序代码
if (m_sImage.IsNull())
{
AfxMessageBox(_T("请打开要处理的图像!"));
return;
}
CImage lsImage;
CString strFilter;
CSimpleArray<GUID> aguidFileTypes;
HRESULT hResult;
// 获取CImage支持的图像文件的过滤字符串
hResult = m_sImage.GetExporterFilterString(strFilter, aguidFileTypes, _T("All Image Files"));
if (FAILED(hResult))
{
AfxMessageBox(_T("GetExporterFilter函数失败!"));
return;
}
CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter);
if (IDOK != dlg.DoModal())
return;
lsImage.Destroy();
// 将外部图像文件装载到CImage对象中
hResult = lsImage.Load(dlg.GetPathName());
if (FAILED(hResult))
{
AfxMessageBox(_T("图像文件加载失败!"));
return;
}
if (lsImage.GetBPP() != 24)
{
lsImage.Destroy();
AfxMessageBox(_T("只处理24位色图像!"));
return;
}
if ((lsImage.GetHeight() != m_sImage.GetHeight()) || (lsImage.GetWidth() != m_sImage.GetWidth()))
{
lsImage.Destroy();
AfxMessageBox(_T("本软件只处理相同大小图像的逻辑与代数运算!"));
return;
}
CDialogImageAddRatio mdlg;
if (mdlg.DoModal() != IDOK)
{
lsImage.Destroy();
return;
}
double lambuda = mdlg.addRatio / 100.0;
int x;
int y;
int nRows = m_sImage.GetHeight();
int nCols = m_sImage.GetWidth();
int nBPP = m_sImage.GetBPP();
m_rImage.Destroy();
if (!m_rImage.Create(nCols, nRows, nBPP))
{
lsImage.Destroy();
return;
}
BYTE *sImageData = (BYTE *)m_sImage.GetBits();
BYTE *lsImageData = (BYTE *)lsImage.GetBits();
BYTE *rImageData = (BYTE *)m_rImage.GetBits();
int nRowBytes = m_sImage.GetPitch();
int sRowBytes = m_sImage.GetPitch();
for (y = 0; y < nRows; y++)
for (x = 0; x < nCols; x++)
{
rImageData[y * nRowBytes + x * 3 + 0] = (int)(lambuda * sImageData[y * nRowBytes + x * 3 + 0] + (1 - lambuda) * lsImageData[y * nRowBytes + x * 3 + 0]);
rImageData[y * nRowBytes + x * 3 + 1] = (int)(lambuda * sImageData[y * nRowBytes + x * 3 + 1] + (1 - lambuda) * lsImageData[y * nRowBytes + x * 3 + 1]);
rImageData[y * nRowBytes + x * 3 + 2] = (int)(lambuda * sImageData[y * nRowBytes + x * 3 + 2] + (1 - lambuda) * lsImageData[y * nRowBytes + x * 3 + 2]);
}
lsImage.Destroy();
UpdateAllViews(NULL);
}
2)减
作用:去除不需要的叠加性图案;检测两幅图像之间的差别;计算物体边界的梯度(差分运算); 定义:g(x,y) = f(x,y) - h(x,y)
void CDigitalImageProcessingDoc::OnImageSub()
{
// TODO: 在此添加命令处理程序代码
if (m_sImage.IsNull())
{
AfxMessageBox(_T("请打开要处理的图像!"));
return;
}
CImage lsImage;
CString strFilter;
CSimpleArray<GUID> aguidFileTypes;
HRESULT hResult;
// 获取CImage支持的图像文件的过滤字符串
hResult = m_sImage.GetExporterFilterString(strFilter, aguidFileTypes, _T("All Image Files"));
if (FAILED(hResult))
{
AfxMessageBox(_T("GetExporterFilter函数失败!"));
return;
}
CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter);
if (IDOK != dlg.DoModal())
return;
lsImage.Destroy();
// 将外部图像文件装载到CImage对象中
hResult = lsImage.Load(dlg.GetPathName());
if (FAILED(hResult))
{
AfxMessageBox(_T("图像文件加载失败!"));
return;
}
if (lsImage.GetBPP() != 24)
{
lsImage.Destroy();
AfxMessageBox(_T("只处理24位色图像!"));
return;
}
if ((lsImage.GetHeight() != m_sImage.GetHeight()) || (lsImage.GetWidth() != m_sImage.GetWidth()))
{
lsImage.Destroy();
AfxMessageBox(_T("本软件只处理相同大小图像的逻辑与代数运算!"));
return;
}
CDialogImageAddRatio mdlg;
if (mdlg.DoModal() != IDOK)
{
lsImage.Destroy();
return;
}
double lambuda = mdlg.addRatio / 100.0;
int x;
int y;
int nRows = m_sImage.GetHeight();
int nCols = m_sImage.GetWidth();
int nBPP = m_sImage.GetBPP();
m_rImage.Destroy();
if (!m_rImage.Create(nCols, nRows, nBPP))
{
lsImage.Destroy();
return;
}
BYTE *sImageData = (BYTE *)m_sImage.GetBits();
BYTE *lsImageData = (BYTE *)lsImage.GetBits();
BYTE *rImageData = (BYTE *)m_rImage.GetBits();
int nRowBytes = m_sImage.GetPitch();
int sRowBytes = m_sImage.GetPitch();
int nrgb;
for (y = 0; y < nRows; y++)
for (x = 0; x < nCols; x++)
{
nrgb = (int)(lambuda * sImageData[y * nRowBytes + x * 3 + 0] - (1 - lambuda) * lsImageData[y * nRowBytes + x * 3 + 0]);
nrgb = (nrgb < 0) ? 0 : ((nrgb>255) ? 255 : nrgb);
rImageData[y * nRowBytes + x * 3 + 0] = nrgb;
nrgb = (int)(lambuda * sImageData[y * nRowBytes + x * 3 + 1] - (1 - lambuda) * lsImageData[y * nRowBytes + x * 3 + 1]);
nrgb = (nrgb < 0) ? 0 : ((nrgb>255) ? 255 : nrgb);
rImageData[y * nRowBytes + x * 3 + 1] = nrgb;
nrgb = (int)(lambuda * sImageData[y * nRowBytes + x * 3 + 2] - (1 - lambuda) * lsImageData[y * nRowBytes + x * 3 + 2]);
nrgb = (nrgb < 0) ? 0 : ((nrgb>255) ? 255 : nrgb);
rImageData[y * nRowBytes + x * 3 + 2] = nrgb;
}
lsImage.Destroy();
UpdateAllViews(NULL);
}
3)乘
作用:图象的局部显示(用二值模板图像与原图像做乘法) 定义:g(x,y) = f(x,y) × h(x,y)
void CDigitalImageProcessingDoc::OnImageMul()
{
// TODO: 在此添加命令处理程序代码
if (m_sImage.IsNull())
{
AfxMessageBox(_T("请打开要处理的图像!"));
return;
}
CImage lsImage;
CString strFilter;
CSimpleArray<GUID> aguidFileTypes;
HRESULT hResult;
// 获取CImage支持的图像文件的过滤字符串
hResult = m_sImage.GetExporterFilterString(strFilter, aguidFileTypes, _T("All Image Files"));
if (FAILED(hResult))
{
AfxMessageBox(_T("GetExporterFilter函数失败!"));
return;
}
CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter);
if (IDOK != dlg.DoModal())
return;
lsImage.Destroy();
// 将外部图像文件装载到CImage对象中
hResult = lsImage.Load(dlg.GetPathName());
if (FAILED(hResult))
{
AfxMessageBox(_T("图像文件加载失败!"));
return;
}
if (lsImage.GetBPP() != 24)
{
lsImage.Destroy();
AfxMessageBox(_T("只处理24位色图像!"));
return;
}
if ((lsImage.GetHeight() != m_sImage.GetHeight()) || (lsImage.GetWidth() != m_sImage.GetWidth()))
{
lsImage.Destroy();
AfxMessageBox(_T("本软件只处理相同大小图像的逻辑与代数运算!"));
return;
}
int x;
int y;
int nRows = m_sImage.GetHeight();
int nCols = m_sImage.GetWidth();
int nBPP = m_sImage.GetBPP();
m_rImage.Destroy();
if (!m_rImage.Create(nCols, nRows, nBPP))
{
lsImage.Destroy();
return;
}
BYTE *sImageData = (BYTE *)m_sImage.GetBits();
BYTE *lsImageData = (BYTE *)lsImage.GetBits();
BYTE *rImageData = (BYTE *)m_rImage.GetBits();
int nRowBytes = m_sImage.GetPitch();
int sRowBytes = m_sImage.GetPitch();
int nrgb;
for (y = 0; y < nRows; y++)
for (x = 0; x < nCols; x++)
{
nrgb = (int)(sImageData[y * nRowBytes + x * 3 + 0] * lsImageData[y * nRowBytes + x * 3 + 0]);
nrgb = (nrgb < 0) ? 0 : ((nrgb>255) ? 255 : nrgb);
rImageData[y * nRowBytes + x * 3 + 0] = nrgb;
nrgb = (int)(sImageData[y * nRowBytes + x * 3 + 1] * lsImageData[y * nRowBytes + x * 3 + 1]);
nrgb = (nrgb < 0) ? 0 : ((nrgb>255) ? 255 : nrgb);
rImageData[y * nRowBytes + x * 3 + 1] = nrgb;
nrgb = (int)(sImageData[y * nRowBytes + x * 3 + 2] * lsImageData[y * nRowBytes + x * 3 + 2]);
nrgb = (nrgb < 0) ? 0 : ((nrgb>255) ? 255 : nrgb);
rImageData[y * nRowBytes + x * 3 + 2] = nrgb;
}
lsImage.Destroy();
UpdateAllViews(NULL);
}
4)除
作用:增强两幅图像的差异。 定义:g(x,y) = f(x,y) ÷ h(x,y) 实际运算是将一幅图像取反后乘上另一幅图像
void CDigitalImageProcessingDoc::OnImageDiv()
{
// TODO: 在此添加命令处理程序代码
if (m_sImage.IsNull())
{
AfxMessageBox(_T("请打开要处理的图像!"));
return;
}
CImage lsImage;
CString strFilter;
CSimpleArray<GUID> aguidFileTypes;
HRESULT hResult;
// 获取CImage支持的图像文件的过滤字符串
hResult = m_sImage.GetExporterFilterString(strFilter, aguidFileTypes, _T("All Image Files"));
if (FAILED(hResult))
{
AfxMessageBox(_T("GetExporterFilter函数失败!"));
return;
}
CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter);
if (IDOK != dlg.DoModal())
return;
lsImage.Destroy();
// 将外部图像文件装载到CImage对象中
hResult = lsImage.Load(dlg.GetPathName());
if (FAILED(hResult))
{
AfxMessageBox(_T("图像文件加载失败!"));
return;
}
if (lsImage.GetBPP() != 24)
{
lsImage.Destroy();
AfxMessageBox(_T("只处理24位色图像!"));
return;
}
if ((lsImage.GetHeight() != m_sImage.GetHeight()) || (lsImage.GetWidth() != m_sImage.GetWidth()))
{
lsImage.Destroy();
AfxMessageBox(_T("本软件只处理相同大小图像的逻辑与代数运算!"));
return;
}
int x;
int y;
int nRows = m_sImage.GetHeight();
int nCols = m_sImage.GetWidth();
int nBPP = m_sImage.GetBPP();
m_rImage.Destroy();
if (!m_rImage.Create(nCols, nRows, nBPP))
{
lsImage.Destroy();
return;
}
BYTE *sImageData = (BYTE *)m_sImage.GetBits();
BYTE *lsImageData = (BYTE *)lsImage.GetBits();
BYTE *rImageData = (BYTE *)m_rImage.GetBits();
int nRowBytes = m_sImage.GetPitch();
double alpha = 0.0001;
int sRowBytes = m_sImage.GetPitch();
int nrgb;
for (y = 0; y < nRows; y++)
for (x = 0; x < nCols; x++)
{
nrgb = (int)(sImageData[y * nRowBytes + x * 3 + 0] / (lsImageData[y * nRowBytes + x * 3 + 0] + alpha));
nrgb = (nrgb < 0) ? 0 : ((nrgb>255) ? 255 : nrgb);
rImageData[y * nRowBytes + x * 3 + 0] = nrgb;
nrgb = (int)(sImageData[y * nRowBytes + x * 3 + 1] / (lsImageData[y * nRowBytes + x * 3 + 1] + alpha));
nrgb = (nrgb < 0) ? 0 : ((nrgb>255) ? 255 : nrgb);
rImageData[y * nRowBytes + x * 3 + 1] = nrgb;
nrgb = (int)(sImageData[y * nRowBytes + x * 3 + 2] / (lsImageData[y * nRowBytes + x * 3 + 2] + alpha));
nrgb = (nrgb < 0) ? 0 : ((nrgb>255) ? 255 : nrgb);
rImageData[y * nRowBytes + x * 3 + 2] = nrgb;
}
lsImage.Destroy();
UpdateAllViews(NULL);
}
2.逻辑操作
在逻辑运算中,象素灰度值作为二进制串处理 “与”、“或”操作可用来从一幅图像中提取子图像。
1)与(&)
void CDigitalImageProcessingDoc::OnImageAnd()
{
// TODO: 在此添加命令处理程序代码
if (m_sImage.IsNull())
{
AfxMessageBox(_T("请打开要处理的图像!"));
return;
}
CImage lsImage;
CString strFilter;
CSimpleArray<GUID> aguidFileTypes;
HRESULT hResult;
// 获取CImage支持的图像文件的过滤字符串
hResult = m_sImage.GetExporterFilterString(strFilter, aguidFileTypes, _T("All Image Files"));
if (FAILED(hResult))
{
AfxMessageBox(_T("GetExporterFilter函数失败!"));
return;
}
CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter);
if (IDOK != dlg.DoModal())
return;
lsImage.Destroy();
// 将外部图像文件装载到CImage对象中
hResult = lsImage.Load(dlg.GetPathName());
if (FAILED(hResult))
{
AfxMessageBox(_T("图像文件加载失败!"));
return;
}
if (lsImage.GetBPP() != 24)
{
lsImage.Destroy();
AfxMessageBox(_T("只处理24位色图像!"));
return;
}
if ((lsImage.GetHeight() != m_sImage.GetHeight()) || (lsImage.GetWidth() != m_sImage.GetWidth()))
{
lsImage.Destroy();
AfxMessageBox(_T("本软件只处理相同大小图像的逻辑与代数运算!"));
return;
}
int x;
int y;
int nRows = m_sImage.GetHeight();
int nCols = m_sImage.GetWidth();
int nBPP = m_sImage.GetBPP();
m_rImage.Destroy();
if (!m_rImage.Create(nCols, nRows, nBPP))
{
lsImage.Destroy();
return;
}
BYTE *sImageData = (BYTE *)m_sImage.GetBits();
BYTE *lsImageData = (BYTE *)lsImage.GetBits();
BYTE *rImageData = (BYTE *)m_rImage.GetBits();
int nRowBytes = m_sImage.GetPitch();
int sRowBytes = m_sImage.GetPitch();
for (y = 0; y < nRows; y++)
for (x = 0; x < nCols; x++)
{
rImageData[y * nRowBytes + x * 3 + 0] = sImageData[y * nRowBytes + x * 3 + 0] & lsImageData[y * nRowBytes + x * 3 + 0];
rImageData[y * nRowBytes + x * 3 + 1] = sImageData[y * nRowBytes + x * 3 + 1] & lsImageData[y * nRowBytes + x * 3 + 1];
rImageData[y * nRowBytes + x * 3 + 2] = sImageData[y * nRowBytes + x * 3 + 2] & lsImageData[y * nRowBytes + x * 3 + 2];
}
lsImage.Destroy();
UpdateAllViews(NULL);
}
2)或(|)
void CDigitalImageProcessingDoc::OnImageOr()
{
// TODO: 在此添加命令处理程序代码
if (m_sImage.IsNull())
{
AfxMessageBox(_T("请打开要处理的图像!"));
return;
}
CImage lsImage;
CString strFilter;
CSimpleArray<GUID> aguidFileTypes;
HRESULT hResult;
// 获取CImage支持的图像文件的过滤字符串
hResult = m_sImage.GetExporterFilterString(strFilter, aguidFileTypes, _T("All Image Files"));
if (FAILED(hResult))
{
AfxMessageBox(_T("GetExporterFilter函数失败!"));
return;
}
CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter);
if (IDOK != dlg.DoModal())
return;
lsImage.Destroy();
// 将外部图像文件装载到CImage对象中
hResult = lsImage.Load(dlg.GetPathName());
if (FAILED(hResult))
{
AfxMessageBox(_T("图像文件加载失败!"));
return;
}
if (lsImage.GetBPP() != 24)
{
lsImage.Destroy();
AfxMessageBox(_T("只处理24位色图像!"));
return;
}
if ((lsImage.GetHeight() != m_sImage.GetHeight()) || (lsImage.GetWidth() != m_sImage.GetWidth()))
{
lsImage.Destroy();
AfxMessageBox(_T("本软件只处理相同大小图像的逻辑与代数运算!"));
return;
}
int x;
int y;
int nRows = m_sImage.GetHeight();
int nCols = m_sImage.GetWidth();
int nBPP = m_sImage.GetBPP();
m_rImage.Destroy();
if (!m_rImage.Create(nCols, nRows, nBPP))
{
lsImage.Destroy();
return;
}
BYTE *sImageData = (BYTE *)m_sImage.GetBits();
BYTE *lsImageData = (BYTE *)lsImage.GetBits();
BYTE *rImageData = (BYTE *)m_rImage.GetBits();
int nRowBytes = m_sImage.GetPitch();
int sRowBytes = m_sImage.GetPitch();
for (y = 0; y < nRows; y++)
for (x = 0; x < nCols; x++)
{
rImageData[y * nRowBytes + x * 3 + 0] = sImageData[y * nRowBytes + x * 3 + 0] | lsImageData[y * nRowBytes + x * 3 + 0];
rImageData[y * nRowBytes + x * 3 + 1] = sImageData[y * nRowBytes + x * 3 + 1] | lsImageData[y * nRowBytes + x * 3 + 1];
rImageData[y * nRowBytes + x * 3 + 2] = sImageData[y * nRowBytes + x * 3 + 2] | lsImageData[y * nRowBytes + x * 3 + 2];
}
lsImage.Destroy();
UpdateAllViews(NULL);
}
3)非(~)
3.空间处理
空间操作直接在给定图像的象素上执行
1)单象素操作
对图像的每个元素进行统一的函数变换操作 说明:
2)邻域操作
处理后图像像素的灰度值由对应位置的原图像像素邻域的灰度值所确定 说明:
3)几何空间变换
几何空间变换(橡皮模变换)要改变图像中象素间的空间关系。 几何变换的两个基本操作:坐标的空间变换;灰度内插; 说明:
图像增强(空间域方法)
如果不考虑图像降质的原因,只将图像中感兴趣的特征(如边缘、轮廓、对比度等)进行强调或有选择的突出,同时衰减其它不需要的特征,以便于显示、观察或分析,此种图像处理称为图像增强(Image Enhancement) 空间域方法是直接以图像中的像素操作为基础的图像增强方法
1.灰度变换(强度映射、点处理)
令表示输入图像,表示处理之后的输出图像;如邻域大小为像素本身,即输出图像 任意点的灰度值仅依赖于输入图象在像素点的灰度值,则定义的操作被称为灰度级变换函数(又称灰度映射) 。 令r和s分别表示输入图像和输出图像在任意点的灰度级(值),灰度变换可表示为: , 即: 关键:寻找一个合适的变换函数T(r),根据灰度变换函数T(r)选择方法的不同,灰度变换可分为:直方图处理方法和直接灰度变换。
1)直接灰度变换:
根据问题,直接选择灰度变换函数,实现图像增强。主要处理对比度、灰度动态范围等问题。 r和s分别是输入图像f (x,y)和输出图像g (x,y)在任意点(x,y)的灰度级。
常用的变换函数有: (1)线性函数(正比、反比、分段线性函数) (2)对数函数 (3)幂律函数(n次幂和n次方根函数) (4)其它特殊非线性函数
图像反转
当输入图像的灰度级范围为的图像反转操作可由反比变换获得,表达式为: 用这种方式倒转图像的强度产生图像反转的对等图像。这种处理尤其适用于增强嵌入图像暗色区域的白色或灰色细节,特别是当黑色面积占主导地位时。
对数变换
其中,c是一个常数,且假定r≥0。 对数变换常用于图像的动态范围压缩。与增强对比度相反,有时原图的动态范围太大,超出某些显示设备的允许动态范围,这时直接使用原图,则一部分细节可能丢失。
幂律(伽马)变换
幂律变换的基本形式为:。其中c和γ为正的常数。 伽马校正:用于图像获取、打印和显示的各种装置根据幂次规律进行响应。习惯上,幂次等式中的指数是指伽马值,用于修正幂次响应现象的过程称做伽马校正。
分段线性变换函数
(1)对比拉伸:低对比度(照明不足、传感器动态范围小)提高图像灰度级的动态范围,改善图像对比度。 拐点的位置控制了变换函数的形状,保证变换函数为单值单调。 (2)灰度级分层:在图像中突出显示特定范围的灰度级别。 (3)位平面分层:可能需要突出显示由特定位对整个图像外观所做的贡献。
2)直方图处理(直方图均衡化、直方图匹配)
如果将图像中像素灰度级看成是一个随机变量,则其取值分布情况就反映了图像的统计特性,这一特性可用灰度直方图来描述。 灰度级范围为的数字图象的灰度直方图是灰度级的离散函数: 式中,是第k级灰度值,是图像中灰度值为的像素的个数,。 灰度直方图是灰度级的函数,它表示图像中具有某种灰度级的像素的个数,反映了图像中每种灰度出现的频率。
归一化
其中,n 为一幅图像中像素总数,是图像中灰度值为的像素的个数。灰度直方图归一化处理后,可视为图像灰度级发生的概率估计值。
直方图均衡
所谓直方图均衡化,是指寻找一个灰度变换函数:使变换后的图像的像素值占有全部的灰度级并且分布均匀,从而得到一幅灰度级丰富且动态范围大的图像(即高对比度图像)。
- 解析证明: 假设输入图像的灰度级r为连续量,并被归一化到区间[0, 1],且r = 0表示黑色,r = 1表示白色。灰度变换函数也为连续函数: 也就是说,通过上述变换,每个原始图像的像素灰度值r都对应产生一个s值。由从s到r的反变换可用下式表示: 变换函数T(r)应满足下列条件:(1)在0≤r≤1区间内,T(r)单值(严格)单调递增;(2)当0≤r≤1时, 0≤T(r)≤1。条件(1)保证了输出图像的灰度级从白到黑的次序不变,并保证 T(r) 的反函数存在;条件(2)则保证了变换后输出灰度级与输入有同样的范围。 一幅图像的灰度级可被视为区间[0,1]的随机变量。令和分别代表随机变量r和s的概率密度函数 。 由概率论理论可知,如果已知随机变量ξ的概率密度函数为 ,而随机变量η是ξ 的函数,即,η 的概率密度可由求出,即: 所以,变换后的图像的灰度级s的概率密度函数由输人图像的灰度级r的概率密度函数,和所选择的变换函数T(r)决定。 变换函数为: 式中:ω是积分变量,而是r的累积分布函数。累积分布函数是r的函数,并且单值单调地从0增加到1,所以这个变换函数满足关于T(r) 的两个条件。 从微积分学(莱布尼茨准则),我们知道关于上限的定积分的导数就是该上限的积分项,即: ,变换后的图像的灰度级 s 的概率密度函数:
- 实现方法: (1)计算输入图像的归一化直方图。 一幅图像中灰度级出现的概率可近似为: 式中:L是灰度级的总数目,是图像中灰度级为的像素的个数,n是图像中像素总数。 (2)直方图均衡化灰度变换函数的离散形式为: (3)经上式变换后的取值为小数,在实际中还要对其取整并重新量化,否则,图像整体偏亮: (4)这样,就完成了由输入图像灰度级到输出图像灰度级的映射变换。对输入图像中任一像素,如果其灰度值为:,输出图像的灰度值为
直方图匹配(规定化)
处理后的输出图像具有指定的灰度直方图形状,以便能对图像中某种灰度级加以增强,即有选择性的增强某个灰度值范围内的对比度。这种用于使处理后的图像具有指定灰度直方图的增强方法,叫做直方图匹配或直方图规定化处理。
2.空间域滤波(模板处理)
输出图像g(x,y)任意像素(x,y)的灰度值为输入图像f(x,y)事先定义的*(x,y)邻域*内所有像素灰度值的某种函数,即:
概念
- 像素(x,y)的邻域(neighborhood) 是以(x,y)像素为中心的正方形或矩形子图像(如3×3),也可以定义为圆形或其他形状的邻域(但矩形邻域操作方便,多被采用)。
- 模板运算 模板又称滤波器、核、掩模、窗口等,是一个小的二维阵列,模板的系数值决定了增强处理的性质,如平滑、锐化等,这种增强方法又称空间域滤波。 主要步骤为: 1)将模板在图中漫游,并将模板中心与图中某个象素位置重合; 2)将模板上系数与模板下对应象素相乘; 3)将所有乘积相加; 4)将和(模板的输出响应)赋给图中对应模板中心位置的象素。 注意: 定义模板时一定指明模板中心。 图像边界处理方法:忽略图像边界数据;另一种方法是在图像四周复制原图像边界像素的值,或补上必需数量的像素灰度值为零的行和列,从而使卷积核悬挂在原图像四周时可以进行正常的计算。
分类
平滑空间滤波器
平滑滤波器用于模糊处理和减小噪声。常见的平滑处理应用就是减噪。然而,由于图像边缘也是由图像灰度尖锐变化带来的特性,所以平滑滤波处理存在着边缘模糊的负面效应。平滑滤波器能减弱或消除图像的高频分量,因为高频分量对应图像中的区域边缘等灰度值变化较大、较快的部分,滤波器将这些分量滤除,从而使图像平滑。
- 均值滤波器:邻域平均滤波器的输出(响应)是包含在滤波掩模邻域内像素的简单平均值。因此,这些滤波器也称为均值滤波器,是一种低通滤波器。还可以在模板中引入了加权系数,以区分邻域中不同位置像素对输出像素值的影响,常称其为加权模板。
- 统计排序滤波器:统计滤波器是一种非线性的空间滤波器,它的响应基于滤波器模板包围的图像区域中像素的排序,然后由统计排序结果决定的值代替中心像素的值。统计滤波器中最常见的例子就是中值滤波器,它是将像素邻域内灰度的中值代替该像素的值。中值滤波器对处理脉冲噪声(也称为椒盐噪声,以黑白点叠加在图像上)非常有效。但是对一些细节多,特别是点、线、尖顶细节多的图像不宜采用中值滤波。中值滤波是一种非线性滤波器。
锐化空间滤波器
锐化处理的主要目的是突出灰度的过渡部分,增强图像中的细节。空间域像素邻域平均法可以使图像变模糊,而均值处理与积分相类似,从逻辑角度我们可以断定,锐化处理可以用空间微分(差分)来完成。 微分(差分)算子的响应强度与图像在该点灰度的突变程度有关,图像微分增强了边缘和其他突变(如噪声)并削弱了灰度变化缓慢的区域。 然而由于边缘和轮廓在一幅图像中常常具有任意方向,而差分运算是有方向性的,因此和差分方向一致的边缘和轮廓便检测不出来,因而希望采用一些各向同性的检测算子,它们对任意方向的边缘和轮廓都有相同的检测能力。 要注意的是,能够进行锐化处理的图像必须有较高的信噪比,否则,图像锐化后,信噪比会更低,因为锐化会提升噪声强度。一般是先去噪,再锐化操作。 一阶微分处理通常会产生较宽的边缘; 二阶微分处理对细节有较强的响应,如细线和孤立点; 一阶微分处理一般对灰度阶梯有较强的响应; 二阶微分处理对灰度级阶梯变化产生双响应。 二阶微分在图像中灰度值变化相似时,对线的响应要比对阶梯强,且点比线响应强。 大多数应用中,对图像增强来说,二阶微分处理比一阶微分好一些,因为形成增强细节的能力好一些。由于这一原因及实现和扩展都简单,对图像增强多应用二阶微分处理。 1)拉普拉斯算子:拉普拉斯算子是常用的边缘增强算子,拉普拉斯运算也是偏导数运算的线性组合运算,而且是一种各向同性(旋转不变性)的线性运算。拉普拉斯算子为: 由于拉普拉斯是一种微分算子,它的应用强调图像中灰度的突变及衰减灰度慢变化区域的灰度,这将产生一幅把图像中的浅灰色边线和突变点叠加到暗背景中的图像。将原始图像和拉普拉斯图像叠加在一起的简单方法可以保护拉普拉斯锐化处理的效果,同时又能复原背景信息。 2)非锐化掩蔽和高升滤波:非锐化掩蔽:从原图像中减去其非锐化(平滑过的)图像的过程(下式k =1时),步骤:模糊原图像;从原图像中减去模糊图像(产生的差值图像称为模板);将模板加到原图像上。高提升滤波:下式k>1时。 式子为: 3)梯度算子: 一般把梯度矢量的模值称为梯度, 像素模板: 水平垂直差分法: 罗伯特梯度法: Sobel算子:\begin{cases}g_x=(z_7+2z_8+z_9)-(z_1+2z_2+z_3)\\\\g_y=(z_3+2z_6+z_9)-(z_1+2z_4+z_7)\\end{cases} ※寻找一个合适的模板(滤波核)
图像增强(频率域方法)
在图像的某种变换域内,对图像的变换值进行处理。如,先对图像进行二维傅立叶变换,再对图像的频谱进行某种修正(滤波),最后将修正后的变换值逆变换到空间域,从而获得增强后的图像。 寻找一个合适的模板(滤波核)
卷积理论
如果原始图像是,处理后的图像是,而是滤波器的单位冲激响应,那么,空间域滤波处理过程可由下式表示: 如果, , 分别是,和的傅里叶变换,由傅里叶变换的卷积定理可知: <=> 经傅里叶逆变换可得到: 两个关键: 1)将图像从图像空间转换到频域空间所需的变换以及再将图像从频域空间转换到图像空间所需的变换。 2)在频域空间对图像进行增强处理的滤波核。
卷积公式: 一维 离散型 二维 离散型
傅立叶变换
连续傅立叶变换
一维傅里叶变化: 通常称为的Fourier变换,为的Fourier反变换。 令 其中 称为的振幅谱(频谱,傅里叶谱);称为的相位谱,称为的能量谱 通常,空间域指的是x的变化范围,而频率域指的是u的变化范围。 二维傅里叶变化: 令 其中 称为的振幅谱(频谱,傅里叶谱); 称为的相位谱,称为的能量谱
离散型傅立叶变换
一维: 逆变换: 二维: 逆变换: 因为Fourier变换是一种正交变换,所以其正、反变换的系数可以有几种表示形式。 按照严格意义上的正交变换,正、反变换的系数相等,为:1/√MN 按照计算方便的角度,正、反变换的系数可以按照前面的方式给出,并且正、反变换的系数可以取反。
Fourier变换有两个好处: 可以得出信号在各个频率点上的强度;可以将卷积运算化为乘积运算。 为简化问题,只讨论离散二维图像函数的Fourier变换,并且图幅参数为N×N的特殊情况。
性质
- 尺度变换:
- 傅里叶变换的周期性:
- 平移 空域平移 频域平移 只要对图像乘以,频谱的原点就会移动到中央位置,从中央到周围边界的变化即为低频到高频的变化。
- 可分性:,可见连续两次应用一维傅立叶变换,就可求得或。
- 共轭对称性:
- 旋转不变性:如果旋转θ, 则也旋转θ
- Parseval定理:
连续Fourier变换与离散傅立叶变换的联系及他们的物理解释
- 采样定理: 一维采样定理,若连续信号f(t)的最高截止频率为fc ,则采样频率必须满足fs≥2fc时,才能保证采样信号不失真地表示原信号; 二维采样定理,如果二维信号f(x,y)的Fourier频谱F(u,v)满足:F(u,v)=F(u,v) ,|u|≤U0,|v|≤V0,其余情况为0,其中,Uc,Vc是相应于空间变量x,y的最高截止频率,则当采样周期ΔxΔy,1/Δx=Us≥2Uc,1/Δy=Vs≥2Vc时,采样信号f(mΔx,nΔy),m,n=0, ±1, ±2…能唯一地恢复原信号f(x,y),一般取:1/Δx=2Uc,1/Δy=2Vc,ΔxΔu=1/N,ΔyΔv=1/N
- Fourier 变换的统计特性: 直流分量:反映了原始图像的平均亮度; 能量集中:在低频区,85%,是数据编码和压缩的基础; 图像的亮度突变或跳变部分对应的高频区,缓变部分分布在低频区。
快速傅里叶变换