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

news2025/2/3 8:59:40

解锁豆瓣高清海报(二): 使用 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,并继续循环尝试。

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

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

相关文章

利用腾讯云cloud studio云端免费部署deepseek-R1

1. cloud studio 1.1 cloud studio介绍 Cloud Studio&#xff08;云端 IDE&#xff09;是基于浏览器的集成式开发环境&#xff0c;为开发者提供了一个稳定的云端工作站。支持CPU与GPU的访问。用户在使用 Cloud Studio 时无需安装&#xff0c;随时随地打开浏览器即可使用。Clo…

DeepSeek r1本地安装全指南

环境基本要求 硬件配置 需要本地跑模型&#xff0c;兼顾质量、性能、速度以及满足日常开发需要&#xff0c;我们需要准备以下硬件&#xff1a; CPU&#xff1a;I9内存&#xff1a;128GB硬盘&#xff1a;3-4TB 最新SSD&#xff0c;C盘确保有400GB&#xff0c;其它都可划成D盘…

基于VMware的ubuntu与vscode建立ssh连接

1.首先安装openssh服务 sudo apt update sudo apt install openssh-server -y 2.启动并检查ssh服务状态 到这里可以按q退出 之后输入命令 &#xff1a; ip a 红色挡住的部分就是我们要的地址&#xff0c;这里就不展示了哈 3.配置vscode 打开vscode 搜索并安装&#xff1a;…

【LLM-agent】(task2)用llama-index搭建AI Agent

note LlamaIndex 实现 Agent 需要导入 ReActAgent 和 Function Tool&#xff0c;循环执行&#xff1a;推理、行动、观察、优化推理、重复进行。可以在 arize_phoenix 中看到 agent 的具体提示词&#xff0c;工具被装换成了提示词ReActAgent 使得业务自动向代码转换成为可能&am…

【Redis】Redis 经典面试题解析:深入理解 Redis 的核心概念与应用

Redis 是一个高性能的键值存储系统&#xff0c;广泛应用于缓存、消息队列、排行榜等场景。在面试中&#xff0c;Redis 是一个高频话题&#xff0c;尤其是其核心概念、数据结构、持久化机制和高可用性方案。 1. Redis 是什么&#xff1f;它的主要特点是什么&#xff1f; 答案&a…

FastExcel使用详解

文章目录 FastExcel使用详解一、引言二、环境准备与依赖引入1、Maven 依赖引入2、实体类定义 三、核心操作&#xff1a;读写 Excel1、读取 Excel1.1 自定义监听器1.2 读取文件 2、写入 Excel2.1 简单写入2.2 模板写入 四、Spring Boot 集成示例1、文件上传&#xff08;导入&…

图漾相机——C++语言属性设置

文章目录 前言1.SDK API功能介绍1.1 Device组件下的API测试1.1.1 相机工作模式设置&#xff08;TY_TRIGGER_PARAM_EX&#xff09;1.1.2 TY_INT_FRAME_PER_TRIGGER1.1.3 TY_INT_PACKET_DELAY1.1.4 TY_INT_PACKET_SIZE1.1.5 TY_BOOL_GVSP_RESEND1.1.6 TY_BOOL_TRIGGER_OUT_IO1.1.…

解决MacOS安装软件时提示“打不开xxx软件,因为Apple无法检查其是否包含恶意软件”的问题

macOS 系统中如何开启“任何来源”以解决安装报错问题&#xff1f; 大家好&#xff01;今天我们来聊聊在使用 macOS 系统 时&#xff0c;遇到安装应用软件时出现报错的情况。这种情况常常发生在安装一些来自第三方开发者的应用时&#xff0c;因为 macOS 会默认阻止不明开发者的…

实验十 Servlet(一)

实验十 Servlet(一) 【实验目的】 1&#xff0e;了解Servlet运行原理 2&#xff0e;掌握Servlet实现方式 【实验内容】 1、参考课堂例子&#xff0c;客户端通过login.jsp发出登录请求&#xff0c;请求提交到loginServlet处理。如果用户名和密码相同则视为登录成功&#xff0c…

MyBatis-Plus笔记-快速入门

大家在日常开发中应该能发现&#xff0c;单表的CRUD功能代码重复度很高&#xff0c;也没有什么难度。而这部分代码量往往比较大&#xff0c;开发起来比较费时。 因此&#xff0c;目前企业中都会使用一些组件来简化或省略单表的CRUD开发工作。目前在国内使用较多的一个组件就是…

《超自然》:科学与灵性融合的自我转变之路

在现代社会中&#xff0c;许多人开始探寻自我成长、身心疗愈与灵性提升的可能性。Bestselling author Dr. Joe Dispenza 的《超自然&#xff1a;普通人如何创造非凡人生》正是在这样的大背景下问世的。书中既融合了量子物理、神经科学和表观遗传学的前沿理论&#xff0c;又吸收…

《Origin画百图》之脊线图

1.数据准备&#xff1a;将数据设置为y 2.选择绘图>统计图>脊线图 3.生成基础图形&#xff0c;并不好看&#xff0c;接下来对图形属性进行设置 4.双击图形>选择图案>颜色选择按点>Y值 5.这里发现颜色有色阶&#xff0c;过度并不平滑&#xff0c;需要对色阶进行更…

w189电商平台的设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

让banner.txt可以自动读取项目版本

文章目录 1.sunrays-dependencies1.配置插件2.pluginManagement统一指定版本 2.common-log4j2-starter1.banner.txt使用$ 符号取出2.查看效果 1.sunrays-dependencies 1.配置插件 <!-- 为了让banner.txt自动获取版本号 --><plugin><groupId>org.apache.mave…

96,【4】 buuctf web [BJDCTF2020]EzPHP

进入靶场 查看源代码 GFXEIM3YFZYGQ4A 一看就是编码后的 1nD3x.php 访问 得到源代码 <?php // 高亮显示当前 PHP 文件的源代码&#xff0c;用于调试或展示代码结构 highlight_file(__FILE__); // 关闭所有 PHP 错误报告&#xff0c;防止错误信息泄露可能的安全漏洞 erro…

基于SpringBoot的智慧康老疗养院管理系统的设计与实现(源码+SQL脚本+LW+部署讲解等)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

音视频多媒体编解码器基础-codec

如果要从事编解码多媒体的工作&#xff0c;需要准备哪些更为基础的内容&#xff0c;这里帮你总结完。 因为数据类型不同所以编解码算法不同&#xff0c;分为图像、视频和音频三大类&#xff1b;因为流程不同&#xff0c;可以分为编码和解码两部分&#xff1b;因为编码器实现不…

Java线程认识和Object的一些方法ObjectMonitor

专栏系列文章地址&#xff1a;https://blog.csdn.net/qq_26437925/article/details/145290162 本文目标&#xff1a; 要对Java线程有整体了解&#xff0c;深入认识到里面的一些方法和Object对象方法的区别。认识到Java对象的ObjectMonitor&#xff0c;这有助于后面的Synchron…

pytorch实现长短期记忆网络 (LSTM)

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 LSTM 通过 记忆单元&#xff08;cell&#xff09; 和 三个门控机制&#xff08;遗忘门、输入门、输出门&#xff09;来控制信息流&#xff1a; 记忆单元&#xff08;Cell State&#xff09; 负责存储长期信息&…

Games104——引擎工具链高级概念与应用

世界编辑器 其实是一个平台&#xff08;hub&#xff09;&#xff0c;集合了所有能够制作地形世界的逻辑 editor viewport&#xff1a;可以说是游戏引擎的特殊视角&#xff0c;会有部分editor only的代码&#xff08;不小心开放就会变成外挂入口&#xff09;Editable Object&…