Rumah  >  Artikel  >  hujung hadapan web  >  Meneroka Siri Kanvas: digabungkan dengan Transformers.js untuk mencapai pemprosesan imej pintar

Meneroka Siri Kanvas: digabungkan dengan Transformers.js untuk mencapai pemprosesan imej pintar

Susan Sarandon
Susan Sarandonasal
2024-11-26 21:26:14115semak imbas

pengenalan

Saya sedang mengekalkan papan lukisan kreatif sumber terbuka yang berkuasa. Papan lukisan ini menyepadukan banyak berus menarik dan fungsi lukisan tambahan, yang membolehkan pengguna mengalami kesan lukisan baharu. Sama ada pada mudah alih atau PC, anda boleh menikmati pengalaman interaktif dan paparan kesan yang lebih baik.

Dalam artikel ini, saya akan menerangkan secara terperinci cara menggabungkan Transformers.js untuk mencapai penyingkiran latar belakang dan segmentasi penandaan imej. Hasilnya adalah seperti berikut

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

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

Github: https://github.com/LHRUN/paint-board Selamat Datang ke Bintang ⭐️

Transformers.js

Transformers.js ialah perpustakaan JavaScript yang berkuasa berdasarkan Transformers Memeluk Wajah yang boleh dijalankan terus dalam penyemak imbas tanpa bergantung pada pengiraan sebelah pelayan. Ini bermakna anda boleh menjalankan model anda secara tempatan, meningkatkan kecekapan dan mengurangkan kos penggunaan dan penyelenggaraan.

Pada masa ini Transformers.js telah menyediakan 1000 model pada Wajah Memeluk, meliputi pelbagai domain, yang boleh memenuhi kebanyakan keperluan anda, seperti pemprosesan imej, penjanaan teks, terjemahan, analisis sentimen dan pemprosesan tugas lain, anda boleh capai dengan mudah melalui Transformers .js. Cari model seperti berikut.

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

Versi utama semasa Transformers.js telah dikemas kini kepada V3, yang menambahkan banyak ciri hebat, butiran: Transformers.js v3: Sokongan WebGPU, Model & Tugasan Baharu dan Lagi….

Kedua-dua ciri yang saya tambahkan pada siaran ini menggunakan sokongan WebGpu, yang hanya tersedia dalam V3, dan telah meningkatkan kelajuan pemprosesan dengan banyak, dengan penghuraian kini dalam milisaat. Walau bagaimanapun, perlu diingatkan bahawa tidak banyak penyemak imbas yang menyokong WebGPU, jadi disyorkan untuk menggunakan versi terkini Google untuk melawati.

Fungsi 1: Alih keluar latar belakang

Untuk mengalih keluar latar belakang saya menggunakan model Xenova/modnet, yang kelihatan seperti ini

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

Logik pemprosesan boleh dibahagikan kepada tiga langkah

  1. mulakan keadaan, dan muatkan model serta pemproses.
  2. paparan antara muka, ini berdasarkan reka bentuk anda sendiri, bukan pada saya.
  3. Tunjukkan kesannya, ini berdasarkan rekaan anda sendiri, bukan rekaan saya. Pada masa kini, lebih popular untuk menggunakan garis sempadan untuk memaparkan kesan kontras secara dinamik sebelum dan selepas mengalih keluar latar belakang.

Logik kod adalah seperti berikut, React TS , lihat kod sumber projek saya untuk butiran, kod sumber terletak dalam 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

Fungsi 2: Segmentasi Penanda Imej

Segmentasi penanda imej dilaksanakan menggunakan model seragam Xenova/slimsam-77. Kesannya adalah seperti berikut, anda boleh mengklik pada imej selepas ia dimuatkan, dan pembahagian dijana mengikut koordinat klik anda.

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

Logik pemprosesan boleh dibahagikan kepada lima langkah

  1. mulakan keadaan, dan muatkan model serta pemproses
  2. Dapatkan imej dan muatkannya, kemudian simpan data pemuatan imej dan data benam.
  3. dengar acara klik imej, rekod data klik, dibahagikan kepada penanda positif dan penanda negatif, selepas setiap klik mengikut data klik dinyahkod untuk menjana data topeng, dan kemudian mengikut data topeng untuk melukis kesan pembahagian .
  4. Paparan antara muka, ini untuk reka bentuk anda sendiri bermain sewenang-wenangnya, bukan keutamaan saya
  5. klik untuk menyimpan imej, mengikut data piksel topeng, padankan data imej asal, dan kemudian dieksport melalui lukisan kanvas

Logik kod adalah seperti berikut, React TS , lihat kod sumber projek saya untuk butiran, kod sumber terletak dalam src/components/boardOperation/uploadImage/imageSegmentation.tsx

import { useState, useRef, useEffect, useMemo, MouseEvent, FC } daripada 'react'
import {
  SamModel,
  AutoProcessor,
  RawImage,
  PreTrainedModel,
  pemproses,
  Tensor,
  SamImageProcessorResult
} daripada '@huggingface/transformers'

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

