基于TensorFlow.js与Web Worker的智能证件照生成方案

功能简介

本文基于TensorFlow.js与Web Worker实现了常用的“证件照”功能,可以对照片实现抠图并替换背景。值得一提的是,正常抠图的操作应该由后端进行,这里只是主要演示该功能实现步骤,并不建议该功能由前端全权处理。
限于个人技术能力有限,当前功能实现的并不怎么良好,抠图不够精细且缺少对图片处理后的画质修复等操作,这些还请诸位大佬见谅了。

效果演示

原图

在这里插入图片描述## 效果图
在这里插入图片描述

功能亮点

  • 智能人像分割:基于BodyPix模型实现精准人像抠图
  • 实时背景替换:支持动态颜色选择和边缘优化算法
  • 多尺寸适配:预设常用证件照尺寸+自定义毫米级精度
  • 高性能处理:Web Worker独立线程保障主线程流畅性
  • 模型加载优化:按需加载MobileNetV1量化模型
  • 边缘平滑算法:卷积核模糊处理提升证件照专业度

主要逻辑详解

加载模型

// 初始化模型
async function loadModel() {
  try {
    console.log('开始加载模型...');
    if (!bodyPixModel) {
      bodyPixModel = await bodyPix.load({
        architecture: 'MobileNetV1',
        outputStride: 16,
        multiplier: 0.75,
        quantBytes: 2
      });
      console.log('模型加载成功');
    }
    return bodyPixModel;
  } catch (error) {
    console.error('模型加载失败:', error);
    throw error;
  }
}

该方法的主要作用便是加载bodyPixModel模型,这里用到的模型为MobileNetV1。关于bodyPix模型下load方法的各种参数可以自行搜索一下官方文档查看。

人像分割核心流程

    // 使用 BodyPix 进行人像分割
    console.log('开始人像分割...');
    const segmentation = await model.segmentPerson(imgDataForSegmentation, {
      flipHorizontal: false,
      internalResolution: 'medium',
      segmentationThreshold: config.segmentationThreshold || 0.7
    });
    console.log('人像分割完成');
    
    // 创建输出 Canvas
    const outputCanvas = new OffscreenCanvas(img.width, img.height);
    const outputCtx = outputCanvas.getContext('2d');
    
    // 绘制原始图片
    outputCtx.drawImage(img, 0, 0);
    
    // 应用背景色
    const backgroundColor = hexToRgb(config.bgColor);
    const outputImageData = outputCtx.getImageData(0, 0, img.width, img.height);
    const pixelData = outputImageData.data;
    
    // 应用分割结果
    for (let i = 0; i < segmentation.data.length; i++) {
      const n = i * 4;
      if (segmentation.data[i] === 0) { // 背景部分
        pixelData[n] = backgroundColor.r;
        pixelData[n + 1] = backgroundColor.g;
        pixelData[n + 2] = backgroundColor.b;
        pixelData[n + 3] = 255;
      }
    }
    
    outputCtx.putImageData(outputImageData, 0, 0);

这里的主要逻辑是分割图像及应用选择的背景色。

边缘平滑算法

// 边缘平滑处理
async function smoothEdges(ctx, width, height) {
  const imageData = ctx.getImageData(0, 0, width, height);
  const data = imageData.data;
  
  const kernel = [
    [0.075, 0.124, 0.075],
    [0.124, 0.204, 0.124],
    [0.075, 0.124, 0.075]
  ];
  
  const tempData = new Uint8ClampedArray(data);
  
  for (let y = 1; y < height - 1; y++) {
    for (let x = 1; x < width - 1; x++) {
      const idx = (y * width + x) * 4;
      
      if (isEdgePixel(data, idx, width)) {
        let r = 0, g = 0, b = 0, a = 0;
        
        for (let ky = -1; ky <= 1; ky++) {
          for (let kx = -1; kx <= 1; kx++) {
            const offset = ((y + ky) * width + (x + kx)) * 4;
            const weight = kernel[ky + 1][kx + 1];
            
            r += tempData[offset] * weight;
            g += tempData[offset + 1] * weight;
            b += tempData[offset + 2] * weight;
            a += tempData[offset + 3] * weight;
          }
        }
        
        data[idx] = r;
        data[idx + 1] = g;
        data[idx + 2] = b;
        data[idx + 3] = a;
      }
    }
  }
  
  ctx.putImageData(imageData, 0, 0);
}

这里使用3*3卷积核对边缘像素进行了加权平均处理,从而消除锯齿效果。这里用到的isEdgePixel()方法目的是判断一个像素是否为图像的边缘像素,方法是通过比较该像素与其相邻像素的alpha通道值(也就是透明度)。

function isEdgePixel(data, idx, width) {
  const alpha = data[idx + 3];
  const leftAlpha = data[idx - 4 + 3];
  const rightAlpha = data[idx + 4 + 3];
  const topAlpha = data[idx - width * 4 + 3];
  const bottomAlpha = data[idx + width * 4 + 3];
  
  return (alpha !== leftAlpha || 
          alpha !== rightAlpha || 
          alpha !== topAlpha || 
          alpha !== bottomAlpha);
}

完整代码

IDPhoto.vue

<template>
  <div class="id-photo-container">
    <!-- 左侧工具栏 -->
    <div class="tools-panel">
      <el-form :model="photoConfig" label-position="top">
        <!-- 预设尺寸选择 -->
        <el-form-item label="证件照尺寸">
          <el-select v-model="photoConfig.selectedSize" placeholder="选择尺寸">
            <el-option
              v-for="size in presetSizes"
              :key="size.value"
              :label="size.label"
              :value="size.value"
            />
          </el-select>
        </el-form-item>

        <!-- 自定义尺寸输入 --> 
        <el-form-item label="自定义尺寸(mm)">
          <div class="custom-size">
            <el-input-number 
              v-model="photoConfig.customWidth" 
              :min="20" 
              :max="1000"
              placeholder="宽度"
            />
            <span class="separator">×</span>
            <el-input-number 
              v-model="photoConfig.customHeight" 
              :min="20" 
              :max="1000"
              placeholder="高度"
            />
          </div>
        </el-form-item>

        <!-- 背景颜色选择 -->
        <el-form-item label="背景颜色">
          <el-color-picker v-model="photoConfig.bgColor" />
        </el-form-item>

        <!-- 操作按钮 -->
        <div class="action-buttons">
          <el-button type="primary" @click="uploadImage">
            上传图片
          </el-button>
          <el-button 
            type="success" 
            :disabled="!hasImage"
            @click="downloadPhoto"
          >
            下载证件照
          </el-button>
        </div>
      </el-form>
    </div>

    <!-- 右侧预览区域 -->
    <div class="preview-panel">
      <div 
        class="preview-area"
        :style="{ backgroundColor: photoConfig.bgColor }"
      >
        <img 
          v-if="previewUrl"
          :src="previewUrl"
          ref="previewImage"
          @load="handleImageLoad"
        />
        <div v-else class="placeholder">
          请上传图片
        </div>
      </div>
    </div>

    <!-- 隐藏的文件输入框 -->
    <input
      type="file"
      ref="fileInput"
      accept="image/*"
      style="display: none"
      @change="handleFileChange"
    />

    <el-loading 
      v-model:visible="loading"
      text="处理中..."
      background="rgba(255, 255, 255, 0.8)"
    />
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, onUnmounted, watch } from 'vue'
import { ElMessage } from 'element-plus'

// 预设尺寸选项
const presetSizes = [
  { label: '一寸照片 (25×35mm)', value: '25x35' },
  { label: '二寸照片 (35×49mm)', value: '35x49' },
  { label: '小二寸 (35×45mm)', value: '35x45' },
  { label: '大二寸 (35×53mm)', value: '35x53' }
]

// 照片配置
const photoConfig = reactive({
  selectedSize: '35x45',
  customWidth: 35,
  customHeight: 45,
  bgColor: '#FFFFFF',
  modelQuality: 'medium', // 可选: 'low', 'medium', 'high'
  segmentationThreshold: 0.7, // 分割阈值,可调整精度
  edgeBlur: 3 // 边缘模糊半径
})

// 组件引用
const fileInput = ref(null)
const previewImage = ref(null)

// 状态变量
const previewUrl = ref('')
const hasImage = ref(false)

// 图片处理 Worker
let imageWorker = null

// 添加加载状态
const loading = ref(false)

// 初始化 Worker
onMounted(() => {
  try {
    // 使用 ?worker 查询参数来告诉 Vite 这是一个 worker 文件
    imageWorker = new Worker(
      new URL('../../workers/idphoto.worker.js?worker', import.meta.url),
      { type: 'module' }
    )
    
    imageWorker.onmessage = (e) => {
      console.log('收到Worker响应:', e.data)
      if (e.data.status === 'success') {
        const blobUrl = URL.createObjectURL(e.data.result)
        previewUrl.value = blobUrl
        loading.value = false // 确保加载状态被重置
      } else {
        console.error('Worker处理失败:', e.data.error)
        ElMessage.error(`处理图片时出错: ${e.data.error}`)
        loading.value = false
      }
    }

    imageWorker.onerror = (error) => {
      console.error('Worker错误:', error)
      ElMessage.error('图片处理服务初始化失败')
      loading.value = false
    }
  } catch (error) {
    console.error('创建Worker失败:', error)
    ElMessage.error('初始化图片处理服务失败')
    loading.value = false
  }
})

// 清理 Worker
onUnmounted(() => {
  if (imageWorker) {
    imageWorker.terminate()
  }
})

// 上传图片
const uploadImage = () => {
  fileInput.value.click()
}

// 处理文件选择
const handleFileChange = async (event) => {
  const file = event.target.files[0]
  if (!file) return

  if (!file.type.startsWith('image/')) {
    ElMessage.error('请上传图片文件')
    return
  }

  if (file.size > 10 * 1024 * 1024) { // 10MB 限制
    ElMessage.error('图片大小不能超过10MB')
    return
  }

  loading.value = true
  ElMessage.info('正在处理图片,首次使用可能需要加载模型...')

  const reader = new FileReader()
  reader.onload = (e) => {
    const img = new Image()
    img.onload = () => {
      console.log('图片加载成功,尺寸:', img.width, 'x', img.height)
      try {
        imageWorker.postMessage({
          imageData: e.target.result,
          config: {
            width: img.width,
            height: img.height,
            bgColor: photoConfig.bgColor,
            segmentationThreshold: photoConfig.segmentationThreshold,
            modelQuality: photoConfig.modelQuality
          }
        })
      } catch (error) {
        loading.value = false
        console.error('发送数据到Worker时出错:', error)
        ElMessage.error('处理图片时出错')
      }
    }
    img.onerror = (error) => {
      loading.value = false
      console.error('图片加载失败:', error)
      ElMessage.error('图片加载失败')
    }
    img.src = e.target.result
    hasImage.value = true
  }
  reader.onerror = (error) => {
    loading.value = false
    console.error('读取文件失败:', error)
    ElMessage.error('读取文件失败')
  }
  reader.readAsDataURL(file)
}

// 处理图片加载
const handleImageLoad = () => {
  // 这里可以添加图片加载后的处理逻辑
}

// 下载证件照
const downloadPhoto = async () => {
  if (!hasImage.value) return

  try {
    const response = await fetch(previewUrl.value)
    const blob = await response.blob()
    
    const link = document.createElement('a')
    link.download = '证件照.png'
    link.href = URL.createObjectURL(blob)
    link.click()
    
    ElMessage.success('下载成功')
  } catch (error) {
    ElMessage.error('下载图片时出错')
    console.error(error)
  }
}

// 添加背景色变化监听
watch(() => photoConfig.bgColor, (newColor) => {
  if (hasImage.value && previewUrl.value) {
    const img = new Image()
    img.onload = () => {
      imageWorker.postMessage({
        imageData: previewUrl.value,
        config: {
          width: img.width,
          height: img.height,
          bgColor: newColor
        }
      })
    }
    img.src = previewUrl.value
  }
})
</script>

<style scoped>
.id-photo-container {
  display: flex;
  gap: 20px;
  padding: 20px;
  height: 100%;
}

.tools-panel {
  width: 300px;
  padding: 20px;
  background: #f5f7fa;
  border-radius: 8px;
}

.preview-panel {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  background: #f5f7fa;
  border-radius: 8px;
  overflow: hidden;
}

