天天看點

Camera 的 yuv420sp 轉 rgb

上次講的是攝像頭的初始化,如果覺得這麼就萬事OK的話,那就大錯特錯了。接下來的東西讓人感到更加頭痛。

在我的這個應用裡,不需要把拍下來的圖檔存儲,隻需要把預覽的圖檔資料處理一下就好,很自然的我隻是用了onPreviewFrame調用,考慮處理傳遞進來的data資料流就是了。

網上很多文章都說,然後用BitmapFactory的decodeByteArray()函數來解析圖檔就行了,我試了一下,發現這真是徹頭徹尾 的謊言,data位元組流預設是YCbCr_420_SP(雖然可以改,但其他的格式未必相容),decodeByteArray()壓根兒不 認!SDK2.2之後,似乎提供了一個YuvImage的類來轉一下(那Google一開始提供這個借口是做什麼的?),難道就要把老機給抛棄了麼??萬 萬不能啊(窮人最了解窮人們了)!

好在這個世界總是不缺少好人和牛人的,有人提供了這麼一段轉換的代碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 static public void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) {         final int frameSize = width * height;           for (int j = 0, yp = 0; j < height; j++) {                 int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;                 for (int i = 0; i < width; i++, yp++) {                         int y = (0xff & ((int) yuv420sp[yp])) - 16;                         if (y < 0) y = 0;                         if ((i & 1) == 0) {                                 v = (0xff & yuv420sp[uvp++]) - 128;                                 u = (0xff & yuv420sp[uvp++]) - 128;                         }                           int y1192 = 1192 * y;                         int r = (y1192 + 1634 * v);                         int g = (y1192 - 833 * v - 400 * u);                         int b = (y1192 + 2066 * u);                           if (r < 0) r = 0; else if (r > 262143) r = 262143;                         if (g < 0) g = 0; else if (g > 262143) g = 262143;                         if (b < 0) b = 0; else if (b > 262143) b = 262143;                           rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);                 }         } }

我不是很清楚這裡面的原理,但是它能在我這裡工作,暫時可以了……然後你才可以吧處理完的rgb[]傳給decodeByteArray()。

順便好心的把使用SDK2.2之後的也貼上吧,萬一有用呢……

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void onPreviewFrame(byte[] data, Camera arg1) {         FileOutputStream outStream = null;         try {                 YuvImage yuvimage = newYuvImage(data,ImageFormat.NV21,arg1.getParameters().getPreviewSize().width,arg1.getParameters().getPreviewSize().height,null);                 ByteArrayOutputStream baos = new ByteArrayOutputStream();                 yuvimage.compressToJpeg(newRect(0,0,arg1.getParameters().getPreviewSize().width,arg1.getParameters().getPreviewSize().height), 80, baos);                   outStream = new FileOutputStream(String.format("/sdcard/%d.jpg", System.currentTimeMillis()));                 outStream.write(baos.toByteArray());                 outStream.close();                   Log.d(TAG, "onPreviewFrame - wrote bytes: " + data.length);         } catch (FileNotFoundException e) {                 e.printStackTrace();         } catch (IOException e) {                 e.printStackTrace();         } finally {         }         Preview.this.invalidate(); }

哦,得到的圖像旋轉了90°(似乎有的機型設定一下setRotation(90)可以搞定,但還是那句話,不通用啊,況且這個是2.1之後的API)。手動轉一下吧……

1 2 3 4 5 6 Matrix matrix = new Matrix(); matrix.postRotate(90); // 這裡的rgb就是剛剛轉換處理的東東 Bitmap bmp = Bitmap.createBitmap(rgb, 0, w, w, h, Bitmap.Config.ARGB_4444); Bitmap nbmp = Bitmap.createBitmap(bmp,                                 0, 0, bmp.getWidth(),   bmp.getHeight(), matrix, true);

終于正常了~~~

考慮到需要做識别,自然得先把它轉成灰階圖像,經典心理公式Gray = R*0.299 + G*0.587 + B*0.114出場了,但是手機的計算速度不那麼快,這樣的浮點運算還是盡量避免吧~ 于是考慮Gray = (R*299 + G*587 + B*114 + 500) / 1000或者Gray = (R*30 + G*59 + B*11 + 50) / 100。但是除法總是還是不夠快,用移位吧……Gray = (R*19595 + G*38469 + B*7472) >> 16,稍微小一點,用Gray = (R*38 + G*75 + B*15) >> 7也足夠了。

經過一番努力學習,把寫就的代碼興緻勃勃的在手機上跑了一下,雖然不夠快結果出來了,想想也是大負荷運算啊,自我安慰客戶應該可以有這樣的耐心吧。

就在這個時候,我突然想起一件很重要的事情!

我需要的是灰階圖,也就是亮度風量,而最開始的YUV,不就是亮度色度飽和度麼?!那麼Y分類不就是我需要的灰階值嗎!!我在做什麼,辛辛苦苦轉成 RGB,再轉成亮度,吃飽了撐着不是。想到這裡我立刻用頭撞牆九九一百八十一次,一悼念我那白白死去的腦細胞的在天之靈。立刻重寫,删除大量代碼,快多 了,效果也好~~ 鄙視一下兩小時前的自己!