ホームページ > 記事 > ウェブフロントエンド > Canvas シリーズの探索: Transformers.js と組み合わせてインテリジェントな画像処理を実現
私は現在、強力なオープンソースのクリエイティブな描画ボードを保守しています。この描画ボードには、興味深いブラシと描画補助機能が多数統合されており、ユーザーは新しい描画効果を体験できます。モバイルでも PC でも、より優れたインタラクティブなエクスペリエンスとエフェクト表示をお楽しみいただけます。
この記事では、Transformers.js を組み合わせて背景の削除と画像マーキングのセグメンテーションを実現する方法を詳しく説明します。結果は以下の通りです
リンク: https://songlh.top/paint-board/
Github: https://github.com/LHRUN/paint-board Star ⭐️へようこそ
Transformers.js は、Hugging Face の Transformers をベースにした強力な JavaScript ライブラリで、サーバー側の計算に依存せずにブラウザーで直接実行できます。つまり、モデルをローカルで実行できるため、効率が向上し、導入とメンテナンスのコストが削減されます。
現在、Transformers.js は Hugging Face で 1000 のモデルを提供しており、さまざまな領域をカバーしており、画像処理、テキスト生成、翻訳、感情分析、その他のタスク処理などのほとんどのニーズを満たすことができ、Transformers を通じて簡単に実現できます。 .js。以下のようにモデルを検索します。
Transformers.js の現在のメジャー バージョンは V3 に更新され、多くの優れた機能が追加されました。詳細: Transformers.js v3: WebGPU サポート、新しいモデルとタスクなど…
この記事に追加した両方の機能は、V3 でのみ利用できる WebGpu サポートを使用しており、処理速度が大幅に向上し、ミリ秒単位で解析できるようになりました。ただし、WebGPU をサポートするブラウザは多くないため、最新バージョンの Google を使用してアクセスすることをお勧めします。
背景を削除するには、次のような Xenova/modnet モデルを使用します
処理ロジックは 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
画像マーカーのセグメンテーションは、Xenova/slimsam-77-uniform モデルを使用して実装されています。効果は次のとおりです。画像が読み込まれた後に画像をクリックすると、クリックした座標に応じてセグメンテーションが生成されます。
処理ロジックは 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 サイトの他の関連記事を参照してください。