这是我做课设软件时候的经历(?)
开场白
一、实验目的
1、学习医学图像处理的相关理论知识,掌握图像处理的相关算法,对图像处理有基础的了解。
2、掌握用C++的基本语言来编写图像处理算法,完成对图像的基本处理操作。
3、使用 VS2019 编写 MFC 界面,掌握 MFC 界面应用程序的开发。
二、实验内容
1、BMP 图像的打开与显示
2、在菜单上实现简单的图像处理。包括几何变换(翻转、平移、旋转),图像平滑(平均模板、高斯模板、自定义模板、中值滤波),灰度变换(线性、阈值),图像分割?边缘检测(梯度锐化)
3、设置弹出对话框(用户与传输的端口),在对话框实现多个参数的输入。
4、实现基于对话框实现一种邻域处理的任务。
5、掌握MFC应用程序,实现对图像的基本页面交互处理。
三、实验环境及工具介绍
1、VS2019简介
【省略】
2、MFC简介
【省略】
3、ITK简介
【省略】
本实验通过VS2019+ITK5.2.1联合操作运行,通过VS2019的MFC制作可视化GUI,利用ITK对图像进行处理。
界面设计、菜单栏
这里是一个非常蠢的界面xs
方法是通过vs2019新建MFC应用程序,命名为Homeworking,应用程序类型选择“基于对话框”,在主界面上新建两个图像控件,并分别修改ID和类变量以用于后续使用。
然后在资源栏中添加菜单栏,并通过菜单的ID将其与主界面链接起来。
可视化窗口的位置在vs2019左侧的资源视图
与ITK的假集成
因为没能成功直接调用ITK库的文件(可能是我哪一步操作做错了,但我觉得应该是可行的)(这里是将ITK变成vs2019的外部库的方法【感谢CSDN(上的人)】
所以最终决定调用已经生成好的ITK的图像处理exe软件,生成exe的方法在这里(并不确定是哪一章了,好像在续章or第一章?)
稍微介绍一下生成exe的方法
首先是建个文件夹,用来存放CmakeList和ITK的.cxx文件,ITK的.cxx文件我不知道怎么写,“哈哈”,是从它自带的范例文件里拷出来的。
巧了,CmakeList我也不知道怎么写,“哈哈”,也是它现成儿的。(其中需要在FlipImageFilter的地方修改相应的项目名称和.cxx文件的名字)
# This is the root ITK CMakeLists file.
cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR)
foreach(p
## Only policies introduced after the cmake_minimum_required
## version need to explicitly be set to NEW.
CMP0070 #3.10.0 Define ``file(GENERATE)`` behavior for relative paths.
CMP0071 #3.10.0 Let ``AUTOMOC`` and ``AUTOUIC`` process ``GENERATED`` files.
)
if(POLICY ${p})
cmake_policy(SET ${p} NEW)
endif()
endforeach()
# This project is designed to be built outside the Insight source tree.
project(FlipImageFilter)
# Find ITK.
find_package(ITK REQUIRED)
include(${ITK_USE_FILE})
add_executable(FlipImageFilter FlipImageFilter.cxx )
target_link_libraries(FlipImageFilter ${ITK_LIBRARIES})
然后在刚刚建的文件夹中再新建一个bin文件夹,用来放置使用cmake后生成的工程文件。
用cmake去编译,上方的资源地址填入第一个创建的文件夹,下面的填bin文件夹,然后点击“Configure”直到没有红色警告,之后依次“Generate”,“Open Project”就可以进入vs2019去“生成解决方案”。最后得到输出的exe文件。
对ITK的范例.cxx文件做了些微的修改
因为后来我发现这个范例不符合我的输入输出要求xs,它最初的版本是由它设定了一个阈值(Threshold)和像素值(Outsidevalue),然后结果输出的是三张分别使用三种方法的图。
但是我想要用户选择方法输入参数,得到结果图的模式。所以对源ThresholdImageFilter.cxx文件做了一些更改:更改了在命令行输入的参数,分别是输入文件名,阈值外像素值,下限,上限,输出文件名和方法。
#include "itkThresholdImageFilter.h"
// Software Guide : EndCodeSnippet
#include "itkImage.h"
#include "itkImageFileReader.h"
#include "itkImageFileWriter.h"
#include <iostream>
int
main(int argc, char * argv[])
{
//在exe程序中如果输入少于设定参数就退出程序并给出范例输入
if (argc < 6)
{
std::cerr << "Usage: " << argv[0] << " inputImageFile ";
std::cerr << "Outsidevalue ThresholdAbove ThresholdBelow outputImageFile method"
<< std::endl;
return EXIT_FAILURE;
}
//判断方法的标志
int i = std::atoi(argv[6]);
using PixelType = unsigned char;
using ImageType = itk::Image<PixelType, 2>;
using FilterType = itk::ThresholdImageFilter<ImageType>;
using ReaderType = itk::ImageFileReader<ImageType>;
using WriterType = itk::ImageFileWriter<ImageType>;
ReaderType::Pointer reader = ReaderType::New();
FilterType::Pointer filter = FilterType::New();
WriterType::Pointer writer = WriterType::New();
writer->SetInput(filter->GetOutput());
reader->SetFileName(argv[1]);
filter->SetInput(reader->GetOutput());
if (i==1)
{
filter->SetOutsideValue(std::stoi(argv[2]));
filter->ThresholdBelow(std::stoi(argv[3]));
filter->Update();
writer->SetFileName(argv[5]);
writer->Update();
}
else if (i==2)
{
filter->SetOutsideValue(std::stoi(argv[2]));
filter->ThresholdAbove(std::stoi(argv[4]));
filter->Update();
writer->SetFileName(argv[5]);
writer->Update();
}
else
{
filter->SetOutsideValue(std::stoi(argv[2]));
filter->ThresholdOutside(std::stoi(argv[3]), std::stoi(argv[4]));
filter->Update();
writer->SetFileName(argv[5]);
writer->Update();
}
return EXIT_SUCCESS;
}
在一个软件中调用外部exe的方法函数(抄来的)
// TODO: 在此添加命令处理程序代码
//Cstring转为const char*(我不会搞itk和mfc的合并使用...)
CString strfile("transit.bmp");
CString File;
//这个File是我用来寄存ITK生成的结果图片的,之后会调取这个地址的图片进行显示
File = Filepath + strfile;
//ITK某功能exe需求的命令行
CString command = strFilePath + " " + File + " " + "1" + " "+"0";
//愚笨的调用之前生成的itk的exe,生成一个图像
//调用外部程序
SHELLEXECUTEINFO ShExecInfo;
ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShExecInfo.hwnd = this->m_hWnd;
ShExecInfo.lpVerb = _T("open");
//输入要调用的exe文件路径
ShExecInfo.lpFile = _T(".\\FlipImageFilter.exe");
//传入命令行参数数据
ShExecInfo.lpParameters = command; //若没有命令行参数,可为NULL
ShExecInfo.lpDirectory = NULL;//这里exe的目录可忽略,写为NULL
ShExecInfo.nShow = SW_HIDE;//这里设置为不显示exe界面,若设置为SW_SHOW,则可以显示exe界面
ShExecInfo.hInstApp = NULL;
//调用exe程序
ShellExecuteEx(&ShExecInfo);
//关闭该exe程序
if (ShExecInfo.hProcess != NULL)
{
//等待程序运行完毕
WaitForSingleObject(ShExecInfo.hProcess, INFINITE);
//关闭程序
TerminateProcess(ShExecInfo.hProcess, 0);
ShExecInfo.hProcess = NULL;
}
打开并显示图像
在打开文件时,通过对选择文件进行过滤,使之只获取bmp图像。此时为了与MFC中的图像控件(PictureControl)相匹配,使用MFC自带的CImage类型读取图像并进行不失真显示(若图片小于控件则正常显示,若大于控件则图像不受限制)
文件选择对话框
void CHomeworkingDlg::OnOpenimage()
{
// TODO: 在此添加命令处理程序代码
//设置过滤器
TCHAR szFilter[] = _T("bmp图像(*.bmp)|*.bmp|所有文件(*.*)|*.*||");
//构建打开文件对话框
CFileDialog fileDlg(TRUE, _T("bmp"), NULL, 0, szFilter, this);
CString filename;
//显示打开文件框
if (IDOK == fileDlg.DoModal())
{
filename = fileDlg.GetFileName();
strFilePath = fileDlg.GetPathName();
}
//这里可以输出路径+文件名的字符串
Filepath = strFilePath.Left(strFilePath.GetLength() - filename.GetLength());
CString strfile("transit.bmp");
File = Filepath + strfile;
//下面是画图
}
在图像控件中不失真显示图片
// 清除图像
GetDlgItem(IDC_begin)->ShowWindow(false);
GetDlgItem(IDC_begin)->ShowWindow(true);
//MFC不失真在控件画图
int height, width;
CRect rect;//定义矩形类
CRect rect1;
CImage image; //创建图片类
image.Load(strFilePath);
height = image.GetHeight();
width = image.GetWidth();
p_begin.GetClientRect(&rect); //获得pictrue控件所在的矩形区域
CDC* pDc = p_begin.GetDC();//获得pictrue控件的Dc
SetStretchBltMode(pDc->m_hDC, STRETCH_HALFTONE);
if (width <= rect.Width() && height <= rect.Width()) //小图片,不缩放
{
rect1 = CRect(rect.TopLeft(), CSize(width, height));
image.StretchBlt(pDc->m_hDC, rect1, SRCCOPY); //将图片画到Picture控件表示的矩形区域
}
else
{
float xScale = (float)rect.Width() / (float)width;
float yScale = (float)rect.Height() / (float)height;
float ScaleIndex = (xScale >= yScale ? xScale : yScale);
rect1 = CRect(rect.TopLeft(), CSize((int)width * ScaleIndex, (int)height * ScaleIndex));
image.StretchBlt(pDc->m_hDC, rect1, SRCCOPY); //将图片画到Picture控件表示的矩形区域
}
ReleaseDC(pDc);//释放picture控件的Dc
随便做些处理——就阈值分割吧!
在二值化分割中,最终得到的是二值图像(图像中只有两种像素值),但有时我们会遇 到另外一种需求,只改变某一阈值范围的像素值,其他部分保留,此时二值化分割已经满足 不了这个需求了,需要寻求另外一种方法。一般阈值分割是二值化的改进版,可以只改变某 一范围内的像素值,并使其他范围内像素值得到保留。
在一般阈值分割中主要有三种分割方式:第一种需要设置一个低临界阈值 Lower Threshold,图像中像素值若低于这个值,其值变为 Outsidevalue,否则像素值不变;第二种 方法需要设置一个高临界阈值 Upper Threshold,图像中像素值若高于这个值,像素值将变成 Outsidevalue,否则像素值不变;第三种方法结合了前两种,该方法需要设置两个阈值临界 Lower Threshold 和 Upper Threshold 两个值,若像素值介于两者之间则不变,否则设为 Outsidevalue。
(需要对ITK给予的原程序进行一些修改)
用户交互界面对话框
对于普通的从对话框的编辑框获取用户输入的信息,传递入主函数
//提前创建窗口用于用户交互
BinThr dlg;
dlg.DoModal();
//获得编辑框内容(int转CString)
int low = dlg.v_lower;
CString lowt;
lowt.Format("%d", low);
CROT dlg ;
dlg.DoModal();
//获得编辑框内容
int degrees = dlg.r_degree;
degree.Format("%d", degrees);
可以为编辑框添加限制
按钮的选择与判断(【笨方法】全局变量)
对于更复杂的对话框,比如需要选择不同的方法时,在上述这个例子中:
在对话框中加入相应的方法单选框,(在对话框初始化加入默认选择方法 1) 并通过单选框的选择来控制编辑框的可编辑性,例如选择方法 2,就只可编辑 OutsideValue 和 UpperThreshold。
然后通过全局变量的方式,来根据单选按钮的不同选择向主界面回传判断方法到底是哪个的参数。具体方法为:
在 .cpp 的开头定义一个 CString method,在相应的.h 文件开头中加入 extern CString method,这样就可以在别的类和对话框的.cpp 文件中随意调用赋值了。
(选择不同的单选键后,对应的编辑框功能被禁用/启用,并为method变量赋值)
cmd的快捷(大概)使用
一开始进入cmd我是“C:\Users\Administrator”
想要回到整个盘的最初的话,输入“cd/",就会变成“C:\”
这之后想要更换盘目录的话可以写“D:”,就会进入D盘,之后再根据想要去的地址,继续“cd xxx\xxx"就可以了(也可以直接在文件夹里复制绝对路径,然后cd过去