Maison > interface Web > js tutoriel > Explorer la série Canvas : combinée avec Transformers.js pour obtenir un traitement d'image intelligent

Explorer la série Canvas : combinée avec Transformers.js pour obtenir un traitement d'image intelligent

Susan Sarandon
Libérer: 2024-11-26 21:26:14
original
203 Les gens l'ont consulté

Introduction

Je gère actuellement une puissante planche à dessin créative open source. Cette planche à dessin intègre de nombreux pinceaux intéressants et fonctions de dessin auxiliaires, ce qui permet aux utilisateurs de découvrir un nouvel effet de dessin. Que ce soit sur mobile ou PC, vous pouvez profiter d'une meilleure expérience interactive et d'un meilleur affichage des effets.

Dans cet article, j'expliquerai en détail comment combiner Transformers.js pour réaliser la suppression de l'arrière-plan et la segmentation du marquage d'image. Le résultat est le suivant

Exploring the Canvas Series: combined with Transformers.js to achieve intelligent image processing

Lien : https://songlh.top/paint-board/

Github : https://github.com/LHRUN/paint-board Bienvenue sur Star ⭐️

Transformateurs.js

Transformers.js est une puissante bibliothèque JavaScript basée sur les Transformers de Hugging Face qui peut être exécutée directement dans le navigateur sans recourir au calcul côté serveur. Cela signifie que vous pouvez exécuter vos modèles localement, augmentant ainsi l'efficacité et réduisant les coûts de déploiement et de maintenance.

Actuellement, Transformers.js a fourni 1 000 modèles sur Hugging Face, couvrant divers domaines, qui peuvent satisfaire la plupart de vos besoins, tels que le traitement d'images, la génération de texte, la traduction, l'analyse des sentiments et d'autres tâches de traitement que vous pouvez facilement réaliser grâce à Transformers. .js. Recherchez des modèles comme suit.

Exploring the Canvas Series: combined with Transformers.js to achieve intelligent image processing

La version majeure actuelle de Transformers.js a été mise à jour vers la V3, qui ajoute de nombreuses fonctionnalités intéressantes, des détails : Transformers.js v3 : prise en charge WebGPU, nouveaux modèles et tâches, et plus encore….

Les deux fonctionnalités que j'ai ajoutées à cet article utilisent le support WebGpu, qui n'est disponible que dans la V3, et ont considérablement amélioré la vitesse de traitement, avec une analyse désormais en millisecondes. Cependant, il convient de noter qu'il n'existe pas beaucoup de navigateurs prenant en charge WebGPU, il est donc recommandé d'utiliser la dernière version de Google pour visiter.

Fonction 1 : Supprimer l'arrière-plan

Pour supprimer l'arrière-plan j'utilise le modèle Xenova/modnet, qui ressemble à ceci

Exploring the Canvas Series: combined with Transformers.js to achieve intelligent image processing

La logique de traitement peut être divisée en trois étapes

  1. initialisez l'état et chargez le modèle et le processeur.
  2. l'affichage de l'interface, celui-ci est basé sur votre propre design, pas sur le mien.
  3. Montrez l'effet, ceci est basé sur votre propre design, pas sur le mien. De nos jours, il est plus courant d'utiliser une bordure pour afficher dynamiquement l'effet de contraste avant et après la suppression de l'arrière-plan.

La logique du code est la suivante, React TS , voir le code source de mon projet pour plus de détails, le code source se trouve dans src/components/boardOperation/uploadImage/index.tsx

import { useState, FC, useRef, useEffect, useMemo } from 'react'
import {
  env,
  AutoModel,
  AutoProcessor,
  RawImage,
  PreTrainedModel,
  Processor
} from '@huggingface/transformers'

const REMOVE_BACKGROUND_STATUS = {
  LOADING: 0,
  NO_SUPPORT_WEBGPU: 1,
  LOAD_ERROR: 2,
  LOAD_SUCCESS: 3,
  PROCESSING: 4,
  PROCESSING_SUCCESS: 5
}

type RemoveBackgroundStatusType =
  (typeof REMOVE_BACKGROUND_STATUS)[keyof typeof REMOVE_BACKGROUND_STATUS]

const UploadImage: FC<{ url: string }> = ({ url }) => {
  const [removeBackgroundStatus, setRemoveBackgroundStatus] =
    useState<RemoveBackgroundStatusType>()
  const [processedImage, setProcessedImage] = useState('')

  const modelRef = useRef<PreTrainedModel>()
  const processorRef = useRef<Processor>()

  const removeBackgroundBtnTip = useMemo(() => {
    switch (removeBackgroundStatus) {
      case REMOVE_BACKGROUND_STATUS.LOADING:
        return 'Remove background function loading'
      case REMOVE_BACKGROUND_STATUS.NO_SUPPORT_WEBGPU:
        return 'WebGPU is not supported in this browser, to use the remove background function, please use the latest version of Google Chrome'
      case REMOVE_BACKGROUND_STATUS.LOAD_ERROR:
        return 'Remove background function failed to load'
      case REMOVE_BACKGROUND_STATUS.LOAD_SUCCESS:
        return 'Remove background function loaded successfully'
      case REMOVE_BACKGROUND_STATUS.PROCESSING:
        return 'Remove Background Processing'
      case REMOVE_BACKGROUND_STATUS.PROCESSING_SUCCESS:
        return 'Remove Background Processing Success'
      default:
        return ''
    }
  }, [removeBackgroundStatus])

  useEffect(() => {
    ;(async () => {
      try {
        if (removeBackgroundStatus === REMOVE_BACKGROUND_STATUS.LOADING) {
          return
        }
        setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.LOADING)

        // Checking WebGPU Support
        if (!navigator?.gpu) {
          setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.NO_SUPPORT_WEBGPU)
          return
        }
        const model_id = 'Xenova/modnet'
        if (env.backends.onnx.wasm) {
          env.backends.onnx.wasm.proxy = false
        }

        // Load model and processor
        modelRef.current ??= await AutoModel.from_pretrained(model_id, {
          device: 'webgpu'
        })
        processorRef.current ??= await AutoProcessor.from_pretrained(model_id)
        setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.LOAD_SUCCESS)
      } catch (err) {
        console.log('err', err)
        setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.LOAD_ERROR)
      }
    })()
  }, [])

  const processImages = async () => {
    const model = modelRef.current
    const processor = processorRef.current

    if (!model || !processor) {
      return
    }

    setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.PROCESSING)

    // load image
    const img = await RawImage.fromURL(url)

    // Pre-processed image
    const { pixel_values } = await processor(img)

    // Generate image mask
    const { output } = await model({ input: pixel_values })
    const maskData = (
      await RawImage.fromTensor(output[0].mul(255).to('uint8')).resize(
        img.width,
        img.height
      )
    ).data

    // Create a new canvas
    const canvas = document.createElement('canvas')
    canvas.width = img.width
    canvas.height = img.height
    const ctx = canvas.getContext('2d') as CanvasRenderingContext2D

    // Draw the original image
    ctx.drawImage(img.toCanvas(), 0, 0)

    // Updating the mask area
    const pixelData = ctx.getImageData(0, 0, img.width, img.height)
    for (let i = 0; i < maskData.length; ++i) {
      pixelData.data[4 * i + 3] = maskData[i]
    }
    ctx.putImageData(pixelData, 0, 0)

    // Save new image
    setProcessedImage(canvas.toDataURL('image/png'))
    setRemoveBackgroundStatus(REMOVE_BACKGROUND_STATUS.PROCESSING_SUCCESS)
  }

  return (
    <div className="card shadow-xl">
      <button
        className={`btn btn-primary btn-sm ${
          ![
            REMOVE_BACKGROUND_STATUS.LOAD_SUCCESS,
            REMOVE_BACKGROUND_STATUS.PROCESSING_SUCCESS,
            undefined
          ].includes(removeBackgroundStatus)
            ? 'btn-disabled'
            : ''
        }`}
        onClick={processImages}
      >
        Remove background
      </button>
      <div className="text-xs text-base-content mt-2 flex">
        {removeBackgroundBtnTip}
      </div>
      <div className="relative mt-4 border border-base-content border-dashed rounded-lg overflow-hidden">
        <img
          className={`w-[50vw] max-w-[400px] h-[50vh] max-h-[400px] object-contain`}
          src={url}
        />
        {processedImage && (
          <img
            className={`w-full h-full absolute top-0 left-0 z-[2] object-contain`}
            src={processedImage}
          />
        )}
      </div>
    </div>
  )
}

export default UploadImage
Copier après la connexion

Fonction 2 : Segmentation des marqueurs d'image

La segmentation des marqueurs d'image est implémentée à l'aide du modèle Xenova/slimsam-77-uniform. L'effet est le suivant, vous pouvez cliquer sur l'image après son chargement, et la segmentation est générée en fonction des coordonnées de votre clic.

Exploring the Canvas Series: combined with Transformers.js to achieve intelligent image processing

La logique de traitement peut être divisée en cinq étapes

  1. initialiser l'état et charger le modèle et le processeur
  2. Récupérez l'image et chargez-la, puis enregistrez les données de chargement de l'image et les données d'intégration.
  3. écoutez l'événement de clic sur l'image, enregistrez les données de clic, divisées en marqueurs positifs et marqueurs négatifs, après chaque clic en fonction des données de clic décodées pour générer les données de masque, puis en fonction des données de masque pour dessiner l'effet de segmentation .
  4. affichage de l'interface, ceci selon votre propre conception, jeu arbitraire, pas mon prédominance
  5. cliquez pour enregistrer l'image, en fonction des données de pixels du masque, faites correspondre les données de l'image d'origine, puis exportez-la via le dessin sur toile

La logique du code est la suivante, React TS , voir le code source de mon projet pour plus de détails, le code source se trouve dans src/components/boardOperation/uploadImage/imageSegmentation.tsx

 importer { useState, useRef, useEffect, useMemo, MouseEvent, FC } depuis 'react'
importer {
  SamModèle,
  Autoprocesseur,
  Image brute,
  Modèle pré-entraîné,
  Processeur,
  Tenseur,
  SamImageProcessorResult
} de '@huggingface/transformers'

importer LoadingIcon depuis '@/components/icons/loading.svg?react'
importer PositiveIcon depuis '@/components/icons/boardOperation/image-segmentation-positive.svg?react'
importer NegativeIcon depuis '@/components/icons/boardOperation/image-segmentation-negative.svg?react'

interface MarkPoint {
  poste : numéro[]
  étiquette : numéro
}

const SEGMENTATION_STATUS = {
  CHARGEMENT : 0,
  NO_SUPPORT_WEBGPU : 1,
  LOAD_ERROR : 2,
  CHARGE_SUCCÈS : 3,
  TRAITEMENT: 4,
  PROCESSING_SUCCESS : 5
}

tapez SegmentationStatusType =
  (type de SEGMENTATION_STATUS)[clé de type de SEGMENTATION_STATUS]

