图像处理与图像分析—图像的读入(C语言)

学习将会依据教材图像处理与图像分析基础(C/C++)版内容展开


什么是数字图像处理

一副图像可以定义为一个二维函数 f(x,y) ,其中 x 和 y 是空间(平面)坐标,任意一对空间坐标 (x,y) 处的幅度值 (x,y) 称为图像在该坐标点的强度或灰度。当 x,y 和灰度值 f 都是有限的离散量时,我们称该图像为数字图像。数字图像处理是指借助于数字计算机来处理数字图像。注意,数字图像由有限数量的元素组成,每个元素都有一定的位置和数值,这些元素称为像素

好,那么图像数字处理的第一步是什么?

一定是对于单个图片的读取,今天的主要目标也是读取一整个图片的完整信息

所用的语言为C语言,更加底层,能够帮助我们更好的理解

C语言编译器主要有Clang、GCC、WIN-TC、SUBLIME、MSVC、Turbo C等。

但是这里我说一下,我之前在编写C语言代码是所使用的都是GCC编译器,但是这一遍学习准备尝试一下MSVC这样一个微软的编译器,同时开始尝试使用VS2022来进行开发,之前使用Clion进行开发


我们先来看书上的【程序2-1】点运算的经典程序结构
void F1(BYTE* pimg, int width, int height)
{
	BYTE* pCur, * pEnd;

	pEnd = pImg + width * height;
	for (pCur = pImg; pCur < pEnd)
	{
		*(pCur++) = f(*pCur);
	}
	return;
}

我先来分析一下这个代码:

//首先这串代码是一个名为F1的函数,输入参数是一个BYTE类型的指针变量,整型的宽度和高度
void F1(BYTE* pimg, int width, int height)
{
	BYTE* pCur, * pEnd;
    //定义了两个指针变量* pCur, * pEnd
	pEnd = pImg + width * height;
    //表示将指针 pEnd 设置为指向图像数据末尾的位置。这里的计算方式是将指针 pImg 指向的内存地址加上图像的宽度乘以高度,从而得到图像数据的最后一个像素的下一个位置。
    
    
	for (pCur = pImg; pCur < pEnd)
    //pCur = pImg;:首先将指针变量 pCur 初始化为指向图像数据的起始位置,即指针 pImg 所指向的位置。
    //pCur < pEnd:这是循环的终止条件。只要 pCur 指针小于 pEnd 指针(即还未到达图像数据的末尾),就会继续执行循环。
	{
		*(pCur++) = f(*pCur);
        //首先会调用函数 f,并将当前指针 pCur 所指向的像素值作为参数传递给函数 f。函数 f 对该像素值进行处理,并返回处理后的结果。然后,将这个处理后的结果写回到当前指针 pCur 所指向的位置,并通过 pCur++ 操作使指针指向下一个像素位置。
	}
	return;
}

因为咱们用的是C语言哈,我写这些代码的时候已经发现报错了

原因是啥? —— C语言里面没有BYTE类型,所以只能用别的类型替代,BYTE我去查了一下,人如其名,一个字节的存储空间,那我这里就用uint8_t来代替这个指针类型

那C语言版本就呼之欲出了

void F1(uint8_t* pImg, int width, int height)
{
    uint8_t* pCur;
    uint8_t* pEnd;

    pEnd = pImg + width * height;
    for (pCur = pImg; pCur < pEnd; pCur++)
    {
        *pCur = f(*pCur);
    }
    return;
}

其实uint8_t变量还是报错了,是因为没有引用头文件,引用#include <stdint.h>就完美解决

那接下来的问题,f函数是什么?—我们先来看下一个例子,再来探讨这个问题


【程序2-2】邻域运算的典型程序结构

void F2(BYTE* pOrgImg, int width, int height, BYTE* pResImg)
{
	BYTE* pCur, * pRes;
	int x, y;
	for (y = 0, pCur = pOrgImg, pRes = pResImg; y < height; y++)
	{
		for (x = 0; x < width; x++, pCur++, pRes++)
		{
			*pRes = f(pOrgImg, x, y);
		}
	}
	return;
}

