掌握C语言:60个编程技巧实例深度解析

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《C语言编程技巧实例60篇》书籍深入讲解了C语言的核心概念,通过60个精心挑选的实例覆盖了语法、控制结构、函数、指针、内存管理等关键编程领域。每个实例旨在提升编程技巧,并帮助读者更好地解决编程难题。本书不仅讲解基础知识,还探讨了进阶主题,如文件操作、错误处理、预处理器和头文件使用,以及高级编程技术,为读者构建从初学到精通的完整学习路径。 C语言编程技巧实例60篇

1. C语言基础语法应用实例

简介

C语言是一种广泛使用的编程语言,它以其高效和灵活著称。本章将通过实例展示C语言的基本语法,为编程初学者以及希望加深对C语言理解的读者提供实际应用的参考。

基本元素

C语言的程序由变量、常量、运算符、表达式、语句和函数等基本元素组成。例如,变量定义、类型转换、算术运算和输入输出都是构成C语言程序的基础。

#include <stdio.h>

int main() {
    int a = 10; // 变量定义与初始化
    float b = 20.5; // 浮点型变量
    double c = 30.25; // 双精度浮点型变量

    // 类型转换
    int result = (int)(b + c); // 将b和c的和转换为整型

    // 算术运算
    printf("a + b + c = %d\n", a + result); // 输出结果

    // 输入输出
    int input;
    printf("Enter a number: ");
    scanf("%d", &input);
    printf("You entered: %d\n", input);

    return 0;
}

以上代码展示了C语言中变量定义、基本运算和输入输出的用法。学习这些基础语法对于掌握C语言至关重要。

2. 控制结构运用实例

2.1 条件控制结构应用

条件控制结构允许程序根据条件执行不同的代码块。在C语言中, if-else switch-case 是最常用的条件控制结构。

2.1.1 if-else条件判断

if-else 语句是进行条件判断的最基础结构,它允许程序根据布尔条件的真假执行不同的代码路径。

#include <stdio.h>

int main() {
    int a = 5;
    if (a > 0) {
        printf("a is positive\n");
    } else if (a == 0) {
        printf("a is zero\n");
    } else {
        printf("a is negative\n");
    }
    return 0;
}

在上述代码中,变量 a 首先被赋予一个整数值 5 。然后,程序检查 a 是否大于 0 。如果是,输出 a is positive 。如果 a 等于 0 ,输出 a is zero 。如果 a 小于 0 ,则执行 else 部分并输出 a is negative 。这样的条件判断让程序能够处理多种不同的输入情况,使程序更加健壮和灵活。

2.1.2 switch-case多分支选择

switch-case 语句提供了一个多分支选择的控制结构,当需要基于变量的值来执行不同的代码块时非常有用。

#include <stdio.h>

int main() {
    int num = 2;
    switch (num) {
        case 1:
            printf("num is 1\n");
            break;
        case 2:
            printf("num is 2\n");
            break;
        default:
            printf("num is not 1 or 2\n");
    }
    return 0;
}

在这个例子中,变量 num 被赋予 2 switch 语句根据 num 的值选择执行 case 2: 分支中的代码。每个 case 后面跟着一个值,并执行与之匹配的代码块。如果 num 的值不匹配任何 case ,则执行 default 分支中的代码。 break 语句用于防止代码继续执行到下一个 case

switch-case 结构比多个 if-else 语句的可读性更好,尤其在处理多值分支时更为清晰。

2.2 循环控制结构应用

2.2.1 for循环的经典应用

for 循环是最常用的循环结构之一,它包含初始化、条件和更新表达式三个部分。

#include <stdio.h>

