基于VS与OpenCV的模板匹配学习(2):边缘匹配+图像金字塔

article/2025/5/7 17:31:28

基于VS与OpenCV的模板匹配学习(2)

边缘模板匹配+图像金字塔

基于C++与OpenCV的模板匹配学习(1)OpenCV matchTemplate()示例


文章目录

  • 基于VS与OpenCV的模板匹配学习(2)
  • 边缘模板匹配+图像金字塔
  • 前言
  • 一、边缘检测
    • 1.1 边缘检测的一般步骤
    • 1.2 sobel算子
    • 1.3 canny算子
    • 1.4 OpenCV sobel与canny函数
    • 1.5 OpenCV findContours函数
  • 二、边缘匹配算法
    • 2.1 图像读入
    • 2.2 模板轮廓点提取
    • 2.3 计算模板轮廓的dx,dy,mag,log
    • 2.4 计算原图像的dx,dy,mag,log
    • 2.5 最优匹配点寻找
    • 2.6 Bug调试
    • 2.7 算法结果展示
  • 三、图像金字塔
    • 3.1 图像金字塔基本原理
    • 3.2 OpenCV pyrDown函数
    • 3.3 高斯金字塔具体实现
    • 3.4 图像金字塔优化结果


前言

在基于C++与OpenCV的模板匹配学习(1)OpenCV matchTemplate()示例中,以OpenCV库的内置函数matchTemplate()为例,展示了基于灰度值的模板匹配算法的效果。尽管算法在示例程序中,匹配效果良好。但在工程应用中,表现出两点缺陷。
(1)在光照条件发生变化时,基于灰度值的模板匹配算法表现较差。
(2)假定原图像像素点个数为m,模板图像像素点个数为n,模板匹配算法的时间复杂度为T(mn),当原图像为2048*2048时,算法的运算时间较长。
为解决上述问题,鉴于图像的边缘很少受光线变化的影响这一光学特性,开始探究基于边缘/轮廓的模板匹配算法;此外,开始结合图像金字塔进行算法时间的优化。


一、边缘检测

这一部分主要讲解下面会用到的有关边缘模板匹配的一些相关知识。例如:常用canny、sobel算子。

1.1 边缘检测的一般步骤

滤波:边缘检测的算法基于图像强度的一阶和二阶导数,导数通常对噪声敏感,因此采用滤波器来改善边缘滤波器的性能。例如高斯滤波,利用高斯核对灰度矩阵加权求和。
增强:边缘增强基于确定图像各点领域强度的变化值,可计算梯度幅值。
检测:采用方法对增强图像的某些边缘点进行取舍,可通过阈值化方法。

1.2 sobel算子

sobel算子用于边缘检测的离散微分算子,结合高斯平滑和微分求导,计算灰度图像的近似梯度。在图像的任何一点使用sobel算子,可得到对应的梯度矢量。
sobel算子的计算过程:

  • 在x和y方向求导:
    在这里插入图片描述

  • 近似梯度:
    在这里插入图片描述

1.3 canny算子

canny边缘检测算子是John F.Canny于1986年开发的一个多级边缘检测算法,也是目前被很多人推崇为最优的边缘检测的算法。
canny边缘检测的步骤一般分为以下几步:
(1)消除噪声
高斯平滑滤波器,以下显示了size=5的高斯内核示例。
在这里插入图片描述
(2)计算梯度幅度与方向,参考sobel算子操作
(3)非极大值抑制
排除非边缘像素,仅仅保留了一些细线条。
(4)滞后阈值
canny使用了滞后阈值,滞后阈值需要两个阈值,高阈值与低阈值。阈值定义如下:
若某一像素的幅值高于高阈值,像素保留为边缘像素。
若某一像素的幅值低于低阈值,像素被排除。
若某一像素的阈值在高低阈值之间,且像素连接到一个边缘像素,该像素保留。
总结,canny设置高低阈值,会保留高于高阈值的像素点以及像素点周围一层像素点。

1.4 OpenCV sobel与canny函数

OpenCV有sobel算子与canny算子,这为我们进行边缘检测操作提供了便利。

sobel(InputArray src,OutArray dst,int ddepth,int dx,int dy,int ksize=3,double scale=1,double delta=0,int borderType=BORDER_DEFAULT)
(1)src、dst为Mat型的输入图像与输出图像
(2)ddepth为输出图像的深度,可理解为图像的位深。
(3)dx、dy为x和y方向上的差分阶数。
(4)ksize为Sobel核的大小,必须取奇数。
(5)sacle缩放因子,delta默认0,borderType边界模式。

canny(InputArray image,OutputArray edges,double threshold1,double threshold2,int apertyewSize=3,bool L2gradient=false)
(1)image、edges为Mat型的输入图像与输出图像
(2)threshold1、threshold2为高低阈值。
(3)apertyewSize表示应用Sobel算子孔径。
(4)L2gradient为计算图像梯度的标识。

1.5 OpenCV findContours函数

在后面的示例中,会使用到findContours()函数,因此先行讲解。在OpenCV,该函数从二值函数中查找轮廓。

findContours(InputArray image,OutputArray contours,OutputArray hierarchy,int mode,int method,Point offset)
(1)image为输入图像,图像的非零像素被视为1,0像素被保留为0,因此图像为二进制图像。
(2)contours为检测的轮廓,每个轮廓存储为一个点向量,用Point类型的vector表示。
(3)hierarchy包含图像的拓扑信息,每个轮廓contour[i]对应四个hierarchy元素hierarchy[i][0]-hierarchy[i][3],分别表示后一个轮廓,前一个轮廓,父轮廓,内嵌轮廓的索引编号。
(4)mode为轮廓检索模式,RETR_EXTERNAL为只检测最外层轮廓,RETR_LIST为检测所有轮廓并不建立等级关系,RETR_CCOMP检测所有轮廓并组织为双层结构,RETR_TREE检测所有轮廓并建立网状的轮廓结构。
(5)method为轮廓近似方法,CHAIN_APPROX_NONE获取每个轮廓的每个像素,CHAIN_APPROX_SIMPLE压缩水平和垂直方向,只保存方向的终点坐标。

二、边缘匹配算法

本文的边缘匹配算法思想源于印度小哥写的开源项目。
Edge Based Template Matching
当然,如果觉得印度小哥的代码不容易理解,这边建议参考。
edgebased_template_matching.cpp
本文参考的代码主要为edgebased_template_matching.cpp。
开源代码存在一些坑,已经成功调试与修改。
下面对代码进行详解。

2.1 图像读入

代码如下:

	Mat src = imread("D:/images/search2.jpg");Mat tpl = imread("D:/images/template.jpg");if (src.empty() || tpl.empty()) {printf("could not load images...\n");return -1;}namedWindow("source", WINDOW_AUTOSIZE);namedWindow("template", WINDOW_AUTOSIZE);imshow("source", src);imshow("template", tpl);

原图像和模板图像的读取,并分别显示。

2.2 模板轮廓点提取

代码如下:

	Mat gray, binary;cvtColor(tpl, gray, COLOR_BGR2GRAY);Canny(gray, binary, 100, 800);vector<vector<Point>> contours;vector<Vec4i> hireachy;findContours(binary, contours, hireachy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(-1, -1));

cvtColor将原图像由彩图转化为灰度图;
Canny对图像进行边缘检测,将图像变为0-1图像。
findContours找出模板图像的轮廓点。

2.3 计算模板轮廓的dx,dy,mag,log

