程序代写代做代考 8 抽象类

8 抽象类
目的
了解为何使用抽象类,学习通过继承实现代码重用的机制和方法
掌握如何声明函数为纯虚函数
掌握如何利用纯虚函数,编写派生类的覆盖函数
内容
定义滤波器抽象类Filter,并派生两个具体的滤波器:类MeanFilter和类MedianFilter,分别完成对图像的均值滤波和中值滤波。在抽象类Filter中定义纯虚函数Filtering,在两个派生类中实现相应的函数。
在调用这些类的图形界面程序中声明基类指针,通过图形界面的输入(菜单选项或者按钮)来决定使用哪个滤波器,即该指针指向不同的派生类,从而使用不同的滤波器对象对图像进行滤波。并显示滤波后的图像,注意观察两种滤波器对图像滤波效果的差异。在构造滤波器时选择不同的filterSize,观察对滤波输出图像的影响。

抽象类滤波器 Filter.h
#ifndef FILTER_H
#define FILTER_H

#include “Image.h”

class Filter
{
public:
Filter(int size); //构造函数
virtual ~Filter(); //析构函数;

virtual Matrix Filtering(const Matrix &input) = 0; //滤波函数(纯虚函数);

protected:
int filterSize;
};

#endif

派生类 均值滤波器 MeanFilter.h
#ifndef MEANFILTER_H
#define MEANFILTER_H

#include “Filter.h”

class MeanFilter : public Filter
{
public:
MeanFilter(int size);
virtual ~MeanFilter();
virtual Matrix Filtering(const Matrix &input); //均值滤波函数
};

#endif

派生类中值滤波器 MedianFilter.h
#ifndef MEDIANFILTER_H
#define MEDIANFILTER_H

#include “Filter.h”

class MedianFilter : public Filter
{
public:
MedianFilter(int size);
virtual ~MedianFilter();
virtual Matrix Filtering(const Matrix &input); // 中值滤波器函数
};

#endif

请完成MeanFilter.cpp、MedianFilter.cpp及图形界面程序

使用滤波器对图像进行滤波的参考代码
Image img(“Lena_gaussian.bmp”);

Filter *filter = NULL;
filter = new MeanFilter(5);
Image result_mean;
result_mean = filter->Filtering(img);
// 显示该结果图像
delete filter;

filter = new MedianFilter(5);
Image result_median;
result_median = filter->Filtering(img);
// 显示该结果图像
delete filter;

要求
完成上述代码,并能显示正确的结果图像,对结果进行比较。
实验附带了两幅添加了不同噪声的图像(高斯白噪声Lena_gaussian.bmp和椒盐噪声Lena_salt_and_pepper.bmp)。注意这两幅图像的通道数是1,而不是3,读取bmp文件的函数要能够处理不同通道数的图像。
设计交互程序,读取不同的图像,更改滤波器大小,观察滤波效果。
说明
• 注意,Filtering函数的输入是const的Matrix对象引用,在Filtering函数内部需要获取图像高度宽度的函数也需要重载一个const类型的函数,否则编译不会通过。比如:
int Height() const {return height;}
• 成员变量filterSize记录的是滤波器的大小,一般是奇数,定义在多大的图像块上进行操作,比如filterSize=3,则在输入图像的每一个像素的3*3区域内进行滤波操作。以3*3的均值滤波器为例,假设当前操作图像以像素(i,j)为中心的3*3区域为:

15
12
87
25
24
40
54
21
46

像素(i,j)的像素值为24,它的3*3邻域的像素值如上所示,那么均值滤波的操作是:对这个3*3邻域的像素求均值:(15+12+87+25+24+40+54+21+46)/9=36。那么输出图像的像素(i,j)处的像素值为36。
针对图像中的每个像素做上述操作,就完成了均值滤波。在编程实现时要特别注意,图像四条边上和四个角的像素应该如何处理。

中值滤波的操作类似,只不过把求均值变成了求中值,即把filterSize*filterSize大小区域内的像素的像素值按照大小排列,结果是位于中间的那个值。比如上例中,如果用中值滤波,那么按像素值从小到大排列后的像素值是:12, 15, 21, 24, 25, 40, 46, 54, 87。中值(中间的值)是25,所以,滤波后的输出图像的像素(i,j)的像素值为25。
filterSize是可变的,表示滤波操作的空间尺度,选取不同的值所产生的效果不一样,请在实验中试着产生不同大小的滤波器,看看对图像有什么影响。

9模板
目的
掌握类模板的定义和使用
内容
在之前的实现中,我们定义了一个矩阵类Matrix和其子类图像类Image。在数据的存储和处理过程中,我们用过unsigned char和double类型的data指针获取矩阵元素或者图像像素的值。但是现实中的矩阵或者图像数据并不仅仅使用浮点类型和8bits的无符号整数类型,比如有的单反相机可以拍摄32bits无符号整数像素值的图像(unsigned long);在图像处理中经常将图像灰度值缩放到区间[0,1]内的浮点数(double);再比如对图像进行傅里叶变换后,得到是复数。因此矩阵的数据类型可以有很多变化。为了实现代码的重用,现在我们使用类模板重构之前的类。

要求:
• 定义类模板Mat描述矩阵,数组元素的数据类型可以是任意类型,即二级指针data所指向的数据类型采用模板技术进行泛化。
• 参考之前的实验,实现Mat类模板的构造函数、析构函数、拷贝构造函数、运算符重载等各种函数。
• 使用“引用计数”机制,实现Mat对象间的“浅拷贝”和“浅赋值”。
• 实现该类模板的Read,Write函数。Write函数需要特别注意,我们在用显示器显示图像或者存储图像文件时,它们的像素值一般选取 unsigned char类型(这也是我们前面实验使用该类型的原因)。但是我们的Mat模板类的数据范围不局限于此,因此需要在这两个函数体内完成数据类型转换操作,即把矩阵的元素转换成[0,255]区间内的整数。通常的做法是计算所有矩阵元素的最大值和最小值,在Write函数内将对应的数据分别赋成255和0,其它中间的数据转换成0和255之间的无符号8bit整数。这么做仅仅是为了显示和存储时迁就显示器和图片文件的格式要求,丢失了原来数据的精度。
• 定义成员函数Normalize,该函数返回以double特化的模板类对象,该对象将调用者的数据缩放到[0,1]区间。
• 将第8章的滤波器类进行模板化改造,能够适用于Mat类模板。

图形界面程序中实现各个函数的调用,尤其需要包括一下功能:
• 读入图像文件,并将数据缩放到[0,1]区间
• 将图像取反色,并输出显示。
• 获取矩阵中某个元素的值。
• 对矩阵进行旋转、缩放、裁剪、重组等操作。
• 对矩阵进行加减等操作。
• 对矩阵进行滤波,显示并保存成图像文件。
• 计算原矩阵和滤波后矩阵的差,并把结果显示。
示例代码
Mat.hpp
template< typename T>
class Mat
{
public:
Mat(); //无参数的构造函数,创建行列都为零的Mat对象
Mat(int h, int w); //构造函数重载,创建h行,w列的Mat对象
Mat(int h, int w, T val); //构造函数重载,矩阵元素的值都为val;
Mat(char* ImageName); //构造函数重载,利用文件名从硬盘加载图像文件成为Mat对象;
Mat(T **m, int h, int w); //构造函数重载,从动态数组创建Mat对象;
Mat(const Mat &m); //拷贝构造函数;
virtual ~Mat(); //析构函数;

void Read(const char *ImageName); //从硬盘文件中读入图像数据;
void Write(const char *filename); //将数据保存为图像文件;

int Height();//得到矩阵高度
int Height() const;//得到矩阵高度
int Width();//得到矩阵宽度
int Width() const;//得到矩阵宽度

T Min(); //得到矩阵元素的最小值
T Min() const; //得到矩阵元素的最小值
T Max(); //得到矩阵元素的最大值
T Max() const; //得到矩阵元素的最大值

T& At(int row,int col); //获取某点的值
T& At(int row,int col) const; //获取某点的值,const重载
void Set(int row, int col, T value); //设置元素(row,col)为某值;
void Set(T value); //设置所有元素为同一值;

void Flip(int code); //翻转; 根据code的值:0:左右翻转,1:上下翻转;
void Resize(int h, int w); //缩放
void Cut(int x1,int y1,int x2,int y2);//裁剪点(x1,y1)到点(x2,y2)
void Rotate(int degree);//旋转
void Transpose(); // 转置
void Reshape(int h, int w); //在元素总数不变的情况下,将矩阵的行列变为参数给定的大小
bool IsEmpty();// 判断是否为空矩阵
bool IsSquare();// 判断矩阵是否为方阵
Mat MajorDiagonal();// 求主对角线上的元素,输出一个N行1列的矩阵,N为主对角线上元素的个数
Mat MinorDiagonal();// 求副对角线上的元素,输出一个N行1列的矩阵,N为副对角线上元素的个数
Mat Row(int n);// 返回矩阵的第n行上的元素,组出一个1行N列的矩阵输出,N为第n行上元素的个数
Mat Column(int n);// 返回矩阵的第n列上的元素,组出一个N行1列的矩阵输出,N为第n列上元素的个数
void Cat(Mat &m, int code); // 将m与当前对象进行拼接,code代表拼接的方式
void CopyTo(Mat &m); // 将矩阵复制给m

Mat void Normalize();//将矩阵元素的值变换到0-1范围内,以double类型的Mat对象输出。注意:在这个函数里,无法访问Mat类型的对象的私有成员data,需要调用其At函数获得某个元素。

Mat& operator=(const Mat &m); //重载赋值运算符,完成对象间的拷贝;
bool operator==(const Mat &m); //判断两个Mat对象是否相等
friend Mat operator+(const Mat &lhs, const Mat &rhs); //对应元素的数值相加;
friend Mat operator-( const Mat &lhs, const Mat &rhs); //对应元素的数值相减;
Mat& operator++(); //前置自加;
Mat& operator–(); //前置自减;
Mat operator ++(int); //后置自加;
Mat operator –(int); //后置自减;
Mat operator-(); // 取反;注意要把矩阵的数据规整到[0,1]区间后,再用1减

friend Mat operator+(Mat &m, T num); //所有元素加上同一数值;
friend Mat operator-(Mat &m, T num); //所有元素减去同一数值;
friend Mat operator*(Mat &m, T num); //所有元素乘上同一数值;
friend Mat operator/(Mat &m, T num); //所有元素除以同一数值;
//另外,用友元函数再写出一个T类型的数和一个Mat对象的加,减,乘,除

Mat gray2bw(T t); //以给定阈值t进行二值化,返回结果对象

friend void Swap(Mat &a, Mat &b);//使用友元函数交换两个Mat对象

private:
// 设计一个结构体,来保存矩阵的行数、列数、引用计数和数据指针
// 这里需要声明指向该结构体对象的指针作为数据成员
};

Filter.hpp
//Filter类
template
class Filter
{
public:
Filter(int size); //构造函数
virtual ~Filter(); //析构函数;
virtual Mat Filtering(const Mat &input) = 0; //滤波函数(纯虚函数);
protected:
int filterSize;
};

//meanFilter类
template
class MeanFilter : public Filter
{
public:
MeanFilter(int size);
virtual ~MeanFilter();
virtual Mat Filtering(const Mat &input); //均值滤波函数
};

//median类
template
class MedianFilter : public Filter
{
public:
MedianFilter(int size);
virtual ~MedianFilter();
virtual Mat Filtering(const Mat &input); // 中值滤波器函数
};
要求
完成上述代码,在图形界面程序中验证以上方法。
注意:类模板的头文件,我们以“hpp”作为文件扩展名。Mat类模板和Filter类模板(包括其子类模板)的所有实现代码都放在它们的hpp文件里,即声明和实现放在一起,不将实现代码单独放在cpp文件。
10 文件
目的
掌握输入输出流和文件的读写
内容
通过使用C++标准库的输入输出流,给Mat类模板增加输入输出功能。

要求:
• 重载输入输出流运算符>>和<<,使Mat类模板产生的对象可以直接向标准输出设备(命令行窗口)进行输出,以及从标准输入设备(键盘)输入。 • 自己设计文件格式,使用ASCII码和二进制两种模式,将Mat类模板产生的对象写入文件,并从文件中读取。 主程序中对相关函数进行测试。 示例代码 Mat.hpp template< typename T>
class Mat
{
// 其他函数略

void Read(const char *name, int code); //从硬盘文件中读入图像数据,根据code的值采用不同的文件格式,如自定义的ascii码文本文件、自定义的二进制文件

void Write(const char *name, int code); //将数据保存为图像文件,同上
};

重载流输入输出运算符……

实验要求
完成上述代码,并验证以上方法。
注意:自定义的ASCII码文件和二进制文件,以及流输入输出时,如何在读取时获知矩阵的行数、列数、数据类型?一个比较简单的办法是放在输入输出最开始的位置,也可以设计你自己的解决方案。
比如使用文本文件(ASCII)存储Mat对象时,第一行存行数height,第二行存列数width,第三行是数据类型编码(比如double编码为4),然后是一行行的数据,例如某个3行4列Mat对象a,调用Write函数以ASCII码保存成文件,然后用记事本打开后是这样的:

3
4
4
13.3 2.4 3.8 51.8
7.8 0.1 3.2 3.2
2.8 26.5 9.2 10.3

在命令行窗口输出该对象a:
std::cout << a; 得到类似这样的输出: 3 * 4 matrix of type double: [13.3 2.4 3.8 51.8 7.8 0.1 3.2 3.2 2.8 26.5 9.2 10.3]