標籤:軟體 影像處理 android 技術 文章標題
在機器視覺實驗室呆了有一年半時間了,但由於自己“任性”。一直以來學習的內容都是自己來安排,我還是堅持認為沒有最好和最簡單的技術,只有自己喜歡的技術。不過說起來還是會覺得慚愧,經常聽到師兄們談論影像處理各種演算法,可是一直到此軟體誕生之前對機器視覺的知識可以說一概不知。自己研究的主要是Android系統的東西,從上層到下層都有所涉及。一直以來都想把自身所長和實驗室主題聯絡上,這樣可以多和實驗室牛人溝通,也順便刷刷存在感~由此向師兄師弟們請教一二,學了一點影像處理技術,做了一款Android平台的影像處理工具,可以協助使用者快速即時預覽所要處理的映像在不同演算法之下的結果。也由於本人所學演算法太少,今後學了更多之後也會慢慢加入到軟體中,將軟體功能壯大起來。
首先講講軟體到目前為止可以實現的功能:
* 開啟手機相機預覽映像
* 將採集到的映像轉換成灰階圖預覽
* 將灰階映像經過Sobel轉換後預覽
* 對Sobel之後的映像進行二值化
* 二值化的過程中可以隨時動態調整分割閾值
* 通過拍照按鈕可以鎖定映像,在按一次進行邊緣提取
軟體如下:
首先是開啟軟體所看到的主介面:
然後點擊菜單,切換到灰階圖:
接著切換到Sobel變換
閾值為50的二值分割
最後進行邊緣提取:
其中涉及到的演算法有四個,具體的演算法實現可以在軟體源碼中找到,點擊下載源碼
整個軟體有3個類,也就三個不同的功能部分,其中MainActivity.java是主類,下面按照三個不同的功能成分對整個軟體架構做一個詳細的說明:
1、主介面
MainActivity.java :
首先是介面布局,主要是一個ImageView用於顯示整個預覽框,另外加上一個拍照按鈕和一個功能表按鈕,點擊功能表按鈕快顯功能表。最後如果當前請求二值化演算法,則顯示一個滑動進度條和一個文本輸入框來動態調整閾值。代碼如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <ImageView android:id="@+id/preview" android:layout_width="match_parent" android:layout_height="match_parent" /> <Button android:id="@+id/takePic_bt" android:layout_width="60dp" android:layout_height="60dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="15dp" android:background="@drawable/shutter" android:onClick="onClick_takePic" /> <ListView android:id="@+id/list_view" android:layout_width="150dp" android:layout_height="match_parent" android:layout_marginTop="20dp" android:divider="#ff555555" android:dividerHeight="1.3dp" android:listSelector="@android:color/holo_blue_bright" android:visibility="gone" > </ListView> <Button android:id="@+id/takePic_bt" android:layout_width="40dp" android:layout_height="40dp" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_marginBottom="15dp" android:layout_marginLeft="5dp" android:background="@drawable/menu" android:onClick="onClick_menu" /> <EditText android:id="@+id/input_threshold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_margin="20dp" android:inputType="numberDecimal" android:text="240" android:textColor="@android:color/holo_blue_bright" android:visibility="gone" /> <SeekBar android:id="@+id/seekbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginBottom="20dp" android:layout_marginLeft="50dp" android:layout_toLeftOf="@id/input_threshold" android:max="255" android:progress="240" android:visibility="gone" /> <View android:id="@+id/topline" android:layout_width="match_parent" android:layout_height="2dp" android:layout_marginLeft="100dp" android:layout_marginRight="100dp" android:layout_marginTop="50dp" android:background="#444" /> <View android:layout_width="2dp" android:layout_height="180dp" android:layout_alignEnd="@id/topline" android:layout_below="@id/topline" android:background="#444" /> <View android:layout_width="2dp" android:layout_height="180dp" android:layout_alignStart="@id/topline" android:layout_below="@id/topline" android:background="#444" /></RelativeLayout>
接下來加入對菜單和拍照按鈕事件的監聽方法,如下:
public void onClick_menu(View view) { if (isMenuVisible) { listView.startAnimation(menuInvisible); listView.setVisibility(View.GONE); isMenuVisible = false; } else { listView.startAnimation(menuVisible); listView.setVisibility(View.VISIBLE); isMenuVisible = true; } } public void onClick_takePic(View view) throws IOException { getScreen.takePic(); //相機拍照 }
其中菜單的顯示我添加了一個透明度動畫,讓軟體介面更柔和。透明度補間動畫比較簡單,在res/anim/目錄下添加兩個動畫資源用於顯示和消失,然後再單擊事件中播放動畫即可。
然後是對彈出來的菜單編寫點擊事件,這裡我用ListView做的菜單,用listView.setOnItemClickListener()方法設定回調方法。代碼如下:
listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (position == 3) // 灰階圖則開啟進度條 { seekbar.setVisibility(View.VISIBLE); inputThreshold.setVisibility(View.VISIBLE); } else { seekbar.setVisibility(View.GONE); inputThreshold.setVisibility(View.GONE); } whichToDisplay = position; //切換演算法 } });
這裡主要作用有兩個:1、開啟和關閉閾值進度條;2、切換使用者點擊的演算法。當然,進度條開啟之後也要有配套的監聽程式,這裡需要同時監聽seekBar和editText兩個控制項,需要保持二者的同步,內容比較簡單易懂,如下:
// 處理SeekBar seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar arg0) { thresholdValue = arg0.getProgress() > 255 ? 255 : arg0 .getProgress(); } @Override public void onStartTrackingTouch(SeekBar arg0) { } @Override public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) { inputThreshold.setText(arg0.getProgress() + ""); } }); // 處理EditText inputThreshold.setOnEditorActionListener(new OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { seekbar.setProgress(Integer.parseInt(inputThreshold.getText() .toString())); return false; } });
好了,到這裡基本上主介面的東西都考慮到了,下面是映像的擷取部分。
2、擷取相機映像並顯示預覽
GetScreen.java:
這一部分主要運用到對Android相機和預覽功能的使用方面的內容。
1. 對相機的使用:
按照我個人對相機的使用,可以簡單歸納為6個步驟:
① 開啟相機—— Camera.open();
② 設定相機參數——Camera.getParameters();
③ 設定圖片預覽容器——Camera.setPreviewTexture();
④ 開始預覽——Camera.startPreview();
⑤ 設定預覽回調介面——Camera.setPreviewCallback();
⑥ 關閉相機——Camera.close();
整個過程其實很簡單,官方文檔中強調了幾個容易出錯的地方:一個是注意3和4兩個步驟的順序不能反,另外是相機用完一定要關閉,否則其他程式將無法啟動相機。經過本人的測試,如果不關閉相機,在退出的時候也會拋出異常,所以需要多加註意!
另外一個需要注意的是這裡回調傳入setPreviewCallback()方法的映像是YuV420p格式,需要轉換成rgb格式方便處理。這裡用到了一個decodeYUV420SP演算法,詳細內容見源碼。轉換成RGB格式之後可以很方便的產生Bitmap圖片,然後在ImageView上顯示即可。
其實這一部分比較麻煩的是對焦的步驟。首先我們通過亮度計算公式:
bright = 0.299 * r + 0.587 * g + 0.114 * b;
每隔500ms計算一次整幅圖片的亮度平均值,然後和上一次的值對比,如果在某一個區間內,則說明相機前端映像已經趨於穩定,此時進行一次對焦,並設定相應標誌位以保證在下一次穩定映像出現之前不在進行對焦。對焦函數如下:
private void autoFocus() { long currentTime = 0, stableTime = 0; currentTime = System.currentTimeMillis(); bright = Pictures.getLight(rgb); if (Math.abs(bright - lastBright) > focusThreshold) { lastBright = bright; hasFocus = false; stableTime = currentTime; } else { if (!hasFocus && currentTime - stableTime >= 500) { camera.autoFocus(new AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { } }); hasFocus = true; } } }
3、映像演算法
Pictures.java:
進入到這裡才真正開始涉及到影像處理演算法的部分,這裡會涉及到幾個比較簡單的演算法,演算法的原理這裡就不多說了,網上關於影像處理技術的講解有很多。
軟體中主要有如下幾個演算法的靜態方法:
* 前面提到的將YuV420p轉成RGB格式:decodeYUV420SP();
* 計算映像的平均亮度: getLight();
* 擷取八位灰階映像數組:getLightArray();
* 彩色圖轉換成灰階圖:convertToGrey();
* Sobel變換:sobel();
* 對圖片進行二值化處理:turnTo2();
* 邊緣提取:findFrame();
在這裡我把每個演算法都寫成了靜態方法,這樣可以很方便的在其他代碼中使用演算法處理映像,對映像的處理都在第二部分GetScreen的相機預覽回呼函數onPreviewFrame()中,這裡也是整個軟體的核心步驟。代碼如下:
@Override public void onPreviewFrame(byte[] data, Camera camera) { rgb = new int[Pictures.PIC_LENGTH]; sobelPic = new int[Pictures.PIC_LENGTH]; grey = new int[Pictures.PIC_LENGTH]; Pictures.decodeYUV420SP(rgb, data, width, height); switch (MainActivity.whichToDisplay) { case 1: // 顯示灰階圖 Pictures.convertToGrey(rgb, grey); display = grey; break; case 2: Pictures.convertToGrey(rgb, grey); // sobel變換之後顯示 Pictures.sobel(grey, width, height, sobelPic); display = sobelPic; break; case 3: // 二值化顯示 Pictures.convertToGrey(rgb, grey); Pictures.sobel(grey, width, height, sobelPic); Pictures.turnTo2(sobelPic); display = sobelPic; break; default: display = rgb; } bitmap = Bitmap.createBitmap(display, width, height, Config.RGB_565); MainActivity.setImageView(bitmap); autoFocus(); }
首先將圖片轉換為RGB格式(為了方便處理),然後根據主介面的菜單選項切換不同的演算法,並將不同的演算法下的圖片顯示到ImageView上,最後進行對焦。
通常在做影像處理過程中,會先在MATLAB或者Visual Stuio中進行模擬。這需要不斷的將圖片匯入→然後編譯→最後輸出。而用這款Android影像處理工具可以做到即時觀測各種映像在不同演算法下的結果圖,相比之下更方便。只是現在支援的演算法不多,今後有時間會繼續學習一些常用的演算法加入到軟體中。我也會即時更新部落格與大家分享。
大家如果有什麼意見或者問題可以隨時留言一起討論~
APK:http://yun.baidu.com/share/link?shareid=515722756&uk=67973003
GitHub源碼地址:https://github.com/hust-MC/Find_Different.git
Android 影像處理軟體