当前位置: 首页 > news >正文

解锁豆瓣高清海报(二) 使用 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 // colscol = i % colsx = col * target_wy = row * target_hcollage[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 // colscol = i % colsif row >= rows or col >= cols:break  # 超出预设的行列范围,停止放置图片x = col * target_wy = row * target_hcollage[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:# 文件大小已满足要求,跳出循环breakelif quality <= 5:# 质量已降至最低,仍然无法满足要求,发出警告并跳出循环print("警告:质量已降至极低,可能无法满足目标大小。")breakelse: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,并继续循环尝试。

相关文章:

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

解锁豆瓣高清海报(二): 使用 OpenCV 拼接和压缩 脚本地址: 项目地址: Gazer PixelWeaver.py pixel_squeezer_cv2.py 前瞻 继上一篇“解锁豆瓣高清海报(一) 深度爬虫与requests进阶之路”成功爬取豆瓣电影海报之后&#xff0c;本文将介绍如何使用 OpenCV 对这些海报进行智…...

我用Ai学Android Jetpack Compose之Card

这篇学习一下Card。回答来自 通义千问。 我想学习Card&#xff0c;麻烦你介绍一下 当然可以&#xff01;在 Jetpack Compose 中&#xff0c;Card 是一个非常常用的组件&#xff0c;用于创建带有阴影和圆角的卡片式布局。它可以帮助你轻松实现美观且一致的 UI 设计&#xff0c…...

NLP深度学习 DAY4:Word2Vec详解:两种模式(CBOW与Skip-gram)

用稀疏向量表示文本&#xff0c;即所谓的词袋模型在 NLP 有着悠久的历史。正如上文中介绍的&#xff0c;早在 2001年就开始使用密集向量表示词或词嵌入。Mikolov等人在2013年提出的创新技术是通过去除隐藏层&#xff0c;逼近目标&#xff0c;进而使这些单词嵌入的训练更加高效。…...

论文阅读(十):用可分解图模型模拟连锁不平衡

1.论文链接&#xff1a;Modeling Linkage Disequilibrium with Decomposable Graphical Models 摘要&#xff1a; 本章介绍了使用可分解的图形模型&#xff08;DGMs&#xff09;表示遗传数据&#xff0c;或连锁不平衡&#xff08;LD&#xff09;&#xff0c;各种下游应用程序之…...

Python中容器类型的数据(上)

若我们想将多个数据打包并且统一管理&#xff0c;应该怎么办? Python内置的数据类型如序列(列表、元组等)、集合和字典等可以容纳多项数据&#xff0c;我们称它们为容器类型的数据。 序列 序列 (sequence) 是一种可迭代的、元素有序的容器类型的数据。 序列包括列表 (list)…...

PySPARK带多组参数和标签的SparkSQL批量数据导出到S3的程序

设计一个基于多个带标签SparkSQL模板作为配置文件和多组参数的PySPARK代码程序&#xff0c;实现根据不同的输入参数自动批量地将数据导出为Parquet、CSV和Excel文件到S3上&#xff0c;标签和多个参数&#xff08;以“_”分割&#xff09;为组成导出数据文件名&#xff0c;文件已…...

蓝桥杯备考:模拟算法之字符串展开

P1098 [NOIP 2007 提高组] 字符串的展开 - 洛谷 | 计算机科学教育新生态 #include <iostream> #include <cctype> #include <algorithm> using namespace std; int p1,p2,p3; string s,ret; void add(char left,char right) {string tmp;for(char ch left1;…...

使用LLaMA-Factory对AI进行认知的微调

使用LLaMA-Factory对AI进行认知的微调 引言1. 安装LLaMA-Factory1.1. 克隆仓库1.2. 创建虚拟环境1.3. 安装LLaMA-Factory1.4. 验证 2. 准备数据2.1. 创建数据集2.2. 更新数据集信息 3. 启动LLaMA-Factory4. 进行微调4.1. 设置模型4.2. 预览数据集4.3. 设置学习率等参数4.4. 预览…...

@Nullable 注解

文章目录 解释 Nullable 注解注解的组成部分&#xff1a;如何使用 Nullable 注解a. 标注方法返回值&#xff1a;b. 标注方法参数&#xff1a;c. 标注字段&#xff1a; 结合其他工具与 Nonnull 配合使用总结 Nullable 注解在 Java 中的使用场景通常与 Nullability&#xff08;空…...

Arduino大师练成手册 -- 控制 AS608 指纹识别模块

要在 Arduino 上控制 AS608 指纹识别模块&#xff0c;你可以按照以下步骤进行&#xff1a; 硬件连接 连接指纹模块&#xff1a;将 AS608 指纹模块与 Arduino 连接。通常&#xff0c;AS608 使用 UART 接口进行通信。你需要将 AS608 的 TX、RX、VCC 和 GND 引脚分别连接到 Ardu…...

Mask R-CNN与YOLOv8的区别

Mask R-CNN与YOLOv8虽然都是深度学习在计算机视觉领域的应用&#xff0c;但它们属于不同类型的视觉框架&#xff0c;各有特点和优势。 以下是关于 Mask R-CNN 和 YOLOv8 的详细对比分析&#xff0c;涵盖核心原理、性能差异、应用场景和选择建议&#xff1a; 1. 核心原理与功能…...

在Ubuntu上使用Docker部署DeepSeek

在Ubuntu上使用Docker部署DeepSeek&#xff0c;并确保其可以访问公网网址进行对话&#xff0c;可以按照以下步骤进行&#xff1a; 一、安装Docker 更新Ubuntu的软件包索引&#xff1a; sudo apt-get update安装必要的软件包&#xff0c;这些软件包允许apt通过HTTPS使用存储库…...

MySQL的覆盖索引

MySQL的覆盖索引 前言 当一个索引包含了查询所需的全部字段时&#xff0c;就可以提高查询效率&#xff0c;这样的索引又被称之为覆盖索引。 以MySQL常见的三种存储引擎为例&#xff1a;InnoDB、MyISAM、Memory&#xff0c;对于覆盖索引提高查询效率的方式均不同&#xff0c;…...

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.12 连续数组:为什么contiguous这么重要?

2.12 连续数组&#xff1a;为什么contiguous这么重要&#xff1f; 目录 #mermaid-svg-wxhozKbHdFIldAkj {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-wxhozKbHdFIldAkj .error-icon{fill:#552222;}#mermaid-svg-…...

在React中使用redux

一、首先安装两个插件 1.Redux Toolkit 2.react-redux 第一步&#xff1a;创建模块counterStore 第二步&#xff1a;在store的入口文件进行子模块的导入组合 第三步&#xff1a;在index.js中进行store的全局注入 第四步&#xff1a;在组件中进行使用 第五步&#xff1a;在组件中…...

lstm预测

import numpy as np import pandas as pd import tensorflow as tf import math import matplotlib.pyplot as plt from sklearn.preprocessing import MinMaxScaler from keras.layers import LSTM,Activation,Dense,Dropout# 时间序列数据转换为监督学习的格式 def creatXY(d…...

《 C++ 点滴漫谈: 二十五 》空指针,隐秘而危险的杀手:程序崩溃的真凶就在你眼前!

摘要 本博客全面解析了 C 中指针与空值的相关知识&#xff0c;从基础概念到现代 C 的改进展开&#xff0c;涵盖了空指针的定义、表示方式、使用场景以及常见注意事项。同时&#xff0c;深入探讨了 nullptr 的引入及智能指针在提升代码安全性和简化内存管理方面的优势。通过实际…...

【AI】探索自然语言处理(NLP):从基础到前沿技术及代码实践

Hi &#xff01; 云边有个稻草人-CSDN博客 必须有为成功付出代价的决心&#xff0c;然后想办法付出这个代价。 目录 引言 1. 什么是自然语言处理&#xff08;NLP&#xff09;&#xff1f; 2. NLP的基础技术 2.1 词袋模型&#xff08;Bag-of-Words&#xff0c;BoW&#xff…...

2025年Android开发趋势全景解读

文章目录 一、界面开发&#xff1a;从"手写代码"到"智能拼装"1.1 Jetpack Compose实战进化1.2 淘汰XML布局的三大信号 二、AI融合开发&#xff1a;无需炼丹的普惠智能2.1 设备端AI三大杀手级应用2.2 成本对比&#xff1a;设备端VS云端AI 三、跨平台演进&am…...

C#面试常考随笔11:Dictionary<K, V>、Hashtable的内部实现原理是什么?效率如何?

Dictionary<K, V> 底层数据结构&#xff1a;使用哈希表&#xff08;Hash Table&#xff09;&#xff0c;由一个数组和链表&#xff08;或在.NET Core 2.1 及之后版本中&#xff0c;当链表长度达到一定阈值时转换为红黑树&#xff09;组成。数组中的每个元素称为一个桶&a…...

Linux防火墙基础

一、Linux防火墙的状态机制 1.iptables是可以配置有状态的防火墙&#xff0c;其有状态的特点是能够指定并记住发送或者接收信息包所建立的连接状态&#xff0c;其一共有四种状态&#xff0c;分别为established invalid new related。 established:该信息包已建立连接&#x…...

Qt u盘自动升级软件

Qt u盘自动升级软件 Chapter1 Qt u盘自动升级软件u盘自动升级软件思路&#xff1a;step1. 获取U盘 判断U盘名字是否正确&#xff0c; 升级文件是否存在。step2. 升级step3. 升级界面 Chapter2 Qt 嵌入式设备应用程序&#xff0c;通过U盘升级的一种思路Chapter3 在开发板上运行的…...

【Conda 和 虚拟环境详细指南】

Conda 和 虚拟环境的详细指南 什么是 Conda&#xff1f; Conda 是一个开源的包管理和环境管理系统&#xff0c;支持多种编程语言&#xff08;如Python、R等&#xff09;&#xff0c;最初由Continuum Analytics开发。 主要功能&#xff1a; 包管理&#xff1a;安装、更新、删…...

Python递归函数深度解析:从原理到实战

Python递归函数深度解析&#xff1a;从原理到实战 递归是计算机科学中重要的编程范式&#xff0c;也是算法设计的核心思想之一。本文将通过20实战案例&#xff0c;带你深入理解Python递归函数的精髓&#xff0c;掌握递归算法的实现技巧。 一、递归函数核心原理 1.1 递归三要…...

OpenGL学习笔记(五):Textures 纹理

文章目录 纹理坐标纹理环绕方式纹理过滤——处理纹理分辨率低的情况多级渐远纹理Mipmap——处理纹理分辨率高的情况加载与创建纹理 &#xff08; <stb_image.h> &#xff09;生成纹理应用纹理纹理单元练习1练习2练习3练习4 通过上一篇着色部分的学习&#xff0c;我们可以…...

【TypeScript】基础:数据类型

文章目录 TypeScript一、简介二、类型声明三、数据类型anyunknownnervervoidobjecttupleenumType一些特殊情况 TypeScript 是JavaScript的超集&#xff0c;代码量比JavaScript复杂、繁多&#xff1b;但是结构更清晰 一、简介 为什么需要TypeScript&#xff1f; JavaScript的…...

Notepad++消除生成bak文件

设置(T) ⇒ 首选项... ⇒ 备份 ⇒ 勾选 "禁用" 勾选禁用 就不会再生成bak文件了 notepad怎么修改字符集编码格式为gbk 如图所示...

Android NDK

Android NDK环境 D:\Android SDK\ndk\25.2.9519653 使用clang而不用gcc D:\Android SDK\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin\clang --version 查看是否安装成功clang ptrace 在 C 语言中&#xff0c;ptrace 已经被 Linux 内核实现&#xff0…...

内部知识库助力组织智力激发与信息共享实现业绩增长

内容概要 内部知识库是企业知识管理的核心组件&#xff0c;具有不可估量的重要性。通过构建有效的知识库&#xff0c;组织能够将孤立的知识和信息整合成为一个系统性的体&#xff0c;极大提高员工访问和利用这些信息的能力。这不仅简化了决策过程&#xff0c;还通过减少重复劳…...

通过F12收集的信息

按 F12 键打开浏览器的开发者工具&#xff08;DevTools&#xff09;可以获取部分操作系统和中间件信息&#xff0c;但能力有限。以下是具体说明&#xff1a; 一、通过 F12 收集的信息 1. 客户端操作系统信息 - Console 控制台 通过 JavaScript 直接获取客户端操作系统信息&am…...