我们还是先看一下这个cpp代码,先根据我的理解注释一下

//四个传入值,指针为BYTE型
//pOrgImg:这是指向原始图像数据的指针。原始图像数据包含了待处理的像素值。
//width:这是图像的宽度,表示图像的水平像素数。
//height:这是图像的高度,表示图像的垂直像素数。
//pResImg:这是指向结果图像数据的指针。结果图像数据将存储经过处理后的像素值。
void F2(BYTE* pOrgImg, int width, int height, BYTE* pResImg)
{
	BYTE* pCur, * pRes;
    //创建两个指针,通过指针 pCur 和 pRes 分别指向原始图像和结果图像中当前位置的像素。
	int x, y;
    
  
	for (y = 0, pCur = pOrgImg, pRes = pResImg; y < height; y++)
    //y = 0,从顶部开始处理,原始图像指针指向原始图像开始的位置,结果图像指针只想结果图像开始的部分
    //y一直递增,直到便利完所有的行数
	{
		for (x = 0; x < width; x++, pCur++, pRes++)
		{
            //x循环来保证遍历完每一行的所有像素块
            //并且,每次原始图像指针和结果图像指针都往后递增来访问正确的操作像素
			*pRes = f(pOrgImg, x, y);
            //调用函数 f,并将原始图像数据指针 pOrgImg、当前位置的水平坐标 x 和垂直坐标 y 作为参数传递给 f 函数。f 函数会对该像素进行处理,并返回处理后的结果。
		}
	}
	return;
}

C语言版本顺手写一下

void F2(uint8_t* pOrgImg, int width, int height, uint8_t* pResImg)
{
    uint8_t* pCur;
    uint8_t* pRes;
    int x, y;

    for (y = 0, pCur = pOrgImg, pRes = pResImg; y < height; y++)
    {
        for (x = 0; x < width; x++, pCur++, pRes++)
        {
            *pRes = f(pOrgImg, x, y);
        }
    }
    return;
}

没有报错!


分析F1和F2

函数 F1
  • 参数
    • 接收三个参数:图像数据指针 pImg、图像宽度 width 和图像高度 height
  • 功能
    • 使用一个循环遍历图像中的每个像素。
    • 对每个像素调用函数 f 进行处理,并将处理结果直接更新到原始图像数据中。
函数 F2
  • 参数
    • 接收四个参数:原始图像数据指针 pOrgImg、图像宽度 width、图像高度 height 和结果图像数据指针 pResImg
  • 功能
    • 使用两个嵌套循环遍历原始图像的每个像素。
    • 对每个像素调用函数 f 进行处理,并将处理结果存储到结果图像数据中。
区别:
  1. 循环方式:函数 F1 使用单层循环来遍历像素,而函数 F2 使用嵌套循环进行遍历。
  2. 参数传递:函数 F1 直接操作原始图像数据,而函数 F2 在处理像素时需要额外传递像素的坐标信息 xy
  3. 结果存储:函数 F1 直接在原始图像数据中更新处理结果,而函数 F2 将处理结果存储到另一个结果图像数据中,保持了原始数据的不变性。
函数f

函数 f 被用作对图像数据进行处理的函数

接受一个像素值,处理后在返回一个像素值存入


过程实现

需要读取的图片

ME

本次要求是可以读取数据,那么我们实现的话F1和F2都可以针对像素进行操作

我们这次就通过F1来实现图像的读取

想了一下F1和F2其实都是针对于像素进行编辑操作,那我们的目标就是通过F1实现对于图片数据的读取,并打印在终端

void F1(uint8_t* pImg, int width, int height)
{
    uint8_t* pCur;
    uint8_t* pEnd;

    pEnd = pImg + width * height;
    for (pCur = pImg; pCur < pEnd; pCur++)
    {
        //*pCur = f(*pCur);
        printf("%d",*pCur);
    }
    return;
}

C语言怎么去读图片呢,问了一下chatgpt

const char* filename = "path/to/your/image.jpg"; // 图片文件路径

// 打开文件
FILE* file = fopen(filename, "rb");
if (file == NULL) {
    printf("无法打开文件:%s\n", filename);
    return 1;
}

// 获取图像宽度和高度(根据图像格式进行解析)
int width = 0;  // 替换为实际的宽度值
int height = 0; // 替换为实际的高度值

// 分配内存来存储图像数据
uint8_t* imageData = (uint8_t*)malloc(width * height);
if (imageData == NULL) {
    printf("内存分配失败\n");
    fclose(file);
    return 1;
}

// 读取图像数据
size_t bytesRead = fread(imageData, 1, width * height, file);
if (bytesRead != width * height) {
    printf("读取图像数据失败\n");
    free(imageData);
    fclose(file);
    return 1;
}

可以参考一下,其中就发现了几个问题

高度和宽度又该如何去自动计算呢?

查了一下jpg格式的图片,其文件结构更为复杂,直接解析文件头部来获取图像的宽度和高度相对困难。JPEG 文件通常包含了大量的压缩数据和标记信息,因此需要专门的 JPEG 解码器来读取并解析这些数据。深度搜索后发现,可以通过开源的 JPEG 解码库来获得

有点难,咱们还是先写死程序

image-20240309175426145

原码:

#include <stdio.h>
#include <stdint.h>

void F1(uint8_t* pImg, int width, int height)
{
    uint8_t* pCur;
    uint8_t* pEnd;

    pEnd = pImg + width * height;
    for (pCur = pImg; pCur < pEnd; pCur++)
    {
        //*pCur = f(*pCur);
        printf("%d", *pCur);
    }
    return;
}

int main()
{
    const char* filename = "C:/Users/25706/Pictures/Camera Roll/ME.jpg";

    FILE* file = fopen(filename, "rb");
    if (file == NULL) {
        printf("无法打开文件:%s\n", filename);
        return 1;
    }

    int width = 1107;  //通过Windows自带的画图工具看到图片尺寸为1107
    int height = 1107; 

    uint8_t* imageData = (uint8_t*)malloc(width * height);//按需分配内存
    if (imageData == NULL) {
        printf("内存分配失败\n");
        fclose(file);
        return 1;
    }

    size_t bytesRead = fread(imageData, 1, width * height, file);
    if (bytesRead != width * height) {
        printf("读取图像数据失败\n");
        free(imageData);
        fclose(file);
        return 1;
    }

    F1(imageData, width, height);//调用F1来打印图片信息

    fclose(file);    // 关闭文件和释放内存
    free(imageData);

    return 0;
}

运行结果:

读取图像数据失败

后面又尝试了png、bmp等图片格式,全都失败了。

失败原因,图片不止这么大,bmp图片还包含别的信息

查了bmp文件格式

BMP文件由4部分组成:

  1. 位图文件头(bitmap-file header)
  2. 位图信息头(bitmap-informationheader)
  3. 颜色表(color table)
  4. 颜色点阵数据(bits data)

24位真彩色位图没有颜色表,所以只有1、2、4这三部分。

所以在 main 函数中,读取图像数据时需要考虑 BMP 图像文件头的大小(通常为 54 字节)。你需要跳过文件头,才能正确读取图像的 RGB 数据。可以通过 fseek 函数将文件指针移动到图像数据的起始位置。

重新修改

重新整理思路

第一步:打开文件

 const char* filename = "E:/code/IDP-Learning-Journey/images/ME.bmp";
//定义常变量指针变量指向文件地址

 FILE* file = fopen(filename, "rb");
//把未见打开为只读模式

第二部:处理图像数据

uint8_t bmpHeader[54];  
// 定义一个数组来存储 BMP 文件头信息,总共 54 字节
fread(bmpHeader, 1, 54, file);  
// 从文件中读取 54 字节的数据到 bmpHeader 数组中

int width = *(int*)&bmpHeader[18];   // 从偏移量为 18 的位置读取图像宽度信息,使用指针强制类型转换将字节数据转换为整型数据
int height = *(int*)&bmpHeader[22];  // 从偏移量为 22 的位置读取图像高度信息,同样使用指针强制类型转换

uint8_t* imageData = (uint8_t*)malloc(width * height);  // 根据图像宽度和高度动态分配内存,用于存储图像数据

fseek(file, 54, SEEK_SET);  
// 将文件指针移动到 54 字节的位置,跳过 BMP 文件头部分

fread(imageData, 1, width * height, file);  
// 从文件中读取图像数据,每个像素点占用 1 个字节

*(int*)&bmpHeader[18] 这个表达式的含义是:

  • &bmpHeader[18] 取得 bmpHeader 数组中第 18 个元素的地址,也就是指向宽度信息的起始位置。
  • (int*) 表示将这个地址强制转换为指向整型数据的指针。
  • * 表示解引用这个指针,获取该地址上的值,即宽度信息。

第三步:调用函数,打印信息

F1(imageData, width, height);

函数在上面讲过了,可以回去看看

void F1(uint8_t* pImg, int width, int height)
{
    uint8_t* pCur;
    uint8_t* pEnd;

    pEnd = pImg + width * height;
    for (pCur = pImg; pCur < pEnd; pCur++)
    {
        //*pCur = f(*pCur);
        printf("%d ", *pCur);  // 输出灰度值
    }
    return;
}

第四步:好程序员的良好习惯

fclose(file);
free(imageData);

return 0;

成功!!!!!!!!!!!!!!

image-20240309183813597

真的太难了,本来以为不是很难的

原码必须记录:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>


void F1(uint8_t* pImg, int width, int height)
{
    uint8_t* pCur;
    uint8_t* pEnd;

    pEnd = pImg + width * height;
    for (pCur = pImg; pCur < pEnd; pCur++)
    {
        //*pCur = f(*pCur);
        printf("%d ", *pCur);  // 输出修改后的像素值
    }
    return;
}

int main()
{
    const char* filename = "E:/code/IDP-Learning-Journey/images/ME.bmp";

    FILE* file = fopen(filename, "rb");

    uint8_t bmpHeader[54];
    size_t bytesRead = fread(bmpHeader, 1, 54, file);

    int width = *(int*)&bmpHeader[18];   // 宽度信息位于偏移量为 18 的位置
    int height = *(int*)&bmpHeader[22];  // 高度信息位于偏移量为 22 的位置

    uint8_t* imageData = (uint8_t*)malloc(width * height); 

    fseek(file, 54, SEEK_SET);  // 跳过 BMP 文件头

    bytesRead = fread(imageData, 1, width * height, file); 

    F1(imageData, width, height);

    fclose(file);
    free(imageData);

    return 0;
}

灰度值和rgb有什么区别呢,查了一下,区别就是在读取像素点的时候所分配的字节个数

那么如果我们给图片的每个像素点分配三个字节

用F1中pCur的遍历图像数据中的每个像素点。pCur 指向当前正在处理的像素点的起始位置,通过 pCur 指针就可以逐个访问每个像素点的颜色数据。

试验了一下也是可以成功的

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

void F1(uint8_t* pImgRGB, int width, int height)
{
    uint8_t* pCur;
    uint8_t* pEnd;

    pEnd = pImgRGB + width * height * 3;  // 每个像素点占用 3 个字节(BGR)
    for (pCur = pImgRGB; pCur < pEnd; pCur += 3) {
        uint8_t blue = pCur[0];
        uint8_t green = pCur[1];
        uint8_t red = pCur[2];
        printf("R: %d, G: %d, B: %d ", red, green, blue);
    }
}

int main()
{
    const char* filename = "E:/code/IDP-Learning-Journey/images/ME.bmp";

    FILE* file = fopen(filename, "rb");

    uint8_t bmpHeader[54];
    fread(bmpHeader, 1, 54, file);

    int width = *(int*)&bmpHeader[18];   // 宽度信息位于偏移量为 18 的位置
    int height = *(int*)&bmpHeader[22];  // 高度信息位于偏移量为 22 的位置

    uint8_t* imageData = (uint8_t*)malloc(width * height * 3);  // 每个像素点占用 3 个字节(BGR)

    fseek(file, 54, SEEK_SET);  // 跳过 BMP 文件头

    fread(imageData, 3, width * height, file);  // 每个像素点占用 3 个字节(BGR)

    F1(imageData, width, height);

    fclose(file);
    free(imageData);

    return 0;
}

image-20240309191106732


感谢您的观看!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/444914.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

了解 HTTPS 中间人攻击:保护你的网络安全

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

二叉树进阶--二叉搜索树的进一步优化--AVL树 Self-balancing binary search tree

前言&#xff1a; 在上一次的文章中&#xff0c;我们详细介绍了二叉树的进阶树型&#xff0c;即BS树(二叉搜索树),但在文章的结尾&#xff0c;二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表…

golang实现正向代理和反向代理

文章目录 正向代理反向代理区别与联系:总结代理服务器实现正向代理反向代理正向代理 正向代理是客户端代理,它位于客户端和目标服务器之间。它的作用是保护客户端的隐私和安全。 如我们现在想要访问谷歌,但是由于某些原因,无法直接访问到谷歌,我们可以通过连接一台代理服务…

Redis缓存过期策略

文章目录 一、面试题二、redis内存1. Redis的内存大小怎么查看&#xff1f;2. 设置redis内存3. redis内存的OOM 三、redis内存淘汰策略1. redis的过期键删除策略2. redis缓存淘汰策略 一、面试题 1. 生产上你们redis内存设置多少&#xff1f; 2. 如何配置、修改redis内存大小…

YOLOV5 初体验:简单猫和老鼠数据集模型训练

1、前言 前两天&#xff0c;通过OpenCV 对猫和老鼠视频的抽取&#xff0c;提取了48张图片。这里不再介绍&#xff0c;可以参考之前的文章&#xff1a;利用OpenCV 抽取视频的图片&#xff0c;并制作目标检测数据集-CSDN博客 数据的目录如下&#xff1a; 项目的下载见文末 2、制…

基于Java的在线课程教学系统(Vue.js+SpringBoot)

目录 一、摘要1.1 系统介绍1.2 项目录屏 二、研究内容2.1 课程类型管理模块2.2 课程管理模块2.3 课时管理模块2.4 课程交互模块2.5 系统基础模块 三、系统设计3.1 用例设计3.2 数据库设计 四、系统展示4.1 管理后台4.2 用户网页 五、样例代码5.1 新增课程类型5.2 网站登录5.3 课…

第十一篇 - 应用于市场营销视频场景中的人工智能和机器学习技术 – Video --- 我为什么要翻译介绍美国人工智能科技巨头IAB公司(1)

IAB平台&#xff0c;使命和功能 IAB成立于1996年&#xff0c;总部位于纽约市。 作为美国的人工智能科技巨头社会媒体和营销专业平台公司&#xff0c;互动广告局&#xff08;IAB- the Interactive Advertising Bureau&#xff09;自1996年成立以来&#xff0c;先后为700多家媒体…

为什么选择 Flink 做实时处理

优质博文&#xff1a;IT-BLOG-CN 为什么选择 Flink 【1】流数据更真实地反映了我们的生活方式&#xff08;实时聊天&#xff09;&#xff1b; 【2】传统的数据架构是基于有限数据集的&#xff08;Spark 是基于微批次数据处理&#xff09;&#xff1b; 【3】我们的目标&#xf…

ROS——ROS环境搭建

Ubuntu 安装完毕后&#xff0c;就可以安装 ROS 操作系统了&#xff0c;大致步骤如下: 配置ubuntu的软件和更新&#xff1b; 设置安装源&#xff1b; 设置key&#xff1b; 安装&#xff1b; 配置环境变量。 1.配置ubuntu的软件和更新 配置ubuntu的软件和更新&#xff0c;…

系统编程--makefile项目管理