const ImageSegmentation : FC<{ url : string }> = ({url}) => {
  const [markPoints, setMarkPoints] = useState<MarkPoint[]>([])
  const [segmentationStatus, setSegmentationStatus] =
    useState<SegmentationStatusType>()
  const [pointStatus, setPointStatus] = useState<boolean>(true)

  const maskCanvasRef = useRef<HTMLCanvasElement>(null) // Masque de segmentation
  const modelRef = useRef<PreTrainedModel>() // modèle
  const processeurRef = useRef<Processor>() // processeur
  const imageInputRef = useRef<RawImage>() // image originale
  const imageProcessed = useRef<SamImageProcessorResult>() // Image traitée
  const imageEmbeddings = useRef<Tensor>() // Incorporation de données

  const segmentationTip = useMemo(() => {
    commutateur (segmentationStatus) {
      cas SEGMENTATION_STATUS.LOADING :
        return 'Fonction de segmentation d'image Chargement'
      cas SEGMENTATION_STATUS.NO_SUPPORT_WEBGPU :
        return 'WebGPU n'est pas pris en charge dans ce navigateur, pour utiliser la fonction de segmentation d'image, veuillez utiliser la dernière version de Google Chrome.'
      cas SEGMENTATION_STATUS.LOAD_ERROR :
        return 'La fonction de segmentation d'image n'a pas pu être chargée'
      cas SEGMENTATION_STATUS.LOAD_SUCCESS :
        return 'Fonction de segmentation d'image chargée avec succès'
      cas SEGMENTATION_STATUS.PROCESSING :
        return 'Traitement d'image...'
      cas SEGMENTATION_STATUS.PROCESSING_SUCCESS :
        return 'L'image a été traitée avec succès, vous pouvez cliquer sur l'image pour la marquer, la zone du masque vert est la zone de segmentation.'
      défaut:
        retour ''
    }
  }, [état de segmentation])

  // 1. charger le modèle et le processeur
  useEffect(() => {
    ;(async () => {
      essayer {
        si (segmentationStatus === SEGMENTATION_STATUS.LOADING) {
          retour
        }

        setSegmentationStatus(SEGMENTATION_STATUS.LOADING)
        si (!navigateur?.gpu) {
          setSegmentationStatus(SEGMENTATION_STATUS.NO_SUPPORT_WEBGPU)
          retour
        }const model_id = 'Xenova/slimsam-77-uniform'
        modelRef.current ??= attendre SamModel.from_pretrained(model_id, {
          dtype : 'fp16', // ou "fp32"
          périphérique : 'webgpu'
        })
        processeurRef.current ??= attendre AutoProcessor.from_pretrained(model_id)

        setSegmentationStatus(SEGMENTATION_STATUS.LOAD_SUCCESS)
      } attraper (erreur) {
        console.log('erreur', erreur)
        setSegmentationStatus(SEGMENTATION_STATUS.LOAD_ERROR)
      }
    })()
  }, [])

  // 2. image de processus
  useEffect(() => {
    ;(async () => {
      essayer {
        si (
          !modelRef.current ||
          !processorRef.current ||
          !URL ||
          segmentationStatus === SEGMENTATION_STATUS.PROCESSING
        ) {
          retour
        }
        setSegmentationStatus(SEGMENTATION_STATUS.PROCESSING)
        points clairs()

        imageInputRef.current = attendre RawImage.fromURL(url)
        imageProcessed.current = attendre processeurRef.current (
          imageInputRef.current
        )
        imageEmbeddings.current = attendre (
          modelRef.current comme n'importe quel autre
        ).get_image_embeddings(imageProcessed.current)

        setSegmentationStatus(SEGMENTATION_STATUS.PROCESSING_SUCCESS)
      } attraper (erreur) {
        console.log('erreur', erreur)
      }
    })()
  }, [url, modelRef.current, processeurRef.current])

  // Mise à jour de l'effet masque
  function updateMaskOverlay (masque : RawImage, scores : Float32Array) {
    const masqueCanvas = masqueCanvasRef.current
    si (!maskCanvas) {
      retour
    }
    const maskContext = maskCanvas.getContext('2d') comme CanvasRenderingContext2D

    // Mettre à jour les dimensions du canevas (si différentes)
    if (maskCanvas.width !== masque.width || maskCanvas.height !== masque.height) {
      masqueCanvas.width = masque.largeur
      masqueCanvas.hauteur = masque.hauteur
    }

    // Allouer un tampon pour les données de pixels
    const imageData = masqueContext.createImageData(
      masqueCanvas.largeur,
      masqueCanvas.hauteur
    )

    // Sélectionnez le meilleur masque
    const numMasks = scores.length // 3
    laissez bestIndex = 0
    pour (soit i = 1 ; i < numMasks ; i) {
      si (scores[i] > scores[bestIndex]) {
        meilleurIndex = je
      }
    }

    // Remplir le masque de couleur
    const pixelData = imageData.data
    pour (soit i = 0; i < pixelData.length; i) {
      if (mask.data[numMasks * i bestIndex] === 1) {
        décalage const = 4 * je
        pixelData[offset] = 101 // r
        pixelData[offset 1] = 204 // g
        pixelData[offset 2] = 138 // b
        pixelData[offset 3] = 255 // un
      }
    }

    // Dessine les données de l'image dans le contexte
    masqueContext.putImageData(imageData, 0, 0)
  }

  // 3. Décodage basé sur les données de clic
  const decode = async (markPoints : MarkPoint[]) => {
    si (
      !modelRef.current ||
      !imageEmbeddings.current ||
      !processorRef.current ||
      !imageProcessed.current
    ) {
      retour
    }// Aucun clic sur les données efface directement l'effet de segmentation
    if (!markPoints.length && maskCanvasRef.current) {
      const masqueContext = masqueCanvasRef.current.getContext(
        '2j'
      ) comme CanvasRenderingContext2D
      masqueContext.clearRect(
        0,
        0,
        masqueCanvasRef.current.width,
        masqueCanvasRef.current.height
      )
      retour
    }

    // Préparer les entrées pour le décodage
    const reshaped = imageProcessed.current.reshape_input_sizes[0]
    const points = markPoints
      .map((x) => [x.position[0] * remodelé[1], x.position[1] * remodelé[0]])
      .flat(Infini)
    const labels = markPoints.map((x) => BigInt(x.label)).flat(Infinity)

    const num_points = markPoints.longueur
    const input_points = new Tensor('float32', points, [1, 1, num_points, 2])
    const input_labels = new Tensor('int64', labels, [1, 1, num_points])

    // Génère le masque
    const { pred_masks, iou_scores } = attendre modelRef.current ({
      ...imageEmbeddings.current,
      points d'entrée,
      étiquettes_d'entrée
    })

    // Post-traiter le masque
    const masques = attendre (processorRef.current comme n'importe quel).post_process_masks(
      pred_masks,
      imageProcessed.current.original_sizes,
      imageProcessed.current.reshape_input_sizes
    )

    updateMaskOverlay(RawImage.fromTensor(masks[0][0]), iou_scores.data)
  }

  const clamp = (x : nombre, min = 0, max = 1) => {
    retourner Math.max(Math.min(x, max), min)
  }

  const clickImage = (e: MouseEvent) => {
    si (segmentationStatus !== SEGMENTATION_STATUS.PROCESSING_SUCCESS) {
      retour
    }

    const { clientX, clientY, currentTarget } = e
    const { gauche, haut } = currentTarget.getBoundingClientRect()

    const x = pince (
      (clientX - gauche currentTarget.scrollLeft) / currentTarget.scrollWidth
    )
    const y = pince (
      (clientY - haut currentTarget.scrollTop) / currentTarget.scrollHeight
    )

    const existantPointIndex = markPoints.findIndex(
      (point) =>
        Math.abs(point.position[0] - x) < 0,01 &&
        Math.abs(point.position[1] - y) < 0,01 &&
        point.label === (pointStatus ? 1 : 0)
    )

    const nouveauxPoints = [...markPoints]
    si (existingPointIndex !== -1) {
      // S'il y a un marqueur dans la zone actuellement cliquée, il est supprimé.
      newPoints.splice (existingPointIndex, 1)
    } autre {
      nouveauxPoints.push({
        position : [x, y],
        étiquette : pointStatus ? 1 : 0
      })
    }

    setMarkPoints (nouveaux Points)
    décoder (nouveaux points)
  }

  const clearPoints = () => {
    setMarkPoints([])
    décoder([])
  }

  retour (
    <div className="card shadow-xl overflow-auto">
      <div className="flex items-center gap-x-3">
        <button className="btn btn-primary btn-sm" onClick={clearPoints}>
          Effacer les points
        </bouton>

        <bouton
          className="btn btn-primaire btn-sm"
          onClick={() => setPointStatus(vrai)}
        >
          {statutpoint ? 'Positif' : 'Négatif'}
        </bouton>
      </div>
      <div className="text-xs text-base-content mt-2">{segmentationTip}</div>
      <div
       >



<h2>
  
  
  Conclusion
</h2>

<p>Merci d'avoir lu. C'est tout le contenu de cet article, j'espère que cet article vous sera utile, bienvenue pour aimer et mettre en favori. Si vous avez des questions, n'hésitez pas à en discuter dans la section commentaires !</p>


          

            
        
Copier après la connexion

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal