C/C++开发,opencv-ml库学习,随机森林(RTrees)应用

news2024/12/23 0:21:10

目录

一、随机森林算法

1.1 算法简介

1.2 OpenCV-随机森林(Random Forest)

二、cv::ml::RTrees应用

2.2 RTrees应用

2.2 程序编译

2.3 main.cpp全代码


一、随机森林算法

1.1 算法简介

        随机森林算法是一种集成学习(Ensemble Learning)方法,由多个决策树组成。它结合了决策树的高效性和集成学习的准确性,具有很强的模型泛化能力。

        随机森林算法的原理主要包括两个方面:随机性和集成。

  1. 随机性:体现在样本的随机性和特征的随机性。算法通过引入随机性来构建多个决策树,每个决策树都是基于随机抽样的训练数据和随机选择的特征进行构建的。这种随机性能够有效地减少过拟合的风险,提高模型的泛化能力。
  2. 集成:通过集成多个决策树的预测结果来完成最终的分类或回归任务。通常采用投票的方式进行集成,即多数表决原则。在随机森林中,每棵决策树的构建过程都是相互独立的,这意味着每棵决策树都是在不同的训练数据和特征子集上进行构建的,这种随机性能够有效地降低模型的方差,提高模型的稳定性。
1.2 OpenCV-随机森林(Random Forest)

        在OpenCV中,随机森林(Random Forest)是一种集成学习方法,通过构建并组合多个决策树来做出预测。OpenCV提供了cv::ml::RTrees类来实现随机森林算法。决策树在使用时仅仅构建一棵树,这样容易出现过拟合现象,可以通过构建多个决策树来避免过拟合现象。当构建多个决策树时,就出现了随机森林。这种方法通过多个决策树的投票来得到在最终的结果。

//创建 RTrees对象
    cv::Ptr<cv::ml::RTrees> rf = cv::ml::RTrees::create();  
RTrees类:
    setMaxDepth()     设置决策树的最大深度 
    setMinSampleCount() 设置叶子节点上的最小样本数
    setRegressionAccuracy() 非必须 回归算法的精度
    setPriors() 非必须 数据类型
    setCalculateVarImportance() 非必须 是否要计算var
    setActiveVarCount() 非必须 设置var的数目
    setTermCriteria() 设置终止条件
    .....

        随机森林通过随机选择特征和样本子集来构建每棵决策树,并将它们的预测结果进行集成。这种方法通常能够降低过拟合的风险,并提高模型的预测性能。

二、cv::ml::RTrees应用

2.1 数据集样本准备

  本文为了快速验证使用,采用mnist数据集,参考本专栏博文《C/C++开发,opencv-ml库学习,支持向量机(SVM)应用-CSDN博客》下载MNIST 数据集(手写数字识别),并解压。

        同时参考该博文“2.4 SVM(支持向量机)实时识别应用”的章节资料,利用python代码解压t10k-images.idx3-ubyte出图片数据文件。

2.2 RTrees应用

        创建了一个 cv::ml::RTrees对象,并设置了训练数据和终止条件。接着,我们调用 train 方法来训练决策树模型。最后,我们使用训练好的模型来预测一个新样本的类别。

   // 3. 设置并训练随机森林模型  
    cv::Ptr<cv::ml::RTrees> rf = cv::ml::RTrees::create();  
    rf->setMaxDepth(30);                    // 设置决策树的最大深度  
    rf->setMinSampleCount(2);             // 设置叶子节点上的最小样本数  
    rf->setTermCriteria(cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 10, 0.1)); // 设置终止条件  
    rf->train(trainingData, cv::ml::ROW_SAMPLE, labelsMat); 
    ......
	cv::Mat testResp;
	float response = rf->predict(testData,testResp); 
    ......
    rf->save("mnist_svm.xml"); 

     训练及测试过的算法模型,保存输出,然后调用

cv::Ptr<cv::ml::RTrees> rf = cv::ml::StatModel::load<cv::ml::RTrees>("mnist_svm.xml");

//预测图片
float ret = rf ->predict(image);
std::cout << "predict val = "<< ret << std::endl;
2.2 程序编译

        和讲述支持向量机(SVM)应用的博文编译类似,采用opencv+mingw+makefile方式编译:

