在機(jī)器視覺實(shí)驗(yàn)室呆了有一年半時(shí)間了,但由于自己“任性”个曙。一直以來學(xué)習(xí)的內(nèi)容都是自己來安排锈嫩,我還是堅(jiān)持認(rèn)為沒有最好和最簡(jiǎn)單的技術(shù)垦搬,只有自己喜歡的技術(shù)。不過說起來還是會(huì)覺得慚愧猴贰,經(jīng)常聽到師兄們談?wù)搱D像處理各種算法,可是一直到此軟件誕生之前對(duì)機(jī)器視覺的知識(shí)可以說一概不知米绕。自己研究的主要是Android系統(tǒng)的東西瑟捣,從上層到下層都有所涉及义郑。一直以來都想把自身所長(zhǎng)和實(shí)驗(yàn)室主題聯(lián)系上,這樣可以多和實(shí)驗(yàn)室牛人溝通非驮,也順便刷刷存在感~由此向師兄師弟們請(qǐng)教一二交汤,學(xué)了一點(diǎn)圖像處理技術(shù)劫笙,做了一款A(yù)ndroid平臺(tái)的圖像處理工具,可以幫助用戶快速實(shí)時(shí)預(yù)覽所要處理的圖像在不同算法之下的結(jié)果填大。也由于本人所學(xué)算法太少戒洼,今后學(xué)了更多之后也會(huì)慢慢加入到軟件中允华,將軟件功能壯大起來。
首先講講軟件到目前為止可以實(shí)現(xiàn)的功能:
- 開啟手機(jī)相機(jī)預(yù)覽圖像
- 將采集到的圖像轉(zhuǎn)換成灰度圖預(yù)覽
- 將灰度圖像經(jīng)過Sobel轉(zhuǎn)換后預(yù)覽
- 對(duì)Sobel之后的圖像進(jìn)行二值化
- 二值化的過程中可以隨時(shí)動(dòng)態(tài)調(diào)整分割閾值
- 通過拍照按鈕可以鎖定圖像靴寂,在按一次進(jìn)行邊緣提取
軟件效果圖如下:
首先是打開軟件所看到的主界面:
然后點(diǎn)擊菜單,切換到灰度圖:
接著切換到Sobel變換
閾值為50的二值分割
最后進(jìn)行邊緣提群致 :
其中涉及到的算法有四個(gè),具體的算法實(shí)現(xiàn)可以在軟件源碼中找到庶弃,點(diǎn)擊下載源碼
整個(gè)軟件有3個(gè)類衫贬,也就三個(gè)不同的功能部分歇攻,其中MainActivity.java是主類固惯,下面按照三個(gè)不同的功能成分對(duì)整個(gè)軟件架構(gòu)做一個(gè)詳細(xì)的說明:
1掉伏、主界面
MainActivity.java :
首先是界面布局缝呕,主要是一個(gè)ImageView用于顯示整個(gè)預(yù)覽框斧散,另外加上一個(gè)拍照按鈕和一個(gè)菜單按鈕供常,點(diǎn)擊菜單按鈕彈出菜單鸡捐。最后如果當(dāng)前請(qǐng)求二值化算法,則顯示一個(gè)滑動(dòng)進(jìn)度條和一個(gè)文本輸入框來動(dòng)態(tài)調(diào)整閾值箍镜。代碼如下:
<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>
接下來加入對(duì)菜單和拍照按鈕事件的監(jiān)聽方法,如下:
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(); //相機(jī)拍照
}
其中菜單的顯示我添加了一個(gè)透明度動(dòng)畫香缺,讓軟件界面更柔和。透明度補(bǔ)間動(dòng)畫比較簡(jiǎn)單歇僧,在res/anim/目錄下添加兩個(gè)動(dòng)畫資源用于顯示和消失,然后再單擊事件中播放動(dòng)畫即可诈悍。
然后是對(duì)彈出來的菜單編寫點(diǎn)擊事件,這里我用ListView做的菜單侥钳,用listView.setOnItemClickListener()方法設(shè)置回調(diào)方法。代碼如下:
listView.setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{
if (position == 3) // 灰度圖則打開進(jìn)度條
{
seekbar.setVisibility(View.VISIBLE);
inputThreshold.setVisibility(View.VISIBLE);
}
else
{
seekbar.setVisibility(View.GONE);
inputThreshold.setVisibility(View.GONE);
}
whichToDisplay = position; //切換算法
}
});
這里主要作用有兩個(gè):1舷夺、打開和關(guān)閉閾值進(jìn)度條苦酱;2给猾、切換用戶點(diǎn)擊的算法。當(dāng)然耙册,進(jìn)度條打開之后也要有配套的監(jiān)聽程序,這里需要同時(shí)監(jiān)聽seekBar和editText兩個(gè)控件详拙,需要保持二者的同步帝际,內(nèi)容比較簡(jiǎn)單易懂饶辙,如下:
// 處理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矿微、獲取相機(jī)圖像并顯示預(yù)覽
GetScreen.java:
這一部分主要運(yùn)用到對(duì)Android相機(jī)和預(yù)覽功能的使用方面的內(nèi)容痕慢。
- 對(duì)相機(jī)的使用:
按照我個(gè)人對(duì)相機(jī)的使用涌矢,可以簡(jiǎn)單歸納為6個(gè)步驟:
① 打開相機(jī)—— Camera.open();
② 設(shè)置相機(jī)參數(shù)——Camera.getParameters();
③ 設(shè)置圖片預(yù)覽容器——Camera.setPreviewTexture();
④ 開始預(yù)覽——Camera.startPreview();
⑤ 設(shè)置預(yù)覽回調(diào)接口——Camera.setPreviewCallback();
⑥ 關(guān)閉相機(jī)——Camera.close();
整個(gè)過程其實(shí)很簡(jiǎn)單,官方文檔中強(qiáng)調(diào)了幾個(gè)容易出錯(cuò)的地方:一個(gè)是注意3和4兩個(gè)步驟的順序不能反娜庇,另外是相機(jī)用完一定要關(guān)閉塔次,否則其他程序?qū)o法啟動(dòng)相機(jī)名秀。經(jīng)過本人的測(cè)試励负,如果不關(guān)閉相機(jī)匕得,在退出的時(shí)候也會(huì)拋出異常继榆,所以需要多加注意耗跛!
另外一個(gè)需要注意的是這里回調(diào)傳入setPreviewCallback()方法的圖像是YuV420p格式裕照,需要轉(zhuǎn)換成rgb格式方便處理调塌。這里用到了一個(gè)decodeYUV420SP算法,詳細(xì)內(nèi)容見源碼羔砾。轉(zhuǎn)換成RGB格式之后可以很方便的生成Bitmap圖片负间,然后在ImageView上顯示即可姜凄。
其實(shí)這一部分比較麻煩的是對(duì)焦的步驟政溃。首先我們通過亮度計(jì)算公式:
bright = 0.299 * r + 0.587 * g + 0.114 * b;
每隔500ms計(jì)算一次整幅圖片的亮度平均值态秧,然后和上一次的值對(duì)比董虱,如果在某一個(gè)區(qū)間內(nèi),則說明相機(jī)前端圖像已經(jīng)趨于穩(wěn)定愤诱,此時(shí)進(jìn)行一次對(duì)焦,并設(shè)置相應(yīng)標(biāo)志位以保證在下一次穩(wěn)定圖像出現(xiàn)之前不在進(jìn)行對(duì)焦淫半。對(duì)焦函數(shù)如下:
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:
進(jìn)入到這里才真正開始涉及到圖像處理算法的部分科吭,這里會(huì)涉及到幾個(gè)比較簡(jiǎn)單的算法,算法的原理這里就不多說了对人,網(wǎng)上關(guān)于圖像處理技術(shù)的講解有很多谣殊。
軟件中主要有如下幾個(gè)算法的靜態(tài)方法:
- 前面提到的將YuV420p轉(zhuǎn)成RGB格式:decodeYUV420SP();
- 計(jì)算圖像的平均亮度: getLight();
- 獲取八位灰度圖像數(shù)組:getLightArray();
- 彩色圖轉(zhuǎn)換成灰度圖:convertToGrey();
- Sobel變換:sobel();
- 對(duì)圖片進(jìn)行二值化處理:turnTo2();
- 邊緣提裙娣ァ:findFrame();
在這里我把每個(gè)算法都寫成了靜態(tài)方法蟹倾,這樣可以很方便的在其他代碼中使用算法處理圖像猖闪,對(duì)圖像的處理都在第二部分GetScreen的相機(jī)預(yù)覽回調(diào)函數(shù)onPreviewFrame()中鲜棠,這里也是整個(gè)軟件的核心步驟培慌。代碼如下:
@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();
}
首先將圖片轉(zhuǎn)換為RGB格式(為了方便處理),然后根據(jù)主界面的菜單選項(xiàng)切換不同的算法吵护,并將不同的算法下的圖片顯示到ImageView上,最后進(jìn)行對(duì)焦馅而。
通常在做圖像處理過程中祥诽,會(huì)先在MATLAB或者Visual Stuio中進(jìn)行仿真瓮恭。這需要不斷的將圖片導(dǎo)入→然后編譯→最后輸出雄坪。而用這款A(yù)ndroid圖像處理工具可以做到實(shí)時(shí)觀測(cè)各種圖像在不同算法下的結(jié)果圖屯蹦,相比之下更方便维哈。只是現(xiàn)在支持的算法不多登澜,今后有時(shí)間會(huì)繼續(xù)學(xué)習(xí)一些常用的算法加入到軟件中阔挠。我也會(huì)即時(shí)更新博客與大家分享脑蠕。
大家如果有什么意見或者問題可以隨時(shí)留言一起討論~
APK下載地址:http://yun.baidu.com/share/link?shareid=515722756&uk=67973003
GitHub源碼地址:https://github.com/hust-MC/Find_Different.git