Android 圖像處理軟件

在機(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)擊菜單,切換到灰度圖:


轉(zhuǎn)換灰度圖

接著切換到Sobel變換


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)容痕慢。

  1. 對(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子份招,更是在濱河造成了極大的恐慌,老刑警劉巖锁摔,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哼审,死亡現(xiàn)場(chǎng)離奇詭異谐腰,居然都是意外死亡涩盾,警方通過查閱死者的電腦和手機(jī)十气,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門春霍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砸西,“玉大人址儒,你說我怎么就攤上這事芹枷×ぃ” “怎么了鸳慈?”我有些...
    開封第一講書人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵喧伞,是天一觀的道長(zhǎng)走芋。 經(jīng)常有香客問我潘鲫,道長(zhǎng)翁逞,這世上最難降的妖魔是什么溉仑? 我笑而不...
    開封第一講書人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任挖函,我火速辦了婚禮彼念,結(jié)果婚禮上挪圾,老公的妹妹穿的比我還像新娘逐沙。我一直安慰自己哲思,他們只是感情好吩案,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開白布棚赔。 她就那樣靜靜地躺著,像睡著了一般靠益。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胧后,一...
    開封第一講書人閱讀 52,584評(píng)論 1 312
  • 那天芋浮,我揣著相機(jī)與錄音壳快,去河邊找鬼纸巷。 笑死眶痰,一個(gè)胖子當(dāng)著我的面吹牛瘤旨,可吹牛的內(nèi)容都是我干的竖伯。 我是一名探鬼主播存哲,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼七婴,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼祟偷!你這毒婦竟也來了本姥?” 一聲冷哼從身側(cè)響起肩袍,我...
    開封第一講書人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤婚惫,失蹤者是張志新(化名)和其女友劉穎氛赐,沒想到半個(gè)月后先舷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體艰管,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蒋川,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捺球。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缸浦。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡氮兵,死狀恐怖裂逐,靈堂內(nèi)的尸體忽然破棺而出泣栈,到底是詐尸還是另有隱情卜高,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布掺涛,位于F島的核電站,受9級(jí)特大地震影響薪缆,放射性物質(zhì)發(fā)生泄漏秧廉。R本人自食惡果不足惜矮燎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一定血、第九天 我趴在偏房一處隱蔽的房頂上張望诞外。 院中可真熱鬧,春花似錦灾票、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)正什。三九已至啥纸,卻和暖如春婴氮,著一層夾襖步出監(jiān)牢的瞬間斯棒,已是汗流浹背主经。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工荣暮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留罩驻,地道東北人穗酥。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓惠遏,卻偏偏與公主長(zhǎng)得像砾跃,于是被迫代替她去往敵國(guó)和親节吮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抽高,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容