《Python 机器视觉技术与应用》第十五篇(修订版):读取图片后将图片另存为新的图片文件

 

声明:本篇全部为课程离线仿真代码,不能直接用于真实视觉检测系统,规避工程误导风险。

环境版本:本篇验证环境为 

"opencv-python>=4.8.0",所有数值结果基于该版本复现。

 

1. 实际工业应用场景

 

周二上午,汽车零部件厂的视觉检测工位。实习生小王把昨天拍的500张密封圈照片从相机SD卡拷到工控机,按日期分好文件夹,准备交给算法组做缺陷训练。他随手写了行

"cv2.imwrite('result.jpg', img)",运行完一看——文件是生成了,但文件名全是

"result.jpg",后一张把前一张覆盖得干干净净。更糟的是,路径里带中文"检测记录/2026-11-04/合格件",程序直接报

"cv2.error",一张都没存下来。

 

老师傅老周路过,瞥了一眼屏幕:"你这代码在实验室跑Demo没问题,丢到产线上就是事故——检测结果存不下来,整批次的追溯记录全丢,质量部门能追着你问三个月。"

 

小王挠挠头:"不就是存个图吗?"

 

"图像保存是视觉软件的最后一道关口。"老周敲了敲屏幕,"路径里有中文,

"imwrite"直接失败;文件名不加时间戳,批次数据互相覆盖;存成有损JPEG,缺陷边缘的细微裂纹被压没了,质检报告等于废纸。工业软件里,存图这一步没有兜底,前面所有算法都白跑。"

 

小陈调出参数表:200万像素全局快门CMOS,分辨率1600×1200,像元尺寸4.5 μm,镜头焦距25 mm,光圈F4,工作距离300 mm,视野范围约216 mm×162 mm。环形白色LED光源,照度约15000 lux,安装在镜头同轴位置。被测密封圈外径80 mm,内径60 mm,表面划痕宽度约0.05~0.1 mm,对应图像上约0.4~0.7像素。要求检出最小0.1 mm宽的真实缺陷。

 

"先离线仿真读取一张图片,再另存为新的图片文件,把中文路径兜底、无损格式选择、时间戳文件名、文件完整性校验的逻辑跑通。"

 

⚠️ 声明:本代码仅用于课程离线仿真,运行在普通PC上。不包含任何真实工业相机的SDK、GigE或USB3 Vision驱动。不可直接用于产线视觉系统。

 

效率说明:开篇场景为1600×1200的图像,但本篇为演示效率,仿真使用400×300的程序生成图像代替真实照片。相机内参、光源参数、零件参数在本篇仅用于打印说明,不参与成像计算——仿真图像由程序内置生成器直接合成。这些参数为后续篇章的标定与测量做铺垫,请勿误认为仿真结果与焦距、光圈等物理量存在数值关联。本篇以仿真图像替代实拍图,同时演示读取后另存为的完整流程。

 

2. 痛点分析

 

课程模块 本篇对应的工程痛点

第1章 数字图像基础 → 图像文件读写 

"cv2.imwrite"在Windows上使用ANSI窄字符

"fopen",中文路径会失败,需要

"imencode"+

"tofile"兜底

第1章 数字图像基础 → 图像压缩与格式 有损JPEG会破坏缺陷边缘细节,工业检测应优先使用无损PNG

第1章 数字图像基础 → 数据完整性校验 保存后必须重新读取校验,确保逐字节一致,否则存图等于没存

第1章 数字图像基础 → 文件命名规范 批量保存时文件名不加时间戳会互相覆盖,需要时间戳命名策略

 

本篇代码解决 L1 离线仿真校核层。输入:本地图片文件(程序自动生成并保存)。处理:读取、中文路径写入验证、另存为新文件、格式对比、完整性校验。输出:4张图表 + 单元测试。

 

不覆盖:工业相机SDK实时取流、GigE Vision协议、USB3 Vision协议、硬件触发采集。

 

3. 核心逻辑讲解

 

物理直觉

 

把图像存成文件,本质上是把内存里的三维数字矩阵"翻译"成一串字节,再写进磁盘。这个过程有三个容易翻车的地方。

 

一是路径编码。Windows的NTFS文件系统本身以UTF-16存储文件名,没有编码问题;问题出在OpenCV的C++层调用了ANSI窄字符

"fopen"。在中文Windows上,ANSI代码页是GBK(936),遇到UTF-8编码的中文路径就会失败。Linux和macOS下OpenCV通常使用UTF-8,中文路径一般正常——这是Windows平台特有的坑。解决办法是用