int main() {
    for (int i = 0; i < 5; i++) {
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}

在这个代码示例中, for 循环初始化 i 0 ,然后检查 i 是否小于 5 ,如果是,则进入循环体,并在每次循环结束时将 i 增加 1 。循环体中的代码打印出 i 的值,直到 i 等于 5 ,此时循环结束。输出结果为 0 1 2 3 4 for 循环使我们能够重复执行代码块固定次数。

2.2.2 while与do-while循环的对比实例

while 循环和 do-while 循环都是基于条件的循环控制结构,但它们在执行逻辑上有细微的差别。

#include <stdio.h>

int main() {
    int i = 0;
    while (i < 5) {
        printf("%d ", i);
        i++;
    }
    printf("\n");

    i = 0;
    do {
        printf("%d ", i);
        i++;
    } while (i < 5);
    printf("\n");
    return 0;
}

在这两个例子中, while 循环首先检查条件 i < 5 ,如果为真,则执行循环体。而 do-while 循环至少执行一次循环体,然后检查条件。两种循环都打印 i 的值,直到 i 等于 5 。输出结果是相同的 0 1 2 3 4 。选择 while do-while 取决于特定场景的需求。

2.2.3 循环嵌套的高级技巧

当循环体内包含另一个循环时,我们称之为循环嵌套。循环嵌套可以用来处理多维数据结构和算法。

#include <stdio.h>

int main() {
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= i; j++) {
            printf("*");
        }
        printf("\n");
    }
    return 0;
}

在上述例子中,外层 for 循环控制行数,内层 for 循环控制每行打印的星号数量。输出结果是:

*

这种结构非常适合处理如矩阵运算、路径搜索等算法问题。

2.3 跳转控制结构应用

2.3.1 break与continue的使用场景

break continue 关键字用于控制循环的执行流程。 break 立即终止最内层的循环,而 continue 跳过当前迭代的剩余代码并开始下一次迭代。

#include <stdio.h>

int main() {
    for (int i = 0; i < 5; i++) {
        if (i == 3) {
            break; // 当i等于3时终止循环
        }
        if (i % 2 != 0) {
            continue; // 当i为奇数时跳过本次循环的剩余部分
        }
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}

输出结果是 0 2 break 使得当 i 等于 3 时,循环立即终止, continue 使得当 i 为奇数时跳过打印操作,继续进行下一次迭代。

2.3.2 goto语句的利弊分析

goto 语句提供了一种直接跳转到程序中任意位置的能力,虽然在某些特定情况下很有用,但通常不推荐使用,因为它会导致代码难以理解和维护。

#include <stdio.h>

int main() {
    int i = 0;
    loop_start:
    if (i < 5) {
        printf("%d ", i);
        i++;
        goto loop_start; // 跳转回循环开始
    }
    return 0;
}

在上面的代码中, goto 语句用于从函数中任意位置跳转到 loop_start 标签所在的位置。尽管这个例子中的 goto 可能看起来使代码简洁了,但大多数情况下,推荐使用其他循环控制结构来代替 goto 。过多使用 goto 会让代码流程难以追踪,增加复杂性,降低代码质量。

下一章节将介绍函数定义与调用实例。

3. 函数定义与调用实例

函数是C语言中进行程序模块化的基本单位,它允许程序员将复杂的问题分解为更小的、可管理的部分。在本章中,我们将深入探讨函数定义与调用的实例,旨在帮助读者理解如何高效地使用函数来编写结构化和可重用的代码。

3.1 函数基础与参数传递

3.1.1 函数的定义与声明

函数的定义包括返回类型、函数名和参数列表,可选地还包括函数体。在C语言中,函数的定义通常遵循以下格式:

返回类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...) {
    // 函数体
}

函数声明则提供了函数的签名,包括函数的返回类型、函数名以及参数的类型,但不包括具体的实现代码。函数声明通常位于头文件(.h文件)中,使得其他源文件能够通过包含该头文件来引用函数。

例如,定义一个计算两个整数和的函数:

// 函数定义
int add(int a, int b) {
    return a + b;
}

// 函数声明
int add(int, int);

3.1.2 值传递与引用传递的区别

在C语言中,函数参数可以通过值传递或引用传递。值传递会创建函数参数的副本,函数内部对参数的修改不会影响到原始数据。而引用传递则是通过指针传递参数的地址,使得函数内部能够直接修改原始数据。

下面是一个值传递的实例:

#include <stdio.h>

void increment(int value) {
    value++;
    printf("Inside increment, value is: %d\n", value);
}

int main() {
    int num = 5;
    printf("Before calling increment, num is: %d\n", num);
    increment(num);
    printf("After calling increment, num is: %d\n", num);
    return 0;
}

输出结果:

Before calling increment, num is: 5
Inside increment, value is: 6
After calling increment, num is: 5

在这个例子中,即使 increment 函数中 value 的值被修改为6,主函数中的 num 仍然是5。

通过引用传递传递值的实例:

#include <stdio.h>

void increment(int *value) {
    (*value)++;
    printf("Inside increment, value is: %d\n", *value);
}

int main() {
    int num = 5;
    printf("Before calling increment, num is: %d\n", num);
    increment(&num);
    printf("After calling increment, num is: %d\n", num);
    return 0;
}

输出结果:

Before calling increment, num is: 5
Inside increment, value is: 6
After calling increment, num is: 6

这个例子中,通过传递 num 的地址给 increment 函数,函数内部的修改直接影响了 main 函数中的 num

3.2 函数的高级特性

3.2.1 可变参数函数的设计与实现

可变参数函数是一种特殊的函数,它能够接受不定数量的参数。在C语言中,通过 stdarg.h 库来处理可变参数。 stdarg.h 提供了一组宏,使得函数可以访问一系列未知数量和类型的参数。

下面是一个使用 stdarg.h 的示例,实现一个名为 average 的函数,该函数计算其所有参数的平均值:

#include <stdio.h>
#include <stdarg.h>

double average(int count, ...) {
    double sum = 0;
    va_list args;
    va_start(args, count); // 初始化args为可变参数的列表

    for (int i = 0; i < count; i++) {
        sum += va_arg(args, int); // 访问参数并将其加到sum上
    }

    va_end(args); // 清理,确保所有资源被释放
    return count ? sum / count : 0;
}

int main() {
    printf("Average of 1, 2, 3, 4 is %f\n", average(4, 1, 2, 3, 4));
    return 0;
}

3.2.2 递归函数的经典问题剖析

递归函数是一种在函数内部调用自身的函数。递归函数通常具有两个基本要素:基本情况和递归情况。基本情况是递归调用的终止条件,而递归情况则是函数在满足某种条件下继续调用自身。

斐波那契数列是递归函数应用的一个经典例子。下面的代码展示了如何使用递归函数计算斐波那契数列的第n项:

#include <stdio.h>

int fibonacci(int n) {
    if (n <= 1) {
        return n; // 基本情况
    } else {
        return fibonacci(n - 1) + fibonacci(n - 2); // 递归情况
    }
}

int main() {
    printf("The 10th Fibonacci number is %d\n", fibonacci(10));
    return 0;
}

然而,上述递归实现的效率非常低,因为它包含了大量重复计算。可以使用递归的优化方法,如“记忆化递归”(memoization),将已计算的值保存起来,避免重复计算,提高效率。

3.3 函数与模块化编程

3.3.1 模块化设计原则

模块化设计是现代软件开发中非常重要的一个概念,它允许开发者将程序分解为独立的模块,每个模块负责一个特定的功能。C语言中的函数是模块化设计的基础,每个函数可以视为一个模块。

模块化设计应该遵循以下几个原则:

  • 单一职责 :每个模块应该只有一个改变的理由。
  • 封装 :模块应该隐藏其内部实现的细节。
  • 解耦 :模块之间应该尽量减少依赖关系。
  • 复用 :模块应该被设计成可以被多次复用。

3.3.2 函数库的创建与使用

函数库是模块化设计的一个典型应用,它允许开发者创建可复用的代码集合。函数库可以是静态链接库,也可以是动态链接库(共享库),在不同的程序或模块中通过声明和链接来复用。

创建一个简单的函数库需要以下步骤:

  1. 定义函数和头文件 :在源文件中定义函数,并创建对应的头文件,头文件中包含函数的声明。
  2. 编译为库文件 :将源文件编译为库文件(.a文件或.so文件,取决于平台)。
  3. 使用库函数 :在其他源文件中包含对应的头文件,链接到相应的库文件。

例如,创建一个简单的数学库:

mathlib.h :

#ifndef MATHLIB_H
#define MATHLIB_H

int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);

#endif // MATHLIB_H

mathlib.c :

#include "mathlib.h"

int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }

编译库文件 (在Linux上):

gcc -c mathlib.c -o mathlib.o
ar rcs libmathlib.a mathlib.o

在其他源文件中使用库:

#include "mathlib.h"

int main() {
    printf("1 + 2 = %d\n", add(1, 2));
    printf("4 - 2 = %d\n", subtract(4, 2));
    printf("3 * 4 = %d\n", multiply(3, 4));
    printf("8 / 2 = %d\n", divide(8, 2));
    return 0;
}

使用 gcc 编译主程序,并链接数学库:

gcc -o main main.c -L. -lmathlib -lm

在以上章节中,我们通过对函数的定义、高级特性,以及模块化编程的讲解,介绍了C语言中函数的应用和重要性。函数是C语言模块化编程的核心,使得代码更易于组织、复用和维护。通过对函数的深入理解,我们可以更有效地解决编程问题,并编写出更清晰、更优雅的代码。

4. 指针操作与应用实例

4.1 指针基础概念与操作

4.1.1 指针的定义与使用

指针是C语言中的核心概念,它存储了一个变量的地址,通过这个地址可以访问存储在该地址的内存单元。指针的概念对于理解C语言的高级特性至关重要。

定义指针变量非常简单,只需在变量前加上星号(*),如以下代码所示:

int *ptr; // 定义一个指向int类型的指针变量ptr

使用指针时,必须先对其进行初始化,即赋予一个有效的内存地址,然后才能通过解引用来访问或修改这个地址上的数据。解引用是指使用指针变量前加星号(*)来访问其指向的内存地址。以下是初始化指针和解引用指针的示例代码:

int value = 10;
int *ptr = &value; // 指针ptr指向变量value的地址
printf("%d\n", *ptr); // 解引用指针,打印出变量value的值

通过上述代码,我们创建了一个指向整型变量 value 的指针 ptr ,并通过 ptr 来访问 value 的值。

4.1.2 指针与数组的关系

指针和数组在C语言中有着非常紧密的联系。数组名在大多数情况下会被当作数组首元素的地址处理。这意味着你可以使用指针来遍历数组,或者使用数组来接收指针传递的地址。

数组到指针的转换是隐式的,而指针到数组的转换则需要显式地类型转换。以下代码展示了如何通过指针访问数组:

int array[] = {1, 2, 3, 4, 5};
int *ptr = array; // 指针ptr指向数组array的第一个元素

for (int i = 0; i < 5; i++) {
    printf("%d ", *(ptr + i)); // 使用指针访问数组中的每个元素
}

在这个例子中,指针 ptr 通过数组名初始化,然后在循环中通过指针运算访问数组的每个元素。指针的移动是通过在指针地址上加上数组元素大小的偏移量来实现的。在这个例子中, ptr + i 实际上表示数组元素的地址。

4.2 指针的高级应用

4.2.1 指针与函数的互动

指针与函数的互动允许我们在函数间直接传递变量地址,并在函数内部修改调用者的变量。这种方式使得函数能够修改传入的参数,并返回多个结果。

例如,如果我们希望一个函数能够交换两个整数变量的值,我们可以使用指针传递参数:

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(&x, &y); // 传递变量x和y的地址
    printf("x = %d, y = %d\n", x, y); // 输出交换后的值
    return 0;
}

通过传递 x y 的地址给 swap 函数,函数内部通过解引用指针来交换变量的值,并在主函数中体现出来。

4.2.2 指针数组与多级指针的应用

指针数组是一种包含指针元素的数组。每个元素都是一个指针,可以指向不同类型的对象。多级指针则是指一个指向另一个指针的指针,例如, int **ptr 是一个指向 int * 的指针。

指针数组的一个典型用途是模拟字符串数组,即每个数组元素都是一个字符串(字符指针)。多级指针则常见于函数指针的声明,或者在动态内存分配中用于二级数组(数组的数组)的创建。

4.2.3 动态内存分配与指针

动态内存分配是利用指针在堆上申请内存的过程,这一过程不是在编译时确定的,而是在程序运行时由程序员控制。C语言中, malloc , calloc , 和 realloc 函数用于动态内存分配。

  • malloc 用于分配指定大小的内存块。
  • calloc 类似于 malloc ,但它会初始化分配的内存块为零。
  • realloc 用于调整之前分配的内存块大小。

以下是使用 malloc 分配内存的一个例子:

int *ptr = (int*)malloc(10 * sizeof(int));
if (ptr == NULL) {
    // 内存分配失败处理
    exit(EXIT_FAILURE);
}

// 使用ptr访问内存并进行操作
free(ptr); // 记得释放内存

在使用动态内存分配时,一定要在不再需要时使用 free 函数释放内存,避免内存泄漏。

4.3 指针与字符串操作

4.3.1 字符串的指针表示

在C语言中,字符串通常表示为字符指针( char * )。当字符指针指向一个字符串常量时,它指向的是字符串的首字符的地址。使用指针遍历字符串是C语言处理字符串的常规方法。

