当前位置: 首页 > news >正文

第96步 深度学习图像目标检测:FCOS建模

基于WIN10的64位系统演示

一、写在前面

本期开始,我们继续学习深度学习图像目标检测系列,FCOS(Fully Convolutional One-Stage Object Detection)模型。

二、FCOS简介

FCOS(Fully Convolutional One-Stage Object Detection)是一种无锚框的目标检测方法,由 Tian et al. 在 2019 年提出。与传统的基于锚框的目标检测方法(如 Faster R-CNN 和 SSD)不同,FCOS 完全摒弃了锚框的概念,使得模型结构更为简洁和高效。

以下是 FCOS 模型的主要特点:

(1)无锚框设计:

FCOS 不使用预定义的锚框来生成候选框。相反,它直接在特征图上的每个位置进行预测。这消除了与锚框大小和形状相关的超参数,简化了模型设计。

(2)位置编码:

对于特征图上的每个位置,FCOS 不仅预测类别分数,还预测与真实边界框的四个边的距离。这四个距离值为:左、右、上、下,与目标中心的相对距离。

(3)训练时的位置限制:

为了使每个位置只对特定大小的目标负责,FCOS 在训练时为特征图的每个层级引入了一个目标大小的范围。这确保了大的物体由底层的特征图来检测,小的物体由高层的特征图来检测。

(4)中心性偏置:

由于物体的中心位置通常包含更明确的语义信息,FCOS 引入了一个中心性分支来预测每个位置是否接近物体的中心。这有助于减少检测的假阳性。

(5)简洁与高效:

由于其无锚框的设计,FCOS 的结构相对简单,计算量较小,但在多个标准数据集上的性能与其他一流的目标检测方法相当或更好。

三、数据源

来源于公共数据,文件设置如下:

大概的任务就是:用一个框框标记出MTB的位置。

四、FCOS实战

直接上代码:

import os
import random
import torch
import torchvision
from torchvision.models.detection import fcos_resnet50_fpn
from torchvision.models.detection.fcos import FCOS_ResNet50_FPN_Weights
from torchvision.transforms import functional as F
from PIL import Image
from torch.utils.data import DataLoader
import xml.etree.ElementTree as ET
import matplotlib.pyplot as plt
from torchvision import transforms
import albumentations as A
from albumentations.pytorch import ToTensorV2
import numpy as np# Function to parse XML annotations
def parse_xml(xml_path):tree = ET.parse(xml_path)root = tree.getroot()boxes = []for obj in root.findall("object"):bndbox = obj.find("bndbox")xmin = int(bndbox.find("xmin").text)ymin = int(bndbox.find("ymin").text)xmax = int(bndbox.find("xmax").text)ymax = int(bndbox.find("ymax").text)# Check if the bounding box is validif xmin < xmax and ymin < ymax:boxes.append((xmin, ymin, xmax, ymax))else:print(f"Warning: Ignored invalid box in {xml_path} - ({xmin}, {ymin}, {xmax}, {ymax})")return boxes# Function to split data into training and validation sets
def split_data(image_dir, split_ratio=0.8):all_images = [f for f in os.listdir(image_dir) if f.endswith(".jpg")]random.shuffle(all_images)split_idx = int(len(all_images) * split_ratio)train_images = all_images[:split_idx]val_images = all_images[split_idx:]return train_images, val_images# Dataset class for the Tuberculosis dataset
class TuberculosisDataset(torch.utils.data.Dataset):def __init__(self, image_dir, annotation_dir, image_list, transform=None):self.image_dir = image_dirself.annotation_dir = annotation_dirself.image_list = image_listself.transform = transformdef __len__(self):return len(self.image_list)def __getitem__(self, idx):image_path = os.path.join(self.image_dir, self.image_list[idx])image = Image.open(image_path).convert("RGB")xml_path = os.path.join(self.annotation_dir, self.image_list[idx].replace(".jpg", ".xml"))boxes = parse_xml(xml_path)# Check for empty bounding boxes and return Noneif len(boxes) == 0:return Noneboxes = torch.as_tensor(boxes, dtype=torch.float32)labels = torch.ones((len(boxes),), dtype=torch.int64)iscrowd = torch.zeros((len(boxes),), dtype=torch.int64)target = {}target["boxes"] = boxestarget["labels"] = labelstarget["image_id"] = torch.tensor([idx])target["iscrowd"] = iscrowd# Apply transformationsif self.transform:image = self.transform(image)return image, target# Define the transformations using torchvision
data_transform = torchvision.transforms.Compose([torchvision.transforms.ToTensor(),  # Convert PIL image to tensortorchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # Normalize the images
])# Adjusting the DataLoader collate function to handle None values
def collate_fn(batch):batch = list(filter(lambda x: x is not None, batch))return tuple(zip(*batch))def get_fcos_model_for_finetuning(num_classes):# Load an FCOS model with a ResNet-50-FPN backbone without pre-trained weightsmodel = fcos_resnet50_fpn(weights=None, num_classes=num_classes)return model# Function to save the model
def save_model(model, path="fcos_mtb.pth", save_full_model=False):if save_full_model:torch.save(model, path)else:torch.save(model.state_dict(), path)print(f"Model saved to {path}")# Function to compute Intersection over Union
def compute_iou(boxA, boxB):xA = max(boxA[0], boxB[0])yA = max(boxA[1], boxB[1])xB = min(boxA[2], boxB[2])yB = min(boxA[3], boxB[3])interArea = max(0, xB - xA + 1) * max(0, yB - yA + 1)boxAArea = (boxA[2] - boxA[0] + 1) * (boxA[3] - boxA[1] + 1)boxBArea = (boxB[2] - boxB[0] + 1) * (boxB[3] - boxB[1] + 1)iou = interArea / float(boxAArea + boxBArea - interArea)return iou# Adjusting the DataLoader collate function to handle None values and entirely empty batches
def collate_fn(batch):batch = list(filter(lambda x: x is not None, batch))if len(batch) == 0:# Return placeholder batch if entirely emptyreturn [torch.zeros(1, 3, 224, 224)], [{}]return tuple(zip(*batch))#Training function with modifications for collecting IoU and loss
def train_model(model, train_loader, optimizer, device, num_epochs=10):model.train()model.to(device)loss_values = []iou_values = []for epoch in range(num_epochs):epoch_loss = 0.0total_ious = 0num_boxes = 0for images, targets in train_loader:# Skip batches with placeholder dataif len(targets) == 1 and not targets[0]:continue# Skip batches with empty targetsif any(len(target["boxes"]) == 0 for target in targets):continueimages = [image.to(device) for image in images]targets = [{k: v.to(device) for k, v in t.items()} for t in targets]loss_dict = model(images, targets)losses = sum(loss for loss in loss_dict.values())optimizer.zero_grad()losses.backward()optimizer.step()epoch_loss += losses.item()# Compute IoU for evaluationwith torch.no_grad():model.eval()predictions = model(images)for i, prediction in enumerate(predictions):pred_boxes = prediction["boxes"].cpu().numpy()true_boxes = targets[i]["boxes"].cpu().numpy()for pred_box in pred_boxes:for true_box in true_boxes:iou = compute_iou(pred_box, true_box)total_ious += iounum_boxes += 1model.train()avg_loss = epoch_loss / len(train_loader)avg_iou = total_ious / num_boxes if num_boxes != 0 else 0loss_values.append(avg_loss)iou_values.append(avg_iou)print(f"Epoch {epoch+1}/{num_epochs} Loss: {avg_loss} Avg IoU: {avg_iou}")# Plotting loss and IoU valuesplt.figure(figsize=(12, 5))plt.subplot(1, 2, 1)plt.plot(loss_values, label="Training Loss")plt.title("Training Loss across Epochs")plt.xlabel("Epochs")plt.ylabel("Loss")plt.subplot(1, 2, 2)plt.plot(iou_values, label="IoU")plt.title("IoU across Epochs")plt.xlabel("Epochs")plt.ylabel("IoU")plt.show()# Save model after trainingsave_model(model)# Validation function
def validate_model(model, val_loader, device):model.eval()model.to(device)with torch.no_grad():for images, targets in val_loader:images = [image.to(device) for image in images]targets = [{k: v.to(device) for k, v in t.items()} for t in targets]model(images)# Paths to your data
image_dir = "tuberculosis-phonecamera"
annotation_dir = "tuberculosis-phonecamera"# Split data
train_images, val_images = split_data(image_dir)# Create datasets and dataloaders
train_dataset = TuberculosisDataset(image_dir, annotation_dir, train_images, transform=data_transform)
val_dataset = TuberculosisDataset(image_dir, annotation_dir, val_images, transform=data_transform)# Updated DataLoader with new collate function
train_loader = DataLoader(train_dataset, batch_size=4, shuffle=True, collate_fn=collate_fn)
val_loader = DataLoader(val_dataset, batch_size=4, shuffle=False, collate_fn=collate_fn)# Model and optimizer
model = get_fcos_model_for_finetuning(2)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)# Train and validate
train_model(model, train_loader, optimizer, device="cuda", num_epochs=100)
validate_model(model, val_loader, device="cuda")#######################################Print Metrics######################################
def calculate_metrics(predictions, ground_truths, iou_threshold=0.5):TP = 0  # True PositivesFP = 0  # False PositivesFN = 0  # False Negativestotal_iou = 0  # to calculate mean IoUfor pred, gt in zip(predictions, ground_truths):pred_boxes = pred["boxes"].cpu().numpy()gt_boxes = gt["boxes"].cpu().numpy()# Match predicted boxes to ground truth boxesfor pred_box in pred_boxes:max_iou = 0matched = Falsefor gt_box in gt_boxes:iou = compute_iou(pred_box, gt_box)if iou > max_iou:max_iou = iouif iou > iou_threshold:matched = Truetotal_iou += max_iouif matched:TP += 1else:FP += 1FN += len(gt_boxes) - TPprecision = TP / (TP + FP) if (TP + FP) != 0 else 0recall = TP / (TP + FN) if (TP + FN) != 0 else 0f1_score = (2 * precision * recall) / (precision + recall) if (precision + recall) != 0 else 0mean_iou = total_iou / (TP + FP)return precision, recall, f1_score, mean_ioudef evaluate_model(model, dataloader, device):model.eval()model.to(device)all_predictions = []all_ground_truths = []with torch.no_grad():for images, targets in dataloader:images = [image.to(device) for image in images]predictions = model(images)all_predictions.extend(predictions)all_ground_truths.extend(targets)precision, recall, f1_score, mean_iou = calculate_metrics(all_predictions, all_ground_truths)return precision, recall, f1_score, mean_ioutrain_precision, train_recall, train_f1, train_iou = evaluate_model(model, train_loader, "cuda")
val_precision, val_recall, val_f1, val_iou = evaluate_model(model, val_loader, "cuda")print("Training Set Metrics:")
print(f"Precision: {train_precision:.4f}, Recall: {train_recall:.4f}, F1 Score: {train_f1:.4f}, Mean IoU: {train_iou:.4f}")print("\nValidation Set Metrics:")
print(f"Precision: {val_precision:.4f}, Recall: {val_recall:.4f}, F1 Score: {val_f1:.4f}, Mean IoU: {val_iou:.4f}")#sheet
header = "| Metric    | Training Set | Validation Set |"
divider = "+----------+--------------+----------------+"train_metrics = f"| Precision | {train_precision:.4f}      | {val_precision:.4f}          |"
recall_metrics = f"| Recall    | {train_recall:.4f}      | {val_recall:.4f}          |"
f1_metrics = f"| F1 Score  | {train_f1:.4f}      | {val_f1:.4f}          |"
iou_metrics = f"| Mean IoU  | {train_iou:.4f}      | {val_iou:.4f}          |"print(header)
print(divider)
print(train_metrics)
print(recall_metrics)
print(f1_metrics)
print(iou_metrics)
print(divider)#######################################Train Set######################################
import numpy as np
import matplotlib.pyplot as pltdef plot_predictions_on_image(model, dataset, device, title):# Select a random image from the datasetidx = np.random.randint(5, len(dataset))image, target = dataset[idx]img_tensor = image.clone().detach().to(device).unsqueeze(0)# Use the model to make predictionsmodel.eval()with torch.no_grad():prediction = model(img_tensor)# Inverse normalization for visualizationinv_normalize = transforms.Normalize(mean=[-0.485/0.229, -0.456/0.224, -0.406/0.225],std=[1/0.229, 1/0.224, 1/0.225])image = inv_normalize(image)image = torch.clamp(image, 0, 1)image = F.to_pil_image(image)# Plot the image with ground truth boxesplt.figure(figsize=(10, 6))plt.title(title + " with Ground Truth Boxes")plt.imshow(image)ax = plt.gca()# Draw the ground truth boxes in bluefor box in target["boxes"]:rect = plt.Rectangle((box[0], box[1]), box[2]-box[0], box[3]-box[1],fill=False, color='blue', linewidth=2)ax.add_patch(rect)plt.show()# Plot the image with predicted boxesplt.figure(figsize=(10, 6))plt.title(title + " with Predicted Boxes")plt.imshow(image)ax = plt.gca()# Draw the predicted boxes in redfor box in prediction[0]["boxes"].cpu():rect = plt.Rectangle((box[0], box[1]), box[2]-box[0], box[3]-box[1],fill=False, color='red', linewidth=2)ax.add_patch(rect)plt.show()# Call the function for a random image from the train dataset
plot_predictions_on_image(model, train_dataset, "cuda", "Selected from Training Set")#######################################Val Set####################################### Call the function for a random image from the validation dataset
plot_predictions_on_image(model, val_dataset, "cuda", "Selected from Validation Set")

