[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片噪声添加工具</title>
<style>
:root {
--primary-color: #4a6fa5;
--secondary-color: #6b8cbc;
--accent-color: #ff6b6b;
--light-bg: #f8f9fa;
--dark-bg: #343a40;
--border-radius: 8px;
--box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
color: #333;
min-height: 100vh;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 30px;
color: white;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
background: linear-gradient(90deg, #ff8a00, #e52e71);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
header p {
font-size: 1.2rem;
max-width: 800px;
margin: 0 auto;
color: rgba(255, 255, 255, 0.85);
}
.app-card {
background-color: rgba(255, 255, 255, 0.92);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
padding: 30px;
margin-bottom: 30px;
backdrop-filter: blur(10px);
}
.upload-section {
text-align: center;
padding: 30px;
border: 2px dashed #ced4da;
border-radius: var(--border-radius);
transition: all 0.3s;
margin-bottom: 30px;
background-color: rgba(255, 255, 255, 0.7);
}
.upload-section:hover {
border-color: var(--primary-color);
background-color: rgba(255, 255, 255, 0.9);
}
.upload-icon {
font-size: 4rem;
color: #6c757d;
margin-bottom: 20px;
}
.controls {
display: flex;
flex-direction: column;
gap: 25px;
margin-bottom: 30px;
}
.control-group {
background-color: white;
padding: 20px;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
}
.control-group h3 {
margin-bottom: 15px;
color: var(--primary-color);
display: flex;
align-items: center;
gap: 10px;
}
.slider-container {
display: flex;
flex-direction: column;
gap: 10px;
}
.slider-label {
display: flex;
justify-content: space-between;
font-weight: 500;
}
input[type="range"] {
width: 100%;
height: 8px;
-webkit-appearance: none;
background: linear-gradient(to right, #4a6fa5, #6b8cbc);
border-radius: 4px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 22px;
height: 22px;
border-radius: 50%;
background: white;
border: 2px solid var(--primary-color);
cursor: pointer;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.preview-section {
display: flex;
flex-wrap: wrap;
gap: 30px;
margin-bottom: 30px;
}
.image-container {
flex: 1;
min-width: 300px;
text-align: center;
}
.image-container h3 {
margin-bottom: 15px;
color: var(--dark-bg);
padding-bottom: 10px;
border-bottom: 2px solid var(--primary-color);
}
canvas {
max-width: 100%;
height: auto;
background-color: #f0f2f5;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
display: none;
}
.placeholder {
width: 100%;
height: 300px;
background: linear-gradient(45deg, #f8f9fa, #e9ecef);
border-radius: var(--border-radius);
display: flex;
justify-content: center;
align-items: center;
color: #6c757d;
font-size: 1.1rem;
box-shadow: var(--box-shadow);
}
.btn-group {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
flex-wrap: wrap;
}
button {
padding: 12px 25px;
border: none;
border-radius: 50px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
background: linear-gradient(135deg, #3a5a90, #5a7cac);
}
.btn-secondary {
background: linear-gradient(135deg, #6c757d, #868e96);
color: white;
}
.btn-accent {
background: linear-gradient(135deg, #ff8a00, #e52e71);
color: white;
}
.noise-types {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 20px;
}
.noise-type {
background: white;
padding: 15px;
border-radius: var(--border-radius);
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
border: 1px solid #e9ecef;
transition: all 0.3s;
}
.noise-type:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
border-color: var(--primary-color);
}
.noise-type h4 {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
color: var(--primary-color);
}
.noise-type p {
font-size: 0.9rem;
color: #495057;
}
footer {
text-align: center;
color: rgba(255, 255, 255, 0.8);
padding: 20px;
font-size: 0.9rem;
}
[url=home.php?mod=space&uid=945662]@media[/url] (max-width: 768px) {
.preview-section {
flex-direction: column;
}
.app-card {
padding: 20px;
}
header h1 {
font-size: 2rem;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>图片噪声添加工具</h1>
<p>突破AI重复图片检测 | 模拟真实场景图像退化 | 机器学习数据增强</p>
</header>
<div class="app-card">
<div class="upload-section" id="dropZone">
<div class="upload-icon">📁</div>
<h2>上传图片添加噪声</h2>
<p>支持 JPG, PNG, WEBP 格式的图片文件</p>
<p class="small">拖放图片到此处或点击下方按钮选择文件</p>
<input type="file" id="fileInput" accept="image/*" style="display: none;">
<button class="btn-primary" onclick="document.getElementById('fileInput').click()">
<span>选择图片</span>
</button>
</div>
<div class="controls">
<div class="control-group">
<h3>⚙️ 噪声设置</h3>
<div class="slider-container">
<div class="slider-label">
<span>噪声强度</span>
<span id="intensityValue">0.30</span>
</div>
<input type="range" id="intensitySlider" min="0" max="100" value="30">
<div class="slider-label">
<span>低</span>
<span>高</span>
</div>
</div>
</div>
<div class="control-group">
<h3>🔊 噪声类型</h3>
<div class="noise-types">
<div class="noise-type">
<h4>🧂 椒盐噪声</h4>
<p>随机添加黑白像素点,模拟传感器缺陷</p>
</div>
<div class="noise-type">
<h4>📊 高斯噪声</h4>
<p>添加正态分布随机噪声,模拟电子干扰</p>
</div>
<div class="noise-type">
<h4>🌈 HSV扰动</h4>
<p>改变色调、饱和度和亮度,破坏颜色特征</p>
</div>
<div class="noise-type">
<h4>⚡️ 梯度对抗</h4>
<p>干扰图像边缘特征,对抗AI识别</p>
</div>
</div>
</div>
</div>
<div class="preview-section">
<div class="image-container">
<h3>原始图片</h3>
<div class="placeholder" id="originalPlaceholder">等待图片上传...</div>
<canvas id="originalCanvas"></canvas>
</div>
<div class="image-container">
<h3>添加噪声后</h3>
<div class="placeholder" id="resultPlaceholder">处理后的图片将显示在这里</div>
<canvas id="resultCanvas"></canvas>
</div>
</div>
<div class="btn-group">
<button class="btn-accent" id="processBtn">
<span>添加噪声</span>
</button>
<button class="btn-primary" id="downloadBtn" disabled>
<span>下载结果</span>
</button>
<button class="btn-secondary" id="resetBtn">
<span>重置</span>
</button>
</div>
</div>
<footer>
<p>基于Python噪声算法实现的HTML5应用 | 使用Canvas进行图像处理</p>
<p>适用于:AI对抗、数据增强、图像退化模拟</p>
</footer>
</div>
<script>
// DOM元素
const fileInput = document.getElementById('fileInput');
const intensitySlider = document.getElementById('intensitySlider');
const intensityValue = document.getElementById('intensityValue');
const processBtn = document.getElementById('processBtn');
const downloadBtn = document.getElementById('downloadBtn');
const resetBtn = document.getElementById('resetBtn');
const originalCanvas = document.getElementById('originalCanvas');
const resultCanvas = document.getElementById('resultCanvas');
const originalCtx = originalCanvas.getContext('2d');
const resultCtx = resultCanvas.getContext('2d');
const dropZone = document.getElementById('dropZone');
const originalPlaceholder = document.getElementById('originalPlaceholder');
const resultPlaceholder = document.getElementById('resultPlaceholder');
// 当前图片状态
let originalImage = null;
let processedImage = null;
// 更新强度显示
intensitySlider.addEventListener('input', () => {
const value = intensitySlider.value / 100;
intensityValue.textContent = value.toFixed(2);
});
// 文件上传处理
fileInput.addEventListener('change', (e) => {
if (e.target.files && e.target.files[0]) {
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
originalImage = img;
displayImage(img, originalCanvas, originalCtx, originalPlaceholder);
resultPlaceholder.textContent = "点击下方按钮添加噪声";
downloadBtn.disabled = true;
};
img.src = event.target.result;
};
reader.readAsDataURL(e.target.files[0]);
}
});
// 拖放功能
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.style.borderColor = '#4a6fa5';
dropZone.style.backgroundColor = 'rgba(255, 255, 255, 0.95)';
});
dropZone.addEventListener('dragleave', () => {
dropZone.style.borderColor = '#ced4da';
dropZone.style.backgroundColor = 'rgba(255, 255, 255, 0.7)';
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.style.borderColor = '#ced4da';
dropZone.style.backgroundColor = 'rgba(255, 255, 255, 0.7)';
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
fileInput.files = e.dataTransfer.files;
const event = new Event('change');
fileInput.dispatchEvent(event);
}
});
// 显示图片到Canvas
function displayImage(img, canvas, ctx, placeholder) {
const maxWidth = 500;
const maxHeight = 400;
let width = img.width;
let height = img.height;
// 调整尺寸以适应容器
if (width > maxWidth) {
height = (maxWidth / width) * height;
width = maxWidth;
}
if (height > maxHeight) {
width = (maxHeight / height) * width;
height = maxHeight;
}
canvas.width = width;
canvas.height = height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, width, height);
canvas.style.display = 'block';
placeholder.style.display = 'none';
}
// 添加噪声的主函数
function addNoise() {
if (!originalImage) {
alert('请先上传图片!');
return;
}
// 显示加载状态
processBtn.innerHTML = '<span>处理中...</span>';
processBtn.disabled = true;
// 复制原始图像到结果Canvas
resultCanvas.width = originalCanvas.width;
resultCanvas.height = originalCanvas.height;
resultCtx.drawImage(originalImage, 0, 0, resultCanvas.width, resultCanvas.height);
// 获取图像数据
const imageData = resultCtx.getImageData(0, 0, resultCanvas.width, resultCanvas.height);
const data = imageData.data;
// 获取噪声强度 (0.0 ~ 0.5)
const intensity = intensitySlider.value / 100;
// 添加椒盐噪声
addSaltPepperNoise(data, intensity);
// 添加高斯噪声
addGaussianNoise(data, intensity);
// 添加HSV扰动
addHSVNoise(data, intensity);
// 添加梯度对抗扰动
addGradientNoise(data, imageData, intensity);
// 将处理后的数据放回Canvas
resultCtx.putImageData(imageData, 0, 0);
// 显示结果
resultCanvas.style.display = 'block';
resultPlaceholder.style.display = 'none';
downloadBtn.disabled = false;
// 恢复按钮状态
processBtn.innerHTML = '<span>添加噪声</span>';
processBtn.disabled = false;
}
// 椒盐噪声
function addSaltPepperNoise(data, intensity) {
const size = data.length;
const noiseCount = Math.floor(size / 4 * intensity * 0.5);
for (let i = 0; i < noiseCount; i++) {
// 随机位置
const idx = Math.floor(Math.random() * size / 4) * 4;
// 椒盐选择 (0 或 255)
const value = Math.random() > 0.5 ? 255 : 0;
// 设置RGB通道
data[idx] = value; // R
data[idx + 1] = value; // G
data[idx + 2] = value; // B
}
}
// 高斯噪声
function addGaussianNoise(data, intensity) {
const strength = intensity * 50;
for (let i = 0; i < data.length; i += 4) {
// 对每个通道添加高斯噪声
for (let j = 0; j < 3; j++) {
// 生成高斯随机数
const noise = strength * (Math.random() - 0.5) * 2;
const newValue = data[i + j] + noise;
// 确保值在0-255之间
data[i + j] = Math.max(0, Math.min(255, newValue));
}
}
}
// HSV扰动
function addHSVNoise(data, intensity) {
// 随机色调偏移
const hueShift = (Math.random() * 20 - 10);
// 饱和度增强系数
const satFactor = 1 + 0.5 * intensity;
// 亮度随机变化
const valueFactor = 1 + (Math.random() - 0.5) * intensity * 2;
for (let i = 0; i < data.length; i += 4) {
let r = data[i] / 255;
let g = data[i + 1] / 255;
let b = data[i + 2] / 255;
// 转换为HSV
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, v = max;
const d = max - min;
s = max === 0 ? 0 : d / max;
if (max === min) {
h = 0; // achromatic
} else {
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
// 应用HSV扰动
h = (h * 360 + hueShift) % 360;
if (h < 0) h += 360;
s = Math.min(1, s * satFactor);
v = Math.min(1, v * valueFactor);
// 转换回RGB
h /= 60;
const c = v * s;
const x = c * (1 - Math.abs(h % 2 - 1));
const m = v - c;
let r1, g1, b1;
if (h >= 0 && h < 1) {
[r1, g1, b1] = [c, x, 0];
} else if (h >= 1 && h < 2) {
[r1, g1, b1] = [x, c, 0];
} else if (h >= 2 && h < 3) {
[r1, g1, b1] = [0, c, x];
} else if (h >= 3 && h < 4) {
[r1, g1, b1] = [0, x, c];
} else if (h >= 4 && h < 5) {
[r1, g1, b1] = [x, 0, c];
} else {
[r1, g1, b1] = [c, 0, x];
}
// 设置新RGB值
data[i] = (r1 + m) * 255;
data[i + 1] = (g1 + m) * 255;
data[i + 2] = (b1 + m) * 255;
}
}
// 梯度对抗扰动(简化版)
function addGradientNoise(data, imageData, intensity) {
const width = resultCanvas.width;
const height = resultCanvas.height;
const tempData = new Uint8ClampedArray(imageData.data);
// 简化的梯度计算
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
const idx = (y * width + x) * 4;
// 计算简单梯度
const dx = tempData[idx + 4] - tempData[idx - 4]; // 水平梯度
const dy = tempData[idx + width * 4] - tempData[idx - width * 4]; // 垂直梯度
// 添加对抗扰动
const perturbation = intensity * 0.1 * Math.sign(dx + dy);
// 应用扰动到RGB通道
for (let c = 0; c < 3; c++) {
const newValue = data[idx + c] + perturbation * 128;
data[idx + c] = Math.max(0, Math.min(255, newValue));
}
}
}
}
// 下载图片
function downloadImage() {
if (!resultCanvas.style.display || resultCanvas.style.display === 'none') {
alert('请先处理图片!');
return;
}
const link = document.createElement('a');
link.download = 'noisy-image.png';
link.href = resultCanvas.toDataURL('image/png');
link.click();
}
// 重置
function reset() {
fileInput.value = '';
intensitySlider.value = 30;
intensityValue.textContent = '0.30';
originalCanvas.style.display = 'none';
resultCanvas.style.display = 'none';
originalPlaceholder.style.display = 'flex';
originalPlaceholder.textContent = '等待图片上传...';
resultPlaceholder.style.display = 'flex';
resultPlaceholder.textContent = '处理后的图片将显示在这里';
downloadBtn.disabled = true;
originalImage = null;
processedImage = null;
}
// 绑定事件
processBtn.addEventListener('click', addNoise);
downloadBtn.addEventListener('click', downloadImage);
resetBtn.addEventListener('click', reset);
</script>
</body>
</html>