文章目录
(左)滑动遮罩宽度对比;(右)实时裁剪滑动对比


github:sneas/img-comparison-slider
InsCode AI:图片对比器台球厅设计
一、什么是图像对比滑块?
图像对比滑块是一种交互式图像展示方式,通过拖动滑块,可以在一张原始图像与一张处理后图像之间来回切换,观察图像前后效果变化。
使用目的
- 直观对比图像处理前后的视觉差异
- 演示图像增强、边缘检测、滤波、超分辨率等处理效果
- 强化教学与展示的视觉冲击力
二、实现思路
总体流程:用OpenCV生成两张图 → 用HTML/JS展示对比滑块
文件结构:
项目/
│
├── generate.py ← 图像处理脚本(生成图像)
├── lena.jpg ← 自动下载的原图
├── before.jpg ← 原图(保存)
├── after.jpg ← 处理图(保存)
└── compare.html ← 可视化网页,双击打开查看效果
-
generate.py:在命令行中运行python generate.py,将自动下载lena.jpg并生成before.jpg与after.jpg。 -
compare.html:双击打开compare.html,或使用右键 → 浏览器打开。
步骤一:generate.py —— 图像处理(使用OpenCV)
import cv2
import os
import urllib.request
def download_image_if_needed(filename: str, url: str) -> None:
"""
如果图像文件不存在,则从指定URL下载
"""
if not os.path.exists(filename):
print(f"正在下载 {filename} ...")
urllib.request.urlretrieve(url, filename)
print("下载完成。")
else:
print(f"{filename} 已存在,跳过下载。")
def main():
# 图像文件名与来源链接
lena_url = "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/lena.jpg"
lena_filename = "lena.jpg"
# 下载 lena.jpg(如果不存在)
download_image_if_needed(lena_filename, lena_url)
# 读取图像
img = cv2.imread(lena_filename)
if img is None:
raise IOError("图像读取失败,请检查 lena.jpg 是否有效")
# 简单图像处理:边缘检测
edges = cv2.Canny(img, 100, 200)
edges_bgr = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
# 保存原图与处理图
cv2.imwrite("before.jpg", img)
cv2.imwrite("after.jpg", edges_bgr)
print("图像保存完成:before.jpg 与 after.jpg")
if __name__ == "__main__":
main()
步骤二:compare.html —— HTML页面实现滑动对比
| 功能名称 | 功能描述 | 典型实现细节 | 适用场景 |
|---|---|---|---|
| 1. 直接宽度遮罩滑动(Width Clipping Slider) | 通过调整上层图像容器的宽度,滑动时逐渐“露出”下层图像,产生遮罩效果。 | 改变上层图像父容器宽度(style.width),滑动时覆盖区域线性变化。 | 快速实现,视觉上直观,适合简单前后对比。 |
| 2. 实时裁剪滑动(Real-time Clip-path Slider) | 利用CSS裁剪(clip-path)或overflow:hidden,滑动时实时裁剪上层图像显示区域。 | 通过裁剪属性控制图像显示区域,滑动响应更精准,动画更平滑。 | 对细节裁剪要求高,交互体验更自然,适合精细视觉对比。 |
功能一:滑动遮罩宽度对比(Width Clipping Slider)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>图像滑动对比演示</title>
<style>
.container {
position: relative;
width: 512px;
height: 512px;
overflow: hidden;
border: 2px solid #ccc;
}
.container img {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
}
.mask {
overflow: hidden;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 50%;
z-index: 2;
}
.slider-line {
position: absolute;
top: 0;
width: 2px;
height: 100%;
background: red;
z-index: 3;
}
</style>
</head>
<body>
<h3>图像处理前后滑动对比</h3>
<div class="container" id="compareBox">
<img src="before.jpg" alt="原图">
<div class="mask" id="mask">
<img src="after.jpg" alt="处理图">
</div>
<div class="slider-line" id="sliderLine" style="left: 50%;"></div>
</div>
<script>
const box = document.getElementById('compareBox');
const mask = document.getElementById('mask');
const slider = document.getElementById('sliderLine');
function updateSlider(x) {
const rect = box.getBoundingClientRect();
const offset = Math.max(0, Math.min(x - rect.left, rect.width));
mask.style.width = offset + 'px';
slider.style.left = offset + 'px';
}
let dragging = false;
box.addEventListener('mousedown', () => dragging = true);
box.addEventListener('mouseup', () => dragging = false);
box.addEventListener('mouseleave', () => dragging = false);
box.addEventListener('mousemove', e => { if (dragging) updateSlider(e.clientX); });
</script>
</body>
</html>
让整个对比容器在页面中水平和垂直居中
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>图像滑动对比演示</title>
<style>
/* 页面整体居中 */
body {
display: flex;
flex-direction: column;
justify-content: center; /* 垂直居中 */
align-items: center; /* 水平居中 */
height: 100vh; /* 视口高度 */
margin: 0; /* 去除默认外边距 */
background: #f8f8f8;
font-family: Arial, sans-serif;
gap: 16px; /* 标题和容器间距 */
}
/* 容器样式 */
.container {
position: relative;
width: 512px;
height: 512px;
overflow: hidden;
border: 2px solid #ccc;
background: #fff;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.container img {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
}
.mask {
overflow: hidden;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 50%;
z-index: 2;
}
.slider-line {
position: absolute;
top: 0;
width: 2px;
height: 100%;
background: red;
z-index: 3;
}
</style>
</head>
<body>
<h3>图像处理前后滑动对比</h3>
<div class="container" id="compareBox">
<img src="before.jpg" alt="原图" />
<div class="mask" id="mask">
<img src="after.jpg" alt="处理图" />
</div>
<div class="slider-line" id="sliderLine" style="left: 50%;"></div>
</div>
<script>
const box = document.getElementById('compareBox');
const mask = document.getElementById('mask');
const slider = document.getElementById('sliderLine');
function updateSlider(x) {
const rect = box.getBoundingClientRect();
const offset = Math.max(0, Math.min(x - rect.left, rect.width));
mask.style.width = offset + 'px';
slider.style.left = offset + 'px';
}
let dragging = false;
box.addEventListener('mousedown', () => dragging = true);
box.addEventListener('mouseup', () => dragging = false);
box.addEventListener('mouseleave', () => dragging = false);
box.addEventListener('mousemove', e => { if (dragging) updateSlider(e.clientX); });
</script>
</body>
</html>
功能二:实时裁剪滑动对比(Real-time Clipping Slider)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>图像前后实时滑动对比</title>
<style>
.container {
position: relative;
width: 512px;
height: 512px;
border: 2px solid #ccc;
overflow: hidden;
user-select: none;
}
.img-wrapper img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.top-mask {
position: absolute;
top: 0;
left: 0;
height: 100%;
overflow: hidden;
z-index: 2;
}
.slider-line {
position: absolute;
top: 0;
width: 2px;
height: 100%;
background-color: red;
z-index: 3;
pointer-events: none;
}
</style>
</head>
<body>
<h3>OpenCV图像处理对比:实时滑动展示</h3>
<div class="container" id="compareBox">
<!-- 下层图像 -->
<div class="img-wrapper">
<img src="before.jpg" alt="原图">
</div>
<!-- 上层图像容器 -->
<div class="top-mask" id="topMask" style="width: 50%;">
<img src="after.jpg" alt="处理图">
</div>
<!-- 滑动指示线 -->
<div class="slider-line" id="sliderLine" style="left: 50%;"></div>
</div>
<script>
const container = document.getElementById('compareBox');
const topMask = document.getElementById('topMask');
const sliderLine = document.getElementById('sliderLine');
function setSlider(x) {
const rect = container.getBoundingClientRect();
let offset = x - rect.left;
offset = Math.max(0, Math.min(offset, rect.width));
// 实时更新裁剪宽度与滑动线位置
topMask.style.width = offset + 'px';
sliderLine.style.left = offset + 'px';
}
let isDragging = false;
container.addEventListener('mousedown', () => isDragging = true);
container.addEventListener('mouseup', () => isDragging = false);
container.addEventListener('mouseleave', () => isDragging = false);
container.addEventListener('mousemove', e => {
if (isDragging) setSlider(e.clientX);
});
container.addEventListener('touchstart', () => isDragging = true);
container.addEventListener('touchend', () => isDragging = false);
container.addEventListener('touchcancel', () => isDragging = false);
container.addEventListener('touchmove', e => {
if (isDragging) setSlider(e.touches[0].clientX);
});
</script>
</body>
</html>
让整个对比容器在页面中水平和垂直居中
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>图像前后实时滑动对比</title>
<style>
/* 页面整体居中 */
body {
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center; /* 垂直居中 */
align-items: center; /* 水平居中 */
background: #f0f0f0;
font-family: Arial, sans-serif;
gap: 16px;
}
h3 {
margin: 0;
font-weight: normal;
}
.container {
position: relative;
width: 512px;
height: 512px;
border: 2px solid #ccc;
overflow: hidden;
user-select: none;
background: #fff;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.img-wrapper img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.top-mask {
position: absolute;
top: 0;
left: 0;
height: 100%;
overflow: hidden;
z-index: 2;
width: 50%;
}
.slider-line {
position: absolute;
top: 0;
width: 2px;
height: 100%;
background-color: red;
z-index: 3;
pointer-events: none;
left: 50%;
transform: translateX(-1px);
}
</style>
</head>
<body>
<h3>OpenCV图像处理对比:实时滑动展示</h3>
<div class="container" id="compareBox">
<!-- 下层图像 -->
<div class="img-wrapper">
<img src="before.jpg" alt="原图" />
</div>
<!-- 上层图像容器 -->
<div class="top-mask" id="topMask" style="width: 50%;">
<img src="after.jpg" alt="处理图" />
</div>
<!-- 滑动指示线 -->
<div class="slider-line" id="sliderLine" style="left: 50%;"></div>
</div>
<script>
const container = document.getElementById('compareBox');
const topMask = document.getElementById('topMask');
const sliderLine = document.getElementById('sliderLine');
function setSlider(x) {
const rect = container.getBoundingClientRect();
let offset = x - rect.left;
offset = Math.max(0, Math.min(offset, rect.width));
topMask.style.width = offset + 'px';
sliderLine.style.left = offset + 'px';
}
let isDragging = false;
container.addEventListener('mousedown', () => isDragging = true);
container.addEventListener('mouseup', () => isDragging = false);
container.addEventListener('mouseleave', () => isDragging = false);
container.addEventListener('mousemove', e => {
if (isDragging) setSlider(e.clientX);
});
container.addEventListener('touchstart', () => isDragging = true);
container.addEventListener('touchend', () => isDragging = false);
container.addEventListener('touchcancel', () => isDragging = false);
container.addEventListener('touchmove', e => {
if (isDragging) setSlider(e.touches[0].clientX);
});
</script>
</body>
</html>
备注:压缩GIF并保存
| 压缩操作 | 实现方式 | 内存影响 |
|---|---|---|
| 缩小图像尺寸 | 改变帧的宽高 | 显著减少 |
| 减少帧数 | 每隔N帧取一帧 | 明显减少 |
| 降低颜色数量 | 使用PIL的 .quantize(colors=...) | 大幅减少 |
| 设置更快播放帧率 | 延长帧时间 | 控制GIF体积 |
"""
功能目标:
读取已有GIF(如compare_slider.gif);
压缩内存占用(主要方法包括:缩小尺寸、减少帧数、降低颜色数或量化图像);
保存为新的GIF文件(如compressed_slider.gif)。
"""
from PIL import Image, ImageSequence # pip install pillow
# === 参数配置 ===
input_path = "2.gif"
output_path = "22.gif"
resize_ratio = 0.5 # 图像尺寸缩小比例(如0.5表示50%)
frame_interval = 2 # 每隔多少帧保留一帧(2表示跳过一半)
target_colors = 64 # 颜色数量上限(可调:64、128、256)
# === 读取GIF帧 ===
with Image.open(input_path) as im:
frames = []
durations = []
for i, frame in enumerate(ImageSequence.Iterator(im)):
if i % frame_interval != 0:
continue
# 转为RGB以便后处理
frame = frame.convert("RGB")
# 缩小尺寸
if resize_ratio < 1.0:
w, h = frame.size
frame = frame.resize((int(w * resize_ratio), int(h * resize_ratio)), Image.LANCZOS)
# 减色
frame = frame.quantize(colors=target_colors)
frames.append(frame)
durations.append(im.info.get("duration", 100)) # 默认每帧100ms
# === 保存压缩后的GIF ===
frames[0].save(
output_path,
save_all=True,
append_images=frames[1:],
duration=durations,
loop=0,
optimize=True
)
print(f"压缩完成,GIF保存为:{output_path}")
335

被折叠的 条评论
为什么被折叠?



