机器学习方法实现水果分类(零基础小白向),手工提取水果图片特征,不一样的特征提取方法

news2024/12/23 13:14:21

文章目录

  • 零、前言:
  • 一、什么是特征向量?
  • 二、数据准备
  • 三、构建数据集
    • 1、什么是数据集?
    • 2、图片对应的标签?
    • 3、特征提取方法
      • (1)生成固定正方形
      • (2)特征提取实现
  • 四、建立模型
  • 五、自定义数据集类
  • 六、训练模型
  • 七、输入图片进行测试
  • 八、结语

零、前言:

在本篇博客中,我们将探讨如何使用机器学习来实现水果分类,重点介绍如何手工提取水果图片特征,以及不同的特征提取方法。这对于初学者来说,了解这些概念是入门机器学习的基础。

一、什么是特征向量?

特征向量是描述数据(如图片)特征的一组数值。在水果分类中,每张图片可以转换为一个特征向量,这个向量包含了描述图片内容的数值信息。例如,颜色、形状和纹理等都可以被转化为数值,从而形成特征向量。


重要的一点是:特征向量的提取方法有多种,也就是说,一张图片,按照一种方法可以提取出来一个特征向量,按照另一种方法可以提取出来另一个特征向量,他们不冲突,只不过描述特征的角度不一样罢了


二、数据准备

数据集如下:kaggle水果分类数据集
这个数据集有100多类水果
在这里插入图片描述
方便起见,我只选择其中14中进行分类。
目录结构如下:dataset文件夹包含train,test两个文件夹
在这里插入图片描述
点开test或train,里面包含我选出来的14类水果
在这里插入图片描述
随便点开一个水果文件夹看看样子:
在这里插入图片描述

三、构建数据集

1、什么是数据集?

数据集是机器学习中用来训练和测试模型的一组数据。可以把它想象成一本书,书中包含了许多章节,每个章节都提供了不同的信息。在水果分类的例子中,数据集可能包含大量水果的图片,每张图片都带有标签(比如“苹果”、“香蕉”等)。这些图片和标签一起构成了模型学习的基础,让模型能够识别和分类不同类型的水果。数据集通常分为两个部分:训练集(用于训练模型)和测试集(用于评估模型的效果)。

2、图片对应的标签?

是的,训练模型的时候,数据集的格式为:一张图片对应一个标签,进一步说:一个特征向量对应一个标签,这就叫监督学习。
可以这么理解:训练的时候模型收到一张图片,提取它的特征向量,发现:他的特征向量长这样,模型学习了成百上千张图片之后,知道了:原来有这种特征向量的图片就是苹果,那种特征向量就是香蕉啊。

于是,一个训练好的模型诞生了,说白了,一个训练好的模型,就是一个权重文件,pytorch框架下,权重文件后缀为.pth

首先,我们需要准备我们的水果图片数据集。我们将通过以下函数获取图片路径和对应的标签:

import os
import json

def get_image_paths(root_dir):
    image_paths = []
    labels = []
    
    for fruit_dir in os.listdir(root_dir):
        fruit_path = os.path.join(root_dir, fruit_dir)
        if os.path.isdir(fruit_path):
            label = fruit_dir  # 使用文件夹名作为标签
            for img_file in os.listdir(fruit_path):
                if img_file.endswith(('png', 'jpg', 'jpeg')):
                    image_paths.append((os.path.join(fruit_path, img_file), label))
    
    return image_paths

train_dir = "dataset/train"
test_dir = "dataset/test"
train_image_paths = get_image_paths(train_dir)
test_image_paths = get_image_paths(test_dir)

3、特征提取方法

我的特征提取方法有点特殊,先来一个总体介绍:
首先随机在图片上选择两个点,以这2个点为中心画2个随机大小的正方形,分别计算正方形1和正方形2区域内的像素之和,将两者相减,若大于0,则记录为1,小于零则记录为0,选取n对这样的正方形进行计算,得到一个包含n个数字(0/1)的特征向量。也就是一个图片对应一个特征向量。
最重要的是:要记录随机选取正方形的顺序和大小,然后用同样的选取取样方法对所有图片样本进行特征提取。因为一定要用同样的选择的流程进行特征提取,图片之间才有可比性!