代码如下:

	Mat gx, gy;Sobel(gray, gx, CV_32F, 1, 0);Sobel(gray, gy, CV_32F, 0, 1);Mat magnitude, direction;cartToPolar(gx, gy, magnitude, direction);long contoursLength = 0;double magnitudeTemp = 0;int originx = contours[0][0].x;int originy = contours[0][0].y;// 提取dx\dy\mag\log信息vector<vector<ptin>> contoursInfo;// 提取相对坐标位置vector<vector<Point>> contoursRelative;// 开始提取for (int i = 0; i < contours.size(); i++) {int n = contours[i].size();contoursLength += n;contoursInfo.push_back(vector<ptin>(n));vector<Point> points(n);for (int j = 0; j < n; j++) {int x = contours[i][j].x;int y = contours[i][j].y;points[j].x = x - originx;points[j].y = y - originy;ptin pointInfo;pointInfo.DerivativeX = gx.at<float>(y, x);pointInfo.DerivativeY = gy.at<float>(y, x);magnitudeTemp = magnitude.at<float>(y, x);pointInfo.Magnitude = magnitudeTemp;if (magnitudeTemp != 0)pointInfo.MagnitudeN = 1 / magnitudeTemp;contoursInfo[i][j] = pointInfo;}contoursRelative.push_back(points);}

这一部分比较冗长,主要内容为计算模板图像每个轮廓点的x和y方向的导数以及幅度与相位。

2.4 计算原图像的dx,dy,mag,log

代码如下:

// 计算目标图像梯度Mat grayImage;cvtColor(src, grayImage, COLOR_BGR2GRAY);Mat gradx, grady;Sobel(grayImage, gradx, CV_32F, 1, 0);Sobel(grayImage, grady, CV_32F, 0, 1);Mat mag, angle;cartToPolar(gradx, grady, mag, angle);

计算原图像的每个像素点的x和y方向的导数以及幅度与相位。

2.5 最优匹配点寻找

代码如下:

	long totalLength = contoursLength;double nMinScore = minScore / totalLength; // normalized min scoredouble nGreediness = (1 - greediness * minScore) / (1 - greediness) / totalLength;double partialScore = 0;double resultScore = 0;int resultX = 0;int resultY = 0;double start = (double)getTickCount();for (int row = 0; row < grayImage.rows; row++) {for (int col = 0; col < grayImage.cols; col++) {double sum = 0;long num = 0;for (int m = 0; m < contoursRelative.size(); m++) {for (int n = 0; n < contoursRelative[m].size(); n++) {num += 1;int curX = col + contoursRelative[m][n].x;int curY = row + contoursRelative[m][n].y;if (curX < 0 || curY < 0 || curX > grayImage.cols - 1 || curY > grayImage.rows - 1) {continue;}// 目标边缘梯度double sdx = gradx.at<float>(curY, curX);double sdy = grady.at<float>(curY, curX);// 模板边缘梯度double tdx = contoursInfo[m][n].DerivativeX;double tdy = contoursInfo[m][n].DerivativeY;// 计算匹配if ((sdy != 0 || sdx != 0) && (tdx != 0 || tdy != 0)){double nMagnitude = mag.at<float>(curY, curX);if (nMagnitude != 0)sum += (sdx * tdx + sdy * tdy) * contoursInfo[m][n].MagnitudeN / nMagnitude;}// 任意节点score之和必须大于最小阈值partialScore = sum / num;if (partialScore < min((minScore - 1) + (nGreediness * num), nMinScore * num))break;}}// 保存匹配起始点if (partialScore > resultScore){resultScore = partialScore;resultX = col;resultY = row;}}}

这一部分是算法思想实现的核心部分,resultX与resultY用来保存最优匹配点的位置,resultScore用来保存最优匹配点的匹配分数。可以看到,该部分套用了四层循环,时间复杂度为原图像像素点数量与模板图像轮廓点数量的乘积。尽管代码设置了内循环的停止条件,这为代码运行速度带来了些许优化,但当原图像大小较长时,算法的运行时间依然很久。例如在我的场景下,原图像为2048*2048格式,算法运行时间为50s,在后一章结合图像金字塔进行时间的优化。

2.6 Bug调试

源代码存在一些Bug,已经解决并总结如下。

(1)canny参数设置不当:

Canny(gray, binary, 100, 800);

canny函数高阈值设定不当,会导致模板图像找不到轮廓点。建议如下修改:

Canny(gray, binary, 100, 200);

(2)模板轮廓点坐标为负数:
在特定场景,会出现轮廓点x或y坐标出现负数的情况,这会导致后续轮廓点作为数组索引坐标的时候,导致超出数组索引下限。

for (int i = 0; i < contours.size(); i++) {int n = contours[i].size();contoursLength += n;contoursInfo.push_back(vector<ptin>(n));vector<Point> points(n);for (int j = 0; j < n; j++) {int x = contours[i][j].x;int y = contours[i][j].y;points[j].x = x - originx;points[j].y = y - originy;ptin pointInfo;pointInfo.DerivativeX = gx.at<float>(y, x);pointInfo.DerivativeY = gy.at<float>(y, x);magnitudeTemp = magnitude.at<float>(y, x);pointInfo.Magnitude = magnitudeTemp;if (magnitudeTemp != 0)pointInfo.MagnitudeN = 1 / magnitudeTemp;contoursInfo[i][j] = pointInfo;}contoursRelative.push_back(points);}

解决如下:

for (int i = 0; i < contours.size(); i++) {int n = contours[i].size();contoursLength += n;contoursInfo.push_back(vector<ptin>(n));vector<Point> points(n);for (int j = 0; j < n; j++) {int x = contours[i][j].x;int y = contours[i][j].y;if (x < 0)x = 0;if (y < 0)y = 0;points[j].x = x - originx;points[j].y = y - originy;ptin pointInfo;pointInfo.DerivativeX = gx.at<float>(y, x);pointInfo.DerivativeY = gy.at<float>(y, x);magnitudeTemp = magnitude.at<float>(y, x);pointInfo.Magnitude = magnitudeTemp;if (magnitudeTemp != 0)pointInfo.MagnitudeN = 1 / magnitudeTemp;contoursInfo[i][j] = pointInfo;}contoursRelative.push_back(points);}

2.7 算法结果展示

在经过Bug调试后,在我的场景中,代码可以正常运行。输入模板图像与目标图像,匹配结果如下。
在这里插入图片描述
根据匹配结果可知,方形边框为结果匹配区域,不难发现,基于边缘梯度的模板匹配在匹配结果上表现良好。其实,可以在不同亮度值进行算法的性能测试,并与基于灰度值的模板匹配进行效率与精度的对比。这边缺乏用于测试的图像数据样本,所以考虑在后续学习中进行综合对比。

三、图像金字塔

上文提到,示例图像为2048*2048时,算法运行时间大概为50s。这是由于算法的时间复杂度为目标图像像素点*模板图像轮廓点,当目标图像像素点个数较高,往往也会导致模板图像的轮廓信息越丰富,因此当目标图像被压缩后,算法速度会得到显著提升。因此,本文考虑结合图像金字塔对算法时间进行优化,首先会介绍图像金字塔的基本原理,然后介绍算法实现与时间优化的结果。

3.1 图像金字塔基本原理

图像金字塔是图像中多尺度表达的一种,主要用于图像的分割。在应用中,图像金字塔最初用于机器视觉和图像压缩,一幅图像的金字塔是一系列以金字塔形状排列的,分辨率逐步降低且来源于同一张原始图的图像集合。金字塔的最底部是原始图像,顶部是低分辨率的近似。
图像金字塔包括高斯金字塔与拉普拉斯金字塔,前者用来向下采样图像,后者用来向上采样,可以对图像进行最大程度的还原,即重建图像。高斯金字塔是通过高斯平滑与亚采样获得一些下采样图像,也就是说第K层高斯金字塔通过平滑、亚采样就可以获得K+1层高斯图像。金字塔的图像如图所示。
在这里插入图片描述

