图片格式小赏,图像有损压缩得与失

  • 图像文件的类型和格式分为两个主要类别:光栅(或者叫作栅格)图像文件和矢量图像文件。

    • 栅格图像文件类型为静态图像,其中每个像素均基于其分辨率具有定义的颜色,位置和比例。文件大小取决于图像分辨率和颜色深度。当放大或缩小图像时,可能会出现锯齿边缘或模糊现象。能够表现丰富的色彩层次和细节。可以很好地呈现照片等连续色调图像。支持多种颜色模式(如RGB, CMYK)。不适合大幅度缩放,因为会导致图像失真。部分栅格图像文件格式:

      • JPEG (Joint Photographic Experts Group): 常用于照片存储,支持有损压缩。
      • PNG (Portable Network Graphics): 提供无损压缩,并且支持透明背景。
      • GIF (Graphics Interchange Format): 支持动画,通常使用较少的颜色位数。
      • BMP (Bitmap): Windows平台常用的未压缩图像格式。
      • TIFF (Tagged Image File Format): 高质量打印和出版中常用,支持多种压缩方式。
    • 与静态栅格图像文件格式(每个设计形状和颜色都与一个像素相关联)不同,矢量图像格式更加灵活。矢量图形使用笛卡尔平面上的直线和曲线系统(即通过数学公式计算获得的),该系统相对于总面积(而不是任何单个像素)进行缩放。这意味着我们可以无限扩大原始图像分辨率,又不会损失质量或失真。文件尺寸较小,适合存储简单的图形和logo。不适用于复杂的照片级图像。在表现细微的色彩变化上不如光栅图像。部分矢量图文件格式:

      • SVG (Scalable Vector Graphics): 基于XML的矢量图像格式,广泛用于网页。
      • AI (Adobe Illustrator): Adobe专有的矢量图形格式。
      • EPS (Encapsulated PostScript): 一种可以包含矢量图形的文件格式。
      • PDF (Portable Document Format): 可以包含矢量和光栅图形。
  • 在将矢量图转换为光栅图时,用户需要指定合适的分辨率以确保最终输出质量。反之,从光栅到矢量的转换则更为复杂,通常通过专用软件进行,并且结果取决于原图的复杂程度。在选择文件格式时,也应考虑到目标用途的具体要求,比如是否需要支持透明度、动画等功能。

  • HEIF是High Efficiency Image File Format的缩写,是由MPEG视频格式背后的技术团队开发的图像格式,是JPEG的直接竞争对手。HEIF压缩效率理论值几乎是JPEG的两倍,换言之,文件大小相同的情况下,HEIF图像质量可以提高一倍。相对于JPG来说,同等质量下,可以做到压缩效率更高。浏览器不支持和有限的操作系统支持。

  • JPEG (.jpg/.jpeg),支持有损压缩,能够显著减小文件大小;广泛支持。重复保存会降低图片质量;不适合需要透明度的图像。适用场景: 网页图像、社交媒体分享的照片。压缩原理: 使用离散余弦变换(DCT),保留重要的视觉信息而舍弃人眼不易察觉的部分。

  • PNG (.png),支持无损压缩和透明度;颜色丰富。文件大小相对于JPEG较大。图标、徽标等需要透明背景的图像;屏幕截图。压缩原理: 使用LZ77派生的无损数据压缩算法加上预测编码。

  • Bitmap(BMP)是一种已经过时的图像文件格式,几乎不压缩情况映射像素。这意味着BMP文件体积经常会比较庞大,不利于存储或处理。与WebP,GIF或PNG等格式相比,体积又大又没有质量上的提升。

  • Raw图像格式是数码相机用来存储高质量图像以供后期制作和编辑的文件类型。RAW文件在一张图片中包含多达16,384种不同的颜色,而不是JPEG文件仅包含256种颜色。也就是说RAW文件在调整颜色和对比度等后期处理时,有着更大的灵活性。RAW图像不适合用于网络演示或共享,并且主流浏览器或图像查看器兼容性极差,一般需要使用专业的相机图像处理软件处理。相机制造商的主要Raw图像文件类型:

    • 柯达:CR,K25,KDC
    • 佳能:CRW CR2 CR3
    • 爱普生:ERF
    • 尼康:NEF NRW
    • 奥林巴斯:ORF
    • 宾得:PEF
    • 松下:RW2
    • 索尼:ARW,SRF,SR2
  • GIF (.gif),支持动画;文件大小较小。最多支持256色;静态图像质量较差。适用场景: 简单动画;小图标。压缩原理: 使用LZW压缩算法。

  • TIFF (.tiff/.tif),支持无损和有损压缩;适用于专业出版。文件体积较大;网络上传输不够高效。适用场景: 打印行业;需要高保真度的图像处理。可以采用多种压缩方法,包括LZW、JPEG等。拓展阅读

    • import cv2
      # 读取图像
      image = cv2.imread('input.jpg')
      # 转换并保存为PNG格式
      cv2.imwrite('output.png', image)
      # 或者转换成其他格式
      cv2.imwrite('output.gif', image)
      cv2.imwrite('output.tiff', image, [int(cv2.IMWRITE_TIFF_COMPRESSION), 1])  # 选择压缩级别
      
    • 对于JPEG,可以通过cv2.IMWRITE_JPEG_QUALITY设置压缩质量,范围是0-100,默认95。PNG支持cv2.IMWRITE_PNG_COMPRESSION,数值越大压缩率越高,但处理时间更长。GIF通常不需要额外设置。TIFF除了选择压缩算法外,还可以调整压缩级别。

  • 在使用OpenCV将BMP格式的图像转换为PNG格式时,实际上涉及到了图像的编码过程。PNG是一种支持无损压缩的图像格式,这意味着在转换过程中原始图像的数据不会丢失,转换后的PNG图像可以完全恢复到原始状态。OpenCV内部利用libpng库来进行PNG图像的编码。使用OpenCV将BMP文件转换为PNG文件的一个简单示例:

    • import cv2
      # 读取BMP图像
      bmp_image = cv2.imread('input.bmp', cv2.IMREAD_UNCHANGED)  # 使用IMREAD_UNCHANGED保持原始图像的alpha通道
      # 写入PNG文件
      cv2.imwrite('output.png', bmp_image, [cv2.IMWRITE_PNG_COMPRESSION, 8])  # 设置压缩级别
      
    • cv2.IMWRITE_PNG_COMPRESSION 参数控制了PNG图像的压缩级别。这个参数的值范围通常是0到9,其中0代表无压缩,而9代表最大压缩。较大的数值意味着更好的压缩比,但同时也意味着更长的编码时间和可能稍慢的解码速度。PNG格式使用了一种基于LZ77算法的变体——deflate压缩算法,它结合了字典查找(LZ77)和霍夫曼编码(Huffman coding)。PNG还采用了预测编码来减少图像数据的冗余,从而提高压缩效率。具体来说:

      • 预处理:PNG先进行预测编码,即根据前一个像素值来预测下一个像素值,并记录实际值与预测值之间的差异。
      • 压缩:然后对这些差异值进行deflate压缩。
      • 编码:最终将压缩后的数据写入文件,同时附加必要的元数据,如图像尺寸、颜色类型等
    • import cv2
      # 读取PNG图像
      png_image = cv2.imread('input.png', cv2.IMREAD_UNCHANGED)  # 保持原始图像的alpha通道
      # 将图像写为BMP格式
      cv2.imwrite('output.bmp', png_image)
      
    • 要将PNG格式的图像解压缩并转换回BMP格式,你可以使用OpenCV来完成这项任务。这个过程实际上是将PNG图像读取进来,然后以BMP格式写回到磁盘。由于BMP是一种未经压缩的图像格式,所以从PNG转换到BMP的过程本质上就是解压缩和格式转换的过程。首先使用cv2.imread函数读取PNG文件。参数cv2.IMREAD_UNCHANGED确保图像的透明度(如果有)得以保留。然后,我们使用cv2.imwrite函数将图像以BMP格式保存到磁盘。BMP格式本身不包含任何压缩,因此这里没有额外的压缩选项需要设置。

      • 颜色空间:确保在读取和写入时颜色空间的一致性。如果PNG图像使用了特定的颜色空间(比如灰度或带有Alpha通道),那么在保存为BMP时也应该保持一致。
      • 透明度:如果PNG图像包含透明度(Alpha通道),在保存为BMP时需要注意。BMP格式支持Alpha通道,但不是所有的BMP文件都包含Alpha通道。如果你的BMP文件需要支持Alpha通道,请确保你的读写操作能够处理这种情况。
      • 文件大小:BMP文件通常比PNG文件大得多,因为它不包含任何压缩。因此,在存储或传输BMP文件时,要考虑到这一点。
  • 当使用OpenCV转换图像格式时,需要注意以下几点:颜色空间:确保输入和输出的颜色空间一致,避免意外的颜色偏差。透明度:如果源图像含有透明度信息(如Alpha通道),确保在读取和写入时都正确处理。压缩级别:适当设置压缩级别,平衡文件大小与处理性能。对于PNG,通常设置在6至9之间可以提供良好的压缩效果而不会过多增加处理时间。图像质量:虽然PNG是无损的,但如果从一个已经经过有损压缩的格式(如JPEG)转换过来,那么原始的质量损失将被继承

  • 测试压缩因子对图片大小,压缩时间,读取时间,内存占用大小(占用压缩计算)的影响。

    • import cv2
      import os
      import time
      import logging
      import matplotlib.pyplot as plt
      import psutil  # 用于获取内存使用情况
      from tqdm import tqdm
      import numpy as np
      # 设置日志
      logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
      class ImageConverter:
          def __init__(self, bmp_image_path, png_image_path, compression_levels, verbose=False, plot_results=False):
              self.bmp_image_path = bmp_image_path
              self.png_image_path = png_image_path
              self.compression_levels = compression_levels
              self.verbose = verbose  # 控制日志详细信息
              self.plot_results = plot_results  # 控制是否绘制图形
              # 确保输出目录存在
              if not os.path.exists(self.png_image_path):
                  os.makedirs(self.png_image_path)
              # 测试统计信息
              self.total_time_bmp_to_png = {}
              self.total_bmp_size = {}
              self.total_png_size = {}
              self.total_time_png_to_bmp = 0  # Initialize attribute
              self.intermediate_results = {}
          def get_memory_usage(self):
              """获取当前进程的内存使用情况"""
              process = psutil.Process(os.getpid())
              mem_info = process.memory_info()
              return mem_info.rss / (1024 ** 2)  # 转换为MB
          def convert_bmp_to_png(self):
              for compression_level in self.compression_levels:
                  self.total_time_bmp_to_png[compression_level] = 0
                  self.total_bmp_size[compression_level] = 0
                  self.total_png_size[compression_level] = 0
                  self.intermediate_results[compression_level] = []
                  bmp_files = [file for file in os.listdir(self.bmp_image_path) if file.endswith('.bmp')]
                  for file in tqdm(bmp_files, desc=f"Processing compression level {compression_level}"):
                      bmp_file_path = os.path.join(self.bmp_image_path, file)
                      # 读取BMP图像并计算读取时间
                      start_time = time.time()
                      bmp_image = cv2.imread(bmp_file_path, cv2.IMREAD_UNCHANGED)
                      read_time = time.time() - start_time
                      if bmp_image is None:
                          logging.warning(f"无法读取图像文件: {bmp_file_path}")
                          continue
                      # 获取图像维度和通道数
                      height, width = bmp_image.shape[:2]
                      channels = bmp_image.shape[2] if len(bmp_image.shape) > 2 else 1  # 通道数
      
                      # 写入PNG文件并计算写入时间
                      png_file_path = os.path.join(self.png_image_path, file.replace('.bmp', f'.png'))
                      start_time = time.time()
                      cv2.imwrite(png_file_path, bmp_image, [cv2.IMWRITE_PNG_COMPRESSION, compression_level])
                      write_time = time.time() - start_time
      
                      # 获取文件大小
                      bmp_size = os.path.getsize(bmp_file_path)
                      png_size = os.path.getsize(png_file_path)
      
                      # 记录统计信息
                      self.total_time_bmp_to_png[compression_level] += (read_time + write_time)
                      self.total_bmp_size[compression_level] += bmp_size
                      self.total_png_size[compression_level] += png_size
      
                      # 记录内存使用情况
                      memory_usage = self.get_memory_usage()
      
                      self.intermediate_results[compression_level].append({
                          "file": file,
                          "read_time": read_time,
                          "write_time": write_time,
                          "bmp_size": bmp_size,
                          "png_size": png_size,
                          "compression_ratio": bmp_size / png_size,
                          "dimensions": (width, height, channels),  # 记录包括通道的维度信息
                          "memory_usage": memory_usage  # 记录内存使用情况
                      })
      
                      # 打印信息
                      if self.verbose:
                          self.print_conversion_info(file, read_time, write_time, bmp_size, png_size, width, height, channels,
                                                     compression_level)
      
          def print_conversion_info(self, file, read_time, write_time, bmp_size, png_size, width, height, channels,
                                    compression_level):
              logging.info(f"转换文件: {file} [压缩因子: {compression_level}]")
              logging.info(f"读取时间: {read_time:.4f}秒")
              logging.info(f"写入时间: {write_time:.4f}秒")
              logging.info(f"BMP大小: {bmp_size / 1024:.2f} KB")
              logging.info(f"PNG大小: {png_size / 1024:.2f} KB")
              logging.info(f"图像维度: {width}x{height} (通道数: {channels})")
              logging.info(f"压缩比: {bmp_size / png_size:.2f}")
              logging.info(f"内存使用: {self.get_memory_usage():.2f} MB")
              logging.info("=" * 30)
      
          def summarize_conversion(self):
              for compression_level in self.compression_levels:
                  logging.info(f"转换总结 - 压缩因子: {compression_level}")
                  logging.info(f"总时间(BMP到PNG转换): {self.total_time_bmp_to_png[compression_level]:.4f}秒")
                  logging.info(f"总BMP大小: {self.total_bmp_size[compression_level] / 1024:.2f} KB")
                  logging.info(f"总PNG大小: {self.total_png_size[compression_level] / 1024:.2f} KB")
                  logging.info(
                      f"平均压缩比: {self.total_bmp_size[compression_level] / self.total_png_size[compression_level]:.2f}")
      
          def show_results(self):
              compression_ratios = []
              png_sizes = []
              total_times = []
              memory_usages = []
      
              for compression_level in self.compression_levels:
                  compression_ratios.append(compression_level)
                  png_sizes.append(self.total_png_size[compression_level])
                  total_times.append(self.total_time_bmp_to_png[compression_level])
      
                  # 计算内存使用情况的平均值
                  avg_memory_usage = np.mean(
                      [result['memory_usage'] for result in self.intermediate_results[compression_level]])
                  memory_usages.append(avg_memory_usage)
      
              # 绘制大小变化
              plt.figure(figsize=(12, 8))
      
              plt.subplot(2, 2, 1)
              plt.plot(compression_ratios, [size / 1024 for size in png_sizes], marker='o')
              plt.title('PNG Size vs Compression Level')
              plt.xlabel('Compression Level')
              plt.ylabel('PNG Size (KB)')
              plt.xticks(compression_ratios)
      
              # 绘制时间变化
              plt.subplot(2, 2, 2)
              plt.plot(compression_ratios, total_times, marker='o', color='orange')
              plt.title('Total Time vs Compression Level')
              plt.xlabel('Compression Level')
              plt.ylabel('Time (seconds)')
              plt.xticks(compression_ratios)
      
              # 绘制内存使用情况
              plt.subplot(2, 2, 3)
              plt.plot(compression_ratios, memory_usages, marker='o', color='green')
              plt.title('Memory Usage vs Compression Level')
              plt.xlabel('Compression Level')
              plt.ylabel('Memory Usage (MB)')
              plt.xticks(compression_ratios)
      
              plt.tight_layout()
              plt.show()
      
          def convert_png_to_bmp(self):
              for file in os.listdir(self.png_image_path):
                  if file.endswith('.png'):
                      png_file_path = os.path.join(self.png_image_path, file)
      
                      # 读取PNG图像并计算读取时间
                      start_time = time.time()
                      png_image = cv2.imread(png_file_path, cv2.IMREAD_UNCHANGED)
                      png_read_time = time.time() - start_time
      
                      if png_image is None:
                          logging.warning(f"无法读取图像文件: {png_file_path}")
                          continue
      
                      # 写入BMP文件并计算写入时间
                      bmp_output_file_path = os.path.join(self.bmp_image_path, file.replace('.png', '.bmp'))
                      start_time = time.time()
                      cv2.imwrite(bmp_output_file_path, png_image)
                      bmp_write_time = time.time() - start_time
      
                      # 获取文件大小
                      new_bmp_size = os.path.getsize(bmp_output_file_path)
      
                      # 记录PNG到BMP的转换时间
                      self.total_time_png_to_bmp += (png_read_time + bmp_write_time)
      
                      # 打印信息
                      self.print_png_to_bmp_info(file, png_read_time, bmp_write_time, new_bmp_size)
      
          def print_png_to_bmp_info(self, file, png_read_time, bmp_write_time, new_bmp_size):
              logging.info(f"PNG转BMP转换文件: {file}")
              logging.info(f"读取时间: {png_read_time:.4f}秒")
              logging.info(f"写入时间: {bmp_write_time:.4f}秒")
              logging.info(f"新BMP大小: {new_bmp_size / 1024:.2f} KB")
              logging.info("=" * 30)
      
          def summarize_png_to_bmp(self):
              logging.info(f"总时间(PNG到BMP转换): {self.total_time_png_to_bmp:.4f}秒")
      
      if __name__ == "__main__":
          bmp_image_path = r'.\tmp'  # BMP图片的输入目录
          png_image_path = r'.\output'  # PNG图片的输出目录
          compression_levels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]  # 定义不同的压缩级别
          verbose_mode = False  # 控制日志信息的详细程度
          plot_mode = False  # 控制是否绘制图形
          converter = ImageConverter(
              bmp_image_path,
              png_image_path,
              compression_levels,
              verbose=verbose_mode,
              plot_results=plot_mode
          )
          converter.convert_bmp_to_png()
          converter.summarize_conversion()
          if plot_mode:
              converter.show_results()
          converter.convert_png_to_bmp()
          converter.summarize_png_to_bmp()
      
    • 在我测得这一批图片中,压缩因子在2或3就可以了,压缩比约为2,再大就会性能增益并不多,反而会浪费更多的压缩时长而得不偿失。

  • PNG和JPEG之间的主要区别是,PNG是一种无损图像格式,而JPEG是一种有损图像格式。换句话说,PNG使用压缩技术而不会牺牲图像的原始质量或细节。对于附带文本的图像,教程屏幕截图,以及标志或其他精细元素需100%可见的照片,PNG才是最佳格式。对于常规博客文章配图等其他无需关注细节的图片,JPEG可以实现足够好的质量,又能够大大减少文件体积。通常可以节省50%或更多的空间,一般人肉眼难以发现质量上的任何差异。

  • BMP是一种与硬件设备无关的图像文件格式,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大。BMP文件的图像深度可选1bit、4bit、8bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。典型的BMP图像文件由三部分组成:位图文件头数据结构,它包含BMP图像文件的类型、显示内容等信息;位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息

  • JPEG是Joint Photographic Experts Group(联合图像专家组)的缩写,文件后辍名为".jpg"或".jpeg",是最常用的图像文件格式,由一个软件开发联合会组织制定,是一种有损压缩格式,能够将图像压缩在很小的储存空间,图像中重复或不重要的资料会被丢失,因此容易造成图像数据的损伤。尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量明显降低,如果追求高品质图像,不宜采用过高压缩比例。但是JPEG压缩技术十分先进,它用有损压缩方式去除冗余的图像数据,在获得极高的压缩率的同时能展现十分丰富生动的图像,换句话说,就是可以用最少的磁盘空间得到较好的图像品质。而且 JPEG是一种很灵活的格式,具有调节图像质量的功能,允许用不同的压缩比例对文件进行压缩,支持多种压缩级别,压缩比率通常在10:1到40:1之间,压缩比越大,品质就越低。比如可以把1.37Mb的BMP位图文件压缩至20.3KB。当然也可以在图像质量和文件尺寸之间找到平衡点。JPEG格式压缩的主要是高频信息,对色彩的信息保留较好,适合应用于互联网,可减少图像的传输时间,可以支持24bit真彩色,也普遍应用于需要连续色调的图像

    • JPEG2000作为JPEG的升级版,其压缩率比JPEG高约30%左右,同时支持有损和无损压缩。JPEG2000格式有一个极其重要的特征在于它能实现渐进传输,即先传输图像的轮廓,然后逐步传输数据,不断提高图像质量,让图像由朦胧到清晰显示。此外,JPEG2000还支持所谓的"感兴趣区域" 特性,可以任意指定影像上感兴趣区域的压缩质量,还可以选择指定的部分先解压缩
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羞儿

写作是兴趣,打赏看心情

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值