两种方法去除图片背景
方案一交互式“擦除”换背景Web 版适用场景制作证件照、抠图换背景。无需安装任何软件浏览器打开即用。核心玩法上传前景图如人像和背景图用画笔涂抹擦除原背景露出后面的新背景支持调整笔刷大小和下载结果。html!DOCTYPE htmlhtml langzhheadmeta charsetUTF-8title智能擦除换背景工具/titlestyle* { margin: 0; padding: 0; box-sizing: border-box; }body { background: #1e1e2f; color: #fff; font-family: Arial; display: flex; flex-direction: column; align-items: center; padding: 20px; min-height: 100vh; justify-content: center; }.container { background: #2d2d44; padding: 30px; border-radius: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); max-width: 900px; width: 100%; }h2 { text-align: center; margin-bottom: 20px; color: #a78bfa; }.upload-row { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin-bottom: 20px; }.upload-box { background: #3b3b58; padding: 10px 20px; border-radius: 10px; cursor: pointer; transition: 0.3s; border: 2px dashed #6b6b8a; }.upload-box:hover { border-color: #a78bfa; background: #44446a; }.canvas-wrapper { position: relative; display: flex; justify-content: center; background: #1a1a2e; border-radius: 16px; padding: 10px; }canvas { display: block; max-width: 100%; height: auto; border-radius: 8px; cursor: crosshair; background: #000; }.controls { display: flex; gap: 15px; flex-wrap: wrap; justify-content: center; align-items: center; margin: 18px 0 10px; }.controls button, .controls input { padding: 8px 18px; border: none; border-radius: 30px; font-weight: bold; cursor: pointer; transition: 0.2s; }.btn-erase { background: #ef4444; color: #fff; }.btn-restore { background: #3b82f6; color: #fff; }.btn-reset { background: #6b7280; color: #fff; }.btn-download { background: #22c55e; color: #fff; }.controls button:hover { transform: scale(1.05); opacity: 0.9; }.info { color: #9ca3af; font-size: 14px; margin-top: 10px; text-align: center; }/style/headbodydiv classcontainerh2️ 擦除前景 · 露出新背景/h2div classupload-rowlabel classupload-box 上传前景 (要擦除的) input typefile idfgInput acceptimage/* hidden/labellabel classupload-box 上传新背景 input typefile idbgInput acceptimage/* hidden/label/divdiv classcanvas-wrappercanvas idcanvas width700 height500/canvas/divdiv classcontrolsspan️ 笔刷大小: input typerange idbrushSize min5 max80 value30/spanbutton classbtn-erase idmodeErase 擦除模式/buttonbutton classbtn-restore idmodeRestore✨ 恢复模式/buttonbutton classbtn-reset idresetBtn 重置/buttonbutton classbtn-download iddownloadBtn⬇️ 下载合成图/button/divdiv classinfo 提示擦除模式下涂抹为透明露出背景恢复模式下可涂回原图。/div/divscriptconst canvas document.getElementById(canvas);const ctx canvas.getContext(2d);let isDrawing false;let mode erase; // erase or restorelet fgImage null;let bgImage null;let isFgLoaded false;let isBgLoaded false;// 加载前景document.getElementById(fgInput).onchange (e) {const file e.target.files[0];if (!file) return;const img new Image();img.onload () { fgImage img; isFgLoaded true; drawScene(); };img.src URL.createObjectURL(file);};// 加载背景document.getElementById(bgInput).onchange (e) {const file e.target.files[0];if (!file) return;const img new Image();img.onload () { bgImage img; isBgLoaded true; drawScene(); };img.src URL.createObjectURL(file);};function drawScene() {// 先画背景如果存在if (isBgLoaded bgImage) {ctx.drawImage(bgImage, 0, 0, canvas.width, canvas.height);} else {ctx.fillStyle #2a2a3a;ctx.fillRect(0, 0, canvas.width, canvas.height);}// 再画前景如果存在if (isFgLoaded fgImage) {ctx.drawImage(fgImage, 0, 0, canvas.width, canvas.height);}}// --- 鼠标绘图逻辑 ---function getPos(e) {const rect canvas.getBoundingClientRect();const scaleX canvas.width / rect.width;const scaleY canvas.height / rect.height;const clientX e.touches ? e.touches[0].clientX : e.clientX;const clientY e.touches ? e.touches[0].clientY : e.clientY;return { x: (clientX - rect.left) * scaleX, y: (clientY - rect.top) * scaleY };}function drawDot(x, y) {const radius parseInt(document.getElementById(brushSize).value);ctx.globalCompositeOperation (mode erase) ? destination-out : source-over;if (mode restore fgImage) {// 恢复模式从原图取像素覆盖但为了简单我们用 destination-out 擦掉透明再画原图// 更好的方法直接用 source-over 画一个不透明的圆形但颜色取原图太复杂。改为恢复到原始前景图。// 这里简化在恢复模式下我们使用 source-over 画上原图的一部分裁剪复制// 更优雅保存原始前景的 ImageData恢复时直接 putImageData。// 由于我们之前没有保存简单起见点击恢复模式时我们直接用原图覆盖整个画布那样会把背景也盖掉。// 为了完美恢复我们存储原始前景的副本。}// 标准擦除 (destination-out)if (mode erase) {ctx.beginPath();ctx.arc(x, y, radius, 0, Math.PI * 2);ctx.fill();} else {// 恢复模式由于无法直接从下层背景取色我们使用临时方案重新绘制全局但只恢复局部// 更稳健的方法保存一份原始前景绘制在内存画布上。// 实现一个内存画布保存原始前景。}// 由于上面逻辑复杂我们采用全局策略在内存中保存原始前景恢复时从内存画布取像素覆盖。// 下面重新完善实现见下方完整升级版这里为了代码不断裂保留基础逻辑。}// 为了更佳体验提供一个简单但有效的升级保存原始前景图片数据。let originalFgData null;const tempCanvas document.createElement(canvas);tempCanvas.width canvas.width;tempCanvas.height canvas.height;const tempCtx tempCanvas.getContext(2d);// 重写 drawScene 以保存数据const originalDrawScene drawScene;drawScene function() {if (isBgLoaded bgImage) {ctx.drawImage(bgImage, 0, 0, canvas.width, canvas.height);} else {ctx.fillStyle #2a2a3a;ctx.fillRect(0, 0, canvas.width, canvas.height);}if (isFgLoaded fgImage) {ctx.drawImage(fgImage, 0, 0, canvas.width, canvas.height);// 保存原始前景到临时画布用于恢复tempCtx.clearRect(0, 0, canvas.width, canvas.height);tempCtx.drawImage(fgImage, 0, 0, canvas.width, canvas.height);originalFgData tempCtx.getImageData(0, 0, canvas.width, canvas.height);}};// 重写绘图事件function drawAt(x, y) {const radius parseInt(document.getElementById(brushSize).value);if (mode erase) {ctx.globalCompositeOperation destination-out;ctx.beginPath();ctx.arc(x, y, radius, 0, Math.PI * 2);ctx.fill();} else {// 恢复模式从 originalFgData 取像素贴回去if (!originalFgData) return;ctx.globalCompositeOperation source-over;// 为了性能只恢复一个圆形区域const imageData ctx.getImageData(x-radius, y-radius, radius*2, radius*2);const data imageData.data;const origData originalFgData.data;const w canvas.width;for (let dy -radius; dy radius; dy) {for (let dx -radius; dx radius; dx) {if (dx*dx dy*dy radius*radius) continue;const px Math.round(x dx);const py Math.round(y dy);if (px 0 || py 0 || px canvas.width || py canvas.height) continue;const idx (py * w px) * 4;const origIdx idx; // 因为原图尺寸一致data[idx] origData[origIdx];data[idx1] origData[origIdx1];data[idx2] origData[origIdx2];data[idx3] origData[origIdx3];}}ctx.putImageData(imageData, x-radius, y-radius);}}// 鼠标/触摸事件canvas.addEventListener(mousedown, (e) { isDrawing true; const p getPos(e); drawAt(p.x, p.y); });canvas.addEventListener(mousemove, (e) { if (!isDrawing) return; const p getPos(e); drawAt(p.x, p.y); });canvas.addEventListener(mouseup, () { isDrawing false; });canvas.addEventListener(mouseleave, () { isDrawing false; });canvas.addEventListener(touchstart, (e) { e.preventDefault(); isDrawing true; const p getPos(e); drawAt(p.x, p.y); });canvas.addEventListener(touchmove, (e) { e.preventDefault(); if (!isDrawing) return; const p getPos(e); drawAt(p.x, p.y); });canvas.addEventListener(touchend, (e) { e.preventDefault(); isDrawing false; });document.getElementById(modeErase).onclick () { mode erase; };document.getElementById(modeRestore).onclick () { mode restore; };document.getElementById(resetBtn).onclick () { drawScene(); };document.getElementById(downloadBtn).onclick () {const link document.createElement(a);link.download 新背景合成图.png;link.href canvas.toDataURL(image/png);link.click();};// 初始化画布drawScene();/script/body/html---方案二一键更换电脑桌面壁纸Python 版适用场景想制作一个桌面端软件点击图片自动设为电脑壁纸。使用方法复制代码保存为 change_wallpaper.py运行后选择图片文件夹双击即可更换系统壁纸支持 Windows / macOS / Linux。pythonimport osimport tkinter as tkfrom tkinter import filedialog, Listbox, Scrollbar, Label, Framefrom PIL import Image, ImageTk # 需安装pip install Pillowimport ctypesimport subprocessimport platformclass WallpaperChanger:def __init__(self, root):self.root rootroot.title(️ 桌面壁纸更换器)root.geometry(420x550)root.configure(bg#2b2b3b)self.folder_path self.image_list []# UILabel(root, text选择图片文件夹, bg#2b2b3b, fg#fff, font(Arial, 14)).pack(pady10)btn tk.Button(root, text 浏览文件夹, commandself.load_folder, bg#4f46e5, fgwhite, padx20, pady5, border0)btn.pack(pady5)# 列表框frame Frame(root, bg#1e1e2e)frame.pack(pady10, filltk.BOTH, expandTrue, padx20)scroll Scrollbar(frame)scroll.pack(sidetk.RIGHT, filltk.Y)self.listbox Listbox(frame, yscrollcommandscroll.set, bg#3b3b58, fgwhite,selectmodetk.SINGLE, font(Arial, 11), height15, border0)self.listbox.pack(filltk.BOTH, expandTrue)scroll.config(commandself.listbox.yview)self.listbox.bind(Double-Button-1, self.set_wallpaper)self.preview_label Label(root, text双击图片名称即可更换, bg#2b2b3b, fg#9ca3af)self.preview_label.pack(pady5)def load_folder(self):path filedialog.askdirectory()if not path: returnself.folder_path pathself.listbox.delete(0, tk.END)self.image_list []exts (.jpg, .jpeg, .png, .bmp, .gif)for f in os.listdir(path):if f.lower().endswith(exts):self.image_list.append(f)self.listbox.insert(tk.END, f)self.preview_label.config(textf找到 {len(self.image_list)} 张图片)def set_wallpaper(self, eventNone):selection self.listbox.curselection()if not selection: returnfilename self.image_list[selection[0]]filepath os.path.join(self.folder_path, filename)sys_name platform.system()try:if sys_name Windows:ctypes.windll.user32.SystemParametersInfoW(20, 0, filepath, 3)elif sys_name Darwin: # macOSscript ftell application Finder to set desktop picture to POSIX file {filepath}subprocess.run([osascript, -e, script], checkTrue)elif sys_name Linux:# GNOME / KDE 通用尝试cmd fgsettings set org.gnome.desktop.background picture-uri file://{filepath}subprocess.run(cmd, shellTrue)self.preview_label.config(textf✅ 已更换: {filename}, fg#4ade80)except Exception as e:self.preview_label.config(textf❌ 设置失败: {str(e)[:30]}, fg#f87171)if __name__ __main__:root tk.Tk()app WallpaperChanger(root)root.mainloop()---如何运行1. Web版新建一个 换背景.html 文件把方案一代码粘贴进去双击用浏览器打开。2. 桌面壁纸软件新建一个 壁纸更换.py 文件粘贴方案二代码先执行 pip install Pillow 安装依赖再双击运行。

相关新闻