ホームページ  >  記事  >  ウェブフロントエンド  >  Canvas シリーズの探索: Transformers.js と組み合わせてインテリジェントな画像処理を実現

Canvas シリーズの探索: Transformers.js と組み合わせてインテリジェントな画像処理を実現

Susan Sarandon
Susan Sarandonオリジナル
2024-11-26 21:26:14115ブラウズ

導入

私は現在、強力なオープンソースのクリエイティブな描画ボードを保守しています。この描画ボードには、興味深いブラシと描画補助機能が多数統合されており、ユーザーは新しい描画効果を体験できます。モバイルでも PC でも、より優れたインタラクティブなエクスペリエンスとエフェクト表示をお楽しみいただけます。

この記事では、Transformers.js を組み合わせて背景の削除と画像マーキングのセグメンテーションを実現する方法を詳しく説明します。結果は以下の通りです

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

リンク: https://songlh.top/paint-board/

Github: https://github.com/LHRUN/paint-board Star ⭐️へようこそ

トランスフォーマー.js

Transformers.js は、Hugging Face の Transformers をベースにした強力な JavaScript ライブラリで、サーバー側の計算に依存せずにブラウザーで直接実行できます。つまり、モデルをローカルで実行できるため、効率が向上し、導入とメンテナンスのコストが削減されます。

現在、Transformers.js は Hugging Face で 1000 のモデルを提供しており、さまざまな領域をカバーしており、画像処理、テキスト生成、翻訳、感情分析、その他のタスク処理などのほとんどのニーズを満たすことができ、Transformers を通じて簡単に実現できます。 .js。以下のようにモデルを検索します。

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

Transformers.js の現在のメジャー バージョンは V3 に更新され、多くの優れた機能が追加されました。詳細: Transformers.js v3: WebGPU サポート、新しいモデルとタスクなど…

この記事に追加した両方の機能は、V3 でのみ利用できる WebGpu サポートを使用しており、処理速度が大幅に向上し、ミリ秒単位で解析できるようになりました。ただし、WebGPU をサポートするブラウザは多くないため、最新バージョンの Google を使用してアクセスすることをお勧めします。

機能 1: 背景を削除する

背景を削除するには、次のような Xenova/modnet モデルを使用します

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

処理ロジックは 3 つのステップに分けることができます

  1. 状態を初期化し、モデルとプロセッサをロードします。
  2. インターフェースの表示。これは私のデザインではなく、あなた自身のデザインに基づいています。
  3. エフェクトを表示します。これは私のデザインではなく、あなた自身のデザインに基づいています。現在では、境界線を使用して、背景を削除する前後のコントラスト効果を動的に表示することが一般的です。

コードロジックは次のとおりです。 React TS 、詳細についてはプロジェクトのソースコードを参照してください。ソースコードは 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

機能 2: 画像マーカーのセグメント化

画像マーカーのセグメンテーションは、Xenova/slimsam-77-uniform モデルを使用して実装されています。効果は次のとおりです。画像が読み込まれた後に画像をクリックすると、クリックした座標に応じてセグメンテーションが生成されます。

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

処理ロジックは 5 つのステップに分けることができます

  1. 状態を初期化し、モデルとプロセッサをロードします
  2. 画像を取得して読み込み、画像の読み込みデータと埋め込みデータを保存します。
  3. 画像のクリック イベントをリッスンし、クリック データを記録し、ポジティブ マーカーとネガティブ マーカーに分けます。各クリック後、デコードされたクリック データに従ってマスク データを生成し、マスク データに従ってセグメンテーション効果を描画します。 .
  4. インターフェース表示、これはあなた自身のデザインの恣意的な遊びであり、私の優先ではありません
  5. クリックして画像を保存し、マスクピクセルデータに従って元の画像データと一致させ、キャンバス描画を通じてエクスポートします

コードロジックは次のとおりです。 React TS 、詳細についてはプロジェクトのソースコードを参照してください。ソースコードは src/components/boardOperation/uploadImage/imageSegmentation.tsx にあります

import { useState, useRef, useEffect, useMemo, MouseEvent, FC } from 'react'
輸入 {
  サムモデル、
  オートプロセッサー、
  生画像、
  事前トレーニング済みモデル、
  プロセッサー、
  テンソル、
  SamImageProcessor結果
} '@huggingface/transformers' より

