C#学写了一个程序记录日志的方法(Log类)

news2024/11/30 12:04:37

1.错误和警告信息单独生产文本进行记录;

2.日志到一定内存阈值可以打包压缩,单独存储起来,修改字段MaxLogFileSizeForCompress的值即可;

3.Log类调用举例:Log.Txt(JB.信息,“日志记录内容”,"通道1");

using System;
using System.IO;
using System.Windows.Forms;
using ICSharpCode.SharpZipLib.Zip;

namespace _程序日志
{
    // 定义日志级别枚举,虽然取消了日志级别过滤功能,但保留该枚举方便后续可能的扩展
    public enum JB
    {
        错误,  //ERROR,代表程序运行过程中出现的错误情况,通常是需要重点关注和排查问题的日志级别
        警告, //WARN,用于表示程序运行时出现的一些可能会引发问题的异常情况,但不像错误那样严重,起到提醒作用
        信息, //INFO,一般记录程序正常运行过程中的一些常规信息,用于记录流程、状态等内容
        调试  //DEBUG,主要用于开发阶段,帮助开发者调试程序,输出更详细的程序执行过程信息
    }

    public class Log
    {
        // 日志文件基础路径,可通过配置等方式修改,这里指定了默认的基础路径,后续所有日志文件都会基于此路径进一步构建具体的文件路径
        private static string _logFilePath = "D:\\Logs\\程序日志";
        // 日志文件大小阈值,这里可按需调整,单位为字节,当日志文件达到这个大小后,会触发相应的文件压缩等处理逻辑
        private static long MaxLogFileSizeForCompress = 100 * 1024 * 1024;
        // 存放压缩日志文件的文件夹路径,可按需配置,用于存放经过压缩处理后的日志文件,便于节省磁盘空间以及对历史日志进行归档管理
        private static string CompressFolderPath = "D:\\Logs\\压缩日志";

        // 用于标记当前正在写入的日志文件是文本A还是文本B,初始化为文本A,通过这个标记来实现类似双缓冲的机制,方便在文件切换、压缩等操作时保证日志记录的连续性
        private static bool isUsingTextA = true;
        // 记录当前正在使用的日志文件路径(文本A或文本B),会根据程序运行过程中的实际情况动态更新,指向当前真正进行日志写入操作的文件路径
        private static string currentLogFilePath;
        // 记录文本A的文件路径,专门用于记录文本A对应的文件完整路径,方便后续针对文本A进行一些特定操作,比如检查文件大小等
        private static string textAFilePath;
        // 新增用于记录错误日志的文件路径(文本A和文本B),在发现有“错误”级别日志时,对应文本A的错误日志文件路径会存储在这里,方便后续操作
        private static string errorTextAFilePath;
        // 对应文本B的错误日志文件路径,与isUsingTextA标记配合,当切换到文本B进行日志记录时,错误日志也会相应切换写入到这个文件路径对应的文件中(如果需要的话)
        private static string errorTextBFilePath;
        // 新增用于记录警告日志的文件路径(文本A和文本B),功能类似上述错误日志文件路径,用于存放“警告”级别日志对应的文件路径
        private static string warnTextAFilePath;
        // 文本B的警告日志文件路径,用于在相应阶段存储“警告”级别日志信息
        private static string warnTextBFilePath;

        // 辅助方法:确保文件夹存在
        private static void EnsureFolderExists(string folderPath)
        {
            // 检查指定的文件夹路径对应的文件夹是否已经存在
            if (!Directory.Exists(folderPath))
            {
                try
                {
                    // 如果文件夹不存在,则尝试创建该文件夹
                    Directory.CreateDirectory(folderPath);
                }
                catch (IOException ex)
                {
                    // 如果在创建文件夹过程中出现IOException异常(比如权限不足、磁盘已满等原因导致无法创建文件夹)
                    // 这里通过消息框向用户展示创建文件夹失败的异常信息,方便用户知晓问题所在
                    MessageBox.Show($"创建文件夹 {folderPath} 失败,异常信息: {ex.Message}");
                    // 重新抛出异常,让调用这个方法的上层代码知道创建文件夹出现了问题,以便进行进一步的处理,比如终止程序或者尝试其他恢复操作
                    throw;
                }
            }
        }

