Rumah > Artikel > hujung hadapan web > Meneroka Siri Kanvas: digabungkan dengan Transformers.js untuk mencapai pemprosesan imej pintar
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
Pautan: https://songlh.top/paint-board/
Github: https://github.com/LHRUN/paint-board Selamat Datang ke Bintang ⭐️
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.
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.
Untuk mengalih keluar latar belakang saya menggunakan model Xenova/modnet, yang kelihatan seperti ini
Logik pemprosesan boleh dibahagikan kepada tiga langkah
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
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.
Logik pemprosesan boleh dibahagikan kepada lima langkah
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!