'@/components/icons/loading.svg?react' から LoadingIcon をインポートします
「@/components/icons/boardOperation/image-segmentation-positive.svg?react」から PositiveIcon をインポートします
「@/components/icons/boardOperation/image-segmentation-negative.svg?react」から NegativeIcon をインポートします

インターフェースMarkPoint {
  位置: 番号[]
  ラベル: 番号
}

const SEGMENTATION_STATUS = {
  読み込み中: 0、
  NO_SUPPORT_WEBGPU: 1、
  ロードエラー: 2、
  LOAD_SUCCESS: 3、
  処理: 4、
  処理_成功: 5
}

type SegmentationStatusType =
  (SEGMENTATION_STATUS のタイプ)[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) // セグメンテーション マスク
  const modelRef = useRef<PreTrainedModel>() // モデル
  constprocessorRef = useRef<Processor>() // プロセッサ
  const imageInputRef = useRef<RawImage>() // 元の画像
  const imageProcessed = useRef<SamImageProcessorResult>() // 処理された画像
  const imageEmbeddings = useRef<tensor>() // データの埋め込み

  constセグメンテーションヒント = useMemo(() => {
    switch (segmentationStatus) {
      SEGMENTATION_STATUS.LOADING の場合:
        return '画像分割機能読み込み中'
      SEGMENTATION_STATUS.NO_SUPPORT_WEBGPU の場合:
        return 'このブラウザでは WebGPU がサポートされていません。画像分割機能を使用するには、最新バージョンの Google Chrome を使用してください。'
      SEGMENTATION_STATUS.LOAD_ERROR の場合:
        return '画像セグメンテーション関数のロードに失敗しました'
      SEGMENTATION_STATUS.LOAD_SUCCESS の場合:
        return '画像分割機能が正常にロードされました'
      ケース SEGMENTATION_STATUS.PROCESSING:
        return '画像処理中...'
      SEGMENTATION_STATUS.PROCESSING_SUCCESS の場合:
        return '画像は正常に処理されました。画像をクリックしてマークを付けることができます。緑色のマスク領域がセグメンテーション領域です。'
      デフォルト:
        戻る ''
    }
  }, [セグメンテーションステータス])

  // 1. モデルとプロセッサをロードします
  useEffect(() => {
    ;(async () => {
      試す {
        if (segmentationStatus === SEGMENTATION_STATUS.LOADING) {
          戻る
        }

        setSegmentationStatus(SEGMENTATION_STATUS.LOADING)
        if (!navigator?.gpu) {
          setSegmentationStatus(SEGMENTATION_STATUS.NO_SUPPORT_WEBGPU)
          戻る
        }const model_id = 'Xenova/slimsam-77-uniform'
        modelRef.current ??= await SamModel.from_pretrained(model_id, {
          dtype: 'fp16'、// または "fp32"
          デバイス: 「webgpu」
        })
        processorRef.current ??= await AutoProcessor.from_pretrained(model_id)

        setSegmentationStatus(SEGMENTATION_STATUS.LOAD_SUCCESS)
      } キャッチ (エラー) {
        console.log('エラー', エラー)
        setSegmentationStatus(SEGMENTATION_STATUS.LOAD_ERROR)
      }
    })()
  }、[])

  // 2.画像処理
  useEffect(() => {
    ;(async () => {
      試す {
        もし (
          !modelRef.current ||
          !processorRef.current ||
          !url ||
          セグメンテーションステータス === SEGMENTATION_STATUS.PROCESSING
        ) {
          戻る
        }
        setSegmentationStatus(SEGMENTATION_STATUS.PROCESSING)
        クリアポイント()

        imageInputRef.current = RawImage.fromURL(url) を待ちます
        imageProcessed.current = awaitprocessorRef.current(
          imageInputRef.current
        )
        imageEmbeddings.current = await (
          任意のmodelRef.current
        ).get_image_embeddings(imageProcessed.current)

        setSegmentationStatus(SEGMENTATION_STATUS.PROCESSING_SUCCESS)
      } キャッチ (エラー) {
        console.log('エラー', エラー)
      }
    })()
  }, [url,modelRef.current,processorRef.current])

  // マスク効果を更新します
  関数 updateMaskOverlay(マスク: RawImage、スコア: Float32Array) {
    const MaskCanvas = MaskCanvasRef.current
    if (!maskCanvas) {
      戻る
    }
    const MaskContext = MaskCanvas.getContext('2d') as CanvasRenderingContext2D

    // キャンバスの寸法を更新します (異なる場合)
    if (maskCanvas.width !== マスク.幅 || マスクキャンバス.高さ !== マスク.高さ) {
      マスクキャンバス.幅 = マスク.幅
      マスクキャンバスの高さ = マスク.高さ
    }

    // ピクセルデータ用のバッファを確保
    const imageData = MaskContext.createImageData(
      マスクキャンバスの幅、
      マスクキャンバスの高さ
    )

    // 最適なマスクを選択
    const numMasks = スコア.length // 3
    bestIndex = 0 にします
    for (let i = 1; i  スコア[bestIndex]) {
        bestIndex = i
      }
    }

    // マスクを色で塗りつぶします
    const ピクセルデータ = imageData.data
    for (let i = 0; i <pixeldata.length i if bestindex r g b a maskcontext.putimagedata const decode="async" markpoint> {
    もし (
      !modelRef.current ||
      !imageEmbeddings.current ||
      !processorRef.current ||
      !imageProcessed.current
    ) {
      戻る
    }// データをクリックしないとセグメンテーション効果が直接クリアされません
    if (!markPoints.length && MaskCanvasRef.current) {
      const マスクコンテキスト = マスクCanvasRef.current.getContext(
        「2D」
      ) CanvasRenderingContext2D として
      マスクコンテキスト.clearRect(
        0、
        0、
        マスクCanvasRef.current.width、
        マスクCanvasRef.current.height
      )
      戻る
    }

    // デコード用の入力を準備します
    const reshape = imageProcessed.current.reshape_input_sizes[0]
    定数ポイント = マークポイント
      .map((x) => [x.position[0] * reshape[1], x.position[1] * reshape[0]])
      .フラット(無限大)
    const label = markPoints.map((x) => BigInt(x.label)). flat(Infinity)

    const num_points = markPoints.length
    const input_points = new Tensor('float32', ポイント, [1, 1, num_points, 2])
    const input_labels = new Tensor('int64', ラベル, [1, 1, num_points])

    // マスクを生成する
    const { pred_masks, iou_scores } = await modelRef.current({
      ...imageEmbeddings.current、
      入力ポイント、
      入力ラベル
    })

    // マスクの後処理
    constマスク = await (processorRef.current as any).post_process_masks(
      pred_マスク、
      imageProcessed.current.original_sizes、
      imageProcessed.current.reshape_input_sizes
    )

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

  const クランプ = (x: 数値、最小 = 0、最大 = 1) => {
    return Math.max(Math.min(x, max), min)
  }

  const clickImage = (e: MouseEvent) => {
    if (segmentationStatus !== SEGMENTATION_STATUS.PROCESSING_SUCCESS) {
      戻る
    }

    const { clientX, clientY, currentTarget } = e
    const { 左、上 } = currentTarget.getBoundingClientRect()

    const x = クランプ(
      (clientX - 左 currentTarget.scrollLeft) / currentTarget.scrollWidth
    )
    const y = クランプ(
      (clientY - トップ currentTarget.scrollTop) / currentTarget.scrollHeight
    )

    const presentPointIndex = markPoints.findIndex(
      (ポイント) =>
        Math.abs(point.position[0] - x) 
          {ポイントステータス ? 'ポジティブ' : 'ネガティブ'}
        </ボタン>
      </div>
      <div className="text-xs text-base-content mt-2">{segmentationTip}</div>
      <div>



<h2>
  
  
  結論
</h2>

<p>読んでいただきありがとうございます。これがこの記事の全内容です。この記事があなたのお役に立てば幸いです。「いいね」やお気に入り登録を歓迎します。ご質問がございましたら、コメント欄でお気軽にご相談ください!</p>


          </div>

            
        </pixeldata.length></tensor></markpoint>

以上がCanvas シリーズの探索: Transformers.js と組み合わせてインテリジェントな画像処理を実現の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。