这里写目录标题 介绍语法结构总览基础规则简介最简单的makefile对于基础规则的理解和应用总结 makefile时尽量使用更独立的命令&#xff0c;减少文件之间的耦合度需求以及解决总结 补充&#xff08;关于makefile中脚本命令的编写顺序&#xff09; 一级目录二级目录二级目录二级…

数据科学中的Python:NumPy和Pandas入门指南【第121篇—NumPy和Pandas】

数据科学中的Python&#xff1a;NumPy和Pandas入门指南 数据科学是当今数字时代中的一个重要领域&#xff0c;而Python是数据科学家们最喜爱的编程语言之一。在这篇博客中&#xff0c;我们将介绍Python中两个强大的库——NumPy和Pandas&#xff0c;它们在数据处理和分析中发挥…

java算法第十八天 | ● 110.平衡二叉树 ● 257. 二叉树的所有路径 ● 404.左叶子之和

110.平衡二叉树 leetcode链接 思路&#xff1a; 使用后序遍历分别求左右子树的高度&#xff0c;若高度只差大于一&#xff0c;则返回-1&#xff0c;否则返回当前节点的最大高度。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* Tree…

爬虫(五)

1. 前端JS相关 三元运算 v1 条件 ? 值A : 值B; # 如果条件成立v1值A&#xff0c;不成立v1等于值Bres 1 1 ? 99 : 88 # res99特殊的逻辑运算 v1 11 || 22 # Ture v2 9 || 14 # 9 v3 0 || 15 # 15 v3 0 || 15 || "zhangfei" # 15赋值和…

x86 Ubuntu上编译eudev给龙芯loongarch64架构主机使用

1、下载eudev库eudev-master.zip&#xff0c;链接&#xff1a;eudev库官方地址 2、下载龙芯的交叉编译工具&#xff1a;loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.2.tar.xz&#xff0c;链接&#xff1a;龙芯交叉编译官方地址 3、交叉编译器环境搭建 (1)、…

latex绘图中\begin{figure}[htbp]中的htbp什么意思

在LaTeX中&#xff0c;\begin{figure}[htbp] 用来开始一个图形环境&#xff0c;其中 [htbp] 是一个位置参数&#xff0c;用来指导LaTeX如何放置这个图形。 具体来说&#xff0c;[htbp] 中的每个字母代表一个放置选项&#xff1a; h&#xff1a;代表“here”&#xff0c;意味着…

【LeetCode: 299. 猜数字游戏 - 模拟 + 计数】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

springcloud第3季 consul服务发现注册,配置中心2

一 consul的作用 1.1 为何使用注册中心 为何要用注册中心&#xff1f; 1.A服务调用B服务&#xff0c;使用ip和端口&#xff0c;如果B服务的ip或者端口发生变化&#xff0c;服务A需要进行改动&#xff1b; 2.如果在分布式集群中&#xff0c;部署多个服务B&#xff0c;多个服…

Linux(Ubuntu)中安装vscode

①首先去vscode的官网下载.deb文件 网址&#xff1a;https://code.visualstudio.com/docs/?dvlinuxarm64_deb 注&#xff1a;如果linux端无法打开网页下载文件&#xff0c;可以在Windows端下载好用WinSCP传输到Linux。下载前注意下你的系统架构是arm还是amd&#xff0c;系统…

常用的加密算法

AES 高级加密标准&#xff08;AES, Advanced Encryption Standard&#xff09;是当今世界范围内应用最广泛的对称加密算法之一。在微信小程序加密传输等场景中&#xff0c;AES算法发挥着至关重要的作用。对称加密算法的特点在于加密和解密过程使用相同的密钥。具体来说&#x…

【MybatisPlus】BaseMapper详解,举例说明

一、BaseMapper 简介 MyBatis-Plus 的核心类 BaseMapper 主要是用于提供基本的 CRUD&#xff08;创建、读取、更新、删除&#xff09;操作的接口定义。它是 MyBatis-Plus 框架中的一个重要组成部分&#xff0c;可以大大简化基于 MyBatis 的数据访问层代码的编写。 BaseMapper…