5 类和对象
目的
掌握类和对象的创建
掌握构造函数、构造函数的重载,拷贝构造函数、析构函数的设计和使用
掌握成员函数的设计和使用
实验内容
下面的代码已经创建了图像类Image的框架,请完善该类。在该类中,实现图像的读入、保存,并实现图像的翻转、缩放、裁剪等操作。在图形界面程序中,读入某个图像文件(比如“fruits.bmp”) ,通过图形界面的交互(菜单栏选项或者按钮),对其进行缩小,翻转,指定区域裁剪等操作,并进行显示。
为了简单起见,后续章节的实验均只处理单通道的图像(灰度图像),即每个像素仅有一个8bit无符号整数的数值。本章的Image类的数据指针设计为二级指针。在读取图像文件时,把3通道转换为1通道,一个简单的做法是求RGB三个通道的平均值。
请编程实现:
图像类:
• 创建图像类Image,实现各个重载的构造函数,拷贝构造函数(深拷贝),析构函数。
• 实现对BMP图像文件的读取。
• 实现函数At()获取图像中某像素点的值。
• 将图像中给定像素点的像素值设置为某值,即实现函数Set。将图像所有像素的像素值设定为某值,即实现函数SetAll。
• 同一个函数实现图像的上下翻转、左右翻转。即实现函数Flip。
• 指定区域对图像进行裁剪。
• 求图像的均值,方差。
• 图像的旋转,缩放。旋转角度为90度的整数倍是基本实现要求,选做任意角度。
• 定义友元函数交换两个Image对象的数据。
在图形界面程序中实现:
• 读入图像文件,创建Image类对象img并显示。
• 通过鼠标交互,获取图像中某像素点的值,并在界面显示。
• 利用Image类的成员函数,对图像进行翻转、旋转,并显示。
• 利用Image类的成员函数,将图像长宽缩小到1/2大小,并显示;将图像长宽放大2倍,并显示。
• 利用拷贝构造函数,创建新的对象new_img。
• 通过鼠标交互或者对话框输入,给定的两个点(Point):左上点(top_left)和右下点(bottom_right),将此指定区域内的new_img对象图像进行裁剪操作,并显示结果。
• 求图像的所有像素点的均值和方差,并在界面显示输出。
• 交换两个Image对象并显示。
Image.h
#ifndef IMAGE_H
#define IMAGE_H
class Image
{
public:
Image(); //无参数的构造函数,创建行列都为零的Image对象
Image(int h, int w); //构造函数重载,创建h行,w列的Image对象
Image(int h, int w, unsigned char val); //构造函数重载,创建的图像像素值都为val
Image(char* ImageName); //构造函数重载,利用文件名从硬盘加载图像文件成为Image对象
Image(unsigned char *m, int rows, int cols); //构造函数重载,从一维静态数组创建Image对象,图像的行数和列数由后面两个参数给出
Image(unsigned char **m, int h, int w); //构造函数重载,从动态数组(二级指针)创建Image对象,图像的行数和列数由后面两个参数给出
Image(const Image &im); //拷贝构造函数(深拷贝)
~Image(); //析构函数
void Read(char* ImageName); //从硬盘文件中读入图像数据
unsigned char& At(int row, int col); //获取第row行第col列的像素点的值
void Set(int row, int col, unsigned char value); //设置像素(row,col)为某值
void Set(unsigned char value); //设置图像所有像素为同一值
void Flip(int code); //图像的翻转; 根据code的值:0:左右翻转,1:上下翻转
void Resize(int code); //图像的缩放;根据code的值:0:缩小一倍,1:放大一倍
void Cut(int x1,int y1,int x2,int y2);//裁剪点(x1,y1)到点(x2,y2)的图像
void Rotate(int degree);//图像旋转的函数(简单起见,旋转角度为90度的整数倍)
void Mean_Variance(float &m, float &var);//求图像的均值和方差,利用参数输出
friend void Swap(Image &a, Image &b);//使用友元函数交换两个Image对象的数据
private:
unsigned char **data;
int height;
int width;
};
#endif
Image.cpp
#include “Image.h”
//构造函数
Image::Image()
{
//write your code here
}
//构造函数重载
Image::Image(int h, int w)
{
//write your code here
}
// 其他重载构造函数的实现
// ……
//拷贝构造函数
Image::Image(const Image &im)
{
//write your code here
}
//析构函数
Image::~Image()
{
//write your code here
}
//从硬盘读入图像文件;
void Image::Read(char* ImageName)
{
}
//获取图像中指定点的值
unsigned char& Image::At(int row, int col)
{
//write your code here
}
//设置图像为同一值
void Image::Set(unsigned char value)
{
//write your code here
}
//0左右,1上下;
void Image::Flip(int code)
{
//write your code here
}
//图像缩小,放大
void Image::Resize(int code)
{
//write your code here
}
//图像裁剪的函数
//图像旋转的函数
//write your code here
//实现友元函数,交换两个Image对象的数据
void Swap(Image &a, Image &b)
{
}
要求
完成上述代码,并能显示正确的结果图像。
6 继承
目的
掌握如何编写基类,如何通过继承基类的属性和函数编写派生类。
掌握如何在派生类成员函数中调用基类的成员函数。
掌握如何声明基类和派生类的对象,如何调用派生类对象的成员函数。
实验内容
• 创建Matrix类,作为Image类的父类。下面的代码已把5中的Image类的数据成员“转移至”了父类Matrix中,并将Image类中适用于矩阵的操作“转移至”Matrix类。请完成Matrix类的相关成员函数和友元函数的实现。
• 从Matrix类公有派生Image类,在Image类中实现一个新的构造函数,该构造函数由基类对象构造派生类对象。
• 对派生类的某些构造函数进行改造,使其能给基类构造函数传递参数完成基类成员的初始化。
• 在图形界面程序中完成对Matrix类和Image类成员函数的调用。
注意:
自己学习关于矩阵的基本知识,包括矩阵的加法、减法、转置、单位矩阵等。
类Matrix中的数据类型是double,注意在派生类Image中如何把从图像文件里的数据类型从unsigned char转化为double。更重要的是,在对矩阵完成变换后存储成图像文件时,如何把数据类型再转换回unsigned char。在转换时如何对数据范围进行改变。
图像相加和相减后,结果图像的数据可能是负的或者大于255的,在写回图像文件时,应该处理这种情况。请参考后面的提示设计你的解决办法。
水果那幅图像的通道数为1,注意你实现的bmp读取函数要能够处理单通道和3通道的图像。
Matrix.h
#ifndef Matrix_H
#define Matrix_H
class Matrix
{
public:
Matrix();
Matrix(int h,int w);
Matrix(int h, int w, double val);
Matrix(const Matrix &m);
virtual ~Matrix();
void Zeros(int h, int w); // 根据参数产生h行w列的全零矩阵
void Ones(int h, int w); // 根据参数产生h行w列的全1矩阵
void Random(int h, int w); //产生h行w列的随机矩阵,矩阵的元素为[0,1]之间的随机实数(double类型)
void Identity(int n); // 根据参数产生n行n列的单位矩阵
int Height(); // 获得矩阵的行数
int Width(); // 获得矩阵的列数
Matrix MajorDiagonal();// 求矩阵主对角线上的元素,输出一个N行1列的矩阵,N为主对角线元素的个数
Matrix MinorDiagonal();// 求矩阵的副对角线上的元素,输出一个N行1列的矩阵,N为副对角线上元素的个数
Matrix Row(int n);// 返回矩阵的第n行上的元素,组出一个1行N列的矩阵输出,N为第n行上元素的个数
Matrix Column(int n);// 返回矩阵的第n列上的元素,组出一个N行1列的矩阵输出,N为第n列上元素的个数
void Transpose(); // 将矩阵转置
double& At(int row, int col); //获取第row行第col列的矩阵元素的值
void Set(int row, int col, double value); //设置第row行第col列矩阵元素的值为value
void Set(double value); //设置矩阵所有元素为同一值value
void Normalize(); // 该函数把矩阵的数据线性缩放至[0,1]区间,即把当前矩阵所有元素中的最小值min变成0,最大值max变为1,其他元素的值线性变到[0,1]区间,公式为:t’=(t-min)/max;
void Reshape(int h, int w); //在矩阵元素总数不变的情况下,将矩阵行列变为参数给定的大小
bool IsEmpty();// 判断矩阵是否为空矩阵
bool IsSquare();// 判断矩阵是否为方阵
void CopyTo(Matrix &m); // 将矩阵复制给m
void Mul(double s); // 矩阵的每个元素都乘以参数s
void Cat(Matrix &m, int code); // 将矩阵m与当前矩阵进行拼接,code代表拼接的方式:将m拼接到当前矩阵的上、下、左、右,具体例子见后面的说明
friend Matrix Add(const Matrix &m1, const Matrix &m2); // 友元函数,将矩阵m1和m2相加,结果矩阵作为函数的返回值
friend Matrix Sub(const Matrix &m1, const Matrix &m2); // 友元函数,将矩阵m1和m2相减,结果矩阵作为函数的返回值
friend void Swap(Matrix &a, Matrix &b); // 友元函数,交换两个矩阵
protected:
int height;
int width;
double **data;
};
#endif
Image.h
#ifndef Image_H
#define Image_H
#include “Matrix.h”
class Image : public Matrix
{
public:
Image(); //构造函数,创建行列都为零的Image对象
Image(int h, int w); //构造函数重载,创建h行,w列的Image对象
Image(int h, int w, unsigned char val); //构造函数重载,创建的图像像素值都为val;
Image(char* ImageName); //构造函数重载,利用文件名从硬盘加载图像文件成为Image对象;
Image(unsigned char **m, int h, int w); //构造函数重载,从动态数组创建Image对象;
Image(const Matrix &m); //构造函数重载,由Matrix类对象构造Image类对象
Image(const Image &im); //拷贝构造函数;
virtual ~Image(); //析构函数;
void Read(char* ImageName); //从硬盘文件中读入图像数据;
void Write(char* filename); //将图像数据保存为图像文件;
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);//图像旋转的函数(旋转角度为任意角度)
double Mean();//返回图像的均值
double Variance();//求图像的方差
};
#endif
说明
1.Reshape函数,顾名思义,将矩阵的形状进行改变。前提是变换前后矩阵元素的个数不能改变,否则应输出错误信息。该变换以行优先的规则将矩阵的元素重新排列。比如有如下3*2的矩阵:
如果对该矩阵进行Reshape(2,3)操作,结果如下:
2.Cat函数将参数m矩阵与调用该函数的矩阵对象进行拼接。比如有矩阵如下:
参数m矩阵为:
假设code=1代表拼接到右边,则结果为:
假设code=2代表拼接到下面,则结果为:
3.矩阵相加和相减的友元函数,注意处理结果的范围。因为所设计的矩阵的数据存储类型是double,而一般的图像文件能表达的范围是整数0~255,因此,相加或者相减的结果如果超过了这个范围,那么如果要把结果写入图像文件,则要做相应的处理。可以参考如下两种办法:
a)如果相加的结果>255,那么就取255,如果相减的结果<0,则取0。这种方法叫做饱和处理,来自相机拍照时的饱和现象。
b)另外一种处理办法是把数据“缩放”到0~255之间。把矩阵所有元素中的最小值设为0,最大值设为255,其他元素线性插入:255*(x - min) / (max - min)
结果示例
产生的矩阵:
上面矩阵的对角线元素组成的矩阵(向量):
上面N*1矩阵经过Reshape变形后的矩阵:
Image1:
Image2:
Image1 + Image2:
Image1–Image2:
Transpose:
左右拼接:
上下拼接:
7 运算符重载
目的
掌握运算符重载
实现对Matrix类的运算符重载
内容
在之前的实验中,我们实现了矩阵类Matrix及其子类Image。本次实验我们给Matrix类添加一些运算符重载,包括赋值运算符“=”,两个Matrix对象的“+”、“-”、“*”、“/”(都是对应元素间的运算,而不是矩阵的乘除),自加自减,矩阵(或图像)对象和一个数(标量)的“=”、“+”、“-”、“*”、“/”操作,判断两个矩阵(或图像)是否相等的“==”。
给Image类添加一个单目运算符“-”,作用是图像的像素值取反,即黑的变成白的,白的变成黑的。再给Image类添加一个成员函数gray2bw,该函数以给定的阈值参数把灰度图像转化为二值图像。假设图像的像素值范围在[0,1]之间的实数,给定阈值double t∈[0,1]。该函数对图像中的所有像素做如下处理:如果该像素的像素值小于等于t,则输出图像对应像素值赋值为0,如果该像素的像素值大于t,则输出图像对应像素赋值为1。此函数的输出图像对象仅含有0和1两种像素值,因此该操作在图像处理里称为“二值化”操作或“阈值分割”。
注意:这两种函数假设操作对象的数据是在[0,1]之间的实数。但是实际对象的元素数值可能不在这个范围内。因此,在这两个函数内部,需要先把所有元素的范围规整到[0,1]区间,然后再做相应的操作。
给的参考图像中有两幅场景图,要求通过图像相减,分离出前景物体。
• 在Matrix类中重载运算符,添加运算符重载函数(成员函数和友元函数):
• 重载两个Matrix对象的赋值运算符“=”,完成类似a=b的操作。要求进行数据的深拷贝。并再次重载赋值运算符,使右操作数可以是一个double类型的数,完成矩阵所有元素赋值为该数,如a=128。
• 重载判断两个Matrix对象是否相等的“==”运算符,两个矩阵相等的条件是行列大小相同,所有元素的值也相等。
• 重载“+”运算符,针对两幅尺寸相同的矩阵,完成对应元素值相加。
• 重载“+”运算符,实现一个矩阵所有元素加上同一数值。
• 重载“-”运算符,针对两个尺寸相同的矩阵,实现对元素值相减。
• 重载“-”运算符,实现一个矩阵所有元素减去同一数值。
• 重载“*”运算符,针对两个尺寸相同的矩阵,实现对元素值相乘。
• 重载“*”运算符,实现一个矩阵所有元素乘以同一数值。
• 重载“/”运算符,针对两个尺寸相同的矩阵,实现对元素值相除;自己设计除数为0时的处理办法。
• 重载“/”运算符,实现一个矩阵所有元素除以同一数值;自己设计除数为0时的处理办法。
• 重载“++”运算符,实现一个矩阵所有元素自加1(分别实现前置版本和后置版本)。
• 重载“--”运算符,实现一个矩阵所有元素自减1(分别实现前置版本和后置版本)。
• 在Image类里,实现如下运算符和函数:
• 重载“-”运算符,实现一个矩阵所有元素“取反”。
• 实现函数gray2bw,以给定的阈值对图像进行二值化,函数返回结果图像。
• 在图形界面程序中可以调用所实现的功能,包括实现以下操作:
• 创建Image对象img1和img2,分别读入两幅图像scene_fg和scene_bg。
• 使用“-”运算符,实现img1和img2相减,并显示。
代码示例
Matrix.h
#ifndef MATRIX_H
#define MATRIX_H
class Matrix
{
public:
// 构造函数等其它成员略
//运算符重载函数;
Matrix& operator=(const Matrix &m); //重载赋值运算符,完成对象间的拷贝;
bool operator==(const Matrix &m); //判断两个Matrix对象是否相等
Matrix& operator++(); //前置自加;
Matrix& operator--(); //前置自减;
Matrix operator++(int); //后置自加;
Matrix operator--(int); //后置自减;
//两个尺寸相同的矩阵,对应元素的数值相加
friend Matrix operator+(const Matrix &m1, const Matrix &m2);
//两个尺寸相同的矩阵,对应元素的数值相减
friend Matrix operator-(const Matrix &m1, const Matrix &m2);
//两幅尺寸相同的矩阵,对应元素的数值相乘
friend Matrix operator*(const Matrix &m1, const Matrix &m2);
//两幅尺寸相同的矩阵,对应元素的数值相除
friend Matrix operator/(const Matrix &m1, const Matrix &m2);
//矩阵所有元素和一个标量相加
friend Matrix operator+(Matrix &m, double num);
//矩阵所有元素和一个标量相减
friend Matrix operator-(Matrix &m, double num);
//矩阵所有元素和一个标量相乘
friend Matrix operator*(Matrix &m, double num);
//矩阵所有元素和一个标量相除
friend Matrix operator/(Matrix &m, double num);
//以上4个友元函数实现了Matrix对象和一个数的加减乘除,如果交换这两个操作数,即一个数和一个Matrix对象的加减乘除,应该如何做?请自行写出相关代码。
//其它成员略
};
#endif
在Image类中实现Image特有的运算符和成员函数
Image operator-(); //对图像取反,把所有像素的值都规整到[0,1]之间,然后每个像素都被1.0减
Image gray2bw(double t); //以给定阈值t对图像进行二值化,返回结果图像对象;
要求
完成上述代码,实现相应功能,在图形界面上进行显示,可以增添设计新功能。