3.2 OpenCV pyrDown函数

要从金字塔第i层生成第i+1层,我们先用高斯核进行卷积,然后删除所有的偶数行和偶数列,新得到图像面积会变为源图像的四分之一。对于高斯金字塔,当图像向金字塔的上层移动时,尺寸和分辨率会降低。在OpenCV中,可以通过pyrDown函数进行操作。
pyrDown函数的作用是向下采样并模糊一张图片。
pyrDown(InputArray src,OutputArray dst,const Size&dstSize=Size(),int borderType=BORDER_DEFAULT)
(1)src、dst为Mat型的输入图像与输出图像
(2)const Size&dstSize为输出图像的大小,有默认值Size(),默认情况下,Size((src.cols+1)/2,(src.rows+1)/2)来进行计算。

3.3 高斯金字塔具体实现

基于高斯金字塔,我们使用分层搜索策略
(1)首先我们根据模板图像与目标图像,计算出需要使用的图像金字塔层数。金字塔层数设置,需要保证最上层目标物体的相关结构必须可辨别出
(2)然后在最高层金字塔进行一次完整的匹配。高斯金字塔每增加一层,图像点数与模板点数都会减少4倍,每增加一层可以提速16倍,例如在金字塔第四层进行一次完整的匹配,计算次数与原始图像减少了4096倍。
(3)在高层搜索到的匹配结果映射到金字塔的下一层中,直接将匹配得到的匹配点坐标乘2。在下一层的搜索区域定为匹配结果周围的一个区域,一般为5乘5或者7乘7的矩阵区域。
(4)这个过程,直到找不到匹配对象或者到金字塔的最底层结束。

高斯金字塔函数如下所示:
输入源图像与金字塔层数,输出下采样结果图像。

cv::Mat paramidGaussImage(cv::Mat src, int paramidNums)
{int paramidCount = paramidNums;cv::Mat srcPre = src;cv::Mat srcTemp;while (paramidCount > 1){cv::pyrDown(srcPre, srcTemp, cv::Size(srcPre.cols / 2, srcPre.rows / 2));srcPre = srcTemp;paramidCount--;}return srcPre;
}

搜索策略实现:
findMatchingPosition输入为当前层数的ROI区域,设置为7乘7的矩阵区域。
findMatchingPosition输出为匹配位置。

	int row_min = 0;int	row_max = src.rows;int col_min = 0;int col_max = src.cols;int resultX = 0, resultY = 0;cv::Mat srcPre, srcTemp, tplPre, tplTemp;for (int paramidPre = paramidNums; paramidPre >= 1; paramidPre--){cv::Mat srcPre, tplPre;srcPre = paramidGaussImage(src, paramidPre);tplPre = paramidGaussImage(tpl, paramidPre);if (paramidPre - paramidNums == 0){row_min = 0;row_max = srcPre.rows;col_min = 0;col_max = srcPre.cols;}paramidPoint = findMatchingPosition(srcPre, tplPre, row_min, row_max, col_min, col_max);resultX = paramidPoint.x;resultY = paramidPoint.y;row_min = resultY * 2 - 3;row_max = resultY * 2 + 4;col_min = resultX * 2 - 3;col_max = resultX * 2 + 4;}

3.4 图像金字塔优化结果

在本场景中,设置高斯金字塔层数为4层,ROI区域为7乘7。在源图像进行模板匹配的运行时间为50s,经过高斯金字塔的时间优化,结果如下所示。
在这里插入图片描述
经过高斯金字塔的时间优化,算法运行速度优化到1s,尽管优化效果显著,但是1s的运行时间在大部分工业场景是不能接受的。因此,后续会进一步进行时间的优化。


参考文献:
《机器视觉算法及应用 Machine vision algorithm and Application》
《OpenCV3编程入门》 毛星云 著


http://chatgpt.dhexx.cn/article/VhEiSUL6.shtml

相关文章

Struts2的基本流程的详细介绍

Struts2的基本流程 10级学员 张帅鹏课堂笔记 概述&#xff1a; Struts2框架由三部分构成&#xff1a;核心控制器、业务控制器和用户实现的业务逻辑组件。在这三部分中&#xff0c;struts2框架提供了核心控制器StrutsPrepareAndExecuteFilter&#xff0c;而用户需要实现业务控…

jQuery插件autoComplete介绍(10级学员 张帅鹏总结)

jQuery插件autoComplete介绍 概述&#xff1a;AutoComplete为自动填充&#xff0c;展示之意。用户在使用文本框搜索信息时&#xff0c;使用插件的autoplete方法绑定文本框。当在文本框中输入某个字符时&#xff0c;通过该方法中的指定的数据URL&#xff0c;返回相匹配的数据&a…

C语言 编写Vector方法

Vector是一个单口进出的数组结构有一点像栈的结构。 首先建立这样一个结构体&#xff0c;里面包含一个数组以及一个位置标记&#xff0c;数组来存放进入的元素&#xff0c;标记指向尾部最后一个没有存放东西的数组位置。当然数组元素可以自定义为任何格式&#xff0c;甚至也可…

多组数据的输入方法(c语言实现)

先说方法之前先来浅聊一下scanf 开始进入正题 1.EOF法 EOF(end of file)就是文件的结束&#xff0c;通常来判断文件的操作是否结束的标志。 EOF不是特殊字符&#xff0c;而是定义在头文件<stdio.h>的常量&#xff0c;等于-1&#xff1b; 就如牛客网上的一道题为例&…

c语言:数组插入处理

规定输入9个元素&#xff0c;排序后&#xff0c;再输入一个数要求按原来排序的规律将它插入数组中。 初始化数组函数&#xff1a; void assign_value_to_array(int val[])//数组赋值 {int star;//数组开始位printf("请输入%d个数&#xff1a;",num);for(star0;star&…

电子测量——用C语言设计测量数据误差处理的通用程序

题目要求 参考例2-2-6的解题过程&#xff0c;用C语言或MATLAB设计测量数据误差处理的通用程序&#xff0c;要求如下&#xff1a; &#xff08;1&#xff09;提供测试数据输入、粗大误差判别准则选择等的人机界面&#xff1b; &#xff08;2&#xff09;编写程序使用说明&#…

c语言区简单数据类型,c语言简单数据类型有哪些

c语言中简单的数据类型分别为&#xff1a;1、整型【int、short、long、long long】&#xff1b;2、浮点型【float&#xff0c;double】&#xff1b;3、字符型【char】。 c语言中简单的数据类型分别为&#xff1a; 1、整(数)型int&#xff1a;基本整数型&#xff0c;用于存储整数…

C语言-基本数据类型

C语言中有3种基本数据类型&#xff0c;分别是整型、字符型和实型&#xff08;浮点型&#xff09;&#xff0c;下表列出的是32位平台数据类型的长度及其取值范围 类别名称类型名数据长度取值范围整型[有符号]整型[signed] int32位-2147483648~2147483647(-2^31 ~ 2^31-1)[有符号…

C语言处理excel

思路来源&#xff1a;https://blog.csdn.net/hongzhen91/article/details/57422897 目录 1 写2 读先看 代码&#xff01;结果分析可知 1 写 .csv 是 excel 后缀&#xff0c;跳跃间隔符是 ‘,’ FILE *fp ;fp fopen("./test.csv","w") ; // 写 for (i0 ;…

C语言基础——数据运算

一 运算基础 运算的本质是根据已有数据&#xff0c;进行各种运算处理&#xff0c;得到新的数据。所以&#xff0c;运算的基础就是数据。在数据的表示方法中&#xff0c;声明的变量只是一个空的指代&#xff0c;并没有和具体的数据进行关联。如果要使用变量&#xff0c;就要把数…

C语言数据溢出

文章目录 int 类型char int 类型 unsigned int num -1;printf("%u", num);解析&#xff1a; 首先在寄存器产生一个负数-1&#xff0c;它在计算机中存储的补码是 32个1&#xff0c;共32个二进制位&#xff0c;当按照赋值号赋给无符号num变量时&#xff0c;符号位变成…

C语言 数据处理

在不使用scanf函数如何对输入的数进行逆序输出呢&#xff1f;首先要得到该数的位数&#xff0c;因为需要在顺序输出里使用。 代码如下: int Getfigure(int n) {int tmp0; //计算循环次数while(n ! 0){n / 10;tmp;}return tmp; //该数的位数 } 其次要清楚该处理…

【Keil 5安装教程】

文章目录 一、安装mdk二、激活mdk三、安装STM32芯片包四、安装C51单片机五、激活C51单片机 一、安装mdk 1、在解压的安装包里&#xff0c;点击运行mdk514.exe文件&#xff1b; 2、在弹出的页面中点击NEXT&#xff1b; 3、打勾I Agree&#xff0c;点击Next&#xff1b; 4、在…

keil3 安装教程

安装前先关闭杀毒软件和360卫士&#xff0c;注意安装路径不能有中文&#xff0c;安装包路径也不要有中文。 1.选中【Keiluvision 3 C51版】压缩包&#xff0c;鼠标右击选择【解压到Keil uvision 3 C51版】。 2.双击打开【Keiluvision 3 C51版】文件夹。 3.选中【KEILc51v802…

keil安装指导

keil arm 5.36下载地址 C51 960a下载地址 注册码 这个自己搜索下载 安装指导 注意&#xff1a; 如果你想C51和ARM一起安装&#xff0c;那就先安装C51再ARM版本的。如果你已经先安装完了ARM的也没关系&#xff0c;安装完C51的再重新安装下ARM的就可以 安装C51 双击你下…

c语言定义函数时形参定义的位置

今天在读Zlib库源码中第三方contrib/minizip文件时&#xff0c;发现一个有意思的东西&#xff1a; 定义函数时&#xff0c;其形参的类型放在函数()后面&#xff0c;()里只放了形参名 虽然这样最终用编译器&#xff08;如gcc等&#xff09;编译时是可以通过的&#xff0c;但是…

C语言函数(函数分类,参数,调用,声名及定义)

文章目录 [TOC](文章目录)一、C语言中函数的分类二、函数的参数及调用三、函数的嵌套调用和链式访问四、函数的声名和定义 一、C语言中函数的分类 库函数自定义函数 库函数&#xff1a;在开发的过程中&#xff0c;每个程序员都可能用得到&#xff0c;为了支持可移植性和提高程…

c语言函数定义的语法格式,C语言函数 -C语言函数定义的语法格式

C语言程序是由多个零件组合而成的&#xff0c;而函数就是最主要的组合零件。C语言程序的主体部分就是一个 main( ) 函数&#xff0c;它在 C语言程序中是必不可少的&#xff0c;每一个 C 程序都首先从 main( ) 函数开始执行。 C语言函数是什么 在前面的学习中&#xff0c;我们知…

c语言函数定义范围,c语言定义函数

函数是一段可以重复使用的代码&#xff0c;用来独立地完成某个功能&#xff0c;它可以接收用户传递的数据&#xff0c;也可以不接收。接收用户数据的函数在定义时要指明参数&#xff0c;不接收用户数据的不需要指明&#xff0c;根据这一点可以将函数分为有参函数和无参函数。 将…

C语言-函数(一):函数的定义和调用

函数是一个完成特定工作的独立程序模块&#xff0c;包括库函数和自定义函数两种。例如&#xff0c;scanf()、printf()等这些都为库函数&#xff0c;是由C语言系统提供定义&#xff0c;编程时直接调用即可&#xff1b;还有一种是自己定义的函数&#xff0c;我们主要介绍的就是这…