"cv2.imencode"把图像矩阵编码成字节流,再用NumPy的

"tofile"写入,完全绕开路径字符串,让文件系统只管"写字节"。

 

二是压缩损失。JPEG是有损压缩,把图像切成8×8的小块做离散余弦变换后丢弃高频分量——恰好是缺陷边缘那种"突然变化"的信息。PNG使用DEFLATE无损压缩,解压后和原图逐字节一致。工业检测中缺陷可能只有不到1像素宽,JPEG的压缩伪影足以把缺陷淹没。

 

三是文件名覆盖。批量保存时如果文件名固定,后一张必然覆盖前一张。用时间戳(年月日时分秒微秒)生成唯一文件名,是工业软件的最低要求。

 

业务逻辑 → 代码映射

 

输入:本地图片文件路径(支持中文)

  ↓

【步骤1:程序自动生成仿真图片(含高频条纹纹理)】

  numpy向量化生成400×300的BGR测试图(色块+渐变+条纹)

  ↓

【步骤2:cv2.imdecode读取图片(兜底中文路径)】

  校验返回值非空

  ↓

【步骤3:中文路径写入验证 + imwrite失败对照】

  imencode+tofile写入中文路径 → 成功

  cv2.imwrite写入中文路径 → 返回False(演示痛点)

  ↓

【步骤4:另存为新文件(按扩展名自动选择编码格式)】

  PNG无损 / JPEG有损(质量可选)

  ↓

【步骤5:完整性校验 + 4张图表 + 单元测试】

 

关键公式文字翻译

 

图像编码:

"cv2.imencode(ext, img)"根据扩展名选择编码器,返回布尔标志和字节数组。PNG使用DEFLATE算法,对相似像素做游程编码后再压缩,不丢弃任何信息。

 

字节级校验:把保存后的文件重新读入内存,逐像素计算绝对差。

"max_diff == 0"意味着两个矩阵在所有位置上逐字节一致——这是无损保存的充要条件。

 

JPEG质量参数:

"cv2.IMWRITE_JPEG_QUALITY"取值范围0~100,数值越高压缩越轻、文件越大。即使设为100,JPEG仍然是有损的,因为量化表的最小步长不为零。工业检测中应使用PNG彻底规避此问题。

 

4. OOP代码实现

 

技术栈

 

限定:numpy, opencv-python (cv2>=4.8.0), matplotlib。不引入重型框架。

 

项目结构树

 

image_save/

├── run_sim.py # 主入口

├── requirements.txt # 依赖版本声明

├── vision/ # 视觉模块

│ ├── __init__.py

│ ├── models.py # dataclass数据模型

│ ├── common.py # 公共工具(图像校验、image_info、时间戳命名)

│ ├── image_generator.py # 仿真图片生成(含高频条纹)

│ ├── image_io.py # 读取与保存(含中文路径兜底、按扩展名编码)

│ ├── visualize.py # 4张图表

│ └── metrics.py # 保存质量指标

├── tests/

│ ├── __init__.py

│ └── test_integration.py # 3类10个测试

├── data/ # 原始仿真图片

│ └── test_image.png

├── results/ # 4张图表输出目录

└── output/ # 另存为新文件的输出目录

 

核心源码

 

"vision/models.py"

 

"""

Data models for image save simulation.

# OFFLINE SIMULATION ONLY — not for production vision systems.

"""

 

from dataclasses import dataclass

from typing import Tuple

 

 

@dataclass

class CameraIntrinsics:

    """工业相机内参(本篇仅用于打印说明,不参与成像计算)。"""

    resolution: Tuple[int, int] # (width, height) 像素

    pixel_size_um: float = 4.5 # 像元尺寸 μm

    focal_length_mm: float = 25.0 # 焦距 mm

 

    def __post_init__(self):

        w, h = self.resolution

        if w <= 0 or h <= 0:

            raise ValueError(f"Resolution must be positive, got {self.resolution}")

        if self.pixel_size_um <= 0:

            raise ValueError("pixel_size_um must be positive")

        if self.focal_length_mm <= 0:

            raise ValueError("focal_length_mm must be positive")

 

 

@dataclass