antara muka MarkPoint {
  kedudukan: nombor[]
  label: nombor
}

const SEGMENTATION_STATUS = {
  MEMBUAT: 0,
  NO_SUPPORT_WEBGPU: 1,
  LOAD_ERROR: 2,
  LOAD_SUCCESS: 3,
  PEMPROSESAN: 4,
  MEMPROSES_KEJAYAAN: 5
}

taip SegmentationStatusType =
  (jenis SEGMENTATION_STATUS)[kunci jenis SEGMENTATION_STATUS]

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

  const maskCanvasRef = useRef<HTMLCanvasElement>(null) // Topeng pembahagian
  const modelRef = useRef<PreTrainedModel>() // model
  const processorRef = useRef<Processor>() // pemproses
  const imageInputRef = useRef<RawImage>() // imej asal
  const imageProcessed = useRef<SamImageProcessorResult>() // Imej yang diproses
  const imageEmbeddings = useRef<Tensor>() // Membenamkan data

  const segmentationTip = useMemo(() => {
    suis (Status pembahagian) {
      kes SEGMENTATION_STATUS.LOADING:
        kembalikan 'Fungsi Segmentasi Imej Memuatkan'
      kes SEGMENTATION_STATUS.NO_SUPPORT_WEBGPU:
        kembali 'WebGPU tidak disokong dalam penyemak imbas ini, untuk menggunakan fungsi pembahagian imej, sila gunakan versi terkini Google Chrome.'
      kes SEGMENTATION_STATUS.LOAD_ERROR:
        kembalikan 'Fungsi Segmentasi Imej gagal dimuatkan'
      kes SEGMENTATION_STATUS.LOAD_SUCCESS:
        kembalikan 'Fungsi Segmentasi Imej berjaya dimuatkan'
      kes SEGMENTATION_STATUS.PROCESSING:
        kembalikan 'Pemprosesan Imej...'
      kes SEGMENTATION_STATUS.PROCESSING_SUCCESS:
        return 'Imej telah berjaya diproses, anda boleh klik pada imej untuk menandakannya, kawasan topeng hijau ialah kawasan segmentasi.'
      lalai:
        kembalikan ''
    }
  }, [Status segmentasi])

  // 1. model beban dan pemproses
  useEffect(() => {
    ;(async () => {
      cuba {
        jika (segmentationStatus === SEGMENTATION_STATUS.LOADING) {
          kembali
        }

        setSegmentationStatus(SEGMENTATION_STATUS.LOADING)
        jika (!navigator?.gpu) {
          setSegmentationStatus(SEGMENTATION_STATUS.NO_SUPPORT_WEBGPU)
          kembali
        }const model_id = 'Xenova/slimsam-77-uniform'
        modelRef.current ??= tunggu SamModel.from_pretrained(model_id, {
          dtype: 'fp16', // atau "fp32"
          peranti: 'webgpu'
        })
        processorRef.current ??= tunggu AutoProcessor.from_pretrained(model_id)

        setSegmentationStatus(SEGMENTATION_STATUS.LOAD_SUCCESS)
      } tangkap (err) {
        console.log('err', err)
        setSegmentationStatus(SEGMENTATION_STATUS.LOAD_ERROR)
      }
    })()
  }, [])

  // 2. proses imej
  useEffect(() => {
    ;(async () => {
      cuba {
        jika (
          !modelRef.current ||
          !processorRef.current ||
          !url ||
          segmentationStatus === SEGMENTATION_STATUS.PROCESSING
        ) {
          kembali
        }
        setSegmentationStatus(SEGMENTATION_STATUS.PROCESSING)
        clearPoints()

        imageInputRef.current = menunggu RawImage.fromURL(url)
        imageProcessed.current = menunggu processorRef.current(
          imageInputRef.current
        )
        imageEmbeddings.current = menunggu (
          modelRef.semasa seperti mana-mana
        ).get_image_embeddings(imageProcessed.current)

        setSegmentationStatus(SEGMENTATION_STATUS.PROCESSING_SUCCESS)
      } tangkap (err) {
        console.log('err', err)
      }
    })()
  }, [url, modelRef.current, processorRef.current])

  // Mengemas kini kesan topeng
  function updateMaskOverlay(topeng: RawImage, markah: Float32Array) {
    const maskCanvas = maskCanvasRef.current
    jika (!maskCanvas) {
      kembali
    }
    const maskContext = maskCanvas.getContext('2d') sebagai CanvasRenderingContext2D

    // Kemas kini dimensi kanvas (jika berbeza)
    jika (maskCanvas.width !== mask.width || maskCanvas.height !== mask.height) {
      maskCanvas.width = mask.width
      maskCanvas.height = mask.height
    }

    // Peruntukkan penimbal untuk data piksel
    const imageData = maskContext.createImageData(
      maskCanvas.width,
      topengKanvas.tinggi
    )

    // Pilih topeng terbaik
    const numMasks = scores.length // 3
    biarkan bestIndex = 0
    untuk (biar i = 1; i < numMasks; i) {
      if (skor[i] > markah[bestIndex]) {
        BestIndex = i
      }
    }

    // Isi topeng dengan warna
    const pixelData = imageData.data
    untuk (biar i = 0; i < pixelData.length; i) {
      if (mask.data[numMasks * i bestIndex] === 1) {
        const offset = 4 * i
        pixelData[offset] = 101 // r
        pixelData[offset 1] = 204 // g
        pixelData[offset 2] = 138 // b
        pixelData[offset 3] = 255 // a
      }
    }

    // Lukis data imej ke konteks
    maskContext.putImageData(imageData, 0, 0)
  }

  // 3. Menyahkod berdasarkan data klik
  dekod const = tak segerak (markPoints: MarkPoint[]) => {
    jika (
      !modelRef.current ||
      !imageEmbeddings.current ||
      !processorRef.current ||
      !imageProcessed.current
    ) {
      kembali
    }// Tiada klik pada data secara langsung mengosongkan kesan pembahagian
    jika (!markPoints.length && maskCanvasRef.current) {
      const maskContext = maskCanvasRef.current.getContext(
        '2h'
      ) sebagai CanvasRenderingContext2D
      maskContext.clearRect(
        0,
        0,
        maskCanvasRef.current.width,
        maskCanvasRef.current.height
      )
      kembali
    }

    // Sediakan input untuk penyahkodan
    const reshaped = imageProcessed.current.reshaped_input_sizes[0]
    mata const = markPoints
      .map((x) => [x.kedudukan[0] * bentuk semula[1], x.kedudukan[1] * bentuk semula[0]])
      .flat(Infiniti)
    label const = markPoints.map((x) => BigInt(x.label)).flat(Infinity)

    const num_points = markPoints.length
    const input_points = Tensor baharu('float32', mata, [1, 1, num_points, 2])
    const input_labels = Tensor baharu('int64', labels, [1, 1, num_points])

    // Hasilkan topeng
    const { pred_masks, iou_scores } = tunggu modelRef.current({
      ...imageEmbeddings.current,
      input_points,
      input_labels
    })

    // Proses pasca topeng
    const masks = menunggu (processorRef.current as any).post_process_masks(
      pred_masks,
      imageProcessed.current.original_sizes,
      imageProcessed.current.reshaped_input_sizes
    )

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

  pengapit const = (x: nombor, min = 0, maks = 1) => {
    kembalikan Math.max(Math.min(x, max), min)
  }

  const clickImage = (e: MouseEvent) => {
    jika (Status segmentasi !== STATUS_SEGMENTASI.KEJAYAAN_PROSES) {
      kembali
    }

    const { clientX, clientY, currentTarget } = e
    const { kiri, atas } = currentTarget.getBoundingClientRect()

    const x = pengapit(
      (clientX - left currentTarget.scrollLeft) / currentTarget.scrollWidth
    )
    const y = pengapit(
      (clientY - atas currentTarget.scrollTop) / currentTarget.scrollHeight
    )

    const existingPointIndex = markPoints.findIndex(
      (titik) =>
        Matematik.abs(titik.kedudukan[0] - x) < 0.01 &&
        Matematik.abs(titik.kedudukan[1] - y) < 0.01 &&
        point.label === (pointStatus ? 1 : 0)
    )

    const newPoints = [...markPoints]
    jika (existingPointIndex !== -1) {
      // Jika terdapat penanda dalam kawasan yang sedang diklik, ia akan dipadamkan.
      newPoints.splice(existingPointIndex, 1)
    } lain {
      newPoints.push({
        kedudukan: [x, y],
        label: pointStatus ? 1: 0
      })
    }

    setMarkPoints(newPoints)
    nyahkod(newPoints)
  }

  const clearPoints = () => {
    setMarkPoints([])
    nyahkod([])
  }

  kembali (
    <div className="card shadow-xl overflow-auto">
      <div className="flex items-center gap-x-3">
        <button className="btn btn-primary btn-sm" onClick={clearPoints}>
          Jelas Mata
        </butang>

        <butang
          className="btn btn-primary btn-sm"
          onClick={() => setPointStatus(benar)}
        >
          {pointStatus ? 'Positif' : 'Negatif'}
        </butang>
      </div>
      <div className="text-xs text-base-content mt-2">{segmentationTip}</div>
      <div
       >



<h2>
  
  
  Kesimpulan
</h2>

<p>Terima kasih kerana membaca. Ini adalah keseluruhan kandungan artikel ini, saya harap artikel ini berguna kepada anda, dialu-alukan untuk menyukai dan kegemaran. Jika anda mempunyai sebarang pertanyaan, sila berbincang di ruangan komen!</p>


          

            
        

Atas ialah kandungan terperinci Meneroka Siri Kanvas: digabungkan dengan Transformers.js untuk mencapai pemprosesan imej pintar. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn