图像对比滑块(Image Comparison Slider)

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

(左)滑动遮罩宽度对比;(右)实时裁剪滑动对比
在这里插入图片描述在这里插入图片描述

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}")

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

胖墩会武术

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值