#/bin/sh
#win32
CX= g++ -DWIN32 
#linux
#CX= g++ -Dlinux 

BIN 		:= ./
TARGET      := opencv_ml03.exe
FLAGS		:= -std=c++11 -static
SRCDIR 		:= ./
#INCLUDES
INCLUDEDIR 	:= -I"../../opencv_MinGW/include" -I"./"
#-I"$(SRCDIR)"
staticDir   := ../../opencv_MinGW/x64/mingw/staticlib/
#LIBDIR		:= $(staticDir)/libopencv_world460.a\
#			   $(staticDir)/libade.a \
#			   $(staticDir)/libIlmImf.a \
#			   $(staticDir)/libquirc.a \
#			   $(staticDir)/libzlib.a \
#			   $(wildcard $(staticDir)/liblib*.a) \
#			   -lgdi32 -lComDlg32 -lOleAut32 -lOle32 -luuid 
#opencv_world放弃前,然后是opencv依赖的第三方库,后面的库是MinGW编译工具的库

LIBDIR 	    := -L $(staticDir) -lopencv_world460 -lade -lIlmImf -lquirc -lzlib \
				-llibjpeg-turbo -llibopenjp2 -llibpng -llibprotobuf -llibtiff -llibwebp \
				-lgdi32 -lComDlg32 -lOleAut32 -lOle32 -luuid 
source		:= $(wildcard $(SRCDIR)/*.cpp) 

$(TARGET) :
	$(CX) $(FLAGS) $(INCLUDEDIR) $(source)  -o $(BIN)/$(TARGET) $(LIBDIR)

clean:
	rm  $(BIN)/$(TARGET)

make编译,make clean 清除可重新编译。

运行效果,同样数据样本,相比决策树算法训练结果,其准确率有了较大改善,大家可以尝试调整参数验证:

2.3 main.cpp全代码

        main.cpp源代码,由于是基于前两篇博文支持向量机(SVM)应用、决策树(DTrees)应用基础上,快速移用实现的,有很多支持向量机(SVM)应用或决策树(DTrees)的痕迹,采用的数据样本也非较合适的,仅仅是为了阐述c++ opencv 随机森林(RTrees)应用说明。

#include <opencv2/opencv.hpp>  
#include <opencv2/ml/ml.hpp>  
#include <opencv2/imgcodecs.hpp>
#include <iostream>  
#include <vector>  
#include <iostream>
#include <fstream>

int intReverse(int num)
{
	return (num>>24|((num&0xFF0000)>>8)|((num&0xFF00)<<8)|((num&0xFF)<<24));
}

std::string intToString(int num)
{
	char buf[32]={0};
	itoa(num,buf,10);
	return std::string(buf);
}


cv::Mat read_mnist_image(const std::string fileName) {
	int magic_number = 0;
	int number_of_images = 0;
	int img_rows = 0;
	int img_cols = 0;

	cv::Mat DataMat;

	std::ifstream file(fileName, std::ios::binary);
	if (file.is_open())
	{
		std::cout << "open images file: "<< fileName << std::endl;

		file.read((char*)&magic_number, sizeof(magic_number));//format
		file.read((char*)&number_of_images, sizeof(number_of_images));//images number
		file.read((char*)&img_rows, sizeof(img_rows));//img rows
		file.read((char*)&img_cols, sizeof(img_cols));//img cols

		magic_number = intReverse(magic_number);
		number_of_images = intReverse(number_of_images);
		img_rows = intReverse(img_rows);
		img_cols = intReverse(img_cols);
		std::cout << "format:" << magic_number
			<< " img num:" << number_of_images
			<< " img row:" << img_rows
			<< " img col:" << img_cols << std::endl;

		std::cout << "read img data" << std::endl;

		DataMat = cv::Mat::zeros(number_of_images, img_rows * img_cols, CV_32FC1);
		unsigned char temp = 0;
		for (int i = 0; i < number_of_images; i++) {
			for (int j = 0; j < img_rows * img_cols; j++) {
				file.read((char*)&temp, sizeof(temp));
				//svm data is CV_32FC1
				float pixel_value = float(temp);
				DataMat.at<float>(i, j) = pixel_value;
			}
		}
		std::cout << "read img data finish!" << std::endl;
	}
	file.close();
	return DataMat;
}

cv::Mat read_mnist_label(const std::string fileName) {
	int magic_number;
	int number_of_items;

	cv::Mat LabelMat;

	std::ifstream file(fileName, std::ios::binary);
	if (file.is_open())
	{
		std::cout << "open label file: "<< fileName << std::endl;

		file.read((char*)&magic_number, sizeof(magic_number));
		file.read((char*)&number_of_items, sizeof(number_of_items));
		magic_number = intReverse(magic_number);
		number_of_items = intReverse(number_of_items);

		std::cout << "format:" << magic_number << "  ;label_num:" << number_of_items << std::endl;

		std::cout << "read Label data" << std::endl;
		//data type:CV_32SC1,channel:1
		LabelMat = cv::Mat::zeros(number_of_items, 1, CV_32SC1);
		for (int i = 0; i < number_of_items; i++) {
			unsigned char temp = 0;
			file.read((char*)&temp, sizeof(temp));
			LabelMat.at<unsigned int>(i, 0) = (unsigned int)temp;
		}
		std::cout << "read label data finish!" << std::endl;

	}
	file.close();
	return LabelMat;
}

//change path for real paths
std::string trainImgFile = "D:\\workForMy\\OpenCVLib\\opencv_demo\\opencv_ml01\\train-images.idx3-ubyte";
std::string trainLabeFile = "D:\\workForMy\\OpenCVLib\\opencv_demo\\opencv_ml01\\train-labels.idx1-ubyte";
std::string testImgFile = "D:\\workForMy\\OpenCVLib\\opencv_demo\\opencv_ml01\\t10k-images.idx3-ubyte";
std::string testLabeFile = "D:\\workForMy\\OpenCVLib\\opencv_demo\\opencv_ml01\\t10k-labels.idx1-ubyte";

void train_SVM()
{
	//read train images, data type CV_32FC1
	cv::Mat trainingData = read_mnist_image(trainImgFile);
	//images data normalization
	trainingData = trainingData/255.0;
	std::cout << "trainingData.size() = " << trainingData.size() << std::endl;
	std::cout << "trainingData.type() = " << trainingData.type() << std::endl;  
	std::cout << "trainingData.rows = " << trainingData.rows << std::endl; 
	std::cout << "trainingData.cols = " << trainingData.cols << std::endl; 
	//read train label, data type CV_32SC1
	cv::Mat labelsMat = read_mnist_label(trainLabeFile);
	std::cout << "labelsMat.size() = " << labelsMat.size() << std::endl; 
	std::cout << "labelsMat.type() = " << labelsMat.type() << std::endl;  
	std::cout << "labelsMat.rows = " << labelsMat.rows << std::endl; 
	std::cout << "labelsMat.cols = " << labelsMat.cols << std::endl; 

	std::cout << "trainingData & labelsMat finish!" << std::endl;  

    // //create SVM model
    // cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();  
	// //set svm args,type and KernelTypes
    // svm->setType(cv::ml::SVM::C_SVC);  
	// svm->setKernel(cv::ml::SVM::POLY);  
	// //KernelTypes POLY is need set gamma and degree
	// svm->setGamma(3.0);
	// svm->setDegree(2.0);
	// //Set iteration termination conditions, maxCount is importance
	// svm->setTermCriteria(cv::TermCriteria(cv::TermCriteria::EPS | cv::TermCriteria::COUNT, 1000, 1e-8)); 
	// std::cout << "create SVM object finish!" << std::endl;  

	// std::cout << "trainingData.rows = " << trainingData.rows << std::endl; 
	// std::cout << "trainingData.cols = " << trainingData.cols << std::endl; 
	// std::cout << "trainingData.type() = " << trainingData.type() << std::endl; 
    // // svm model train 
    // svm->train(trainingData, cv::ml::ROW_SAMPLE, labelsMat);  
	// std::cout << "SVM training finish!" << std::endl; 

    // // 创建决策树对象  
    // cv::Ptr<cv::ml::DTrees> dtree = cv::ml::DTrees::create();  
    // dtree->setMaxDepth(30);          // 设置树的最大深度  
	// dtree->setCVFolds(0);
    // dtree->setMinSampleCount(1);   // 设置分裂内部节点所需的最小样本数 
    // std::cout << "create dtree object finish!" << std::endl;  
    // // 训练决策树--trainingData训练数据,labelsMat训练标签
    // cv::Ptr<cv::ml::TrainData> td = cv::ml::TrainData::create(trainingData, cv::ml::ROW_SAMPLE, labelsMat);  
    // std::cout << "create TrainData object finish!" << std::endl; 
    // if(dtree->train(td))
	// {
	// 	std::cout << "dtree training finish!" << std::endl;
	// }else{
	// 	std::cout << "dtree training fail!" << std::endl; 
	// }
  
   // 3. 设置并训练随机森林模型  
    cv::Ptr<cv::ml::RTrees> rf = cv::ml::RTrees::create();  
    rf->setMaxDepth(30);                    // 设置决策树的最大深度  
    rf->setMinSampleCount(2);             // 设置叶子节点上的最小样本数  
    rf->setTermCriteria(cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 10, 0.1)); // 设置终止条件  
    rf->train(trainingData, cv::ml::ROW_SAMPLE, labelsMat);  

    // svm model test  
	cv::Mat testData = read_mnist_image(testImgFile);
	//images data normalization
	testData = testData/255.0;
	std::cout << "testData.rows = " << testData.rows << std::endl; 
	std::cout << "testData.cols = " << testData.cols << std::endl; 
	std::cout << "testData.type() = " << testData.type() << std::endl; 
	//read test label, data type CV_32SC1
	cv::Mat testlabel = read_mnist_label(testLabeFile);
	cv::Mat testResp;
	// float response = svm->predict(testData,testResp); 
    // float response = dtree->predict(testData,testResp); 
	float response = rf->predict(testData,testResp); 
	// std::cout << "response = " << response << std::endl; 
	testResp.convertTo(testResp,CV_32SC1);
	int map_num = 0;
	for (int i = 0; i <testResp.rows&&testResp.rows==testlabel.rows; i++)
	{
		if (testResp.at<int>(i, 0) == testlabel.at<int>(i, 0))
		{
			map_num++;
		}
		// else{
		// 	std::cout << "testResp.at<int>(i, 0) " << testResp.at<int>(i, 0) << std::endl;
		// 	std::cout << "testlabel.at<int>(i, 0) " << testlabel.at<int>(i, 0) << std::endl;
		// }
	}
	float proportion  = float(map_num) / float(testResp.rows);
	std::cout << "map rate: " << proportion * 100 << "%" << std::endl;
	std::cout << "SVM testing finish!" << std::endl; 
	//save svm model
	// svm->save("mnist_svm.xml");
    // dtree->save("mnist_svm.xml");
	rf->save("mnist_svm.xml");
}

void prediction(const std::string fileName,cv::Ptr<cv::ml::DTrees> dtree)
// void prediction(const std::string fileName,cv::Ptr<cv::ml::SVM> svm)
{
	//read img 28*28 size
	cv::Mat image = cv::imread(fileName, cv::IMREAD_GRAYSCALE);
	//uchar->float32
	image.convertTo(image, CV_32F);
	//image data normalization
	image = image / 255.0;
	//28*28 -> 1*784
	image = image.reshape(1, 1);

	//预测图片
	float ret = dtree->predict(image);
	std::cout << "predict val = "<< ret << std::endl;
}

std::string imgDir = "D:\\workForMy\\OpenCVLib\\opencv_demo\\opencv_ml01\\t10k-images\\";
std::string ImgFiles[5] = {"image_0.png","image_10.png","image_20.png","image_30.png","image_40.png",};
void predictimgs()
{
	//load svm model
	// cv::Ptr<cv::ml::SVM> svm = cv::ml::StatModel::load<cv::ml::SVM>("mnist_svm.xml");
    //load DTrees model
    // cv::Ptr<cv::ml::DTrees> dtree = cv::ml::StatModel::load<cv::ml::DTrees>("mnist_svm.xml");
	cv::Ptr<cv::ml::RTrees> rf = cv::ml::StatModel::load<cv::ml::RTrees>("mnist_svm.xml");
	for (size_t i = 0; i < 5; i++)
	{
		prediction(imgDir+ImgFiles[i],rf);
	}
}

int main()  
{  
	train_SVM();
	predictimgs();	
    return 0;  
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SVN泄露+HG泄露

一、SVN泄露 &#xff08;1&#xff09;什么是SVN 一种开放源代码版本控制系统&#xff0c;用于管理项目的文件和代码 SVN能做什么 1.共享资源并修改 2.记录资源变更&#xff0c;可恢复到任意一个修改点 漏洞描述 版本控制系统中的一个安全漏洞&#xff0c;允许未授权的…

xgp加速器免费 微软商店xgp用什么加速器

2001年11月14日深夜&#xff0c;比尔盖茨亲自来到时代广场&#xff0c;在午夜时分将第一台Xbox交给了来自新泽西的20岁年轻人爱德华格拉克曼&#xff0c;后者在回忆中说&#xff1a;“比尔盖茨就是上帝。”性能超越顶级PC的Xbox让他们趋之若鹜。2000年3月10日&#xff0c;微软宣…

2024年vue 开发环境 Node.js于win10环境下的安装

2024年vue 开发环境 Node.js于win10环境下的安装 导航 文章目录 2024年vue 开发环境 Node.js于win10环境下的安装导航一、下载node.js二、安装node.js三、测试(一)四、环境配置五、测试(二)六、安装淘宝镜像七、安装vue脚手架 一、下载node.js Node.js 官方网站下载&#xff…

hive搭建完整教学

目录 简介准备工作安装步骤&#xff08;一&#xff09;、下载hive包并解压到指定目录下&#xff08;二&#xff09;、设置环境变量&#xff08;三&#xff09;、下载MySQL驱动包到hive的lib目录下&#xff08;四&#xff09;、将hadoop的guava包拷贝到hive&#xff08;五&#…

揭露 FileSystem 引起的线上 JVM 内存溢出问题

作者&#xff1a;来自 vivo 互联网大数据团队-Ye Jidong 本文主要介绍了由FileSystem类引起的一次线上内存泄漏导致内存溢出的问题分析解决全过程。 内存泄漏定义&#xff08;memory leak&#xff09;&#xff1a;一个不再被程序使用的对象或变量还在内存中占有存储空间&#x…

区块链基础——区块链应用架构概览

目录 区块链应用架构概览&#xff1a; 1、区块链技术回顾 1.1、以太坊结点结构 1.2、多种应用场景 2、区块链应用架构概览 2.1、传统的Web2 应用程序架构 2.2、Web3 应用程序架构——最简架构 2.3、Web3 应用程序架构——前端web3.js ether.js 2.4、Web3 应用程序架构—…

无人零售与传统便利店的竞争优势

无人零售与传统便利店的竞争优势 成本控制 • 无人零售 显著降低了人力成本&#xff0c;无需支付店员薪资和相关福利&#xff0c;且通过智能化管理减少能源消耗与维护费用&#xff0c;尤其在高租金和高人流区域效益突出。 • 传统便利店 则承担较高的人员开支&#xff0c;…

如何申请免费SSL证书,把网站升级成HTTPS

HTTPS&#xff08;Hyper Text Transfer Protocol Secure&#xff09;是一种用于安全数据传输的网络协议&#xff0c;它可以有效地保护网站和用户之间的通信安全。然而&#xff0c;要使一个网站从HTTP升级到HTTPS&#xff0c;就需要一个SSL证书。那么&#xff0c;如何申请免费的…

java8 Stream流常用方法(持续更新中...)

java8 Stream流常用方法 1.过滤数据中年龄大于等于十八的学生2.获取对象中其中的一个字段并添加到集合(以学生姓名&#xff08;name&#xff09;为例)3.获取对象中其中的一个字段并转为其他数据类型最后添加到集合(以学生性别&#xff08;sex&#xff09;为例&#xff0c;将Str…

Django框架之Django安装与使用

一、Django框架下载 首先我们需要先确定好自己电脑上的python解释器环境&#xff0c;否则会导致后面项目所需要的库安装不了以及项目无法运行的问题。 要下载Django并开始使用它&#xff0c;你可以按照以下步骤进行&#xff1a; 1、安装Python 首先&#xff0c;确保你的计算…

Oracle 监控 SQL 精选 (一)

Oracle数据库的监控通常涉及性能、空间、会话、对象、备份、安全等多个层面。 有效的监控可以帮助 DBA 及时发现和解决问题&#xff0c;提高数据库的稳定性和性能&#xff0c;保障企业的数据安全和业务连续性。 常用的监控指标有&#xff1a; 性能指标&#xff1a; 查询响应时间…

vue+springboot项目的登录验证码(JAVA自带)

后台springboot CaptureController package com.example.controller;import com.example.common.Result; import com.example.service.AuthCodeService; import com.example.utils.CodeUtils; import lombok.SneakyThrows; import org.apache.ibatis.annotations.Param; impo…

ELF 1技术贴|CAN接口浅析:从原理到对测

引言 在当今智能化、网络化的时代&#xff0c;各种电子设备间的高效通信成为了技术发展的关键。而控制器局域网络&#xff08;Controller Area Network&#xff0c;简称CAN&#xff09;&#xff0c;作为嵌入式系统中不可或缺的通信协议&#xff0c;正扮演着链接桥梁的重要角色…

大厂常见算法50题-用两个栈实现队列

专栏持续更新50道算法题&#xff0c;都是大厂高频算法题&#xff0c;建议关注, 一起巧‘背’算法! 文章目录 题目解法总结 题目 解法 先搞清队列与栈的特点&#xff1a;队列先进先出&#xff0c;栈先进后出两个栈的分工&#xff1a;栈A入数据&#xff0c;栈B出数据需要保证取数…

COOIS 生产订单显示系统增强

需求说明&#xff1a;订单系统显示页面新增批量打印功能 增强点&#xff1a;CL_COIS_DISP_LIST_NAVIGATION -->TOOLBAR方法中新增隐式增强添加自定义打印按钮 增强点&#xff1a;BADI-->WORKORDER_INFOSYSTEM新增增强实施 实现位置&#xff1a;IF_EX_WORKORDER_INFOSYS…

【Leetcode】377. 组合总和 Ⅳ

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 给你一个由 不同 整数组成的数组 n u m s nums nums&#xff0c;和一个目标整数 t a r g e t target target 。请你从 n u m s nums nums 中找出并返回总和为 t a r g e t targ…

【STM32+HAL+Proteus】系列学习教程---ADC(查询、中断、DMA模式下的电压采集)

实现目标 1、学会STM32CubeMX软件关于ADC的配置 2、掌握ADC三种模式&#xff08;查询、中断、DMA&#xff09;编程 3、具体目标&#xff1a;1、将开发板单片机采集到的电压值上传至上位机串口调试助手显示。 一、ADC 概述 1、什么是ADC? ADC&#xff08;Analog to Digit…

实验一: 设备密码配置与远程管理

1.实验环境 用路由器和交换机搭建实验环境 2.需求描述 实现管理员主机对交换机和路由器的远程管理 设备上配置的密码都要被加密 3.推荐步骤 对路由器配置的步骤如下&#xff1a; 实现路由器和PC的连通性配置VTY密码和特权模式密码在PC上Telnet 到路由器。 对交换机配置的…

智慧文旅:引领旅游产业智慧升级的创新模式

一、智慧文旅是什么&#xff1f; 智慧文旅是指以当地特色文化为核心&#xff0c;借助现代科技手段&#xff0c;实现旅游景区全面智慧升级的旅游模式。在智慧文旅中&#xff0c;新一代信息网络技术和装备得到充分运用&#xff0c;文化旅游基础设施得到新建和改善&#xff0c;特…

无源DWDM与有源DWDM:两种系统在5G时代的作用与挑战

随着互联网、大数据和云计算等技术的快速发展&#xff0c;光纤通信技术在现代通信领域扮演着越来越重要的角色。作为光纤通信的关键技术之一&#xff0c;波分复用&#xff08;DWDM&#xff09;技术在提高光纤传输容量、优化网络结构等方面具有重要意义。根据系统是否需要外部能…