4.3.2 字符串操作函数的应用实例

C语言标准库提供了许多处理字符串的函数,如 strcpy , strcat , strlen , strcmp 等,它们都使用指针操作字符串。

例如, strcpy 函数用于复制一个字符串到另一个字符串,其原型如下:

char *strcpy(char *dest, const char *src);

使用 strcpy 函数需要包含头文件 <string.h> 。使用时,确保目标字符串有足够的空间来存储源字符串的内容,否则会导致溢出错误。

char str1[20] = "Hello";
char str2[] = "World";
strcpy(str1, str2); // 将str2复制到str1
printf("%s\n", str1); // 输出 "World"

以上是 strcpy 函数的应用实例。在实际应用中,对字符串操作的每一个细节都需要小心处理,以防止溢出、空指针异常等问题。

在本章节中,通过详细的代码示例与分析,指针的概念、特性以及其在数组、字符串操作中的应用均得到了深入的展示。掌握这些知识对于成为一位优秀的C语言开发者至关重要。

5. 动态内存管理与优化实例

5.1 内存分配与释放

在C语言中,动态内存管理主要涉及到 malloc calloc realloc 这几个函数,它们都定义在 <stdlib.h> 头文件中。

5.1.1 malloc、calloc、realloc的使用方法

  • malloc :动态分配一块指定大小的内存区域,用于存放任意类型的数据。返回一个指向内存首地址的指针。 c void* malloc(size_t size); 例如: c int *p = (int*)malloc(sizeof(int) * 10);

  • calloc :为多个元素分配内存,每个元素初始化为零,并返回指向内存首地址的指针。 c void* calloc(size_t num, size_t size); 例如: c int *p = (int*)calloc(10, sizeof(int));

  • realloc :重新分配之前通过 malloc calloc realloc 分配的内存块,可以增大或减小内存块的大小。 c void* realloc(void* ptr, size_t new_size); 例如: c int *p = (int*)malloc(sizeof(int) * 10); p = (int*)realloc(p, sizeof(int) * 20);

在使用这些函数时,需要检查返回值是否为 NULL ,以确保内存分配成功。

5.1.2 内存泄漏的检测与防范

内存泄漏是指程序在申请内存后,未能正确释放已不再使用的内存。防范内存泄漏的措施包括:

  • 使用智能指针,例如C++中的 std::unique_ptr
  • 在函数结束前释放内存。
  • 使用内存检测工具,如 valgrind ,来检测程序运行时的内存泄漏。

5.2 内存管理技巧

5.2.1 动态内存管理的高效模式

动态内存管理的高效模式要求避免频繁的内存分配和释放,可以采用对象池或内存池策略。这种方式预先分配一块较大的内存区域,之后根据需要从这个区域中分配和释放内存。

5.2.2 内存池的概念与应用

内存池是一种特殊的内存管理技术,常用于游戏开发和服务器编程。内存池通过预先分配一组固定大小的内存块来减少内存碎片,提高内存分配的效率。使用内存池时,当需要分配内存时,直接从内存池中获取,不需要进行系统调用,从而减少了开销。

5.3 性能优化实例

5.3.1 内存访问优化技术

内存访问优化的核心是减少缓存未命中的次数,提高数据访问效率。具体技巧包括:

  • 数据局部性原理:将频繁访问的数据放在一起,例如将相关的数据结构放在一起。
  • 循环展开:减少循环次数,减少循环控制的开销。
  • 预取数据:利用缓存预取指令,预先加载即将使用的数据到缓存中。

5.3.2 缓存友好的数据结构设计

设计数据结构时应考虑其对缓存的友好程度。例如,链表虽然是动态数据结构,但在遍历时经常导致缓存未命中,而数组由于其连续的内存空间,在遍历时能够很好地利用缓存。

一个缓存友好的数据结构示例是“缓存行感知数组”(cache-aware array),该数组的大小设计为尽量不超过缓存行的大小,以减少内存访问延迟。

缓存友好的设计可以大幅提高程序的性能,尤其是在数据密集型的算法中。通过合理安排内存访问模式和数据结构布局,可以显著提升程序的执行效率和整体性能。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《C语言编程技巧实例60篇》书籍深入讲解了C语言的核心概念,通过60个精心挑选的实例覆盖了语法、控制结构、函数、指针、内存管理等关键编程领域。每个实例旨在提升编程技巧,并帮助读者更好地解决编程难题。本书不仅讲解基础知识,还探讨了进阶主题,如文件操作、错误处理、预处理器和头文件使用,以及高级编程技术,为读者构建从初学到精通的完整学习路径。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 目标检测的定义 目标检测(Object Detection)的任务是找出图像中所有感兴趣的目标(物体),确定它们的类别和位置,是计算机视觉领域的核心问题之一。由于各类物体有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具有挑战性的问题。 目标检测任务可分为两个关键的子任务,目标定位和目标分类。首先检测图像中目标的位置(目标定位),然后给出每个目标的具体类别(目标分类)。输出结果是一个边界框(称为Bounding-box,一般形式为(x1,y1,x2,y2),表示框的左上角坐标和右下角坐标),一个置信度分数(Confidence Score),表示边界框中是否包含检测对象的概率和各个类别的概率(首先得到类别概率,经过Softmax可得到类别标签)。 1.1 Two stage方法 目前主流的基于深度学习的目标检测算法主要分为两类:Two stage和One stage。Two stage方法将目标检测过程分为两个阶段。第一个阶段是 Region Proposal 生成阶段,主要用于生成潜在的目标候选框(Bounding-box proposals)。这个阶段通常使用卷积神经网络(CNN)从输入图像中提取特征,然后通过一些技巧(如选择性搜索)来生成候选框。第二个阶段是分类和位置精修阶段,将第一个阶段生成的候选框输入到另一个 CNN 中进行分类,并根据分类结果对候选框的位置进行微调。Two stage 方法的优点是准确度较高,缺点是速度相对较慢。 常见Tow stage目标检测算法有:R-CNN系列、SPPNet等。 1.2 One stage方法 One stage方法直接利用模型提取特征值,并利用这些特征值进行目标的分类和定位,不需要生成Region Proposal。这种方法的优点是速度快,因为省略了Region Proposal生成的过程。One stage方法的缺点是准确度相对较低,因为它没有对潜在的目标进行预先筛选。 常见的One stage目标检测算法有:YOLO系列、SSD系列和RetinaNet等。 2 常见名词解释 2.1 NMS(Non-Maximum Suppression) 目标检测模型一般会给出目标的多个预测边界框,对成百上千的预测边界框都进行调整肯定是不可行的,需要对这些结果先进行一个大体的挑选。NMS称为非极大值抑制,作用是从众多预测边界框中挑选出最具代表性的结果,这样可以加快算法效率,其主要流程如下: 设定一个置信度分数阈值,将置信度分数小于阈值的直接过滤掉 将剩下框的置信度分数从大到小排序,选中值最大的框 遍历其余的框,如果和当前框的重叠面积(IOU)大于设定的阈值(一般为0.7),就将框删除(超过设定阈值,认为两个框的里面的物体属于同一个类别) 从未处理的框中继续选一个置信度分数最大的,重复上述过程,直至所有框处理完毕 2.2 IoU(Intersection over Union) 定义了两个边界框的重叠度,当预测边界框和真实边界框差异很小时,或重叠度很大时,表示模型产生的预测边界框很准确。边界框A、B的IOU计算公式为: 2.3 mAP(mean Average Precision) mAP即均值平均精度,是评估目标检测模型效果的最重要指标,这个值介于0到1之间,且越大越好。mAP是AP(Average Precision)的平均值,那么首先需要了解AP的概念。想要了解AP的概念,还要首先了解目标检测中Precision和Recall的概念。 首先我们设置置信度阈值(Confidence Threshold)和IoU阈值(一般设置为0.5,也会衡量0.75以及0.9的mAP值): 当一个预测边界框被认为是True Positive(TP)时,需要同时满足下面三个条件: Confidence Score > Confidence Threshold 预测类别匹配真实值(Ground truth)的类别 预测边界框的IoU大于设定的IoU阈值 不满足条件2或条件3,则认为是False Positive(FP)。当对应同一个真值有多个预测结果时,只有最高置信度分数的预测结果被认为是True Positive,其余被认为是False Positive。 Precision和Recall的概念如下图所示: Precision表示TP与预测边界框数量的比值 Recall表示TP与真实边界框数量的比值 改变不同的置信度阈值,可以获得多组Precision和Recall,Recall放X轴,Precision放Y轴,可以画出一个Precision-Recall曲线,简称P-R
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值