非专业做图像处理的,如有错漏,敬请指正。
这是课程作业,对我这种之前没用过C语言图像处理的人来说很费劲。在此分享一是做学习记录,二是希望为遇到此类问题的朋友提供一种思路和方法;但如果有同样作业的同学看到此文,不建议直接下载源码抄作业,没意义,建议看懂原理之后自己敲处理核心部分代码、改改界面框架。所以这个没有百度云免费分享,而且我这个做得也很low,如果真的有学习需要的,希望能帮到:
gray-level_correction_noel202007.zip
需要用到teechart控件,没有的朋友看这里:VS2013 简单MFC应用以及teechart使用方法
若出现以下错误(没出现就不管)
那么点击项目属性,更改平台工具集
搭建一个图像处理程序框架,实现将图片像素读取至二维数组,对图片的打开、显示、保存等基本功能,并做灰度修正处理。图像处理的部分是c写的,主要是将图片读入二维数组,然后对每个像素的灰度值进行操作,c++主要是用于搭建处理程序框架;
处理内容是利用分段折线变换关系对灰度图像进行修正,显示、保存变换结果和直方图;
图片显示使用picture控件,直方图用的teechart绘制。
参考书目:share_noel/图像处理/数字图像处理-夏良正.pdf
https://blog.csdn.net/qq_41102371/article/details/125646840
灰度变换原理(教材P140):
灰度变换结果:前面两张待处理原图在此:share_noel/图像处理/lena.bmp(lena1.bmp) 提取码:0ooc
第一行第一张是lena.bmp原图,后面4张是lena.bmp的分段折线变换图,第二行第一张是lena1原图,后面4张是lena1.bmp的分段折线变换图。左下角是直方图,可通过下拉框选择显示。
这里涉及bmp图片格式及灰度图片转换,参考这几篇博客:
c语言数字图像处理(一):bmp图片格式及灰度图片转换
BMP图片结构解析(文中是用UltraEdit软件打开的BMP文件,UltraEdit的安装推荐微信公众号“软件安装管家”)
BMP文件结构
BMP图片文件读取及灰度图转化代码参考了一个cnblogs的博主,推荐阅读,这是他的主页https://www.cnblogs.com/GoldBeetle/
用vs2013新建一个基于对话框的MFC应用程序
添加多个picture控件、编辑框控件、按钮,一个下拉框控件、一个teechart控件
(按钮添加使用见此处:VS2013 简单MFC应用以及teechart使用方法)
整个函数目的是获取当前选择的图片完整路径,然后准备好新文件的完整路径
在按钮的处理函数里面添加代码:
void Cgraylevel_correction_LXDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
int a = 10,i,j;
char *cc;
CString str;
CString filter;
filter = "所有文件(*.bmp,*.jpg,*.gif)|*.bmp;*.jpg| BMP(*.bmp)|*.bmp| JPG(*.jpg)|*.jpg||";
CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY, filter, NULL);
//直方图数组清零
for (i = 0; i < h1; i++)
{
gramf1[i] = 0;
}
for (i = 0; i < h2; i++)
{
gramf2[i] = 0;
}
//弹框提示
MessageBox(_TEXT("请选择图片1"));
if (dlg.DoModal() == IDOK)
{
bmpfilepath1 = dlg.GetPathName(); //获取文件路径名 如D:\pic\abc.bmp
}
if (pFileName1a!=NULL)//先检查是否是第二次选择文件,是的话先重新初始化变量,防止文件名追加
{
file1fa = _TEXT("file1fa.bmp"); file1fb = _TEXT("file1fb.bmp");//处理后图像保存的文件名
file1fc = _TEXT("file1fc.bmp"); file1fd = _TEXT("file1fd.bmp");
file2fa = _TEXT("file2fa.bmp"); file2fb = _TEXT("file2fb.bmp");
file2fc = _TEXT("file2fc.bmp"); file2fd = _TEXT("file2fd.bmp");
free(pFileName1a); free(pFileName1b);
free(pFileName1c); free(pFileName1d);
free(pFileName2a); free(pFileName2b);
free(pFileName2c); free(pFileName2d);
}
//CString转char
CStringtochar(bmpfilepath1, &pFileName1);//记得要free(pFileName)
cc = strstr(pFileName1, ".bmp");//判断是否为bmp文件
if (cc == NULL){
str.Format(_T("%s不是bmp文件"), pFileName1);
MessageBox(str);
return;
}
/*文件路径保存
pFileName1是通过选择原图获取的路径如“D:\\csdn\\gray-level_correction_noel202007\\test_picture\\lena.bmp”
newbmppath函数的作用是将pFileName1的文件名去掉变成路径D:\\csdn\\gray-level_correction_noel202007\\test_picture\\
然后再加上比如"file1fa.bmp"变成新文件的完整路径D:\\csdn\\gray-level_correction_noel202007\\test_picture\\file1fa.bmp
最后将新文件名放入两个变量file1fa与pFileName1a
file1fa是CString类型的,用于在showbmp函数里面加载图片
pFileName1a是char类型的,用于c语言对文件的读写*/
newbmppath(pFileName1, &file1fa, &pFileName1a);
newbmppath(pFileName1, &file1fb, &pFileName1b);
newbmppath(pFileName1, &file1fc, &pFileName1c);
newbmppath(pFileName1, &file1fd, &pFileName1d);
MessageBox(_TEXT("请选择图片2"));
if (dlg.DoModal() == IDOK)
{
bmpfilepath2 = dlg.GetPathName(); //获取文件路径名 如D:\pic\abc.bmp
}
CStringtochar(bmpfilepath2, &pFileName2);//记得要free(pFileName)
cc = strstr(pFileName2, ".bmp");
if (cc == NULL){
str.Format(_T("%s不是bmp文件"), pFileName2);
MessageBox(str);
return;
}
newbmppath(pFileName2, &file2fa, &pFileName2a);
newbmppath(pFileName2, &file2fb, &pFileName2b);
newbmppath(pFileName2, &file2fc, &pFileName2c);
newbmppath(pFileName2, &file2fd, &pFileName2d);
Cgraylevel_correction_LXDlg::showbmp(bmpfilepath1, 1);//显示原图1
Cgraylevel_correction_LXDlg::showbmp(bmpfilepath2, 2);//显示原图2
get_image_size(pFileName1, &h1, &w1);//获取图像尺寸
in_array1 = allocate_image_array(h1, w1);//根据图像尺寸分配内存
read_bmp_image(pFileName1, in_array1);//读像素进数组in_array1
get_image_size(pFileName2, &h2, &w2);
in_array2 = allocate_image_array(h2, w2);
read_bmp_image(pFileName2, in_array2);
for (i = 0; i < h1; i++)
{
for (j = 0; j < w1; j++)
{
gramf1[in_array1[i][j]]++;//直方图数组
}
}
for (i = 0; i < h2; i++)
{
for (j = 0; j < w2; j++)
{
gramf2[in_array2[i][j]]++;
}
}
}
编辑框控件用于输入阈值更新至变量以做灰度变换
添加一个编辑框控件,方便自己记忆和编程改下ID
添加编辑框控件后,右键添加变量
类别选value,类型选int,变量名自己起一个,我这里已经添加好了,所以是灰色。其他编辑框同理
添加好以后可以看到xxxDlg.h里面已经为我们定义好了int型的变量
在xxxDlg.cpp的DoDataExchange函数里面为我们做了变量m_p1dvalueA与控件ID IDC_P1AVALUEA的绑定
所以在编辑框里面输入数字,就能通过
UpdateData(true);
来更新输入值到变量使用了。
另外,可以在OnInitDialog()函数里面设定默认值
添加控件,改ID
在showbmp函数里面将图片显示至对应控件ID,传入的形参CString filepath是文件要显示的图片路径(这时候已经生成了对应的灰度变换图了并保存至文件中了),int ID形参ID是对应是10个picture控件,自己给他们编的号。不要和前面讲的控件ID名字混了
void Cgraylevel_correction_LXDlg::showbmp(CString filepath, int ID)
{
CWnd* m_pWnd=NULL;
switch (ID)
{
case 1:m_pWnd = this->GetDlgItem(IDC_PICTURE_LX1); break;// IDC_PICTURE_LX1为Picture控件ID
case 2:m_pWnd = this->GetDlgItem(IDC_PICTURE_LX2); break;
case 3:m_pWnd = this->GetDlgItem(IDC_PIC1_F1); break;
case 4:m_pWnd = this->GetDlgItem(IDC_PIC1_F2); break;
case 5:m_pWnd = this->GetDlgItem(IDC_PIC1_F3); break;
case 6:m_pWnd = this->GetDlgItem(IDC_PIC1_F4); break;
case 7:m_pWnd = this->GetDlgItem(IDC_PIC2_F1); break;
case 8:m_pWnd = this->GetDlgItem(IDC_PIC2_F2); break;
case 9:m_pWnd = this->GetDlgItem(IDC_PIC2_F3); break;
case 10:m_pWnd = this->GetDlgItem(IDC_PIC2_F4); break;
default: ; break;
}
CRect rect;
CImage image;
image.Load(filepath);
m_pWnd->GetWindowRect(&rect);//将客户区选中到控件表示的矩形区域内
CWnd *pWnd1 = NULL;
pWnd1 = m_pWnd;//获取控件句柄
pWnd1->GetClientRect(&rect);//获取句柄指向控件区域的大小
CDC *pDc = NULL;
pDc = pWnd1->GetDC();//获取picture的DC
pDc->SetStretchBltMode(STRETCH_HALFTONE);
image.Draw(pDc->m_hDC, rect);//
ReleaseDC(pDc);
}
按钮的使用看另一篇博客:VS2013 简单MFC应用以及teechart使用方法
以“灰度修正1(a)”为例,目的是把加载的原图1按下图进行灰度变换。就是将A,B区间里面的灰度值扩展到Z1和Zk,其余区间灰度值不变。A、B、Z1、Z2对应变量m_p1avalueA、m_p1avalueB、m_p1avalueZ1、m_p1avalueZ2
void Cgraylevel_correction_LXDlg::OnBnClickedCorrection1a()
{
// TODO: 在此添加控件通知处理程序代码
if (bmpfilepath1 == "" || bmpfilepath2 == "")
{
MessageBox(_T("请先加载原图"));
return;
}
int i, j, a=0,b=0,c=0;
UpdateData(true);//更新编辑框值到变量
if (m_p1avalueA == m_p1avalueB)
{
MessageBox(_T("A、B不能相等"));
return;
}
//直方图数组清零
for (i = 0; i < h1; i++)
{
gramf1a[i] = 0;
}
((CSeries)m_Chart.Series(0)).Clear();
((CSeries)m_Chart.Series(1)).Clear();
out_array = allocate_image_array(h1, w1);
bmheader.height = h1;
bmheader.width = w1;
create_allocate_bmp_file(pFileName1a, &bmp_file_header, &bmheader);
//灰度变换核心代码
//for循环逐像素读取并进行灰度修正
// in_array1是原图的像素值数组,out_array是灰度修正后存放像素值的数组
for (i = 0; i < h1; i++){//h1*w1是图片尺寸
for (j = 0; j < w1; j++){
if ((in_array1[i][j] >= m_p1avalueA) && (in_array1[i][j] <= m_p1avalueB))
{//如果灰度值在(A,B)区间,则将区间扩展至(Z1,Z2)
out_array[i][j] = (in_array1[i][j] - m_p1avalueA)*(m_p1avalueZ2 - m_p1avalueZ1) / (m_p1avalueB - m_p1avalueA) + m_p1avalueZ1;
}//如果不在,则不变
if ((0 <= in_array1[i][j] && in_array1[i][j] < m_p1avalueA) || (m_p1avalueB < in_array1[i][j] && in_array1[i][j] < 255))
{
out_array[i][j] = in_array1[i][j];
//out_array[i][j] = 0;
}
}
}
for (i = 0; i < h1; i++)
{
for (j = 0; j < w1; j++)
{
gramf1a[out_array[i][j]]++;//灰度值统计,用于灰度直方图
}
}
write_bmp_image(pFileName1a, out_array);
free_image_array(out_array, h1);
Cgraylevel_correction_LXDlg::showbmp(file1fa, 3);//显示灰度变换图1
//显示灰度变换折线
((CSeries)m_Chart.Series(0)).AddXY(m_p1avalueA, m_p1avalueZ1, NULL, 0);//A为横坐标 Z1为纵坐标画连线
((CSeries)m_Chart.Series(0)).AddXY(m_p1avalueB, m_p1avalueZ2, NULL, 0);//B为横坐标 Z2为纵坐标画连线
}
变换前直方图
变换关系(左下)
变换后直方图(左下)
由原图直方图可知,图像大部分灰度集中在50-215,所以将此范围的灰度扩展到了20-255增加了图像的对比度。结果的灰度直方图可以看到灰度已经扩展到了255,符合预期。
在在初始化函数OnInitDialog()里面初始化下拉列表
在控件属性的控件事件里面添加处理函数
处理代码
void Cgraylevel_correction_LXDlg::OnCbnSelchangeCombo1()
{
// TODO: 在此添加控件通知处理程序代码
((CSeries)m_Chart.Series(0)).Clear();
((CSeries)m_Chart.Series(1)).Clear();
switch (m_comboxdraw.GetCurSel()+1)
{
//调用直方图显示showhistogram函数
case 1:Cgraylevel_correction_LXDlg::showhistogram(gramf1); break;
case 2:Cgraylevel_correction_LXDlg::showhistogram(gramf1a); break;
case 3:Cgraylevel_correction_LXDlg::showhistogram(gramf1b); break;
case 4:Cgraylevel_correction_LXDlg::showhistogram(gramf1c); break;
case 5:Cgraylevel_correction_LXDlg::showhistogram(gramf1d); break;
case 6:Cgraylevel_correction_LXDlg::showhistogram(gramf2); break;
case 7:Cgraylevel_correction_LXDlg::showhistogram(gramf2a); break;
case 8:Cgraylevel_correction_LXDlg::showhistogram(gramf2b); break;
case 9:Cgraylevel_correction_LXDlg::showhistogram(gramf2c); break;
case 10:Cgraylevel_correction_LXDlg::showhistogram(gramf2d); break;
default:; break;
}
}
直方图的数组已经在前面处理部分准备好了
teechart的使用见VS2013 简单MFC应用以及teechart使用方法
void Cgraylevel_correction_LXDlg::showhistogram(int *histogram)//输入直方图数组
{
int i = 0, j = 0;
for (i = 0; i < 255; i++)
{
//teechart绘直方图
((CSeries)m_Chart.Series(1)).AddXY(i, histogram[i], NULL, 0);//A为横坐标 Z1为纵坐标画连线
}
}
//for循环逐像素读取并进行灰度修正
// in_array1是原图的像素值数组,out_array是灰度修正后存放像素值的数组
for (i = 0; i < h1; i++){//h1*w1是图片尺寸
for (j = 0; j < w1; j++){
if ((in_array1[i][j] >= m_p1avalueA) && (in_array1[i][j] <= m_p1avalueB))
{//如果灰度值在(A,B)区间,则将区间扩展至(Z1,Z2)
out_array[i][j] = (in_array1[i][j] - m_p1avalueA)*(m_p1avalueZ2 - m_p1avalueZ1) / (m_p1avalueB - m_p1avalueA) + m_p1avalueZ1;
if (out_array[i][j] ==255)
{
a++;
}
}//如果不在,则不变
if ((0 <= in_array1[i][j] && in_array1[i][j] < m_p1avalueA) || (m_p1avalueB < in_array1[i][j] && in_array1[i][j] < 255))
{
out_array[i][j] = in_array1[i][j];
//out_array[i][j] = 0;
}
}
}
//for循环逐像素读取并进行灰度修正
// in_array1是原图的像素值数组,out_array是灰度修正后存放像素值的数组
for (i = 0; i < h1; i++){
for (j = 0; j < w1; j++){
//如果灰度值在(A,B)区间,则将区间扩展至(Z1,Z2)
if ((in_array1[i][j] >= m_p1bvalueA) && (in_array1[i][j] <= m_p1bvalueB))
{
out_array[i][j] = (in_array1[i][j] - m_p1bvalueA)*(m_p1bvalueZ2 - m_p1bvalueZ1) / (m_p1bvalueB - m_p1bvalueA) + m_p1bvalueZ1;
}
//(0, A)区间的灰度置为Z1
if (in_array1[i][j] < m_p1bvalueA)
{
out_array[i][j] = m_p1bvalueZ1;
//out_array[i][j] = 0;
}
//(B, 255)区间的灰度置为Z2
if ((in_array1[i][j]>m_p1bvalueB ) && (in_array1[i][j] <= 255))
{
out_array[i][j] = m_p1bvalueZ2;
//out_array[i][j] = 0;
}
}
}
//for循环逐像素读取并进行灰度修正
// in_array1是原图的像素值数组,out_array是灰度修正后存放像素值的数组
for (i = 0; i < h1; i++){
for (j = 0; j < w1; j++){
//如果灰度值在(A,B)区间,则将区间扩展至(Z1,Z2)
if ((in_array1[i][j] >= m_p1cvalueA) && (in_array1[i][j] <= m_p1cvalueB))
{
out_array[i][j] = (in_array1[i][j] - m_p1cvalueA)*(m_p1cvalueZ2 - m_p1cvalueZ1) / (m_p1cvalueB - m_p1cvalueA) + m_p1cvalueZ1;
if (out_array[i][j] == 255)
{
a++;
}
}
//(0,A)区间的灰度压缩至(0,Z1)
if ((in_array1[i][j] < m_p1cvalueA) && (in_array1[i][j] >= 0))
{
out_array[i][j] = (m_p1cvalueZ1 / m_p1cvalueA)*in_array1[i][j];
}
//(B,255)区间的灰度压缩至(Z2,255)
if ((in_array1[i][j] > m_p1cvalueB) && (in_array1[i][j] <= 255))
{
out_array[i][j] = ((255 - m_p1cvalueZ2) / (255 - m_p1cvalueB))*(in_array1[i][j] - m_p1cvalueB) + m_p1cvalueZ2;
}
}
}
//for循环逐像素读取并进行灰度修正
// in_array1是原图的像素值数组,out_array是灰度修正后存放像素值的数组
for (i = 0; i < h1; i++){
for (j = 0; j < w1; j++){
//将灰度区间在(0, A)上的灰度值扩展到(0, 255)区间
if ((in_array1[i][j] >= 0) && (in_array1[i][j] < m_p1dvalueA))
{
out_array[i][j] = (255 / m_p1dvalueA)*in_array1[i][j];
//out_array[i][j] = 0;
}
//(A,B)区间的灰度扩展至(0,255)
if ((in_array1[i][j] >= m_p1dvalueA) && (in_array1[i][j] < m_p1dvalueB))
{
out_array[i][j] = (255 / (m_p1dvalueB - m_p1dvalueA))*(in_array1[i][j] - m_p1dvalueA);
//out_array[i][j] = in_array1[i][j];
}
//(B, 255)区间的灰度扩展至(0, 255)
if ((in_array1[i][j] >= m_p1dvalueB) && (in_array1[i][j] <= 255))
{
out_array[i][j] = (255 / (255 - m_p1dvalueB))*(in_array1[i][j] - m_p1dvalueB);
//out_array[i][j] = 0;
}
}
}
学习笔记,如有错漏,敬请指正
--------------------------------------------------------------------------------------------诺有缸的高飞鸟202007