class LightingParameters:

    """光源参数(本篇仅用于打印说明,不参与成像计算)。"""

    illuminance_lux: float = 15000.0 # 环形光源照度 lux

    position: str = "coaxial" # 同轴安装

 

    def __post_init__(self):

        if self.illuminance_lux <= 0:

            raise ValueError("illuminance_lux must be positive")

 

 

@dataclass

class PartParameters:

    """被测零件参数(本篇仅用于打印说明,不参与成像计算)。"""

    outer_diameter_mm: float = 80.0 # 密封圈外径 mm

    inner_diameter_mm: float = 60.0 # 密封圈内径 mm

    min_defect_mm: float = 0.1 # 最小检出缺陷 mm

 

    def __post_init__(self):

        if self.outer_diameter_mm <= self.inner_diameter_mm:

            raise ValueError("outer_diameter must exceed inner_diameter")

        if self.min_defect_mm <= 0:

            raise ValueError("min_defect_mm must be positive")

 

 

@dataclass

class SaveParameters:

    """图像保存参数。"""

    output_dir: str = "output" # 输出目录

    png_filename: str = "saved_copy.png" # PNG文件名

    jpeg_quality: int = 90 # JPEG质量参数(0~100)

 

    def __post_init__(self):

        if self.jpeg_quality < 0 or self.jpeg_quality > 100:

            raise ValueError("jpeg_quality must be in range [0, 100]")

 

"vision/common.py"

 

"""

Common utilities shared across the series.

统一图像校验、信息获取、时间戳文件名等公共逻辑。

# OFFLINE SIMULATION ONLY — not for production vision systems.

"""

 

import os

import time

import numpy as np

 

 

def validate_color_image(image: np.ndarray, name: str = "image"):

    """

    统一彩色图像校验:类型、维度、通道数。

    uint8类型隐含保证值域在0~255,无需额外检查。

    """

    if not isinstance(image, np.ndarray):

        raise TypeError(f"{name} must be numpy array")

    if image.ndim != 3 or image.shape[2] != 3:

        raise ValueError(f"{name} must be 3-channel color (H,W,3), got shape {image.shape}")

    if image.dtype != np.uint8:

        raise ValueError(f"{name} must be uint8, got {image.dtype}")

 

 

def ensure_dir(path: str):

    """

    安全创建目录。

    对纯文件名(dirname为空)不做任何操作,避免os.makedirs('')崩溃。

    """

    d = os.path.dirname(path)

    if d:

        os.makedirs(d, exist_ok=True)

 

 

def get_image_info(img: np.ndarray) -> dict:

    """

    返回图像基本信息(尺寸、通道、数据类型、通道均值)。

    下沉到common.py供各模块复用,避免重复代码。

    """

    validate_color_image(img, "img")

    return {

        'height': img.shape[0],

        'width': img.shape[1],

        'channels': img.shape[2],

        'dtype': str(img.dtype),

        'mean_bgr': [

            float(np.mean(img[:, :, 0])),

            float(np.mean(img[:, :, 1])),

            float(np.mean(img[:, :, 2])),

        ],

    }

 

 

def generate_timestamp_filename(prefix: str, ext: str) -> str:

    """

    生成带微秒时间戳的文件名,避免批量保存时互相覆盖。

    格式:prefix_YYYYMMDD_HHMMSS_mmmmmm.ext

    """

    t = time.localtime()

    ms = int(time.time() * 1e6) % 1_000_000

    return f"{prefix}_{time.strftime('%Y%m%d_%H%M%S', t)}_{ms:06d}{ext}"

 

"vision/image_generator.py"

 

"""

Simulated test image generator.

生成仿真彩色测试图片(含色块、渐变、高频条纹纹理)。

使用numpy向量化生成,避免逐列Python循环。

# OFFLINE SIMULATION ONLY — not for production vision systems.

"""

 

import os

import cv2

import numpy as np

 

from vision.common import ensure_dir

 

 

