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
273 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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

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!

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
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal