深度学习项目实例(一)—— 实时AI换脸项目

一、前言

人工智能(AI)技术的快速发展为各个领域带来了革命性的变化,其中之一就是人脸识别与图像处理技术。在这之中,AI换脸技术尤其引人注目。这种技术不仅在娱乐行业中得到广泛应用,如电影制作、视频特效等,还在社交媒体上掀起了一股风潮。AI换脸技术不仅可以实现实时的面部替换,还能够在图像和视频中生成高度逼真的换脸效果。

AI换脸技术的核心在于多种机器学习和深度学习模型的结合。它通常涉及几个关键步骤:人脸检测、人脸特征点检测、人脸对齐、换脸处理以及图像增强。每个步骤都依赖于不同的深度学习模型,以确保最终的换脸效果逼真且自然。

本项目实现了一个完整的AI换脸系统,集成了多个深度学习模型,包括YOLO人脸检测模型、68关键点检测模型、ArcFace人脸识别模型、InSwapper换脸模型以及GFPGAN人脸增强模型。通过这些模型的协同工作,我们能够从源图像中提取人脸特征,并将其无缝地替换到目标图像或视频中,生成自然的换脸效果。

接下来,我们将详细介绍这个AI换脸系统的实现细节和工作原理。通过这些介绍,读者可以深入了解AI换脸技术的实际应用和技术实现过程。

二、系统架构与工作流程

2.1 系统整体架构

在这里插入图片描述

2.1 主要模块与功能介绍(附代码)

该项目主要由5个主要模块组成,他们分别是人脸检测,人脸关键点检测,人脸对齐,换脸处理和图像增强。

2.1.1 人脸检测

首先我们需要检测源图像和目标图像中的人脸相关数据,获取图像中包含的人脸坐标,即由左上和右下坐标决定的矩阵框,对应的面部关键点和置信度分数。在该部分中所采用的检测模型是YOLOv8,它是最新一代的 YOLO(You Only Look Once)系列模型之一,专为实时目标检测任务而设计。它在精度和速度方面相比之前的模型均有显著提升,非常适用于需要快速响应的应用场景,如视频监控、自动驾驶和增强现实等。所以在实时换脸项目中,YOLOv8显然非常适合用于人脸检测。以下是具体步骤:

  1. 模型初始化
    首先设定模型的参数置信度阈值和iou阈值,之后加载YOLOv8的ONNX 模型,并设置推理会话的选项。需要在初始化中获取模型的输入名称和形状,以便后续进行图像预处理。

    def __init__(self, modelpath, conf_thres=0.5, iou_thresh=0.4):self.conf_threshold = conf_thresself.iou_threshold = iou_threshsession_option = onnxruntime.SessionOptions()session_option.log_severity_level = 3self.session = onnxruntime.InferenceSession(modelpath, sess_options=session_option)model_inputs = self.session.get_inputs()self.input_names = [model_inputs[i].name for i in range(len(model_inputs))]self.input_shape = model_inputs[0].shapeself.input_height = int(self.input_shape[2])self.input_width = int(self.input_shape[3])
    
  2. 图像预处理
    在使用YOLOv8进行推理之前需要先调整输入图像大小并进行边界填充,还需要将图像像素值归一化到 [-1, 1] 的范围,并调整通道顺序,使其符合模型的输入要求。

    def preprocess(self, srcimg):height, width = srcimg.shape[:2]temp_image = srcimg.copy()if height > self.input_height or width > self.input_width:scale = min(self.input_height / height, self.input_width / width)new_width = int(width * scale)new_height = int(height * scale)temp_image = cv2.resize(srcimg, (new_width, new_height))self.ratio_height = height / temp_image.shape[0]self.ratio_width = width / temp_image.shape[1]input_img = cv2.copyMakeBorder(temp_image, 0, self.input_height - temp_image.shape[0], 0, self.input_width - temp_image.shape[1], cv2.BORDER_CONSTANT, value=0)input_img = (input_img.astype(np.float32) - 127.5) / 128.0input_img = input_img.transpose(2, 0, 1)input_img = input_img[np.newaxis, :, :, :]return input_img
    
  3. 进行推理
    在推理过程中,首先要调用 preprocess 方法对输入图像进行预处理获得符合模型要求的输入。再使用 ONNX Runtime 进行推理,得到检测结果。之后调用 postprocess 方法(下面提到)处理输出结果。

    def detect(self, srcimg):input_tensor = self.preprocess(srcimg)outputs = self.session.run(None, {self.input_names[0]: input_tensor})[0]boxes, kpts, scores = self.postprocess(outputs)return boxes, kpts, scores
    
  4. 后处理
    在执行推理后调用后处理函数解析模型输出,获取边界框、关键点和得分。同时使用非极大值抑制(NMS)去除冗余的检测框。再根据缩放比例调整边界框和关键点的坐标。

    def postprocess(self, outputs):bounding_box_list, face_landmark5_list, score_list = [], [], []outputs = np.squeeze(outputs, axis=0).Tbounding_box_raw, score_raw, face_landmark_5_raw = np.split(outputs, [4, 5], axis=1)keep_indices = np.where(score_raw > self.conf_threshold)[0]if keep_indices.any():bounding_box_raw, face_landmark_5_raw, score_raw = bounding_box_raw[keep_indices], face_landmark_5_raw[keep_indices], score_raw[keep_indices]bboxes_wh = bounding_box_raw.copy()bboxes_wh[:, :2] = bounding_box_raw[:, :2] - 0.5 * bounding_box_raw[:, 2:]bboxes_wh *= np.array([[self.ratio_width, self.ratio_height, self.ratio_width, self.ratio_height]])face_landmark_5_raw *= np.tile(np.array([self.ratio_width, self.ratio_height, 1]), 5).reshape((1, 15))score_raw = score_raw.flatten()indices = cv2.dnn.NMSBoxes(bboxes_wh.tolist(), score_raw.tolist(), self.conf_threshold, self.iou_threshold)if isinstance(indices, np.ndarray):indices = indices.flatten()if len(indices) > 0:bounding_box_list = list(map(lambda x: np.array([x[0], x[1], x[0] + x[2], x[1] + x[3]], dtype=np.float64), bboxes_wh[indices]))score_list = list(score_raw[indices])face_landmark5_list = list(face_landmark_5_raw[indices])return bounding_box_list, face_landmark5_list, score_list
  5. 绘制检测结果
    最后将得到的边界框,关键点以及对应的置信度绘制在输入图像上,这里为了方便换脸后前后对比,把输入图像复制了一份,在该副本上进行绘制。得到的结果如下:
    在这里插入图片描述

2.1.2人脸关键点检测

这里我们来介绍一个可以识别人脸图像关键点的模型,2DFAN4 模型。该模型可以检测人脸上的68个关键点,这些关键点包括眼睛、眉毛、鼻子、嘴巴和面部轮廓等。

  1. 模型初始化:
    和上一步类似,初始化 ONNX 模型会话,设置模型路径并获取模型输入信息。
  2. 图像预处理
    计算缩放比例和平移量,使边界框居中到 256x256 的图像中。使用 warp_face_by_translation 方法进行仿射变换,返回裁剪后的图像和仿射矩阵。转置图像通道顺序,并进行归一化处理。
    def preprocess(self, srcimg, bounding_box):'''bounding_box里的数据格式是[xmin. ymin, xmax, ymax]'''scale = 195 / np.subtract(bounding_box[2:], bounding_box[:2]).max()natranslation = (256 - np.add(bounding_box[2:], bounding_box[:2]) * scale) * 0.5crop_img, affine_matrix = warp_face_by_translation(srcimg, translation, scale, (256, 256))crop_img = crop_img.transpose(2, 0, 1).astype(np.float32) / 255.0crop_img = crop_img[np.newaxis, :, :, :]return crop_img, affine_matrix
    
  3. 人脸关键点检测
    调用 preprocess 方法,得到输入张量和仿射矩阵,再使用 ONNX 模型进行推理,得到人脸的 68 个关键点。对关键点进行归一化处理,并应用逆仿射变换,将关键点坐标转换回原图像坐标系中。将 68 个关键点转换为 5 个关键点(这里其实和上面的YOLOv8实现的功能类似)。
        def detect(self, srcimg, bounding_box):'''如果直接crop+resize,最后返回的人脸关键点有偏差'''input_tensor, affine_matrix = self.preprocess(srcimg, bounding_box)face_landmark_68 = self.session.run(None, {self.input_names[0]: input_tensor})[0]face_landmark_68 = face_landmark_68[:, :, :2][0] / 64face_landmark_68 = face_landmark_68.reshape(1, -1, 2) * 256face_landmark_68 = cv2.transform(face_landmark_68, cv2.invertAffineTransform(affine_matrix))face_landmark_68 = face_landmark_68.reshape(-1, 2)face_landmark_5of68 = convert_face_landmark_68_to_5(face_landmark_68)return face_landmark_68, face_landmark_5of68
    
  4. 绘制检测结果
    最后将得到的68个人脸面部关键点绘制在输入图像上。得到的结果如下:
    在这里插入图片描述

2.1.3 人脸对齐

  1. 模型初始化
    同上一步,所有onnx模型初始化的步骤都是一样的。
  2. 图像预处理
    使用 warp_face_by_face_landmark_5 函数按人脸特征点进行裁剪和对齐。将图像像素值从原始范围 [0, 255] 转换到范围 [-1, 1]。转置图像通道顺序,使其符合模型的输入格式。
       def preprocess(self, srcimg, face_landmark_5):crop_img, _ = warp_face_by_face_landmark_5(srcimg, face_landmark_5, 'arcface_112_v2', (112, 112))crop_img = crop_img / 127.5 - 1crop_img = crop_img[:, :, ::-1].transpose(2, 0, 1).astype(np.float32)crop_img = np.expand_dims(crop_img, axis = 0)return crop_img
    
  3. 特征向量提取
    首先调用 preprocess 方法对输入图像进行预处理。使用 ONNX Runtime 进行推理,提取人脸特征向量(embedding)。对特征向量进行归一化处理,得到归一化后的特征向量(normed_embedding)。
        def detect(self, srcimg, face_landmark_5):input_tensor = self.preprocess(srcimg, face_landmark_5)# Perform inference on the imageembedding = self.session.run(None, {self.input_names[0]: input_tensor})[0]embedding = embedding.ravel()normed_embedding = embedding / np.linalg.norm(embedding)return embedding, normed_embedding
    

该模型的主要功能是通过人脸对齐来提取人脸特征向量。人脸对齐是人脸识别任务中的关键步骤,它有助于将输入的人脸图像标准化,使其在不同的拍摄角度、光照和表情变化下具有一致的表示。

2.1.4换脸处理

前面做了那么多处理,终于我们来到了关键步骤:换脸处理!此处用到的模型是inswapper_128,该模型通过将源图像中的人脸特征嵌入到目标图像中的人脸区域,实现自然逼真的换脸效果。

  1. 模型初始化
    继续同样地加载 ONNX 模型,并创建 ONNX Runtime 会话,并获取模型的输入名称和输入形状。和之前不同的是这一步需要加载模型矩阵,用于对源人脸特征向量进行变换。
    def __init__(self, modelpath):# Initialize modelsession_option = onnxruntime.SessionOptions()session_option.log_severity_level = 3self.session = onnxruntime.InferenceSession(modelpath, sess_options=session_option)model_inputs = self.session.get_inputs()self.input_names = [model_inputs[i].name for i in range(len(model_inputs))]self.input_shape = model_inputs[0].shapeself.input_height = int(self.input_shape[2])self.input_width = int(self.input_shape[3])self.model_matrix = np.load('model_matrix.npy')
    
  2. 图像处理和换脸
    • 图像预处理
      • 人脸对齐:使用 warp_face_by_face_landmark_5 函数将目标图像按人脸特征点进行裁剪和对齐。
      • 创建遮罩:使用 create_static_box_mask 创建静态盒子遮罩,方便后续将换脸结果融合回原图像。
      • 归一化处理:将图像像素值从原始范围 [0, 255] 转换到 [0, 1],并进行标准化处理,使其符合模型的输入要求。
    • 特征向量变换
      • 源人脸特征变换:将源人脸特征向量进行变换,并归一化处理,以符合模型的输入要求。
    • 模型推理
      • 换脸推理:使用 ONNX Runtime 对预处理后的图像和源人脸特征向量进行推理,得到换脸结果。
      • 结果处理:将换脸结果图像转换回原始图像格式。
    • 融合换脸结果
      • 融合处理:将换脸结果图像融合回原图像中,确保换脸区域自然逼真。
      def process(self, target_img, source_face_embedding, target_landmark_5):###preprocesscrop_img, affine_matrix = warp_face_by_face_landmark_5(target_img, target_landmark_5, 'arcface_128_v2', (128, 128))crop_mask_list = []box_mask = create_static_box_mask((crop_img.shape[1],crop_img.shape[0]), FACE_MASK_BLUR, FACE_MASK_PADDING)crop_mask_list.append(box_mask)crop_img = crop_img[:, :, ::-1].astype(np.float32) / 255.0crop_img = (crop_img - INSWAPPER_128_MODEL_MEAN) / INSWAPPER_128_MODEL_STDcrop_img = np.expand_dims(crop_img.transpose(2, 0, 1), axis = 0).astype(np.float32)source_embedding = source_face_embedding.reshape((1, -1))source_embedding = np.dot(source_embedding, self.model_matrix) / np.linalg.norm(source_embedding)###Perform inference on the imageresult = self.session.run(None, {'target':crop_img, 'source':source_embedding})[0][0]###normalize_crop_frameresult = result.transpose(1, 2, 0)result = (result * 255.0).round()result = result[:, :, ::-1]crop_mask = np.minimum.reduce(crop_mask_list).clip(0, 1)dstimg = paste_back(target_img, result, crop_mask, affine_matrix)return dstimg

2.1.5图像增强

此处采用的模型是gfpgan_1.4,用于人脸图像增强,旨在提高图像的清晰度和质量,使得换脸效果更为自然逼真。

  1. 模型初始化
    同上上一步一致。
  2. 图像处理和增强
  • 图像预处理
    • 人脸对齐:使用 warp_face_by_face_landmark_5 函数将目标图像按人脸特征点进行裁剪和对齐。
    • 创建遮罩:使用 create_static_box_mask 创建静态盒子遮罩,方便后续将增强结果融合回原图像。
    • 归一化处理:将图像像素值从原始范围 [0, 255] 转换到 [-1, 1],这有助于提高模型的性能。
  • 模型推理
    • 图像增强推理:使用 ONNX Runtime 对预处理后的图像进行推理,得到增强后的图像。
    • 结果处理:将增强后的图像从 [-1, 1] 转换回 [0, 255] 的范围,并转换为 uint8 类型。(这一步是不是量化?)
  • 融合增强结果
    • 融合处理:将增强后的图像融合回原图像中,确保增强区域自然逼真。
    def process(self, target_img, target_landmark_5):###preprocesscrop_img, affine_matrix = warp_face_by_face_landmark_5(target_img, target_landmark_5, 'ffhq_512', (512, 512))box_mask = create_static_box_mask((crop_img.shape[1],crop_img.shape[0]), FACE_MASK_BLUR, FACE_MASK_PADDING)crop_mask_list = [box_mask]crop_img = crop_img[:, :, ::-1].astype(np.float32) / 255.0crop_img = (crop_img - 0.5) / 0.5crop_img = np.expand_dims(crop_img.transpose(2, 0, 1), axis = 0).astype(np.float32)###Perform inference on the imageresult = self.session.run(None, {'input':crop_img})[0][0]###normalize_crop_frameresult = np.clip(result, -1, 1)result = (result + 1) / 2result = result.transpose(1, 2, 0)result = (result * 255.0).round()result = result.astype(np.uint8)[:, :, ::-1]crop_mask = np.minimum.reduce(crop_mask_list).clip(0, 1)paste_frame = paste_back(target_img, result, crop_mask, affine_matrix)dstimg = blend_frame(target_img, paste_frame)return dstimg
    

最终结果展示

  • 源图片
    在这里插入图片描述
  • 目标图片
    在这里插入图片描述
  • 最终结果
    在这里插入图片描述

四、结论

本项目通过使用多个先进的深度学习模型,实现了高效且逼真的AI换脸功能。首先,利用YOLOface_8n模型进行人脸检测,并通过face_68_landmarks模型获取面部68个关键点,确保了检测结果的精确性和一致性。接着,arcface_w600k_r50.onnx模型提取源人脸的高维特征向量,通过对齐和归一化处理,确保特征向量的稳定性和准确性。然后,inswapper_128.onnx模型负责将源人脸特征嵌入到目标人脸图像中,实现自然逼真的人脸替换。最后,使用gfpgan_1.4.onnx模型对换脸结果进行图像增强和修复,进一步提高图像的清晰度和细节,使最终结果更加自然逼真。本项目展示了AI换脸技术的强大潜力和广泛应用前景,为影视制作、社交媒体和隐私保护等领域提供了有力的技术支持

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

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

相关文章

目前公认最好用充电宝!四款高性价比充电宝推荐,一文看懂!

当我们在旅行途中,手机和相机等设备必不可少。长时间使用这些设备,电量很容易耗尽。此时,充电宝就能派上用场,让我们在欣赏美景、记录美好时光的同时,不再担心电量不足。特别在假期出游的时候在我们玩的特别尽兴的时候…

【C++】关于虚函数的理解

深入探索C虚函数:原理、应用与实例分析 一、虚函数的原理二、虚函数的应用三、代码实例分析四、总结 在C面向对象编程的世界里,虚函数(Virtual Function)扮演着至关重要的角色。它不仅实现了多态性这一核心特性,还使得…

红酒与珠宝:璀璨与醇香的奢华交响,双重诱惑难挡

在璀璨的灯光下,红酒与珠宝各自闪耀着迷人的光芒,它们如同夜空中的繁星,交相辉映,共同演绎着奢华的双重诱惑。今天,就让我们一起走进这个充满魅力的世界,感受红酒与珠宝带来的无尽魅力。 首先,让…

基于STM32+ESP8266打造智能家居温湿度监控系统(附源码接线图)

摘要: 本文将介绍如何使用STM32单片机、ESP8266 Wi-Fi模块和Python Flask框架构建一个完整的物联网系统,实现传感器数据采集、无线传输、云端存储及Web可视化展示。 关键词: STM32, ESP8266, 传感器, Flask, 物联网, 云平台, 数据可视化 1. 系统概述 本系统以STM…

Spring底层原理之proxyBeanMenthod实例 动态代理 反射 Bean的拦截

proxyBeanMenthod 假设我们要进行一个系统的二次开发 然后第一次开发我们实用的是XML声明bean 二次开发的时候要用注解 我们如何把bean都加载上来呢 我们首先创建一个全新的配置类 package com.bigdata1421.config;public class SpringConfig32 { } 我们创建一个APP 加载…

全球最快的 JSON 文件查询工具

本文字数:1684;估计阅读时间:5分钟 审校:庄晓东(魏庄) 本文在公众号【ClickHouseInc】首发 介绍 在 ClickHouse,我们热衷于基准测试和性能优化。所以当我第一次看到 Hacker News 上那篇“查询大…

代码随想录算法训练营day31|134.加油站、135. 分发糖果、406.根据身高重建队列

134.加油站 如下图所示&#xff1a; 当索引一道2的时候&#xff0c;剩余油量的总量13-6 < 0&#xff0c;这个时候说明以索引0为起点不合适&#xff0c;将起点更新为索引3. 两点证明&#xff1a; 1.如果我们从蓝色段中间选一个点开始&#xff0c;是不是最后sumGas就不小于0…

从灵感到成品:使用AI生成博客文章的完整指南

在信息爆炸的时代&#xff0c;每个人都有讲述自己故事的权利和需求。博客作为一种表达方式&#xff0c;不仅能记录个人经历&#xff0c;还能分享知识和观点。然而&#xff0c;许多人在写博客文章时&#xff0c;常常会遇到灵感枯竭、时间不够用或者不知道如何开始等问题。幸运的…

光伏储能系统/安科瑞DTSD1352-CF双向计量表-安科瑞 蒋静

1 长期以来&#xff0c;我国施行居民用电低价政策&#xff0c;居民电价大幅低于供电成本&#xff0c;虽然实施了全天分三时段的阶梯电价政策&#xff0c;但过去近10年中高峰节电的效果却不够明显。从分时用电运作机制来看&#xff0c;居民用电价格的波动幅度不大&#xff0c;但…

华为云鲲鹏架构docker部署2048小游戏

华为云鲲鹏架构docker部署2048小游戏 1. 鲲鹏架构ESC2. 配置docker3. 上传2048镜像4. 删除容器,镜像 1. 鲲鹏架构ESC 2. 配置docker 安装dockeryum -y install docker开机启动 systemctl enable docker启动docker服务 systemctl start docker查询docker的运行版本 docker -v3…

时序分析基本概念介绍——min pulse width 最小脉冲宽度

文章目录 前言一、什么是 min pulse width&#xff1f;二、为什么检查 min pulse width&#xff1f;三、如何设置 min pulse width约束&#xff1f;1. 在sdc里面定义2. library里面定义 四、如何检查 min pulse width&#xff1f;五、如何修复 min pulse width&#xff1f;总结…

docker启动ws-scrcpy和redroid记录

git克隆最新的ws-scrcpy代码 git clone gitgithub.com:NetrisTV/ws-scrcpy.git进入ws-scrcpy目录新建Dockerfile文件&#xff0c;内容如下 FROM node:16-alpine WORKDIR /appRUN npm config set registry http://mirrors.cloud.tencent.com/npm/ RUN npm install -g node-gyp…

攻防世界-Web题目1

目录 cookie 1、题目 2、知识点 3、思路 get_post 1、题目 2、知识点 3、思路 disabled_button 1、题目 2、知识点 3、思路 backup 1、题目 2、知识点 3、思路 cookie 1、题目 2、知识点 cookie&#xff0c;数据包 3、思路 题目提示我们cookie&#xff0c;抓…

【ai】trition:tritonclient.utils.shared_memory 仅支持linux

Can’t find tritonclient.utils.shared_memory on WIN10 #4149yolov4的python客户端 导入以后,windows 的pycharm 就是看不到折腾了很久:SaviorEnv 环境下安装tritonclient[all]也会失败 (base) C:\Users\zhangbin>conda create -n SaviorEnv python=3.8 Collecting pack…

ubuntu 18 虚拟机安装(1)

ubuntu 18 虚拟机安装 ubuntu 18.04.6 Ubuntu 18.04.6 LTS (Bionic Beaver) https://releases.ubuntu.com/bionic/ 参考&#xff1a; 设置固定IP地址 https://blog.csdn.net/wowocpp/article/details/126160428 https://www.jianshu.com/p/1d133c0dec9d ubuntu-18.04.6-l…

元数据管理的发展历程你了解吗?元数据管理要克服哪些挑战?

在当今的信息化时代&#xff0c;数据的价值已被广泛认可&#xff0c;而元数据作为描述数据的数据&#xff0c;其作用日益凸显。元数据管理&#xff0c;作为确保数据质量、促进数据共享和提高数据透明度的关键环节&#xff0c;对企业的数据战略至关重要。随着技术的发展&#xf…

C#1.0-11.0所有历史版本主要特性总结

文章目录 前言名词解释主要版本一览表各版本主要特性一句话总结 C# 1.0 (Visual Studio 2002, .Net Framework 1.0)C# 2.0 (Visual Studio 2005, .Net Framework 2.0)C# 3.0 (Visual Studio 2008, .Net Framework 3.0)C# 4.0 (Visual Studio 2010, .Net Framework 4)C# 5.0 (V…

DataWhale - 吃瓜教程学习笔记(三)

学习视频&#xff1a;第3章-对数几率回归_哔哩哔哩_bilibili 西瓜书对应章节&#xff1a; 3.3 对数几率回归 sigmoid函数 极大似然估计建模 信息论 以概率论、随机过程为基本研究工具&#xff0c;研究广义通信系统的整个过程 - 信息熵 &#xff08;信息期望&#xff09; 度…

Windows bat 提取多个目录下的文件,到一个目录

批处理命令 echo off setlocalrem 设置源目录和目标目录 set "sourceDirE:\motrix" set "targetDirE:\新建文件夹"rem 创建目标目录&#xff0c;如果不存在 if not exist "%targetDir%" mkdir "%targetDir%"rem 循环遍历源目录中的所…

TCP:TCP连接的建立与终止

TCP连接的建立与终止 建立连接第一次握手第二次握手第三次握手 终止连接第一次挥手第二次挥手第三次挥手第四次挥手 T C P是一个面向连接的协议。无论哪一方向另一方发送数据之前&#xff0c;都必须先在双方之间建立一条连接。本文将详细讨论一个T C P连接是如何建立的以及通信…