这个小程序并不是完全不使用opencv库函数的,图像输入和显示等基本功能还是要使用库函数的。主要实现的是不使用库函数实现腐蚀和膨胀这两个问题。主要的目的是学习它的算法和提高C++代码编程能力。
整个程序包含macro.h,Morp.h,Morp.cpp和main.cpp四个文件:
1. macro.h:程序的宏定义,支持的腐蚀和膨胀的类型
#define SQUARE 0 //正方形
#define DISK 1 //圆形
2. Morp.h:类的声明
#include"opencv2/core/core.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"macro.h"
using namespace cv;
class Morp{
public:
Mat toContr(Mat &); //对图像求反
Mat toThreshold(const Mat &,double); //阈值分割
Mat toErode(Mat &,int type = SQUARE,int valve = 3); //腐蚀函数,默认是0,默认结构元是3行3列的矩阵
Mat toDilate(Mat &, int type = SQUARE, int valve = 3); //膨胀函数,默认是0,默认结构元是3行3列的矩阵
~Morp();
private:
void struElem(int, int); //确定结构元类型
Mat taskImage; //待处理的图像
int depth; //图像的深度
int **pElement; //定义结构元
int ele_row; //结构元的行
int ele_col; //结构元的列
int centre_x = 1; //结构元的原点x
int centre_y = 1; //结构元的原点y
double dist; //结构元中(x,y)点到原点的距离
};
3. Morp.cpp:各个函数的定义
#include
#include"Morp.h"
#include"opencv2/core/core.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"macro.h"
using namespace cv;
using namespace std;
Mat Morp::toContr(Mat &image)
{
switch (image.depth()) //image.depth()返回的值是0~6的值,分别对应下列的值
{
case 0:
depth = 255;
break;
default:
break;
}
for (int i = 0; i < image.rows; i++)
{
uchar* imageData = image.ptr(i);
for (int j = 0; j < image.cols; j++)
{
imageData[j] = depth - imageData[j];
}
}
return image;
}
void Morp::struElem(int type, int value)
{
switch (type) //确定结构元的类型
{
case SQUARE: //正方形
if (value % 2 == 0) //只处理奇数的value
value++;
pElement = new int*[value]; //定义动态二维数组,作为操作的结构元
ele_row = value;
ele_col = value;
for (int i = 0; i < value; i++)
{
pElement[i] = new int[value];
}
centre_x = value / 2;
centre_y = value / 2;
for (int i = 0; i < value; i++)
for (int j = 0; j < value; j++)
pElement[i][j] = 1;
break;
case DISK: //圆形
pElement = new int*[2 * value + 1]; //定义动态二维数组,作为操作的结构元
ele_row = 2 * value + 1;
ele_col = 2 * value + 1;
for (int i = 0; i < 2 * value + 1; i++)
{
pElement[i] = new int[2 * value + 1];
}
centre_x = value;
centre_y = value;
for (int i = 0; i < 2 * value + 1; i++)
for (int j = 0; j < 2 * value + 1; j++)
{
dist = sqrt((centre_x - i)*(centre_x - i) + (centre_y - j)*(centre_y - j)); //求数组pElement点(i,j)到原点的距离
if (dist <= value)
{
pElement[i][j] = 1;
}
else{
pElement[i][j] = 0;
}
}
break;
default:
cout << "输入参数不对,请从新输入!";
break;
}
}
Mat Morp::toThreshold(const Mat &image, double perc)
{
taskImage.create(image.size(),image.type());
switch (image.depth()) //image.depth()返回的值是0~6的值,分别对应下列的值
{
case 0:
depth = 255;
break;
default:
break;
}
double thresh = depth*perc;
for (int i = 0; i < image.rows; i++) //阈值操作,大于thresh为255,其他为0;
{
const uchar* imageData = image.ptr(i);
uchar* taskData = taskImage.ptr(i);
for (int j = 0; j < image.cols; j++)
{
if (imageData[j]>thresh)
{
taskData[j] = depth;
}
else{
taskData[j] = 0;
}
}
}
return taskImage;
}
Mat Morp::toErode(Mat &image,int type,int value)
{
struElem(type,value); //确定结构元的类型及大小
taskImage.create(image.rows+2*centre_y, image.cols+2*centre_x, image.type()); //创建一个比image图片大一个结构元的图片
uchar* taskData = NULL; //taskImage图像data指针
uchar* imageData = NULL; //image图像data指针
for (int i = 0; i < taskImage.rows; i++) //将image图片复制到taskImage中,周围为0
{
taskData = taskImage.ptr(i);
if ((centre_y <= i) && (i < (taskImage.rows - centre_y)))
imageData = image.ptr(i - centre_y);
for (int j = 0; j < taskImage.cols; j++)
{
if ((centre_y <= i) && (i < (taskImage.rows - centre_y))&&(centre_x <= j) && (j < (taskImage.cols - centre_x)))//在taskImage中判断出和image图片大小相同的中心区域
{
taskData[j] = imageData[j - centre_x];
}
else
{
taskData[j] = 0;
}
}
}
uchar **pChar = NULL; //定义一个二维数组,以获得和结构元同行数的行首地址
pChar = new uchar*[2 * centre_y + 1]; //使pChar和结构元具有相同的行
int elStart_i = 0; //得到pChar数组行地址
int elStart_j = 0; //得到pChar数组列地址
for (int i = 0; i < taskImage.rows; i++)
{
if ((centre_y <= i) && (i < (taskImage.rows - centre_y)))//在taskImage中判断出和image图片大小相同的中心区域
{
imageData = image.ptr(i - centre_y);
elStart_i = i - centre_y;
for (int k = 0; k < (2 * centre_y + 1); k++)
{
pChar[k] = taskImage.ptr(elStart_i+k);
}
}
for (int j = 0; j < taskImage.cols; j++)
{
if ((centre_y <= i) && (i < (taskImage.rows - centre_y)) && (centre_x <= j) && (j < (taskImage.cols - centre_x)))//在taskImage中判断出和image图片大小相同的中心区域
{
int containStru = 1; //作为taskImage图片是否包含结构元判断依据
elStart_i = i - centre_y;
elStart_j = j - centre_x;
for (int ii = 0; ii < ele_row;ii++)
for (int jj = 0; jj < ele_col; jj++)
{
if (pElement[ii][jj] == 1 && pChar[ii][elStart_j + jj] == 255) //只要有一个没有被包含,就对containStru操作
{
containStru = 0;
}
}
if (containStru == 1)
{
imageData[j] = 0;
}
else{
imageData[j] = 255;
}
}
}
}
delete[]pChar;
return image;
}
Mat Morp::toDilate(Mat &image,int type, int value)
{
struElem(type, value); //确定结构元的类型及大小
taskImage.create(image.rows + 2 * centre_y, image.cols + 2 * centre_x, image.type()); //创建一个比image图片大一个结构元的图片
uchar* taskData = NULL; //taskImage图像data指针
uchar* imageData = NULL; //image图像data指针
for (int i = 0; i < taskImage.rows; i++) //将image图片复制到taskImage中,周围为0
{
taskData = taskImage.ptr(i);
if ((centre_y <= i) && (i < (taskImage.rows - centre_y)))
imageData = image.ptr(i - centre_y);
for (int j = 0; j < taskImage.cols; j++)
{
if ((centre_y <= i) && (i < (taskImage.rows - centre_y)) && (centre_x <= j) && (j < (taskImage.cols - centre_x)))//在taskImage中判断出和image图片大小相同的中心区域
{
taskData[j] = imageData[j - centre_x];
}
else
{
taskData[j] = 0;
}
}
}
uchar **pChar = NULL; //定义一个二维数组,以获得和结构元同行数的行首地址
pChar = new uchar*[2 * centre_y + 1]; //使pChar和结构元具有相同的行
int elStart_i = 0; //得到pChar数组行地址
int elStart_j = 0; //得到pChar数组列地址
for (int i = 0; i < taskImage.rows; i++)
{
if ((centre_y <= i) && (i < (taskImage.rows - centre_y)))//在taskImage中判断出和image图片大小相同的中心区域
{
imageData = image.ptr(i - centre_y);
elStart_i = i - centre_y;
for (int k = 0; k < (2 * centre_y + 1); k++)
{
pChar[k] = taskImage.ptr(elStart_i + k);
}
}
for (int j = 0; j < taskImage.cols; j++)
{
if ((centre_y <= i) && (i < (taskImage.rows - centre_y)) && (centre_x <= j) && (j < (taskImage.cols - centre_x)))//在taskImage中判断出和image图片大小相同的中心区域
{
int containStru = 0; //作为taskImage图片是否和结构元相加的依据
elStart_i = i - centre_y;
elStart_j = j - centre_x;
for (int ii = 0; ii < ele_row; ii++)
for (int jj = 0; jj < ele_col; jj++)
{
if (pElement[ii][jj] == 1 && pChar[ii][elStart_j + jj] == 255) //只要有一个被包含,就对containStru操作
{
containStru = 1;
}
}
if (containStru == 1)
{
imageData[j] = 255;
}
else{
imageData[j] = 0;
}
}
}
}
delete[]pChar;
return image;
}
Morp::~Morp()
{
delete[]pElement;
}
4. main.cpp:程序的入口
#include
#include"opencv2/core/core.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"Morp.h"
#include"macro.h"
using namespace cv;
using namespace std;
int main()
{
Mat srcImage = imread("image/container-01.jpg"); //图片读入
if (srcImage.empty()) //判断图片是否读入或用src.Image.data判断
{
cout << "图像加载不成功!";
return 0;
}
//将srcImage灰度化
Mat grayImage;
cvtColor(srcImage,grayImage,COLOR_RGB2GRAY);
//阈值操作
Morp morp;
Mat mImage;
mImage = morp.toThreshold(grayImage,0.4);
mImage = morp.toContr(mImage); //求反
imshow("求反", mImage);
//腐蚀
mImage= morp.toErode(mImage,DISK,4);
imshow("腐蚀", mImage);
//膨胀
mImage = morp.toDilate(mImage, DISK, 4);
imshow("膨胀", mImage);
waitKey();
return 0;
}
注:如果mImage= morp.toErode(mImage,DISK,5);和mImage= morp.toErode(mImage,SQUARE,10);会报错的,小于就不会!
通过后续的学习,现在知道这个程序为什么会错,怎么改才能运行不报错了。另外一篇内存问题可以看这里。
这个程序之所以会错是因为对成员变量int **pElement操作之前没有对它进行内存检测。
mImage= morp.toErode(mImage,DISK,5);
mImage= morp.toErode(mImage,SQUARE,10);
在运行上面的第一行代码时,变量pElement会指向一个5*5的矩阵;当运行上面的第二行时,变量pElement并没有delete[]就又申请了10*10的内存块,所以这个程序最后回收内存时会报错。并不是上面“注”写的那样,运行下面
mImage= morp.toErode(mImage,DISK,4);
mImage= morp.toErode(mImage,DISK,4);
代码时,可能使用相同的内存块导致程序不崩溃。如果要程序不奔溃,也可以只让一个对象使用一次。如下:
Morp m1,m2;
mImage= m1.toErode(mImage,DISK,4);
mImage= m2.toErode(mImage,DISK,10);
初步的修改方案是在使用成员变量int **pElement时,查看它是否分配了内存,如果有就delete[] pElement。可以把这个if语句放到void Morp::struElem(int type, int value)成员函数前面,其他的不变。
修改后。