.preview-area {
  width: 80%;
  height: 80%;
  display: flex;
  justify-content: center;
  align-items: center;
  background: #fff;
  border-radius: 4px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

.preview-area img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.placeholder {
  color: #909399;
  font-size: 16px;
}

.custom-size {
  display: flex;
  align-items: center;
  gap: 10px;
}

.separator {
  color: #909399;
}

.action-buttons {
  display: flex;
  gap: 10px;
  margin-top: 20px;
}
</style>

idphoto.workder.js

import * as tf from '@tensorflow/tfjs'
import * as bodyPix from '@tensorflow-models/body-pix'

let bodyPixModel = null;

// 初始化模型
async function loadModel() {
  try {
    console.log('开始加载模型...');
    if (!bodyPixModel) {
      bodyPixModel = await bodyPix.load({
        architecture: 'MobileNetV1',
        outputStride: 16,
        multiplier: 0.75,
        quantBytes: 2
      });
      console.log('模型加载成功');
    }
    return bodyPixModel;
  } catch (error) {
    console.error('模型加载失败:', error);
    throw error;
  }
}

// 处理图片的 Worker
self.onmessage = async function(e) {
  console.log('Worker 收到消息:', e.data);
  const { imageData, config } = e.data;

  try {
    if (!imageData || !config) {
      throw new Error('缺少必要的参数')
    }

    // 加载模型
    const model = await loadModel();
    console.log('模型准备就绪');
    
    // 创建图片元素
    const img = await createImageBitmap(
      await fetch(imageData).then(r => r.blob())
    );
    
    // 创建离屏 Canvas
    const canvas = new OffscreenCanvas(img.width, img.height);
    const ctx = canvas.getContext('2d');
    
    if (!ctx) {
      throw new Error('无法创建Canvas上下文')
    }
    
    // 绘制原始图片
    ctx.drawImage(img, 0, 0);
    console.log('图片绘制完成');
    
    // 获取图片数据
    const imgDataForSegmentation = ctx.getImageData(0, 0, img.width, img.height);
    
    // 使用 BodyPix 进行人像分割
    console.log('开始人像分割...');
    const segmentation = await model.segmentPerson(imgDataForSegmentation, {
      flipHorizontal: false,
      internalResolution: 'medium',
      segmentationThreshold: config.segmentationThreshold || 0.7
    });
    console.log('人像分割完成');
    
    // 创建输出 Canvas
    const outputCanvas = new OffscreenCanvas(img.width, img.height);
    const outputCtx = outputCanvas.getContext('2d');
    
    // 绘制原始图片
    outputCtx.drawImage(img, 0, 0);
    
    // 应用背景色
    const backgroundColor = hexToRgb(config.bgColor);
    const outputImageData = outputCtx.getImageData(0, 0, img.width, img.height);
    const pixelData = outputImageData.data;
    
    // 应用分割结果
    for (let i = 0; i < segmentation.data.length; i++) {
      const n = i * 4;
      if (segmentation.data[i] === 0) { // 背景部分
        pixelData[n] = backgroundColor.r;
        pixelData[n + 1] = backgroundColor.g;
        pixelData[n + 2] = backgroundColor.b;
        pixelData[n + 3] = 255;
      }
    }
    
    outputCtx.putImageData(outputImageData, 0, 0);
    
    // 优化边缘
    await smoothEdges(outputCtx, img.width, img.height);
    
    // 转换为 Blob
    const resultBlob = await outputCanvas.convertToBlob({
      type: 'image/png'
    });
    
    console.log('处理完成,发送结果');
    self.postMessage({
      status: 'success',
      result: resultBlob
    });
    
  } catch (error) {
    console.error('Worker处理错误:', error);
    self.postMessage({
      status: 'error',
      error: error.message || '处理图片时发生未知错误'
    });
  }
};

// 边缘平滑处理
async function smoothEdges(ctx, width, height) {
  const imageData = ctx.getImageData(0, 0, width, height);
  const data = imageData.data;
  
  const kernel = [
    [0.075, 0.124, 0.075],
    [0.124, 0.204, 0.124],
    [0.075, 0.124, 0.075]
  ];
  
  const tempData = new Uint8ClampedArray(data);
  
  for (let y = 1; y < height - 1; y++) {
    for (let x = 1; x < width - 1; x++) {
      const idx = (y * width + x) * 4;
      
      if (isEdgePixel(data, idx, width)) {
        let r = 0, g = 0, b = 0, a = 0;
        
        for (let ky = -1; ky <= 1; ky++) {
          for (let kx = -1; kx <= 1; kx++) {
            const offset = ((y + ky) * width + (x + kx)) * 4;
            const weight = kernel[ky + 1][kx + 1];
            
            r += tempData[offset] * weight;
            g += tempData[offset + 1] * weight;
            b += tempData[offset + 2] * weight;
            a += tempData[offset + 3] * weight;
          }
        }
        
        data[idx] = r;
        data[idx + 1] = g;
        data[idx + 2] = b;
        data[idx + 3] = a;
      }
    }
  }
  
  ctx.putImageData(imageData, 0, 0);
}

function isEdgePixel(data, idx, width) {
  const alpha = data[idx + 3];
  const leftAlpha = data[idx - 4 + 3];
  const rightAlpha = data[idx + 4 + 3];
  const topAlpha = data[idx - width * 4 + 3];
  const bottomAlpha = data[idx + width * 4 + 3];
  
  return (alpha !== leftAlpha || 
          alpha !== rightAlpha || 
          alpha !== topAlpha || 
          alpha !== bottomAlpha);
}

function hexToRgb(hex) {
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result ? {
    r: parseInt(result[1], 16),
    g: parseInt(result[2], 16),
    b: parseInt(result[3], 16)
  } : null;
} 

以上便是证件照功能的全部逻辑,其实我这里写的相当简陋且具有很大的扩展空间。各位大佬如果有精力,可以在这个基础上 增加服装替换、美颜等功能,以及可以进一步优化UI界面(我这里样式写的不是特别好)。
总之,感谢阅读了,愿你我都能在技术之路上更进一步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/976232.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3D模型在线转换工具:轻松实现3DM转OBJ

3D模型在线转换是一款功能强大的在线工具&#xff0c;支持多种3D模型格式的在线预览和互转。无论是工业设计、建筑设计&#xff0c;还是数字艺术领域&#xff0c;这款工具都能满足您的需求。 3DM与OBJ格式简介 3DM格式&#xff1a;3DM是一种广泛应用于三维建模的文件格式&…

GEO数据结构

目录 1. GEOADD 2. GEODIST 3. GEOHASH 3. GEOHASH 4. GEOPOS 6. GEOSEARCH 7. GEOSEARCHSTORE 应用场景 代码的逻辑分解&#xff1a; 比较难懂的部分&#xff1a; Redis GEO 查询与分页 results 的结构&#xff1a; 分页处理与截取数据 附加距离信息 1. GEOADD…

Java基础常见的面试题(易错!!)

面试题一&#xff1a;为什么 Java 不支持多继承 Java 不支持多继承主要是为避免 “菱形继承问题”&#xff08;又称 “钻石问题”&#xff09;&#xff0c;即一个子类从多个父类继承到同名方法或属性时&#xff0c;编译器无法确定该调用哪个父类的成员。同时&#xff0c;多继承…

基于Python/Flask/机器学习链家网新房数据可视化及预测系统+万字文档+答辩PPT+指导搭建视频

技术栈&#xff1a; 编程语言&#xff1a;python 涉及技术&#xff1a;requests爬虫、mysql数据库、flask框架、scikit-learn机器学习预测算法、多元线性回归、Echarts可视化。 ①.需求分析&#xff1a; 1.数据爬取&#xff1a;自动化获取链家网新房数据。 2.数据存储&…

【DeepSeek-R1背后的技术】系列十一:RAG原理介绍和本地部署(DeepSeekR1+RAGFlow构建个人知识库)

【DeepSeek-R1背后的技术】系列博文&#xff1a; 第1篇&#xff1a;混合专家模型&#xff08;MoE&#xff09; 第2篇&#xff1a;大模型知识蒸馏&#xff08;Knowledge Distillation&#xff09; 第3篇&#xff1a;强化学习&#xff08;Reinforcement Learning, RL&#xff09;…

力扣LeetCode:1656 设计有序流

题目&#xff1a; 有 n 个 (id, value) 对&#xff0c;其中 id 是 1 到 n 之间的一个整数&#xff0c;value 是一个字符串。不存在 id 相同的两个 (id, value) 对。 设计一个流&#xff0c;以 任意 顺序获取 n 个 (id, value) 对&#xff0c;并在多次调用时 按 id 递增的顺序…

MATLAB在数据分析和绘图中的应用:从基础到实践

引言 股票数据分析是金融领域中的重要研究方向&#xff0c;通过对历史价格、成交量等数据的分析&#xff0c;可以帮助投资者更好地理解市场趋势和做出决策。MATLAB作为一种强大的科学计算工具&#xff0c;提供了丰富的数据处理和可视化功能&#xff0c;非常适合用于股票数据的…

2025年02月17日Github流行趋势

项目名称&#xff1a;OmniParser 项目地址url&#xff1a;https://github.com/microsoft/OmniParser 项目语言&#xff1a;Jupyter Notebook 历史star数&#xff1a;8971 今日star数&#xff1a;969 项目维护者&#xff1a;yadong-lu, ThomasDh-C, aliencaocao, nmstoker, kris…

Keepalive基础

一。简介和功能 vrrp协议的软件实现&#xff0c;原生设计目的是为了高可用ipvs服务 功能&#xff1a; 1.基于vrrp协议完成地址流动 2.为vip地址所在的节点生成ipvs规则&#xff08;在配置文件中预先定义&#xff09; 3.为ipvs集群的各RS做健康状况检测 4.基于脚本调用接口…

vue3: directive自定义指令防止重复点击

第一章 前言 相信很多小伙伴会在各个渠道上搜如何防止重复点击&#xff0c;之后会推荐什么防抖、节流来避免这一操作&#xff0c;该方法小编就不继续往下说了。接下来说说小编的场景&#xff0c;项目已经完成的差不多了&#xff0c;但是由于之前大家都是直接点击事件调用方法的…

第4章 4.3 EF Core 的实体类配置 Data Annatation Fluent API

4.3.1 约定大于配置 主要的约定规则&#xff1a; 规则 1: 数据库表名采用上下文类中对应的 DbSet 的属性名。 规则 2:数据库表列的名字采用实体类属性的名字&#xff0c;列的数据类型采用和实体类属性类型 兼容的类型。比如在 SQLServer 中&#xff0c;string 类型对应 nvarc…

【Redis 原理】通信协议 内存回收

文章目录 通信协议--RESP内存回收内存过期策略惰性删除周期删除 内存淘汰策略 通信协议–RESP Redis是一个CS架构的软件&#xff0c;通信一般分两步&#xff08;不包括pipeline和PubSub&#xff09;&#xff1a; 客户端&#xff08;client&#xff09;向服务端&#xff08;se…

【GreenHills】GHS合并库文件

1、 文档目标 解决Green Hills对于多个库文件合并问题 2、 问题场景 客户具有多个工程库文件。但是&#xff0c;客户想要在项目最终交付的时候&#xff0c;通过将多个库文件打包成一个库文件&#xff0c;进行交付。 3、软硬件环境 1&#xff09;、软件版本&#xff1a;MULTI…

山东大学软件学院nosql实验四

实验题目&#xff1a; 使用Java做简单数据插入 实验内容 用API方式&#xff0c;做数据插入。 使用Java语言实现数据插入界面&#xff0c;为实验一建立的学生、教师、课程表插入数据&#xff0c;可以在前端界面中录入数据之后保存&#xff0c;也可以导入Excel中的数据。 实…

nodejs npm install、npm run dev运行的坎坷之路

1、前面的种种都不说了&#xff0c;好不容易运行起来oap-portal项目&#xff0c;运行idm-ui项目死活运行不起来&#xff0c;各种报错&#xff0c;各种安装&#xff0c;各种卸载nodejs&#xff0c;卸载nvm&#xff0c;重装&#xff0c;都不好使。 2、甚至后来运行npm install会…

20250223下载并制作RTX2080Ti显卡的显存的测试工具mats

20250223下载并制作RTX2080Ti显卡的显存的测试工具mats 2025/2/23 23:23 缘起&#xff1a;我使用X99的主板&#xff0c;使用二手的RTX2080Ti显卡【显存22GB版本&#xff0c;准备学习AI的】 但是半年后发现看大码率的视频容易花屏&#xff0c;最初以为是WIN10经常更换显卡/来回更…

计算机毕业设计Hadoop+Spark+DeepSeek-R1大模型民宿推荐系统 hive民宿可视化 民宿爬虫 大数据毕业设计(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

centos 7 安装python3 及pycharm远程连接方法

安装openssl 使用pip3安装 virtualenv的时候会提示WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available. 这是因为缺少openssl 2.0以上版本 解决办法&#xff1a; 一、先确认版本 openssl version 二、安…

DeepSeek 助力 Vue 开发:打造丝滑的文本输入框(Text Input)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

Bybit最大资金盗窃事件技术分析 by CertiK

事件概述 2025年2月21日UTC时间下午02:16:11,Bybit的以太坊冷钱包(0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4[1])因恶意合约升级遭到资金盗取。根据Bybit CEO Ben Zhou的声明[2],攻击者通过钓鱼攻击诱骗冷钱包签名者错误签署恶意交易。他提到,该交易被伪装为合法操作:…