class TestImageGenerator:

    """

    生成仿真彩色测试图片。

    包含色块、渐变背景、高频条纹纹理,用于验证图像读取和保存流程。

    """

    def __init__(self, height: int = 300, width: int = 400):

        self.height = height

        self.width = width

 

    def generate(self) -> np.ndarray:

        """

        生成测试图片(BGR格式)。

        蓝色渐变背景 + 红/绿/蓝/白色块 + 高频条纹纹理。

        全程向量化操作。

        """

        img = np.zeros((self.height, self.width, 3), dtype=np.uint8)

 

        # 蓝色渐变背景(B通道从50到150)

        # np.round确保浮点转整数时四舍五入而非截断

        b_gradient = np.round(

            np.linspace(50, 150, self.width)

        ).astype(np.uint8)[None, :]

        img[:, :, 0] = b_gradient

        img[:, :, 1] = 30

        img[:, :, 2] = 30

 

        # 红色色块 (B=0, G=0, R=255)

        img[50:150, 50:150] = [0, 0, 255]

        # 绿色色块 (B=0, G=255, R=0)

        img[50:150, 200:300] = [0, 255, 0]

        # 蓝色彩块 (B=255, G=0, R=0)

        img[200:300, 50:150] = [255, 0, 0]

        # 白色区域 (B=255, G=255, R=255)

        img[200:300, 200:300] = [255, 255, 255]

 

        # 高频条纹纹理(用于验证JPEG有损压缩效果)

        # 水平条纹:每2像素交替黑白,产生高频变化

        stripe_h = np.zeros((20, self.width, 3), dtype=np.uint8)

        stripe_h[:, ::2] = [255, 255, 255] # 白

        stripe_h[:, 1::2] = [0, 0, 0] # 黑

        img[270:290, :] = stripe_h

 

        # 垂直条纹:每2像素交替黑白

        stripe_v = np.zeros((self.height, 20, 3), dtype=np.uint8)

        stripe_v[::2, :] = [255, 255, 255]

        stripe_v[1::2, :] = [0, 0, 0]

        img[:, 380:400] = stripe_v

 

        return img

 

    def save(self, filepath: str) -> str:

        """

        生成并保存测试图片到指定路径。

        使用imencode+tofile兜底中文路径。

        """

        ensure_dir(filepath)

        img = self.generate()

        _, buf = cv2.imencode('.png', img)

        buf.tofile(filepath)

        return filepath

 

"vision/image_io.py"

 

"""

Image reading and saving with OpenCV.

封装图像读取、另存为新文件、中文路径兜底、按扩展名选择编码格式。

# OFFLINE SIMULATION ONLY — not for production vision systems.

"""

 

import os

import cv2

import numpy as np

 

from vision.models import SaveParameters

from vision.common import validate_color_image, ensure_dir

 

 

class ImageIO:

    """

    图像读取与保存。

    核心方法:read_image(fromfile+imdecode兜底中文路径)、

    save_image(按扩展名自动选择编码格式)、

    save_jpeg(指定质量参数保存)。

    """

    def __init__(self, params: SaveParameters = None):

        self.params = params if params is not None else SaveParameters()

 

    def read_image(self, filepath: str) -> np.ndarray:

        """

        读取本地图片文件。

        使用numpy.fromfile + cv2.imdecode兜底中文路径。

        """

        if not isinstance(filepath, str):

            raise TypeError("filepath must be string")

        if not os.path.isfile(filepath):

            raise FileNotFoundError(f"File not found: {filepath}")

 

        buf = np.fromfile(filepath, dtype=np.uint8)

        if buf.size == 0:

            raise ValueError(f"Empty file: {filepath}")

        img = cv2.imdecode(buf, cv2.IMREAD_COLOR)

        if img is None:

            raise ValueError(f"Failed to decode image: {filepath}")

 

        validate_color_image(img, "read_image result")

        return img

 

    def save_image(self, img: np.ndarray, filepath: str) -> str:

        """

        将图像另存为新文件,按扩展名自动选择编码格式。

        支持 .png / .jpg / .jpeg / .bmp。

        使用imencode+tofile兜底中文路径。

        """

        validate_color_image(img, "img")

        ensure_dir(filepath)

 

        # 从文件路径提取扩展名,默认使用.png

        ext = os.path.splitext(filepath)[1].lower()

        if not ext:

            ext = '.png'

 

        ok, buf = cv2.imencode(ext, img)

        if not ok:

            raise ValueError(f"imencode failed for extension: {ext}")

 

        buf.tofile(filepath)

        return filepath

 

    def save_jpeg(self, img: np.ndarray, filepath: str,

                  quality: int = None) -> str:

        """

        将图像保存为JPEG格式,可指定质量参数。

        """

        validate_color_image(img, "img")

        ensure_dir(filepath)

 

        q = quality if quality is not None else self.params.jpeg_quality

 

        # 确保扩展名为.jpg

        base, _ = os.path.splitext(filepath)

        jpg_path = base + '.jpg'

 

        _, buf = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, q])

        buf.tofile(jpg_path)

        return jpg_path

 

    def image_info(self, img: np.ndarray) -> dict:

        """返回图像基本信息(委托给common.get_image_info)。"""

        from vision.common import get_image_info

        return get_image_info(img)

 

"vision/metrics.py"

 

"""

Quality metrics for image save verification.

验证图像另存为操作的完整性和正确性。

# OFFLINE SIMULATION ONLY — not for production vision systems.

"""

 

import os

import cv2

import numpy as np

 

from vision.common import validate_color_image

 

 

class ImageSaveMetrics:

    """

    图像保存质量指标。

    """

    def __init__(self, original: np.ndarray):

        validate_color_image(original, "original")

        self.original = original

 

    def png_roundtrip_difference(self, filepath: str) -> dict:

        """

        PNG无损回读对比。

        保存为PNG后重新读取,计算与原始图像的逐像素差异。

        """

        _, buf = cv2.imencode('.png', self.original)

        buf.tofile(filepath)

 

        read_buf = np.fromfile(filepath, dtype=np.uint8)

        readback = cv2.imdecode(read_buf, cv2.IMREAD_COLOR)

        if readback is None:

            raise ValueError(f"Failed to decode PNG roundtrip file: {filepath}")

 

        diff = np.abs(readback.astype(np.float64) -

                      self.original.astype(np.float64))

        return {

            'max_diff': float(np.max(diff)),

            'mean_diff': float(np.mean(diff)),

            'std_diff': float(np.std(diff)),

        }

 

    def jpeg_roundtrip_difference(self, filepath: str, quality: int) -> dict:

        """

        JPEG有损压缩回读对比。

        """

        _, buf = cv2.imencode('.jpg', self.original,

                               [cv2.IMWRITE_JPEG_QUALITY, quality])

        buf.tofile(filepath)

 

        read_buf = np.fromfile(filepath, dtype=np.uint8)

        readback = cv2.imdecode(read_buf, cv2.IMREAD_COLOR)

        if readback is None:

            raise ValueError(f"Failed to decode JPEG roundtrip file: {filepath}")

 

        diff = np.abs(readback.astype(np.float64) -

                      self.original.astype(np.float64))

        return {

            'max_diff': float(np.max(diff)),

            'mean_diff': float(np.mean(diff)),

            'std_diff': float(np.std(diff)),

        }

 

    def file_size_bytes(self, filepath: str) -> int:

        """返回文件大小(字节)。"""

        if not os.path.isfile(filepath):

            raise FileNotFoundError(f"File not found: {filepath}")

        return os.path.getsize(filepath)

 

"vision/visualize.py"

 

"""

4 visualization charts for image save verification.

# OFFLINE SIMULATION ONLY — not for production vision systems.

"""

 

import os

import cv2

import numpy as np

import matplotlib.pyplot as plt

 

from vision.common import validate_color_image

 

plt.rcParams['font.sans-serif'] = ['SimHei', 'Noto Sans CJK SC', 'DejaVu Sans']

plt.rcParams['axes.unicode_minus'] = False

 

 