这回是从头训练的,因此结果不理想:

(1)loss曲线图:

(2)性能指标:

(3)训练的图片测试结果:

(4)验证集的图片测试结果:

五、写在后面

这回没有使用预训练模型,因为在运行过程中有个问题还没解决,因此只能从头训练,但默认参数也没达到很好的效果。哪位大佬解决了告诉我一声~

相关文章:

第96步 深度学习图像目标检测:FCOS建模

基于WIN10的64位系统演示 一、写在前面 本期开始&#xff0c;我们继续学习深度学习图像目标检测系列&#xff0c;FCOS&#xff08;Fully Convolutional One-Stage Object Detection&#xff09;模型。 二、FCOS简介 FCOS&#xff08;Fully Convolutional One-Stage Object D…...

常用的git命令完整详细109条

Git是一个很强大的分布式版本控制系统&#xff0c;以下是一些常用的git命令&#xff1a; git init&#xff1a;在当前目录下创建一个新的Git仓库。git add 文件名&#xff1a;将指定的文件添加到暂存区&#xff0c;准备提交。git commit -m “备注”&#xff1a;提交暂存区的文…...

Ansible的错误处理

环境 管理节点&#xff1a;Ubuntu 22.04控制节点&#xff1a;CentOS 8Ansible&#xff1a;2.15.6 ignore_errors 使用 ignore_errors: true 来让Ansible忽略错误&#xff08;运行结果是 failed &#xff09;&#xff1a; --- - hosts: alltasks:- name: task1shell: cat /t…...

MySQL-04-InnoDB存储引擎锁和加锁分析

Latch一般称为闩锁&#xff08;轻量级锁&#xff09;&#xff0c;因为其要求锁定的时间必须非常短。在InnoDB存储引擎中&#xff0c;latch又分为mutex&#xff08;互斥量&#xff09;和rwlock&#xff08;读写锁&#xff09;。 Lock的对象是事务&#xff0c;用来锁定的是…...

tcp/ip协议2实现的插图,数据结构2 (19 - 章)

(68) 68 十九1 选路请求与消息 函rtalloc,rtalloc1,rtfree (69) 69 十九2 选路请求与消息 函rtrequest (70)...

2023.11.22 -数据仓库的概念和发展

目录 https://blog.csdn.net/m0_49956154/article/details/134320307?spm1001.2014.3001.5501 1经典传统数仓架构 2离线大数据数仓架构 3数据仓库三层 数据运营层,源数据层&#xff08;ODS&#xff09;&#xff08;Operational Data Store&#xff09; 数据仓库层&#…...

java springboot测试类虚拟MVC环境 匹配请求头指定key与预期值是否相同

上文 java springboot测试类虚拟MVC环境 匹配返回值与预期内容是否相同 (JSON数据格式) 版 中 我们展示 json匹配内容的方式 那么 本文我们来看看Content-Type属性的匹配方式 首先 我们从返回体可以看出 Content-Type 在请求头信息 Headers 中 我们直接将测试类代码更改如下 …...

Rust生态系统:探索常用的库和框架

大家好&#xff01;我是lincyang。 今天我们来探索Rust的生态系统&#xff0c;特别是其中的一些常用库和框架。 Rust生态系统虽然相比于一些更成熟的语言还在成长阶段&#xff0c;但已经有很多强大的工具和库支持各种应用的开发。 常用的Rust库和框架 Serde&#xff1a;一个…...

01-了解微服务架构的演变过程和微服务技术栈

微服务 微服务架构演变 单体架构:将业务的所有功能集中在一个项目中开发最后打成一个包部署 优点: 架构简单, 部署成本低,适合小型项目缺点: 耦合度高, 升级维护困难 分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发称为一个服务 优点: 降低服务耦合…...

阿里入局鸿蒙!鸿蒙原生应用再添两员新丁

今日HarmonyOS微博称&#xff0c;阿里钉钉、蚂蚁集团旗下的移动开发平台mPaaS与华为达成合作&#xff0c;宣布启动鸿蒙原生应用的开发&#xff01;相关应用将以原生方式适配#HarmonyOS NEXT#系统。 #HarmonyOS#市场或迎来爆发式增长&#xff01; 阿里钉钉 阿里钉钉与华为达成合…...

亚马逊运营中动态/静态住宅IP代理的应用有哪些?

作为全球最大的电商平台之一&#xff0c;亚马逊已经成为许多商家的首选销售平台。而代理IP作为近几天互联网的热门工具&#xff0c;在跨境电商界也起着非常强大的作用。那么在亚马逊运营中&#xff0c;适合动态住宅代理还是静态住宅代理呢&#xff1f;下面我们一起来探索&#…...

redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】

redis数据结构介绍 redis是一个key-value的数据库&#xff0c;key一般是String类型&#xff0c;但是value的类型多种多样。 redis 通用命令 keys : 查看符合模板的所有key &#xff08;keys partten ,匹配表达式支持一些特殊字符 * &#xff1f;&#xff09;del&#xff1a;删…...

Ubuntu 22.04.3编译AOSP13刷机

文章目录 设备信息下载AOSP并切换分支获取设备驱动编译系统编译遇到的问题Cannot allocate memoryUbuntu设置USB调试刷机参考链接 设备信息 手机&#xff1a;Pixel 4XL 下载AOSP并切换分支 在清华大学开源软件镜像站下载初始化包aosp-latest.tar。 解压缩&#xff0c;切换到…...

Navicat 技术指引 | 适用于 GaussDB 的数据迁移工具

Navicat Premium&#xff08;16.2.8 Windows版或以上&#xff09; 已支持对 GaussDB 主备版的管理和开发功能。它不仅具备轻松、便捷的可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结构同步、协同合作、数据迁移等&#xff09;&#xff0c;这…...

算法基础之表达式求值

算法基础之表达式求值 中序表达式求值 用栈 将字符和数字分别用栈存储 由下往上计算 左子树算完再算右子树 判断方法&#xff1a;当前符号优先级<前一个符号优先级 则左右子树已遍历完 #include<iostream>#include<cstring>#include<stack>#include&l…...

【matlab程序】图像最大化填充画布

【matlab程序】图像最大化填充画布 不做任何修饰&#xff1a; 修饰&#xff1a; 图片 往期推荐 图片 【python海洋专题一】查看数据nc文件的属性并输出属性到txt文件 【python海洋专题二】读取水深nc文件并水深地形图 【python海洋专题三】图像修饰之画布和坐标轴 【Pytho…...

C3 多媒体查询

文章目录 前言CSS3 多媒体查询CSS2 多媒体类型CSS3 多媒体查询浏览器支持多媒体查询语法CSS3 多媒体类型多媒体查询简单实例 媒体类型媒体功能更多实例后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;CSS &#x1f431;‍&#x1f453;博…...

网站监控是什么

在当今高度互联的世界中&#xff0c;网站已成为企业和个人成功的关键因素。无论是提供产品或服务&#xff0c;还是建立品牌形象&#xff0c;网站都是不可或缺的工具。然而&#xff0c;随着互联网用户对访问速度和用户体验的高要求&#xff0c;保持网站的稳定性和可用性变得至关…...

基于DCT变换的图像压缩解压缩算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、DCT变换原理 4.2、基于DCT的图像压缩 4.3、基于DCT的图像解压缩 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ...................…...

基于单片机压力传感器MPX4115检测-报警系统proteus仿真+源程序

一、系统方案 1、本设计采用这51单片机作为主控器。 2、MPX4115采集压力值、DS18B20采集温度值送到液晶1602显示。 3、按键设置报警值。 4、蜂鸣器报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 /*********************************…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

QT3D学习笔记——圆台、圆锥

类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体&#xff08;对象或容器&#xff09;QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质&#xff08;定义颜色、反光等&#xff09;QFirstPersonC…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

PostgreSQL——环境搭建

一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在&#xff0…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践

前言&#xff1a;本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中&#xff0c;跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南&#xff0c;你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案&#xff0c;并结合内网…...

《信号与系统》第 6 章 信号与系统的时域和频域特性

目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...