解锁豆瓣高清海报(二) 使用 OpenCV 拼接和压缩

解锁豆瓣高清海报(二): 使用 OpenCV 拼接和压缩

脚本地址:

项目地址: Gazer

PixelWeaver.py

pixel_squeezer_cv2.py

前瞻

继上一篇“解锁豆瓣高清海报(一) 深度爬虫与requests进阶之路”成功爬取豆瓣电影海报之后,本文将介绍如何使用 OpenCV 对这些海报进行智能拼接和压缩。我们将开发两个实用的 Python 脚本:PixelWeaver.pypixel_squeezer_cv2.py,前者可以根据指定的行列数和目标尺寸,智能地裁剪、调整并拼接多张图片,实现近似无损的拼接效果;后者则可以将大型图片压缩到指定大小,同时尽可能地保持图片质量。这两个脚本也可以作为通用的图片拼接/压缩工具使用。

使用方法

  1. 克隆或下载项目代码。
  2. 安装依赖: pip install opencv-python numpy ,或者克隆项目代码后 pip install -r requirements.txt

PixelWeaver.py

  • 指定 image_dirs,保存的图片文件夹路径, 支持填写多个文件夹。
  • 指定 output_path 保存路径, 一定要写文件名, 如r"E:\Gazer\DoubanGaze\data\poster\movie_2022.jpg"
  • 设置拼接的行和列 rowscols
  • 设置目标尺寸/分辨率(px) target_size

可选参数

  • 按照日期顺序排列: filenames.sort(key=extract_date, reverse=True) 改为 filenames.sort(key=extract_date, reverse=False)
  • 指定正则表达式: re.search(r"(\d{4}_\d{2}_\d{2})", filename) # 匹配2024_12_30, 不匹配的文件会按照文件名顺序排在后面

pixel_squeezer_cv2.py

  • 指定 image_dirs,输入图片路径
  • 指定 output_path 保存路径, 一定要写文件名, 如r"E:\Gazer\DoubanGaze\data\poster\movie_2022_compressed.jpg"
  • 指定压缩到多大和最大宽度 target_size_mb=10 max_width=1600, 这里目标大小为 10MB 以下, 最大宽度为1600

可选参数

  • 质量参数可选 0 到 100(包含 0 和 100) quality = 100

    说明
    经过测试, 合成图大小为 115MB, jpg 格式, quality = 100 可以压缩到 5.28MB. 所以脚本里默认是 100 了.

PixelWeaver.py

代码结构

  1. process_image 处理单张图片,进行智能裁剪或调整 (OpenCV)
  2. extract_date 从文件名中提取日期,用于排序 (regex)
  3. process_all_images 处理目录下的所有图片,返回处理后的图片列表
  4. create_collage 拼接海报并输出

说明和测试结果

上一篇中提到了仍有可能碰到 418 错误导致不能成功保存图片, 故建议按照日志中的链接保存图片并规范命名(只要以日期形式如 2024_12_31 开头即可), 以保证这个脚本中的正则表达式能愉快工作.

关于设置目标尺寸 target_size, 可以观察爬取的图片的分辨率, 我测试之后使用的数据是 1080 × 1600. 过大的图片会根据中心位置裁剪, 过小的图片使用插值缩放.
在这里插入图片描述

输入图片数量不足以填满拼接网格的处理

当输入的图片数量无法填满 rows * cols 的拼接网格时,create_collage 函数会在空白位置填充黑色。为了避免程序出错, 修改后的代码会在计算 rowcol 超出预设范围时,自动停止放置图片, 不会尝试访问不存在的图片元素。

原始的 create_collage 函数的核心循环部分:

  for i in range(num_images):
      row = i // cols
      col = i % cols
      x = col * target_w
      y = row * target_h
      collage[y:y+target_h, x:x+target_w] = processed_images[i]

这段代码的逻辑是:依次取出 processed_images 列表中的每一张图片,计算它应该放置的行列位置 (row, col) 和坐标 (x, y),然后将图片复制到 collage 画布的相应位置。

问题出在哪里呢?

在拼接图片的过程中,如果遇到图片数量 num_images 少于预设的行列数 rows * cols 的情况,原始的代码逻辑可能会导致程序尝试访问 processed_images 列表中不存在的元素,从而引发错误。

为了解决这个问题,修改代码逻辑,加入安全检查机制。

这是修改后的代码:

for i in range(num_images):
    row = i // cols
    col = i % cols
    if row >= rows or col >= cols:
        break  # 超出预设的行列范围,停止放置图片
    x = col * target_w
    y = row * target_h
    collage[y:y+target_h, x:x+target_w] = processed_images[i]

关键在于这行代码:

    if row >= rows or col >= cols:
        break  # 超出预设的行列范围,停止放置图片

它的作用是:在每次循环迭代中,除了根据 i 计算当前图片的 rowcol 之外,还增加了一个额外的判断:如果计算出来的 row 大于等于预设的 rows,或者 col 大于等于预设的 cols,就立即跳出循环,不再继续放置图片,并结束循环。这确保了循环不会尝试访问 processed_images 列表中不存在的元素,也不会在 collage 画布的错误位置放置图片。

为什么这样可以解决问题?

因为 for i in range(num_images) 循环只会执行 num_images 次,并且 rowcol 是基于 i 计算得出的,而 if row >= rows or col >= cols: break 这行代码可以确保,即使 num_images 小于 rows * cols,循环也不会尝试访问不存在的图片元素或在超出预设行列范围的位置放置图片。即使 num_images 小于 rows * cols,循环也会在 num_images 次之内结束, 提前结束循环, 避免访问不存在的元素或者在错误位置放置图片。

让我们通过一个例子来说明:

  • rows = 3 (预设 3 行)
  • cols = 3 (预设 3 列)
  • rows * cols = 9 (总共 9 个格子)
  • num_images = 7 (实际只有 7 张图片)

在这种情况下,for 循环会执行 7 次 (因为 num_images 是 7),i 的值依次为 0, 1, 2, 3, 4, 5, 6。

i 循环到 6 的时候 (放置第 7 张图片):

  • row = 6 // 3 = 2
  • col = 6 % 3 = 0
  • row (2) 小于 rows (3), col (0) 小于 cols (3),所以会放置第 7 张图片 (processed_images[6])

当循环结束时, 不会计算 i=7 时的 rowcol 了. 因为 num_images 是 7, 所以 for i in range(num_images) 最后一次循环 i 的值是 6.

由于循环在 i=6 时结束, 因此循环不会尝试访问 processed_images[7], 也不会计算 i=7 时的 rowcol

这样,即使只有 7 张图片,程序也不会出错,并且会正确地将 7 张图片放置到 collage 画布的前 7 个格子上,剩下的格子将保持空白(在代码中看起来是黑色,因为 collage 初始化为黑色背景)。

总结一下,if row >= rows or col >= cols: break 这行代码在这里起到了一个额外的保护作用。它确保了在 num_images 小于 rows * cols 的情况下,循环不会尝试访问不存在的图片元素或在超出预设行列范围的位置放置图片,从而避免了程序出错。即使我们确定 num_images 小于 rows * cols,这行代码也能增强程序的健壮性,使其能够处理更多不同的情况,并使代码的逻辑更加清晰。

近似无损拼接

PixelWeaver.py 脚本在拼接图片时,通过精心的裁剪和缩放策略,以及高质量的插值算法 (cv2.INTER_AREA 用于缩小, cv2.INTER_CUBIC 用于放大), 尽可能地保留了原始图片的细节, 实现了肉眼难以察觉损失的近似无损拼接。即使是 110 张海报拼接成一张大图,最终的图片质量依然非常出色。

原因:

  1. 图片处理过程(process_image 函数):
    • 在这个脚本中,每张图片都会经过 process_image 函数的处理。这个函数主要做了两件事:

      1. 裁剪: 当图片尺寸大于目标尺寸时,会裁剪图片中心区域。
      2. 缩放: 当图片尺寸不等于目标尺寸时, 会进行缩放, 确保所有图片尺寸一致。
    • 裁剪操作本身就是一种有损操作。因为它会直接丢弃图片边缘的部分像素信息。

    • 缩放操作虽然理论上有可能造成图像信息的损失, 但是, 如果使用高质量的插值算法, 可以将损失控制在肉眼几乎无法察觉的范围内.

  2. 拼接过程 (create_collage 函数):
    • 拼接过程本身并不会对图片数据进行修改, 可以看作是无损的. 它只是将所有图片数据按顺序拷贝到一张大的画布上。
  3. 图片保存 (cv2.imwrite 函数):
    • cv2.imwrite() 函数在保存图片时, 会根据你指定的文件格式 (例如 .jpg, .png) 进行编码压缩.
    • 如果保存为 .jpg 格式,那么这肯定是一个有损压缩的过程。JPEG 格式是一种有损压缩的图片格式,它通过牺牲一定的图片质量来换取较小的文件体积。
    • 如果保存为 .png 格式,那么可以认为是无损压缩, 或者损失极小。 PNG 格式是一种无损压缩的图片格式,它可以保留图片的原始信息,但文件体积通常比 JPEG 格式大.

所以, 110张海报拼接后得到59.2MB, 并且感觉质量特别棒, 这也印证了我们上面的分析. 因为之前使用的是 .jpg 格式来保存的, 所以图片有压缩, 但是这个压缩带来的损失, 从观感上来说, 是很小的, 几乎无法察觉.

插值算法的功劳:

如果要说哪个算法起到了最大的作用,那应该是在缩放操作中使用的 插值算法

在代码中,有两种情况会进行缩放:

  1. 图片尺寸大于目标尺寸,裁剪后如果尺寸仍然和目标尺寸不同, 则进行缩放:

    if (cropped_img.shape[1] != target_w) or (cropped_img.shape[0] != target_h) :
              cropped_img = cv2.resize(cropped_img, target_size, interpolation = cv2.INTER_AREA)
    

    这里使用了 cv2.INTER_AREA 插值算法。

  2. 图片尺寸小于目标尺寸:

        resized_img = cv2.resize(img, target_size, interpolation = cv2.INTER_CUBIC) # 使用 cv2.INTER_CUBIC
    

    这里使用了 cv2.INTER_CUBIC 插值算法。

  • cv2.INTER_AREA 基于局部像素的区域重采样。它适用于缩小图像。这个算法在进行图片缩小的时候, 能较好地保留图片的质量和细节.
  • cv2.INTER_CUBIC 基于 4x4 像素邻域的立方插值。它适用于放大图像。这个算法在图片放大的时候, 能获得一个相对平滑和清晰的结果.

这两个插值算法在 OpenCV 中都属于高质量的插值算法,能够在缩放过程中最大程度地保留图像的细节,减少失真。 因此,即使图片经过了缩放,最终的拼接结果仍然能够保持很好的质量。

process_image 函数详解

process_image 函数负责处理每一张输入图片,它的主要任务是根据目标尺寸 (target_size) 对图片进行智能裁剪或调整。

裁剪策略:

当图片尺寸大于目标尺寸时,为了尽可能保留图片的关键信息,process_image裁剪图片的中心区域。这是因为大多数海报的主体内容通常位于图片的中心位置。

缩放策略:

为了保证所有图片都能无缝拼接,process_image 会将所有图片都调整到相同的尺寸 (target_size)。在缩放过程中,根据不同的情况选择不同的插值算法:

# 代码摘自 PixelWeaver.py 的 process_image 函数
if (img.shape[1]  target_w) or (img.shape[0]  target_h):
    # 裁剪图片中心区域
    # ...
    if (cropped_img.shape[1] != target_w) or (cropped_img.shape[0] != target_h) :
        cropped_img = cv2.resize(cropped_img, target_size, interpolation = cv2.INTER_AREA) # 使用 cv2.INTER_AREA 进行缩小
elif (img.shape[1] != target_w) or (img.shape[0] != target_h):
    # 缩放图片以匹配目标尺寸
    resized_img = cv2.resize(img, target_size, interpolation = cv2.INTER_CUBIC) # 使用 cv2.INTER_CUBIC 进行放大

问题来了, 115MB 的图无法分享和上传到网上了, 所以还是使用pixel_squeezer_cv2.py压缩一下吧.

pixel_squeezer_cv2.py

说明和测试结果

使用 OpenCV 压缩 JPEG 图片的参数主要是 cv2.IMWRITE_JPEG_QUALITY,它的范围是 0-100,数值越小,压缩率越高,图片质量越差。

因为不同的图片内容(例如纹理复杂程度、颜色丰富程度)对压缩的敏感度不同。 有些图片可能压缩到 quality=40 还能接受,有些可能到 quality=50 就已经出现明显的失真了。

压缩效果示例 (压缩后为 5.28MB ):

海报压缩效果.jpg

compress_image_cv2 函数详解

compress_image_cv2 函数负责将图片压缩到指定大小 (MB) 以下,同时尽可能地保持图片质量。它接受以下几个关键参数:

  • target_size_mb: 目标文件大小,单位为 MB。
  • max_width: 图片的最大宽度。如果图片的宽度超过这个值,将会被等比例缩小。
  • max_height: 图片的最大高度。如果图片的高度超过这个值,将会被等比例缩小。

工作原理:

compress_image_cv2 函数使用一个 while 循环来不断尝试不同的 JPEG 压缩质量 (quality),直到压缩后的文件大小小于等于 target_size_mb

quality = 75  # 初始质量值
while True:
    # ...
    cv2.imwrite(temp_output_path, image, [cv2.IMWRITE_JPEG_QUALITY, quality]) # 使用当前 quality 值保存图片
    file_size_mb = os.path.getsize(temp_output_path) / (1024 * 1024) # 获取文件大小
    print(f"Quality: {quality}, Size: {file_size_mb:.2f} MB")

    if file_size_mb <= target_size_mb:
        # 文件大小已满足要求,跳出循环
        break
    elif quality <= 5:
        # 质量已降至最低,仍然无法满足要求,发出警告并跳出循环
        print("警告:质量已降至极低,可能无法满足目标大小。")
        break
    else:
        quality -= 5  # 降低 quality 值,继续尝试

代码解释:

  1. 函数首先根据 max_widthmax_height 参数调整图片的尺寸。
  2. 然后,它从初始的 quality 值 (默认为 75) 开始,在一个 while 循环中不断尝试压缩图片。
  3. 每次循环中,它使用当前的 quality 值将图片保存为一个临时文件,并获取临时文件的大小。
  4. 如果文件大小小于等于 target_size_mb,则说明压缩成功,函数将临时文件重命名为最终的输出文件,并跳出循环。
  5. 如果文件大小仍然大于 target_size_mb,并且 quality 值已经降至 5 或以下,则说明无法在保持可接受质量的前提下将图片压缩到目标大小,函数会发出警告,并将当前质量下的临时文件作为最终输出。
  6. 否则,函数会将 quality 值降低 5,并继续循环尝试。
    环中,它使用当前的 quality 值将图片保存为一个临时文件,并获取临时文件的大小。
  7. 如果文件大小小于等于 target_size_mb,则说明压缩成功,函数将临时文件重命名为最终的输出文件,并跳出循环。
  8. 如果文件大小仍然大于 target_size_mb,并且 quality 值已经降至 5 或以下,则说明无法在保持可接受质量的前提下将图片压缩到目标大小,函数会发出警告,并将当前质量下的临时文件作为最终输出。
  9. 否则,函数会将 quality 值降低 5,并继续循环尝试。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++:虚函数与多态性习题2

题目内容&#xff1a; 编写程序&#xff0c;声明抽象基类Shape&#xff0c;由它派生出3个派生类&#xff1a;Circle、Rectangle、Triangle&#xff0c;用虚函数分别计算图形面积&#xff0c;并求它们的和。要求用基类指针数组&#xff0c;使它每一个元素指向一个派生类对象。 …

JVM运行时数据区域-附面试题

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域 有各自的用途&#xff0c;以及创建和销毁的时间&#xff0c;有的区域随着虚拟机进程的启动而一直存在&#xff0c;有些区域则是 依赖用户线程的启动和结束而建立和销毁。 1. 程序计…

电脑优化大师-解决电脑卡顿问题

我们常常会遇到电脑运行缓慢、网速卡顿的情况&#xff0c;但又不知道是哪个程序在占用过多资源。这时候&#xff0c;一款能够实时监控网络和系统状态的工具就显得尤为重要了。今天&#xff0c;就来给大家介绍一款小巧实用的监控工具「TrafficMonitor」。 「TrafficMonitor 」是…

跨组织环境下 MQTT 桥接架构的评估

论文标题 中文标题&#xff1a; 跨组织环境下 MQTT 桥接架构的评估 英文标题&#xff1a; Evaluation of MQTT Bridge Architectures in a Cross-Organizational Context 作者信息 Keila Lima, Tosin Daniel Oyetoyan, Rogardt Heldal, Wilhelm Hasselbring Western Norway …

Baklib揭示内容中台实施最佳实践的策略与实战经验

内容概要 在当前数字化转型的浪潮中&#xff0c;内容中台的概念日益受到关注。它不再仅仅是一个内容管理系统&#xff0c;而是企业提升运营效率与灵活应对市场变化的重要支撑平台。内容中台的实施离不开最佳实践的指导&#xff0c;这些实践为企业在建设高效内容中台时提供了宝…

牛客周赛round78 B,C

B.一起做很甜的梦 题意&#xff1a;就是输出n个数&#xff08;1-n&#xff09;&#xff0c;使输出的序列中任意选连续的小序列&#xff08;小序列长度>2&&<n-1&#xff09;不符合排列&#xff08;例如如果所选长度为2&#xff0c;在所有长度为2 的小序列里不能出…

微机原理与接口技术期末大作业——4位抢答器仿真

在微机原理与接口技术的学习旅程中&#xff0c;期末大作业成为了检验知识掌握程度与实践能力的关键环节。本次我选择设计并仿真一个 4 位抢答器系统&#xff0c;通过这个项目&#xff0c;深入探索 8086CPU 及其接口技术的实际应用。附完整压缩包下载。 一、系统设计思路 &…

Android记事本App设计开发项目实战教程2025最新版Android Studio

平时上课录了个视频&#xff0c;从新建工程到打包Apk&#xff0c;从头做到尾&#xff0c;没有遗漏任何实现细节&#xff0c;欢迎学过Android基础的同学参加&#xff0c;如果你做过其他终端软件开发&#xff0c;也可以学习&#xff0c;快速上手Android基础开发。 Android记事本课…

设计模式Python版 组合模式

文章目录 前言一、组合模式二、组合模式实现方式三、组合模式示例四、组合模式在Django中的应用 前言 GOF设计模式分三大类&#xff1a; 创建型模式&#xff1a;关注对象的创建过程&#xff0c;包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式…

4-图像梯度计算

文章目录 4.图像梯度计算(1)Sobel算子(2)梯度计算方法(3)Scharr与Laplacian算子4.图像梯度计算 (1)Sobel算子 图像梯度-Sobel算子 Sobel算子是一种经典的图像边缘检测算子,广泛应用于图像处理和计算机视觉领域。以下是关于Sobel算子的详细介绍: 基本原理 Sobel算子…

MATLAB实现多种群遗传算法

多种群遗传算法&#xff08;MPGA, Multi-Population Genetic Algorithm&#xff09;是一种改进的遗传算法&#xff0c;它通过将种群分成多个子种群并在不同的子种群之间进行交叉和交换&#xff0c;旨在提高全局搜索能力并避免早期收敛。下面是多种群遗传算法的主要步骤和流程&a…

WebRtc06: 音视频数据采集

音视频采集API 通过getUserMedia这个API去获取视频音频&#xff0c; 通过constraints这个对象去配置偏好&#xff0c;比如视频宽高、音频降噪等 测试代码 index.html <html><head><title>WebRtc capture video and audio</title></head><…

浅析CDN安全策略防范

CDN&#xff08;内容分发网络&#xff09;信息安全策略是保障内容分发网络在提供高效服务的同时&#xff0c;确保数据传输安全、防止恶意攻击和保护用户隐私的重要手段。以下从多个方面详细介绍CDN的信息安全策略&#xff1a; 1. 数据加密 数据加密是CDN信息安全策略的核心之…

高温环境对电机性能的影响与LabVIEW应用

电机在高温环境下的性能可能受到多种因素的影响&#xff0c;尤其是对于持续工作和高负荷条件下的电机。高温会影响电机的效率、寿命以及可靠性&#xff0c;导致设备出现过热、绝缘损坏等问题。因此&#xff0c;在设计电机控制系统时&#xff0c;特别是在高温环境下&#xff0c;…

MapReduce简单应用(一)——WordCount

目录 1. 执行过程1.1 分割1.2 Map1.3 Combine1.4 Reduce 2. 代码和结果2.1 pom.xml中依赖配置2.2 工具类util2.3 WordCount2.4 结果 参考 1. 执行过程 假设WordCount的两个输入文本text1.txt和text2.txt如下。 Hello World Bye WorldHello Hadoop Bye Hadoop1.1 分割 将每个文…

PPT演示设置:插入音频同步切换播放时长计算

PPT中插入音频&同步切换&放时长计算 一、 插入音频及音频设置二、设置页面切换和音频同步三、播放时长计算 一、 插入音频及音频设置 1.插入音频&#xff1a;点击菜单栏插入-音频-选择PC上的音频&#xff08;已存在的音频&#xff09;或者录制音频&#xff08;现场录制…

32.Word:巧克力知识宣传【32】

目录 NO1.2.3 NO4.5 NO5制表位设置​ ​NO6.7​ NO8.9图表 NO10​ NO11.12 NO1.2.3 FnF12或另存为&#xff1a;考生文件夹&#xff1a;Word.docx布局→纸张大小→页面设置对话框→页边距&#xff1a;上下左右ctrlx剪切文本→插入→文本框选择对应的→手动拖拉文本框到合…

【零拷贝】

目录 一&#xff1a;了解IO基础概念 二&#xff1a;数据流动的层次结构 三&#xff1a;零拷贝 1.传统IO文件读写 2.mmap 零拷贝技术 3.sendFile 零拷贝技术 一&#xff1a;了解IO基础概念 理解CPU拷贝和DMA拷贝 ​ 我们知道&#xff0c;操作系统对于内存空间&…

数据分析系列--⑨RapidMiner训练集、测试集、验证集划分

一、数据集获取 二、划分数据集 1.导入和加载数据 2.数据集划分 2.1 划分说明 2.2 方法一 2.3 方法二 一、数据集获取 点击下载数据集 此数据集包含538312条数据. 二、划分数据集 1.导入和加载数据 2.数据集划分 2.1 划分说明 2.2 方法一 使用Filter Example Range算子. …

vsnprintf() 将可变参数格式化输出到字符数组

vsnprintf{} 将可变参数格式化输出到一个字符数组 1. function vsnprintf()1.1. const int num_bytes vsnprintf(NULL, 0, format, arg); 2. Parameters3. Return value4. Example5. llama.cppReferences 1. function vsnprintf() https://cplusplus.com/reference/cstdio/vs…