开始提取
我们采用固定正方形区域进行特征提取,这意味着我们将在图片上随机选择固定区域,计算这些区域的平均值,来判断它们之间的差异。

(1)生成固定正方形

import random

def generate_fixed_squares(num_pairs, image_size):
    width, height = image_size
    squares = []
    
    for _ in range(num_pairs):
        x1, y1 = random.randint(0, width-1), random.randint(0, height-1)
        x2, y2 = random.randint(0, width-1), random.randint(0, height-1)
        size1 = random.randint(1, min(width, height) // 2)
        size2 = random.randint(1, min(width, height) // 2)
        
        squares.append(((x1, y1, size1), (x2, y2, size2)))
    
    return squares

# 假设图像大小为100×100,生成100对正方形的选择方案
num_pairs =300
image_size = (100, 100)  # 假设图片大小固定为 100×100
fixed_squares = generate_fixed_squares(num_pairs, image_size)

# 保存方案到JSON文件
with open('fixed_squares.json', 'w') as f:
    json.dump(fixed_squares, f, indent=4)

(2)特征提取实现

我们将利用这些固定区域来提取特征:


import numpy as np
from PIL import Image

# 使用固定正方形提取图片的特征
def extract_features_with_fixed_squares(image, squares):
    width, height = image.size
    image = np.array(image)
    features = []
    
    for square1, square2 in squares:
        x1, y1, size1 = square1
        x2, y2, size2 = square2
        
        # 限制正方形在图片内
        square1_region = image[max(0, y1-size1//2):min(height, y1+size1//2), max(0, x1-size1//2):min(width, x1+size1//2)]
        square2_region = image[max(0, y2-size2//2):min(height, y2+size2//2), max(0, x2-size2//2):min(width, x2+size2//2)]
        
        # 计算区域的平均值并记录差异为 0 或 1
        mean1 = np.mean(square1_region)
        mean2 = np.mean(square2_region)
        features.append(1 if mean1 > mean2 else 0)
    
    return features

# 处理整个数据集,使用固定的正方形选择方案
def process_images_with_fixed_squares(image_paths, squares):
    all_features = []
    all_labels = []
    
    for image_path, label in image_paths:
        # image = Image.open(image_path).convert("L")  # 转为灰度图
        image = Image.open(image_path)  # 保留RGB
        features = extract_features_with_fixed_squares(image, squares)
        all_features.append(features)
        all_labels.append(label)
    
    return np.array(all_features), np.array(all_labels)

# 使用固定的正方形选择方案提取训练集和测试集特征
train_features, train_labels = process_images_with_fixed_squares(labeled_train_image_paths, fixed_squares)
test_features, test_labels = process_images_with_fixed_squares(labeled_test_image_paths, fixed_squares)

四、建立模型

接下来,我们将使用多层感知机(MLP)作为分类模型。MLP是一种基础的神经网络,适合处理分类问题。
model.py代码如下:

import torch.nn as nn
import torch
# 定义 MLP 模型
class MLP(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super(MLP, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_size, num_classes)
    
    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        return x

在这里,我仍然希望对上述模型代码进行逐行解释,我认为对于小白来说是有必要的,有助于更好地理解什么是机器学习,什么是神经网络。


这是一个简单的多层感知机(MLP)模型,通常用于分类任务。以下是逐行解释:

import torch
from torch.utils.data import Dataset, DataLoader
import torch.nn as nn
import torch.optim as optim
  • 这些是导入需要的库。torch是PyTorch的核心库,DatasetDataLoader用于处理数据,nn用于构建神经网络,optim用于优化算法。
class MLP(nn.Module):
  • 定义一个名为MLP的类,继承自nn.Module,表示这是一个神经网络模型。
def __init__(self, input_size, hidden_size, num_classes):
  • 初始化方法,接受三个参数:input_size(输入特征的维度)、hidden_size(隐藏层的神经元数量)、num_classes(输出类别的数量)。
super(MLP, self).__init__()
  • 调用父类的初始化方法,以确保所有父类的属性被正确初始化。
self.fc1 = nn.Linear(input_size, hidden_size)
  • 定义第一个全连接层(线性层),从输入层到隐藏层,输入特征数量为input_size,输出特征数量为hidden_size
self.relu = nn.ReLU()
  • 使用ReLU激活函数,这是一种常用的非线性激活函数,用于增加模型的表达能力。
self.fc2 = nn.Linear(hidden_size, num_classes)
  • 定义第二个全连接层,从隐藏层到输出层,输入特征数量为hidden_size,输出特征数量为num_classes,即最终的分类结果。
def forward(self, x):
  • 定义前向传播方法,接收输入数据x
out = self.fc1(x)
  • 将输入数据通过第一个全连接层,得到隐藏层的输出。
out = self.relu(out)
  • 对隐藏层的输出应用ReLU激活函数,增加非线性特性。
out = self.fc2(out)
  • 将经过激活的输出传递到第二个全连接层,得到最终的输出。
return out
  • 返回模型的输出,通常是对每个类别的得分或概率,用于进行分类决策。

这个模型通过简单的三层结构,能够学习输入特征与分类标签之间的关系,适用于简单的分类任务。之后可能会有更多层的网络。不过这个任务比较简单,三层就够了。

五、自定义数据集类

# 自定义数据集类
class FruitFeatureDataset(Dataset):
    def __init__(self, features, labels):
        self.features = features
        self.labels = labels
    
    def __len__(self):
        return len(self.features)
    
    def __getitem__(self, idx):
        return torch.tensor(self.features[idx], dtype=torch.float32), torch.tensor(self.labels[idx], dtype=torch.long)

同样逐行解释:
这是一个自定义数据集类,用于处理水果特征和标签。以下是逐行解释:

# 自定义数据集类
class FruitFeatureDataset(Dataset):
  • 定义一个名为FruitFeatureDataset的类,继承自Dataset,表示这是一个自定义数据集。
def __init__(self, features, labels):
  • 初始化方法,接受两个参数:features(特征数据)和labels(对应的标签)。
self.features = features
self.labels = labels
  • 将传入的特征和标签保存为类的属性,以便在其他方法中使用。
def __len__(self):
  • 定义一个方法,用于返回数据集的大小。
return len(self.features)
  • 返回特征数组的长度,即数据集中样本的数量。
def __getitem__(self, idx):
  • 定义一个方法,通过索引idx获取特定样本的特征和标签。
return torch.tensor(self.features[idx], dtype=torch.float32), torch.tensor(self.labels[idx], dtype=torch.long)
  • 将特征和标签转换为PyTorch张量(tensor),特征类型为float32(浮点数),标签类型为long(整数),并返回这对特征和标签。这使得数据可以被模型直接使用。

这个自定义数据集类的主要目的是方便地将特征和标签组织在一起,并提供索引访问功能,以便在训练过程中使用DataLoader进行批处理。

六、训练模型

内有每行注释


import torch
from torch.utils.data import Dataset, DataLoader
import torch.nn as nn
import torch.optim as optim
from model import MLP
# 训练模型
def train_model(model, train_loader, criterion, optimizer, num_epochs=30):
    # 遍历每个训练轮次
    for epoch in range(num_epochs):
        model.train()  # 设置模型为训练模式
        running_loss = 0.0  # 初始化当前轮次的损失为0
        # 遍历训练数据加载器
        for inputs, labels in train_loader:
            # 前向传播,获取模型输出
            outputs = model(inputs)
            # 计算损失值
            loss = criterion(outputs, labels)
            
            # 反向传播和优化
            optimizer.zero_grad()  # 清空梯度
            loss.backward()  # 计算梯度
            optimizer.step()  # 更新模型参数
            
            running_loss += loss.item()  # 累加当前批次的损失
        
        # 输出当前轮次的平均损失
        print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}")

# 测试模型
def test_model(model, test_loader):
    model.eval()  # 设置模型为评估模式
    correct = 0  # 初始化正确预测的数量
    total = 0  # 初始化总预测的数量
    with torch.no_grad():  # 不计算梯度
        # 遍历测试数据加载器
        for inputs, labels in test_loader:
            outputs = model(inputs)  # 前向传播,获取输出
            _, predicted = torch.max(outputs.data, 1)  # 获取预测的类别
            total += labels.size(0)  # 更新总样本数
            correct += (predicted == labels).sum().item()  # 统计正确预测的数量
    
    # 输出测试集的准确率
    print(f'Accuracy: {100 * correct / total:.2f}%')

# 创建 DataLoader
batch_size = 32  # 定义批次大小
train_dataset = FruitFeatureDataset(train_features, train_labels)  # 创建训练数据集
test_dataset = FruitFeatureDataset(test_features, test_labels)  # 创建测试数据集

train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)  # 创建训练数据加载器
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)  # 创建测试数据加载器

# 初始化模型参数
input_size = train_features.shape[1]  # 获取特征向量的长度
hidden_size = 256  # 定义隐藏层的神经元数量
num_classes = len(unique_labels)  # 获取输出类别的数量
print(input_size)  # 输出特征向量的长度
model = MLP(input_size, hidden_size, num_classes)  # 初始化模型

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()  # 使用交叉熵损失函数
optimizer = optim.Adam(model.parameters(), lr=0.001)  # 使用Adam优化器

# 训练和测试模型
train_model(model, train_loader, criterion, optimizer, num_epochs=40)  # 训练模型
test_model(model, test_loader)  # 测试模型

# 在训练完成后,保存模型
torch.save(model.state_dict(), "fruit_classifier_mlp.pth")  # 保存模型参数
print("哈哈哈模型已保存为 fruit_classifier_mlp.pth")  # 输出保存成功的信息

七、输入图片进行测试

test.py内容如下:

import torch
from PIL import Image
import numpy as np
import json
from model import MLP  # 导入模型定义
import os
# 定义模型参数
input_size = 100  # 示例输入尺寸
hidden_size = 256  # 示例隐藏层尺寸
num_classes = 14  # 示例类别数
# 加载模型
model = MLP(input_size, hidden_size, num_classes)
model.load_state_dict(torch.load("fruit_classifier_mlp.pth"))
model.eval()



# 读取 JSON 文件
with open('fixed_squares.json', 'r') as f:
    fixed_squares_list = json.load(f)
# 将列表转换为元组
fixed_squares = [tuple(map(tuple, pair)) for pair in fixed_squares_list]


# 使用固定正方形提取图片的特征
def extract_features_with_fixed_squares(image, squares):
    width, height = image.size
    image = np.array(image)
    features = []
    
    for square1, square2 in squares:
        x1, y1, size1 = square1
        x2, y2, size2 = square2
        
        # 限制正方形在图片内
        square1_region = image[max(0, y1-size1//2):min(height, y1+size1//2), max(0, x1-size1//2):min(width, x1+size1//2)]
        square2_region = image[max(0, y2-size2//2):min(height, y2+size2//2), max(0, x2-size2//2):min(width, x2+size2//2)]
        
        # 计算区域的平均值并记录差异为 0 或 1
        mean1 = np.mean(square1_region)
        mean2 = np.mean(square2_region)
        features.append(1 if mean1 > mean2 else 0)
    
    return features
# 定义图像预处理函数
def preprocess_image(image_path, squares):
    image = Image.open(image_path)  # 转为灰度图
    features = extract_features_with_fixed_squares(image, squares)
    return np.array(features)

# 输入一张图片进行测试
def predict_image(image_path, squares, label_map):
    features = preprocess_image(image_path, squares)
    features_tensor = torch.tensor(features, dtype=torch.float32).unsqueeze(0)  # 增加一个维度

    with torch.no_grad():
        outputs = model(features_tensor)
        _, predicted = torch.max(outputs, 1)

    # 获取预测的标签
    predicted_label = [label for label, idx in label_map.items() if idx == predicted.item()]
    return predicted_label[0] if predicted_label else None

# 批量预测函数
def predict_images(folder_path, squares, label_map):
    predictions = {}

    # 获取文件夹中的所有图片
    image_paths = [os.path.join(folder_path, filename) for filename in os.listdir(folder_path) if filename.endswith(".jpg")]

    # 预处理并预测每张图片
    for image_path in image_paths:
        features = preprocess_image(image_path, squares)
        features_tensor = torch.tensor(features, dtype=torch.float32).unsqueeze(0)  # 增加一个维度

        with torch.no_grad():
            outputs = model(features_tensor)
            _, predicted = torch.max(outputs, 1)

        # 获取预测的标签
        predicted_label = [label for label, idx in label_map.items() if idx == predicted.item()][0]
        predictions[image_path] = predicted_label

    return predictions

label_map={'Apple Braeburn 1': 0, 'Apricot 1': 1, 'Avocado 1': 2, 'Avocado ripe 1': 3, 'Banana 1': 4, 'Beetroot 1': 5, 'Blueberry 1': 6, 'Cabbage white 1': 7, 'Cactus fruit 1': 8, 'Strawberry Wedge 1': 9, 'Tamarillo 1': 10, 'Tangelo 1': 11, 'Tomato 1': 12, 'Walnut 1': 13}
# 测试

# 单个预测
# test_image_path="dataset\\test\\Walnut 1\\12_100.jpg"
# predicted_fruit = predict_image(test_image_path, fixed_squares, label_map)
# print(f"预测的水果类型: {predicted_fruit}")

#批量预测

# 测试文件夹路径
folder_path = "dataset/test/Walnut 1"

# 批量预测
predictions = predict_images(folder_path, fixed_squares, label_map)

# 输出预测结果
for image_path, predicted_fruit in predictions.items():
    print(f"文件 {image_path} 预测的水果类型: {predicted_fruit}")

八、结语

通过本文的介绍,您应该对使用机器学习进行水果分类的过程有了初步了解。从数据准备到特征提取,再到模型训练,每一步都至关重要。希望这篇博客能为您的机器学习之旅提供帮助,激励您深入探索这一领域的无限可能!
我的代码并不完美,有一些不自动化的地方,有点懒不想改了(bushi)


anyway,感谢你的点赞与关注,我们共同进步!
在这里插入图片描述

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

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

相关文章

【Python】遇见的问题:为项目选择的 Python 解释器无效

一、问题说明 导入项目文件后,提示“为项目选择的 Python 解释器无效” 二、问题原因 暂时不知道 三、解决办法 第一步:添加本地解释器 第二步:点击确定 位置:当前项目所在目录 基础解释器:python.exe所在目录 第三…

五子棋双人对战项目(2)——登录模块

目录 一、数据库模块 1、创建数据库 2、使用MyBatis连接并操作数据库 编写后端数据库代码 二、约定前后端交互接口 三、后端代码编写 文件路径如下: UserAPI: UserMapper: 四、前端代码 登录页面 login.html: 注册页面…

C++语法—引用

引用变量 概念 简单理解就是对一个已存在的变量起别名,与那个已存在的变量共用一块内存空间。 用法:已存在变量的类型 & 引用变量名 (引用实体)已存在变量 int main() {int a 1;int& b a;return 0; }在上面这个示例…

minio 快速入门+单机部署+集群

目录 原理 概念 名词解释 Set /Drive 的关系 MinIO部署 单机 单机单盘 单机多盘 集群 多机单盘 多机多盘 配置负载均衡 调优 原理 MinIO是一个S3兼容的高性能对象存储,其主要特点如下: 适合存储大容量非结构化的数据,如图片&…

骨传导耳机品牌排行榜分享:360度实测分析10款抢手骨传导耳机!

随着科技的不断进步和人们生活方式的变化,骨传导耳机以其独特的传声方式和开放式设计,逐渐成为运动爱好者、户外活动家以及听力障碍人士的新宠。不同于传统耳机将声音直接导入耳道,骨传导耳机通过振动颅骨将声音传递至内耳,不仅能…

数据结构与算法——Java实现 20.习题——二叉树层序遍历

认真的人改变自己,执着的人改变命运 —— 24.9.27 102. 二叉树的层序遍历 示例 1: 输入:root [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]]示例 2: 输入:root [1] 输出:[[1]]示例 3&…

Java单体服务和集群分布式SpringCloud微服务的理解

单体应用存在的问题 1.随着业务的发展开发变得越来越复杂。 2.修改或者新增,需要对整个系统进行测试、重新部署。 3.一个模块出现问题,很可能导致整个系统崩溃。 4.多个开发团队同时对数据进行管理,容易产生安全漏洞。 5.各个模块使用同…

Spring Session学习

系列文章目录 JavaSE基础知识、数据类型学习万年历项目代码逻辑训练习题代码逻辑训练习题方法、数组学习图书管理系统项目面向对象编程:封装、继承、多态学习封装继承多态习题常用类、包装类、异常处理机制学习集合学习IO流、多线程学习仓库管理系统JavaSE项目员工…

如何在算家云搭建MVSEP-MDX23(音频分离)

一、MVSEP-MDX23简介 模型GitHub网址:MVSEP-MDX23-music-separation-model/README.md 在 main ZFTurbo/MVSEP-MDX23-音乐分离模型 GitHub 上 在音视频领域,把已经发布的混音歌曲或者音频文件逆向分离一直是世界性的课题。音波混合的物理特性导致在没有…

快消品海外仓应该如何选择合适WMS仓储系统?

快消品的“快”属性天然契合海外仓,快消品大部分是必需品,库存周转快,需保持一定量的安全库存,及时、稳定补货尤为重要;快消品最适合全渠道销售,线上线下等,比较考验备货和统筹能力;…

基于RustDesk自建远程桌面服务

最近向日葵越来越难用了,官方好像限制了免费用户的带宽,但是限制的有点过头了,卡的基本没法用。 向日葵的平替todesk对于免费用户又有时长限制,对于经常用的小伙伴不大友好。 咱也不是说非得白嫖,但是向日葵和todesk这…

观测云链路追踪分析最佳实践

背景 如果要在开发、运维和工程层面持续改进一个涉及多服务的应用,以链路追踪、日志检索、指标收集、用户体验监测、性能剖析、关联分析等作为代表性技术的可观测性必不可少,这一看法已成为共识,但在采用这项技术的过程中,如何分…

msvcr100.dll丢失的解决方法,六种解决msvcr100.dll丢失使用技巧

在使用计算机的过程中,我们经常会遇到一些错误提示,其中之一就是“msvcr100.dll丢失”。这个问题可能会让我们感到困惑和无助,但是不用担心,本文将为大家介绍六种实用的解决方法,帮助你轻松解决这个问题。 一&#xff…

【JAVA报错已解决】Java.lang.NullPointerException

🎬 鸽芷咕:个人主页 🔥 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 专栏介绍 在软件开发和日常使用中,BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

朋友圈信息流广告投放,曝光成本是多少?

微信作为国内最流行的社交平台之一,其朋友圈广告凭借精准的用户画像和强大的社交属性,成为了众多品牌商家进行市场推广的重要渠道。云衔科技推出了专业的微信朋友圈广告开户及代运营服务,旨在帮助企业轻松跨越技术门槛,精准触达目…

【Verilog学习日常】—牛客网刷题—Verilog企业真题—VL64

时钟切换 描述 题目描述: 存在两个同步的倍频时钟clk0 clk1,已知clk0是clk1的二倍频,现在要设计一个切换电路,sel选择时候进行切换,要求没有毛刺。 信号示意图: 波形示意图: 输入描述: …

D20【python接口自动化学习】-python基础之内置数据类型

day20 内置数据类型的常见错误 学习日期:20240927 学习目标:内置数据类型--29 内置数据类型的常见错误 学习笔记: 访问错误 不同数据类型之间的操作报错 解决错误的方法 对只读类型进行写入报错 解决错误的方法 引用错误 解决错误的方法 …

产销皆下行,造势口碑遭“反噬”,魏建军能否重振长城汽车?

今年以来,长城汽车可谓多次被打在舆论聚光灯下,既有“一把手”魏建军一反此前低调务实作风而在今年多次于公共场合慨慷激昂频出“金句”宏观层面的;也有旗下多款车型销量数据出现下滑的微观层面。 近日,长城汽车披露了2024年1-8月…

python的 __name__和__doc__属性

__name__属性 __name__属性 用于判断当前模块是不是程序入口,如果当前程序正在使用,__name__的值为__main__。 在编写程序时,通常需要给每个模块添加条件语句,用于单独测试该模块的功能。 每个模块都有一个名称,当一…

ArduSub程序学习(11)--EKF实现逻辑①

1.read_AHRS() 进入EKF,路径ArduSub.cpp里面的fast_loop()里面的read_AHRS(); //从 AHRS(姿态与航向参考系统)中读取并更新与飞行器姿态有关的信息 void Sub::read_AHRS() {// Perform IMU calculations and get attitude info//----------…