        /// <summary>
        /// 保存txt文档,即实现日志记录功能,是整个日志记录模块的核心方法,接收日志级别、要保存的内容以及通道等参数来决定如何将日志信息保存到对应的文件中
        /// </summary>
        /// <param name="jb">日志级别(目前未使用级别过滤功能),通过传入不同的日志级别来区分不同重要程度的日志信息,方便后续查看和分析日志时筛选不同级别的内容</param>
        /// <param name="zhi">保存内容,即具体要记录到日志文件中的文本信息,描述了程序运行过程中发生的相关事件、状态等内容</param>
        /// <param name="tongdao">通道,可用于对日志进行分类,比如按照不同的功能模块、业务流程等划分不同的通道,便于对日志进行归类查看和管理</param>
        public static void Txt(Enum jb, string zhi, string tongdao)
        {
            string basePath = _logFilePath;
            string year = DateTime.Now.ToString("yyyy-MM");//年月日文件夹,根据当前时间获取年份和月份信息,用于构建日志文件所在的年月文件夹路径,便于按时间对日志进行分类存储
            string passageway = tongdao;//通道文件夹,使用传入的通道参数作为文件夹名称,用于进一步细分日志文件的存储位置

            // 构建日志文件所在文件夹路径,将基础路径、通道文件夹和年月文件夹路径组合起来,形成完整的日志文件所在文件夹的路径
            string logFolderPath = Path.Combine(basePath, passageway, year);
            EnsureFolderExists(logFolderPath);

            // 拼接完整日志文件路径,文件名格式保持不变(这里以简单的日期格式为例,你可按需调整),以当前日期作为文件名(格式为yyyy-MM-dd.txt),方便按天查看和管理日志文件
            string filename = DateTime.Now.ToString("yyyy-MM-dd") + ".txt";
            if (isUsingTextA)
            {
                textAFilePath = Path.Combine(logFolderPath, filename);
                currentLogFilePath = textAFilePath;
                // 同时确定错误和警告日志对应的文本A文件路径,在原文件名基础上添加相应前缀(Error_和WARN_),用于区分不同级别的专用日志文件
                errorTextAFilePath = Path.Combine(logFolderPath, "Error_" + filename);
                warnTextAFilePath = Path.Combine(logFolderPath, "WARN_" + filename);
            }
            else
            {
                currentLogFilePath = Path.Combine(logFolderPath, filename);
                // 同时确定错误和警告日志对应的文本B文件路径,同样添加相应前缀,以对应文本B的情况
                errorTextBFilePath = Path.Combine(logFolderPath, "Error_" + filename);
                warnTextBFilePath = Path.Combine(logFolderPath, "WARN_" + filename);
            }

            bool fileExists = File.Exists(currentLogFilePath);

            // 先处理文件已存在且未超过阈值的情况,直接打开并追加内容,这种情况下无需进行复杂的文件切换或压缩等操作,直接在现有文件末尾追加新的日志信息即可
            if (fileExists)
            {
                long fileSize = new FileInfo(currentLogFilePath).Length;
                if (fileSize < MaxLogFileSizeForCompress)
                {
                    WriteToExistingFile(jb, zhi, currentLogFilePath);
                    // 根据日志级别额外处理错误和警告日志写入对应的专用文件,调用该方法来处理将“错误”和“警告”级别日志分别写入对应的专用文件的操作
                    HandleSpecialLogLevel(jb, zhi);
                    return;
                }
            }

            // 如果文件不存在或者已超过阈值,执行创建文件或切换文件等逻辑,比如首次创建日志文件或者当前文件已满需要切换到新文件进行日志记录等情况
            FileStream fs = null;
            try
            {
                fs = fileExists ? File.Open(currentLogFilePath, FileMode.Append) : File.Create(currentLogFilePath);
                WriteToFileAndHandleSize(jb, zhi, fs, currentLogFilePath);
            }
            catch (IOException ex)
            {
                MessageBox.Show($"打开或创建日志文件 {currentLogFilePath} 失败,异常信息: {ex.Message}");
                throw; // 重新抛出异常,避免程序继续执行可能导致的数据不一致等问题,确保上层代码知道日志文件操作出现了异常
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();
                }
            }
        }

        /// <summary>
        /// 根据日志级别处理错误和警告日志写入对应的专用文件,该方法根据传入的日志级别判断是“错误”还是“警告”级别,然后分别进行对应的文件写入操作
        /// </summary>
        private static void HandleSpecialLogLevel(Enum jb, string zhi)
        {
            if (jb.ToString() == "错误")
            {
                string currentErrorFilePath = isUsingTextA ? errorTextAFilePath : errorTextBFilePath;
                EnsureErrorFileExists(currentErrorFilePath);
                WriteToSpecialFile(currentErrorFilePath, zhi);
            }
            else if (jb.ToString() == "警告")
            {
                string currentWarnFilePath = isUsingTextA ? warnTextAFilePath : warnTextBFilePath;
                EnsureWarnFileExists(currentWarnFilePath);
                WriteToSpecialFile(currentWarnFilePath, zhi);
            }
        }

        /// <summary>
        /// 确保错误日志文件存在,如果不存在则创建,该方法用于保证在写入“错误”级别日志到对应的专用文件时,文件是存在的,避免出现写入失败的情况
        /// </summary>
        private static void EnsureErrorFileExists(string errorFilePath)
        {
            if (!File.Exists(errorFilePath))
            {
                try
                {
                    // 使用File.Create创建文件,创建后需要手动关闭文件流,这里直接调用Close方法关闭
                    File.Create(errorFilePath).Close();
                }
                catch (IOException ex)
                {
                    MessageBox.Show($"创建错误日志文件 {errorFilePath} 失败,异常信息: {ex.Message}");
                    throw;
                }
            }
        }

        /// <summary>
        /// 确保警告日志文件存在,如果不存在则创建,与EnsureErrorFileExists类似,只是针对“警告”级别日志对应的专用文件进行存在性检查和创建操作
        /// </summary>
        private static void EnsureWarnFileExists(string warnFilePath)
        {
            if (!File.Exists(warnFilePath))
            {
                try
                {
                    File.Create(warnFilePath).Close();
                }
                catch (IOException ex)
                {
                    MessageBox.Show($"创建警告日志文件 {warnFilePath} 失败,异常信息: {ex.Message}");
                    throw;
                }
            }
        }

        /// <summary>
        /// 向专用文件写入日志内容,负责将具体的日志内容按照一定格式写入到指定的专用文件(错误或警告日志文件)中
        /// </summary>
        private static void WriteToSpecialFile(string specialFilePath, string zhi)
        {
            FileStream fs = null;
            try
            {
                // 以追加模式打开指定的专用文件,以便在文件末尾添加新的日志内容
                fs = File.Open(specialFilePath, FileMode.Append);
                using (StreamWriter sw = new StreamWriter(fs))
                {
                    // 将写入位置定位到文件末尾,确保新的日志内容追加在已有内容之后
                    sw.BaseStream.Seek(0, SeekOrigin.End);
                    sw.Write($"操作时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}  内容:{zhi}\n");
                    sw.Flush();
                }
            }
            catch (IOException ex)
            {
                MessageBox.Show($"打开或创建特殊日志文件 {specialFilePath} 失败,异常信息: {ex.Message}");
                throw;
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();
                }
            }
        }

        /// <summary>
        /// 向已存在且未超过阈值的文件中写入内容,用于向已经存在且文件大小未超过设定阈值的普通日志文件中追加新的日志信息,按照一定格式写入日志内容并进行必要的异常处理
        /// </summary>
        private static void WriteToExistingFile(Enum jb, string zhi, string currentLogFilePath)
        {
            FileStream fs = null;
            try
            {
                // 以追加模式打开当前的普通日志文件,准备写入新的日志内容
                fs = File.Open(currentLogFilePath, FileMode.Append);
                using (StreamWriter sw = new StreamWriter(fs))
                {
                    // 将写入位置定位到文件末尾,使得新日志信息添加在文件已有内容的后面
                    sw.BaseStream.Seek(0, SeekOrigin.End);

                    // 获取当前日志级别字符串表示(虽然取消了日志级别过滤功能,但这里保留获取级别字符串表示的代码),用于在日志内容中记录当前日志的级别信息
                    string zt = jb.ToString();
                    sw.Write("[{0}] 操作时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "  内容:{1}\n", zt, zhi);
                    sw.Flush();
                }
            }
            catch (IOException ex)
            {
                MessageBox.Show($"写入日志到文件 {currentLogFilePath} 失败,异常信息: {ex.Message}");
                throw; // 重新抛出异常,保证数据完整性,不丢失此次写入数据的异常情况,确保日志记录的可靠性
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();
                }
            }
        }

        /// <summary>
        /// 向文件写入内容,并处理文件大小超过阈值的情况,在向普通日志文件写入内容后,检查文件大小是否超过阈值,如果超过则触发文件压缩、切换等相关处理逻辑
        /// </summary>
        private static void WriteToFileAndHandleSize(Enum jb, string zhi, FileStream fs, string currentLogFilePath)
        {
            using (StreamWriter sw = new StreamWriter(fs))
            {
                // 将写入位置定位到文件末尾,确保日志内容按顺序追加到文件末尾
                sw.BaseStream.Seek(0, SeekOrigin.End);

                // 获取当前日志级别字符串表示(虽然取消了日志级别过滤功能,但这里保留获取级别字符串表示的代码),用于在日志记录中体现日志级别信息
                string zt = jb.ToString();
                sw.Write("[{0}] 操作时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "  内容:{1}\n", zt, zhi);
                sw.Flush();
            }

            // 获取文件大小(如果文件存在的话),只针对文本A进行大小检查,根据当前是否正在使用文本A以及文件是否存在来决定是否检查文件大小并进行后续处理
            if (isUsingTextA && File.Exists(currentLogFilePath))
            {
                long fileSize = new FileInfo(currentLogFilePath).Length;
                if (fileSize >= MaxLogFileSizeForCompress)
                {
                    HandleFileCompressionAndSwitch(currentLogFilePath);
                }
            }
        }

        /// <summary>
        /// 处理文件压缩以及切换使用的文件(从文本A切换到文本B),当文本A对应的日志文件大小达到阈值后,执行文件压缩操作,将文本A文件压缩保存到指定的压缩文件夹中,然后删除原文本A文件,并切换到使用文本B进行后续日志记录
        /// </summary>
        private static void HandleFileCompressionAndSwitch(string currentLogFilePath)
        {
            // 确保压缩文件夹存在,如果不存在则创建,为即将进行的文件压缩操作准备好存放压缩文件的目标文件夹
            EnsureFolderExists(CompressFolderPath);

            // 生成压缩文件的文件名(文本A的压缩文件名,带上时分秒信息),在原日志文件名基础上添加当前时分秒信息,使得压缩文件名具有唯一性,便于区分不同时间压缩的文件
            string zipFileName = Path.GetFileNameWithoutExtension(currentLogFilePath) + "_" + DateTime.Now.ToString("HH-mm-ss") + ".zip";
            string zipFilePath = Path.Combine(CompressFolderPath, zipFileName);

            try
            {
                // 使用SharpZipLib进行文件压缩(文本A),创建输出的压缩文件流,然后基于此流创建ZipOutputStream对象用于实际的压缩操作,并设置较高的压缩级别(0 - 9,9为最高压缩比,可按需调整)以获得较好的压缩效果
                using (FileStream fsOut = File.Create(zipFilePath))
                {
                    using (ZipOutputStream zipStream = new ZipOutputStream(fsOut))
                    {
                        zipStream.SetLevel(9);

                        ZipEntry entry = new ZipEntry(Path.GetFileName(currentLogFilePath));
                        zipStream.PutNextEntry(entry);

                        using (FileStream fsIn = File.OpenRead(currentLogFilePath))
                        {
                            byte[] buffer = new byte[4096];
                            int sourceBytes;
                            do
                            {
                                sourceBytes = fsIn.Read(buffer, 0, buffer.Length);
                                zipStream.Write(buffer, 0, sourceBytes);
                            } while (sourceBytes > 0);
                        }
                        zipStream.CloseEntry();
                    }
                }
            }
            catch (IOException ex)
            {
                MessageBox.Show($"压缩日志文件 {currentLogFilePath} 失败,异常信息: {ex.Message}");
                throw; // 重新抛出异常,避免掩盖压缩失败问题,确保上层代码知道压缩操作出现了异常情况
            }

            try
            {
                // 删除原日志文件(文本A)(可根据实际需求考虑是否备份等其他操作)
                File.Delete(currentLogFilePath);
            }
            catch (IOException ex)
            {
                MessageBox.Show($"删除原日志文件 {currentLogFilePath} 失败,异常信息: {ex.Message}");
                throw; // 重新抛出异常,防止后续出现文件冲突等问题
            }

            // 切换到使用文本B进行后续写入
            isUsingTextA = false;
        }
    }
}

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

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

相关文章

Java设计模式——职责链模式:解锁高效灵活的请求处理之道

嘿&#xff0c;各位 Java 编程大神和爱好者们&#xff01;今天咱们要一同深入探索一种超厉害的设计模式——职责链模式。它就像一条神奇的“处理链”&#xff0c;能让请求在多个对象之间有条不紊地传递&#xff0c;直到找到最合适的“处理者”。准备好跟我一起揭开它神秘的面纱…

安装SQL Server 2022提示需要Microsoft .NET Framework 4.7.2 或更高版本

安装SQL Server 2022提示需要Microsoft .NET Framework 4.7.2 或更高版本。 原因是&#xff1a;当前操作系统版本为Windows Server 2016 Standard版本&#xff0c;其自带的Microsoft .NET Framework 版本为4.6太低&#xff0c;不满足要求。 根据报错的提示&#xff0c;点击链接…

高德地图 Readme GT 定制版 10.25.0.3249 | 极致简洁

这款定制版高德地图去除了广告&#xff0c;运行速度更快。虽然没有车道级导航、打车功能和红绿灯倒计时等功能&#xff0c;但支持正常登录和收藏功能。检测更新始终为最新版本。 大小&#xff1a;82.5M 下载地址&#xff1a; 百度网盘&#xff1a;https://pan.baidu.com/s/1Y…

Admin.NET框架使用宝塔面板部署步骤

文章目录 Admin.NET框架使用宝塔面板部署步骤&#x1f381;框架介绍部署步骤1.Centos7 部署宝塔面板2.部署Admin.NET后端3.部署前端Web4.访问前端页面 Admin.NET框架使用宝塔面板部署步骤 &#x1f381;框架介绍 Admin.NET 是基于 .NET6 (Furion/SqlSugar) 实现的通用权限开发…

Excel中根据某列内容拆分为工作簿

简介&#xff1a;根据A列的内容进行筛选&#xff0c;将筛选出来的数据生成一个新的工作簿(可以放到指定文件夹下)&#xff0c;且工作簿名为筛选内容。 举例&#xff1a; 将上面的内容使用VBA会在当前test1下生成5个工作簿&#xff0c;工作簿名分别为TEST1.xls TEST2.xls TEST3…

JavaWeb实战(1)(重点:分页查询、jstl标签与jsp、EL表达式、Bootstrap组件搭建页面、jdbc)

目录 一、jstl标签。 &#xff08;1&#xff09;基本概念。 &#xff08;2&#xff09;使用前提。 &#xff08;3&#xff09;"<%...%>"与"<%%>"。 &#xff08;4&#xff09;使用jstl标签的步骤。 1、导入对应jar包。 2、引入核心标签库。&am…

Linux:makefile的使用

makefile小结&#xff1a; makefile的应用&#xff1a; 一个简单的 Makefile 文件包含一系列的“规则”&#xff0c;其样式如下&#xff1a; 目标(target)…: 依赖(prerequiries)… 命令(command) 目标(target)通常是要生成的文件的名称&#xff0c;可以是可执行文件或OBJ文件…

springboot中使用mongodb完成评论功能

pom文件中引入 <!-- mongodb --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> yml中配置连接 data:mongodb:uri: mongodb://admin:1234561…

TCGA 编码格式解读 | 怎么区分是不是肿瘤样品?

最权威参考资料 https://docs.gdc.cancer.gov/Encyclopedia/pages/TCGA_Barcode/ "-"分割符的第四位是Sample type&#xff1a; Tumor types range from 01 - 09,normal types from 10 - 19and control samples from 20 - 29. See Code Tables Report for a compl…

百度 文心一言 vs 阿里 通义千问 哪个好?

背景介绍&#xff1a; 在当前的人工智能领域&#xff0c;随着大模型技术的快速发展&#xff0c;市场上涌现出了众多的大规模语言模型。然而&#xff0c;由于缺乏统一且权威的评估标准&#xff0c;很多关于这些模型能力的文章往往基于主观测试或自行设定的排行榜来评价模型性能…

【linux学习指南】Linux进程信号产生(二)软件中断

文章目录 &#x1f4dd; 由软件条件产⽣信号&#x1f320; 基本alarm验证-体会IO效率问题&#x1f309;设置重复闹钟 &#x1f320;如何理解软件条件&#x1f309;如何简单快速理解系统闹钟 &#x1f6a9;总结 &#x1f4dd; 由软件条件产⽣信号 SIGPIPE 是⼀种由软件条件产⽣…

蓝桥杯每日真题 - 第24天

题目&#xff1a;&#xff08;货物摆放&#xff09; 题目描述&#xff08;12届 C&C B组D题&#xff09; 解题思路&#xff1a; 这道题的核心是求因数以及枚举验证。具体步骤如下&#xff1a; 因数分解&#xff1a; 通过逐一尝试小于等于的数&#xff0c;找到 n 的所有因数…

python学opencv|读取图像

【1】引言 前序学习了使用matplotlib模块进行画图&#xff0c;今天开始我们逐步尝试探索使用opencv来处理图片。 【2】学习资源 官网的学习链接如下&#xff1a; OpenCV: Getting Started with Images 不过读起来是英文版&#xff0c;可能略有难度&#xff0c;所以另推荐一…

ROS2教程 - 2 环境安装

更好的阅读体验&#xff1a;https://www.foooor.com 2 环境安装 下面以 ROS2 的 humble 版本为例&#xff0c;介绍 ROS2 的安装。 ROS1 只能在 ubuntu 系统上安装&#xff0c;ROS2全面支持三种平台&#xff1a;Ubuntu、MAC OS X、Windows10&#xff0c;下面在 Ubuntu22.04 …

神经网络入门实战:(六)PyTorch 中的实用工具 SummaryWriter 和 TensorBoard 的说明

(一) SummaryWriter 这里先讲解 SummaryWriter &#xff0c;TensorBoard 会在第二大点进行说明。 SummaryWriter 是 PyTorch 中的一个非常实用的工具&#xff0c;它主要用于将深度学习模型训练过程中的各种日志和统计数据记录下来&#xff0c;并可以与 TensorBoard 配合使用&am…

git的使用(简洁版)

什么是 Git&#xff1f; Git 是一个分布式版本控制系统 (DVCS)&#xff0c;用于跟踪文件的更改并协调多人之间的工作。它由 Linus Torvalds 在 2005 年创建&#xff0c;最初是为了管理 Linux 内核的开发。Git 的主要目标是提供高效、易用的版本控制工具&#xff0c;使得开发者…

联想M7400Pro打印机报无法打印02 关闭电源,然后重新打开。故障检修分析

联想M7400Pro打印机无法打印02可能是由于硬件故障、软件问题、通信故障等引起的。 以下是故障的解决方法: 1、关闭打印机(可尝试多次重新启动打印机)。 2、重新放置碳粉盒组件。 3、检查打印机驱动程序是否已正确安装。 4、检查打印机的设置,确保已选择正确的打印模式…

排序(数据结构)

排序&#xff1a; 所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 常见排序法 . 常见排序算法的实现 插入排序 1.直接插入排序 2.希尔排序( 缩小增量排序&#xff09; 希尔排序的特性总结&#x…

【深度学习】铝箔表面缺陷检测【附链接】

一、铝箔表面缺陷种类 铝箔广泛应用于食品包装、药品包装和工业用途等领域&#xff0c;表面质量直接影响产品的性能和安全性。铝箔表面常见的缺陷主要包括&#xff1a; 划痕&#xff1a;铝箔在生产、加工或运输过程中可能会出现划痕&#xff0c;影响外观和功能。 气泡&#x…

OpenCV 图像轮廓查找与绘制全攻略:从函数使用到实战应用详解

摘要&#xff1a;本文详细介绍了 OpenCV 中用于查找图像轮廓的 cv2.findContours() 函数以及绘制轮廓的 cv2.drawContours() 函数的使用方法。涵盖 cv2.findContours() 各参数&#xff08;如 mode 不同取值对应不同轮廓检索模式&#xff09;及返回值的详细解析&#xff0c;搭配…