概述
在計(jì)算機(jī)圖形學(xué)和圖像處理中,stride通常指的是在內(nèi)存中存儲(chǔ)多維數(shù)組(如圖像或紋理)時(shí),行與行之間的字節(jié)間隔,即每一行的起始地址與下一行的起始地址之間的距離,在本文中stride指的是圖像的一行數(shù)據(jù)在內(nèi)存中實(shí)際占用的字節(jié)數(shù),為了內(nèi)存對(duì)齊和提高讀取效率的要求,通常大于圖像的寬度。在解析圖像內(nèi)容時(shí),如果未考慮stride,直接通過使用width*height讀取圖像內(nèi)容去解析圖像,會(huì)導(dǎo)致相機(jī)預(yù)覽異常;當(dāng)預(yù)覽流圖像stride與width不一致時(shí),需要對(duì)stride進(jìn)行無(wú)效像素去除處理。
實(shí)現(xiàn)原理
當(dāng)圖像存儲(chǔ)在內(nèi)存中時(shí),內(nèi)存緩沖區(qū)可能在每行像素之后包含額外的填充字節(jié)。填充字節(jié)會(huì)影響圖像在內(nèi)存中的存儲(chǔ)方式,但不會(huì)影響圖像的顯示方式。stride是內(nèi)存中一行像素到內(nèi)存中下一行像素的字節(jié)數(shù);如果存在填充字節(jié),則步幅比圖像的寬度寬。
說(shuō)明:stride在不同的平臺(tái)底層上報(bào)的值不同,開發(fā)者需根據(jù)實(shí)際業(yè)務(wù)獲取stride后做處理適配。在本文中通過預(yù)覽流幀數(shù)據(jù)的返回值image.Component.rowStride獲取stride。
如下圖:在一個(gè)width為3,height為3,stride為4的圖片上(例如定義了一個(gè)480*480分辨率的圖像),實(shí)際分配內(nèi)存并不是width*height即3*3(此處為定義的預(yù)覽流分辨率的寬高比,即實(shí)際分配內(nèi)存不是480*480),而是stride*height即4*3,這樣實(shí)現(xiàn)了內(nèi)存對(duì)齊,方便硬件處理。
圖1:需正確處理stride
如果開發(fā)者根據(jù)width和height數(shù)據(jù)去處理像素?cái)?shù)據(jù),即把0x00-0x09地址的數(shù)據(jù)當(dāng)做像素去處理,就會(huì)出現(xiàn)解析了錯(cuò)誤的像素?cái)?shù)據(jù)的問題,并且使用了無(wú)效的像素0x03,0x07,會(huì)導(dǎo)致圖片無(wú)法正常顯示導(dǎo)致“相機(jī)花屏”現(xiàn)象。因此,要根據(jù)stride值處理預(yù)覽數(shù)據(jù)流,去除無(wú)效的像素后送顯,才能獲取正確的預(yù)覽流圖像。
場(chǎng)景案例
以一種高頻的用戶使用場(chǎng)景為例,應(yīng)用需要定義一個(gè)1080*1080分辨率的預(yù)覽流圖像,此時(shí)的stride在相關(guān)平臺(tái)的返回值為1088,此時(shí)需要對(duì)stride進(jìn)行處理,處理無(wú)效像素后解析出正確的像素?cái)?shù)據(jù),避免出現(xiàn)預(yù)覽流花屏。
【反例】未處理stride:當(dāng)開發(fā)者創(chuàng)建PixelMap解析buffer時(shí),直接按照寬去讀取每行數(shù)據(jù),沒有處理stride,此時(shí)若解析了無(wú)效像素?cái)?shù)據(jù)并傳給Image組件直接送顯,可能會(huì)出現(xiàn)預(yù)覽流花屏現(xiàn)象。
以下為部分示例代碼:
1. 應(yīng)用通過image.ImageReceiver注冊(cè)imageArrival圖像回調(diào)方法,獲取每幀圖像數(shù)據(jù)實(shí)例image.Image,應(yīng)用通過定義一個(gè)width為1080*height為1080分辨率的預(yù)覽流直接創(chuàng)建pixelMap,此時(shí)獲取到的stride的值為1088,解析buffer時(shí)若直接按照寬去讀取每行數(shù)據(jù)(使用了無(wú)效像素?cái)?shù)據(jù))并存儲(chǔ)到全局變量stridePixel中,傳給Image送顯,會(huì)導(dǎo)致預(yù)覽流花屏。
onImageArrival(receiver: image.ImageReceiver):void{ receiver.on('imageArrival',() =>{ receiver.readNextImage((err: BusinessError, nextImage: image.Image) =>{ if(err || nextImage ===undefined) { Logger.error(TAG,`requestPermissionsFromUser call Failed! error:${err.code}`); return; } if(nextImage) { nextImage.getComponent(image.ComponentType.JPEG,async(_err,component: image.Component) => { letwidth =1080;// width為應(yīng)用創(chuàng)建預(yù)覽流分辨率對(duì)應(yīng)的寬 letheight =1080;// height為應(yīng)用創(chuàng)建預(yù)覽流分辨率對(duì)應(yīng)的高 // component.byteBuffer為相機(jī)返回的預(yù)覽流數(shù)據(jù),其中包含了stride對(duì)齊數(shù)據(jù) letpixelMap =awaitimage.createPixelMap(component.byteBuffer, { size: { height: height, width: width }, // 反例:width沒有處理stride值,創(chuàng)建PixelMap解析buffer時(shí)直接按照寬去讀取每行數(shù)據(jù),可能使用了無(wú)效像素?cái)?shù)據(jù),導(dǎo)致預(yù)覽流花屏。 srcPixelFormat: image.PixelMapFormat.NV21 }) AppStorage.setOrCreate('stridePixel', pixelMap);// 將創(chuàng)建出的PixelMap存儲(chǔ)到全局變量stridePixel中并傳給Image組件送顯。 nextImage.release(); }) } }); }) }
2. 在初始相機(jī)模塊時(shí),調(diào)用onImageArrival(),將未處理的width和height作為size,創(chuàng)建PixelMap,通過在Image中傳入被@StorageLink修飾的變量stridePixel進(jìn)行數(shù)據(jù)刷新,圖片送顯。
@Component exportstructPageThree{ pathStack:NavPathStack=newNavPathStack(); @StateisShowStridePixel:boolean=false; @StorageLink('stridePixel')@Watch('onStridePixel')stridePixel: image.PixelMap|undefined=undefined; @StateimageWidth:number=1080; @StateimageHeight:number=1080; @StorageLink('previewRotation')previewRotate:number=0; onStridePixel():void{ this.isShowStridePixel=true; } aboutToAppear():void{ CameraService.initCamera(0); } aboutToDisappear():void{ CameraService.releaseCamera(); } // ... build() { NavDestination() { // ... Column() { if(this.isShowStridePixel) { Image(this.stridePixel)// 反例:解析了錯(cuò)誤的像素?cái)?shù)據(jù),并存儲(chǔ)到全局變量stridePixel中,傳給Image送顯,會(huì)導(dǎo)致相機(jī)預(yù)覽流花屏。 .width(px2vp(this.imageWidth)) .height(px2vp(this.imageHeight)) .margin({top:150}) .rotate({ z:0.5, angle:this.previewRotate }) } // ... } .justifyContent(FlexAlign.Center) .height('90%') .width('100%') } .backgroundColor(Color.White) .hideTitleBar(true) .onBackPressed(() =>{ this.pathStack.pop(); returntrue; }) .onReady((context: NavDestinationContext) =>{ this.pathStack= context.pathStack; }) } }
【正例一】開發(fā)者使用width,height,stride三個(gè)值,處理相機(jī)預(yù)覽流數(shù)據(jù),處理stride方法一如下。分兩種情況:
1. 當(dāng)stride和width相等時(shí),按寬讀取buffer不影響結(jié)果。
2. 當(dāng)stride和width不等時(shí),將相機(jī)返回的預(yù)覽流數(shù)據(jù)即component.byteBuffer的數(shù)據(jù)去除stride,拷貝得到新的dstArr數(shù)據(jù)進(jìn)行數(shù)據(jù)處理,將處理后的dstArr數(shù)組buffer,通過width和height直接創(chuàng)建pixelMap, 并存儲(chǔ)到全局變量stridePixel中,傳給Image送顯。
onImageArrival(receiver: image.ImageReceiver):void{ receiver.on('imageArrival',() =>{ receiver.readNextImage((err: BusinessError, nextImage: image.Image) =>{ // ... if(nextImage) { nextImage.getComponent(image.ComponentType.JPEG, async(err,component: image.Component) => { letwidth =1080;// width為應(yīng)用創(chuàng)建預(yù)覽流分辨率對(duì)應(yīng)的寬 letheight =1080;// height為應(yīng)用創(chuàng)建預(yù)覽流分辨率對(duì)應(yīng)的高 letstride = component.rowStride;// 通過component.rowStride獲取stride // 正例:情況1. 當(dāng)圖片的width等于相機(jī)預(yù)覽流返回的行跨距stride,此時(shí)無(wú)需處理stride,通過width和height直接創(chuàng)建pixelMap, // 并存儲(chǔ)到全局變量stridePixel中,傳給Image送顯。 if(stride === width) { letpixelMap =awaitimage.createPixelMap(component.byteBuffer, { size: {height: height,width: width }, srcPixelFormat: image.PixelMapFormat.NV21, }) AppStorage.setOrCreate('stridePixel', pixelMap); }else{ // 正例:情況2. 當(dāng)圖片的width不等于相機(jī)預(yù)覽流返回的行跨距stride, // 此時(shí)將相機(jī)返回的預(yù)覽流數(shù)據(jù)component.byteBuffer去除掉stride,拷貝得到新的dstArr數(shù)據(jù),數(shù)據(jù)處理后傳給其他不支持stride的接口處理。 constdstBufferSize = width * height *1.5;// 創(chuàng)建一個(gè)width * height * 1.5的dstBufferSize空間,此處為NV21數(shù)據(jù)格式。 constdstArr =newUint8Array(dstBufferSize);// 存放去掉stride后的buffer。 // 讀取每行數(shù)據(jù),相機(jī)支持的profile寬高均為偶數(shù),不涉及取整問題。 for(letj =0; j < height *?1.5; j++) {?// 循環(huán)dstArr的每一行數(shù)據(jù)。 ? ? ? ? ? ? ? ??// 拷貝component.byteBuffer的每行數(shù)據(jù)前width個(gè)字節(jié)到dstArr中(去除無(wú)效像素,剛好每行得到一個(gè)width*height的八字節(jié)數(shù)組空間)。 ? ? ? ? ? ? ? ??const?srcBuf =?new?Uint8Array(component.byteBuffer, j * stride, ? ? ? ? ? ? ? ? width);?// 將component.byteBuffer返回的buffer,每行遍歷,從首位開始,每行截取出width字節(jié)。 ? ? ? ? ? ? ? ? dstArr.set(srcBuf, j * width);?// 將width*height大小的數(shù)據(jù)存儲(chǔ)到dstArr中。 ? ? ? ? ? ? } ? ? ? ? ? ??let?pixelMap =?await?image.createPixelMap(dstArr.buffer, { ? ? ? ? ? ? ? ??// 將處理后的dstArr數(shù)組buffer,通過width和height直接創(chuàng)建pixelMap,并存儲(chǔ)到全局變量stridePixel中,傳給Image送顯。 ? ? ? ? ? ? ? ??size: {?height: height,?width: width }, ? ? ? ? ? ? ? ??srcPixelFormat: image.PixelMapFormat.NV21, ? ? ? ? ? ? }) ? ? ? ? ? ??AppStorage.setOrCreate('stridePixel', pixelMap); ? ? ? ? ? ? } ? ? ? ? ? ? nextImage.release(); ? ? ? ? }) ? ? } ? ? }); }) }
【正例二】開發(fā)者使用width,height,stride三個(gè)值,處理相機(jī)預(yù)覽流數(shù)據(jù),處理stride方法二如下。分兩種情況:
1. 當(dāng)stride和width相等時(shí),與正例一情況一致,此處不再贅述。
2. 當(dāng)stride和width不等時(shí),如果應(yīng)用想使用byteBuffer預(yù)覽流數(shù)據(jù)創(chuàng)建pixelMap直接顯示,可以根據(jù)stride*height字節(jié)的大小先創(chuàng)建pixelMap,然后調(diào)用PixelMap的cropSync方法裁剪掉多余的像素,從而正確處理stride,解決預(yù)覽流花屏問題。
onImageArrival(receiver: image.ImageReceiver):void{ receiver.on('imageArrival',() =>{ receiver.readNextImage((err: BusinessError, nextImage: image.Image) =>{ // ... if(nextImage) { nextImage.getComponent(image.ComponentType.JPEG,async(_err,component: image.Component) => { letwidth =1080;// width為應(yīng)用創(chuàng)建預(yù)覽流分辨率對(duì)應(yīng)的寬 letheight =1080;// height為應(yīng)用創(chuàng)建預(yù)覽流分辨率對(duì)應(yīng)的高 letstride = component.rowStride;// 通過component.rowStride獲取stride Logger.info(TAG,`receiver getComponent width:${width}height:${height}stride:${stride}`); // stride和width相等,按寬讀取buffer不影響結(jié)果 if(stride === width) { letpixelMap =awaitimage.createPixelMap(component.byteBuffer, { size: {height: height,width: width }, srcPixelFormat: image.PixelMapFormat.NV21, }) AppStorage.setOrCreate('stridePixel', pixelMap); }else{ letpixelMap =awaitimage.createPixelMap(component.byteBuffer, { // 正例:1、創(chuàng)建PixelMap時(shí)width傳stride, size: {height: height,width: stride }, srcPixelFormat:8, }) // 2、然后調(diào)用PixelMap的cropSync方法裁剪掉多余的像素。 pixelMap.cropSync({ size: {width: width,height: height }, x:0, y:0 })// 根據(jù)輸入的尺寸裁剪圖片,從(0,0)開始,裁剪width*height字節(jié)的區(qū)域。 letpixelBefore:PixelMap|undefined=AppStorage.get('stridePixel'); awaitpixelBefore?.release(); AppStorage.setOrCreate('stridePixel', pixelMap); } nextImage.release(); }) } }); }) }
常見問題
如何獲取相機(jī)預(yù)覽流幀數(shù)據(jù)
通過ImageReceiver中imageArrival事件監(jiān)聽獲取底層返回的圖像數(shù)據(jù)。
如何獲取預(yù)覽流圖像的stride的值
可以通過預(yù)覽流幀數(shù)據(jù)的返回值image.Component.rowStride獲取stride。
-
計(jì)算機(jī)
+關(guān)注
關(guān)注
19文章
7637瀏覽量
90338 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
3115瀏覽量
75054 -
圖像
+關(guān)注
關(guān)注
2文章
1094瀏覽量
41080 -
HarmonyOS
+關(guān)注
關(guān)注
80文章
2085瀏覽量
32323
原文標(biāo)題:HarmonyOS應(yīng)用圖像stride處理方案
文章出處:【微信號(hào):HarmonyOS_Dev,微信公眾號(hào):HarmonyOS開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
模糊圖像處理解決方案
《DNK210使用指南 -CanMV版 V1.0》第三十五章 image圖像特征檢測(cè)實(shí)驗(yàn)
ADAS方案設(shè)計(jì)成功關(guān)鍵:圖像處理技術(shù)
DSP圖像處理系統(tǒng)中信號(hào)完整性問題及解決方案
如何使用Stride模式?
Altera的視頻和圖像處理解決方案
圖像處理技術(shù)是什么_圖像處理技術(shù)現(xiàn)狀和發(fā)展前景
基于matlab GUI的彩色圖像處理技術(shù)設(shè)計(jì)方案資料下載
HarmonyOS的組件化設(shè)計(jì)方案
HarmonyOS測(cè)試技術(shù)與實(shí)戰(zhàn)-分布式應(yīng)用測(cè)試解決方案

機(jī)器視覺之圖像增強(qiáng)和圖像處理

評(píng)論