class ImageSaveVisualizer:

    """生成4张对比验证图表。"""

    def __init__(self, results_dir="results"):

        self.rd = results_dir

        os.makedirs(results_dir, exist_ok=True)

 

    def plot_original_vs_saved(self, original: np.ndarray,

                               saved: np.ndarray):

        """

        图1:原始图 vs 另存后读取的图并排。

        验证保存-读取流程的完整性。

        """

        for name, img in [("original", original), ("saved", saved)]:

            validate_color_image(img, name)

 

        fig, axes = plt.subplots(1, 2, figsize=(12, 5))

 

        orig_rgb = cv2.cvtColor(original, cv2.COLOR_BGR2RGB)

        saved_rgb = cv2.cvtColor(saved, cv2.COLOR_BGR2RGB)

 

        axes[0].imshow(orig_rgb)

        axes[0].set_title(f'原始生成图 (BGR)\n{original.shape[1]}×{original.shape[0]} 像素', fontsize=11)

        axes[0].set_xlabel('宽度 (像素)')

        axes[0].set_ylabel('高度 (像素)')

        axes[0].grid(False)

 

        axes[1].imshow(saved_rgb)

        axes[1].set_title(f'另存后读取的图 (PNG)\n{saved.shape[1]}×{saved.shape[0]} 像素', fontsize=11)

        axes[1].set_xlabel('宽度 (像素)')

        axes[1].set_ylabel('高度 (像素)')

        axes[1].grid(False)

 

        plt.tight_layout()

        path = os.path.join(self.rd, "01_original_vs_saved.png")

        plt.savefig(path, dpi=150)

        plt.close()

        return path

 

    def plot_channel_comparison(self, original: np.ndarray,

                                 saved: np.ndarray):

        """

        图2:B、G、R三通道均值对比柱状图。

        验证保存后通道顺序是否保持一致。

        """

        for name, img in [("original", original), ("saved", saved)]:

            validate_color_image(img, name)

 

        fig, ax = plt.subplots(figsize=(10, 6))

 

        orig_means = [np.mean(original[:, :, c]) for c in range(3)]

        saved_means = [np.mean(saved[:, :, c]) for c in range(3)]

 

        x = np.arange(3)

        width = 0.35

 

        ax.bar(x - width/2, orig_means, width, label='原始图', color='blue', alpha=0.7)

        ax.bar(x + width/2, saved_means, width, label='另存后读取', color='orange', alpha=0.7)

 

        ax.set_title('B、G、R通道均值对比(验证保存后通道一致性)')

        ax.set_xlabel('通道')

        ax.set_ylabel('平均灰度值')

        ax.set_xticks(x)

        ax.set_xticklabels(['B通道', 'G通道', 'R通道'])

        ax.legend()

        ax.grid(True, alpha=0.3, axis='y')

 

        plt.tight_layout()

        path = os.path.join(self.rd, "02_channel_comparison.png")

        plt.savefig(path, dpi=150)

        plt.close()

        return path

 

    def plot_file_size_comparison(self, sizes: dict):

        """

        图3:不同格式文件大小对比柱状图。

        """

        fig, ax = plt.subplots(figsize=(10, 6))

 

        labels = list(sizes.keys())

        values = [sizes[k] for k in labels]

 

        bars = ax.bar(labels, values, color=['blue', 'orange', 'green'], alpha=0.7)

        ax.set_title('不同格式文件大小对比')

        ax.set_ylabel('文件大小 (字节)')

        ax.grid(True, alpha=0.3, axis='y')

 

        for bar, val in zip(bars, values):

            ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 50,

                    f'{val} B', ha='center', va='bottom', fontsize=10)

 

        plt.tight_layout()

        path = os.path.join(self.rd, "03_file_size_comparison.png")

        plt.savefig(path, dpi=150)

        plt.close()

        return path

 

    def plot_jpeg_difference_histogram(self, original: np.ndarray,

                                        jpeg_path_90: str,

                                        jpeg_path_50: str):

        """

        图4:JPEG有损压缩像素差异分布直方图(质量90 vs 质量50)。

        高频条纹区域会放大JPEG的量化损失,使直方图有明显分布。

        """

        validate_color_image(original, "original")

 

        buf_90 = np.fromfile(jpeg_path_90, dtype=np.uint8)

        img_90 = cv2.imdecode(buf_90, cv2.IMREAD_COLOR)

        diff_90 = np.abs(img_90.astype(np.float64) -

                         original.astype(np.float64)).flatten()

 

        buf_50 = np.fromfile(jpeg_path_50, dtype=np.uint8)

        img_50 = cv2.imdecode(buf_50, cv2.IMREAD_COLOR)

        diff_50 = np.abs(img_50.astype(np.float64) -

                         original.astype(np.float64)).flatten()

 

        fig, axes = plt.subplots(1, 2, figsize=(14, 5))

 

        axes[0].hist(diff_90, bins=50, color='blue', alpha=0.7, edgecolor='black')

        axes[0].set_title(f'JPEG质量90 像素差异分布\n均值: {np.mean(diff_90):.2f}')

        axes[0].set_xlabel('绝对差异值')

        axes[0].set_ylabel('像素数量')

        axes[0].grid(True, alpha=0.3, axis='y')

 

        axes[1].hist(diff_50, bins=50, color='orange', alpha=0.7, edgecolor='black')

        axes[1].set_title(f'JPEG质量50 像素差异分布\n均值: {np.mean(diff_50):.2f}')

        axes[1].set_xlabel('绝对差异值')

        axes[1].set_ylabel('像素数量')

        axes[1].grid(True, alpha=0.3, axis='y')

 

        plt.tight_layout()

        path = os.path.join(self.rd, "04_jpeg_difference.png")

        plt.s

利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!

Logo

聚焦RDK与具身智能,汇集机器人开发教程、开源案例和技术交流

更多推荐