Android 圖片文字識(shí)別DEMO(基于百度OCR)

前言

??OCR 是 Optical Character Recognition 的縮寫(xiě),翻譯為光學(xué)字符識(shí)別,指的是針對(duì)印刷體字符,采用光學(xué)的方式將紙質(zhì)文檔中的文字轉(zhuǎn)換成為黑白點(diǎn)陣的圖像文件,通過(guò)識(shí)別軟件將圖像中的文字轉(zhuǎn)換成文本格式,供文字處理軟件進(jìn)一步編輯加工的技術(shù)(好吧体啰,這是我查來(lái)的)。簡(jiǎn)單的來(lái)說(shuō)嗽仪,OCR技術(shù)就是可以把圖片上的文字識(shí)別出來(lái)荒勇,并以文本格式的形式提取出來(lái)。

??這個(gè)技術(shù)的應(yīng)用方面很廣泛闻坚,比如說(shuō)把紙質(zhì)書(shū)籍的內(nèi)容轉(zhuǎn)化為電子書(shū)沽翔,之前都需要人手打,但是現(xiàn)在只要掃描一下窿凤,將掃描出來(lái)的圖片通過(guò)OCR技術(shù)轉(zhuǎn)化成文本格式仅偎,效率和成本不知提升了多少倍。

??有人可能會(huì)想雳殊,這個(gè)技術(shù)聽(tīng)起來(lái)好高端橘沥,不懂計(jì)算機(jī)圖形學(xué),模式識(shí)別夯秃,機(jī)器學(xué)習(xí)巴拉巴拉東西的人座咆,是不是無(wú)法接觸到這樣的技術(shù),實(shí)現(xiàn)這樣的功能寝并,(好像是的箫措,emmm...)腹备,不過(guò)衬潦,雖然咱們自己實(shí)現(xiàn)不了,但是有人把輪子造好了呀植酥,我們只要使用人家造好的輪子镀岛,就能實(shí)現(xiàn)圖片文字識(shí)別的功能。

??先來(lái)看一下我實(shí)現(xiàn)的效果吧友驮,隨手拿了辦公桌上有字的東西(拿了枸杞茶和菊花茶的包裝...)漂羊,用自己寫(xiě)的demo拍了照識(shí)別了一下


圖片文字識(shí)別1

圖片文字識(shí)別2

??界面下方顯示的圖片是手機(jī)拍的照片,界面上方顯示的是從照片中識(shí)別出來(lái)的文字信息卸留∽咴剑可以看出識(shí)別的正確率還是挺高的。

??我這邊用的輪子是 百度文字識(shí)別 耻瑟。下面我將介紹一下我是如何實(shí)現(xiàn)上述的文字識(shí)別功能旨指。

請(qǐng)求模塊定義

??百度其實(shí)有提供圖片識(shí)別Android的SDK赏酥,就像其他的SDK一樣,只要導(dǎo)入一系列包之后就可以調(diào)用識(shí)別谆构。尋求快速開(kāi)發(fā)的小伙伴可以了解一下裸扶,我看了一下文檔,實(shí)現(xiàn)還是十分容易的搬素。

OCR Android SDK

??但是呵晨,我在demo中使用的并非是SDK,而是使用另外一種方法——以網(wǎng)絡(luò)api的方式來(lái)進(jìn)行識(shí)別熬尺。涉及到的技術(shù)有 retrofit+rxjava 進(jìn)行網(wǎng)絡(luò)請(qǐng)求(在之前的一篇博客中有介紹如何使用 retrofit+rxjava 摸屠,貼一下鏈接),Android應(yīng)用動(dòng)態(tài)權(quán)限的申請(qǐng)猪杭,F(xiàn)ileProvider餐塘,圖片的base64轉(zhuǎn)碼,以及熱門(mén)的MVP框架皂吮。

??先看一下項(xiàng)目結(jié)構(gòu)
項(xiàng)目結(jié)構(gòu).png

??module目錄下存放的是MVP架構(gòu)的三個(gè)模塊戒傻,bean目錄下存放的是網(wǎng)絡(luò)請(qǐng)求返回的數(shù)據(jù)類(lèi)型,apiservice中存放的是retrofit有關(guān)網(wǎng)絡(luò)請(qǐng)求的接口蜂筹。

??根據(jù)百度OCR官方給出的接口
api請(qǐng)求說(shuō)明.png

??我們定義出如下的接口方法需纳。
 /**
     * 通過(guò)圖片URL的形式,獲取圖片內(nèi)的文字信息
     * @param accessToken 通過(guò)API Key和Secret Key獲取的access_token
     * @param url 圖片的url
     * @return observable對(duì)象用于rxjava,從RecognitionResultBean中可以獲得圖片文字識(shí)別的信息
     */
    @POST("rest/2.0/ocr/v1/general_basic")
    @FormUrlEncoded
    Observable<RecognitionResultBean> getRecognitionResultByUrl(@Field("access_token") String accessToken, @Field("url") String url);

    /**
     * 通過(guò)圖片艺挪,獲取圖片內(nèi)的文字信息
     * @param accessToken 通過(guò)API Key和Secret Key獲取的access_token
     * @param image 圖像數(shù)據(jù)base64編碼后進(jìn)行urlencode后的String
     * @return  observable對(duì)象用于rxjava,從RecognitionResultBean中可以獲得圖片文字識(shí)別的信息
     */
    @POST("rest/2.0/ocr/v1/general_basic")
    @FormUrlEncoded
    Observable<RecognitionResultBean> getRecognitionResultByImage(@Field("access_token") String accessToken, @Field("image") String image);

??在第一個(gè)方法中不翩,我們需要傳入兩個(gè)參數(shù),一個(gè)是access_token麻裳,需申請(qǐng)百度文字識(shí)別的開(kāi)發(fā)者資格口蝠,得到API key和Secret Key后獲取,還有一個(gè)參數(shù)是圖片的網(wǎng)絡(luò)地址url津坑,可以直接通過(guò)這個(gè)url直接訪(fǎng)問(wèn)到圖片妙蔗。

??第二個(gè)方法中,第一個(gè)參數(shù)也是同上的access_token疆瑰,第二個(gè)參數(shù)則是String類(lèi)型眉反,這個(gè)參數(shù)是在本地將圖片base64轉(zhuǎn)碼之后生成。

??因?yàn)槲覀円獙?shí)現(xiàn)的功能是用手機(jī)拍照穆役,然后將照片信息傳遞給服務(wù)器寸五,因此我們之后調(diào)用的是第二個(gè)方法(第一個(gè)方法只是我按照api說(shuō)明隨手寫(xiě)了一下),這些參數(shù)我們以POST的形式發(fā)送耿币,按照百度OCRapi 的要求梳杏,需要加上@FormUrlEncode注釋?zhuān)覀兪褂聾Field的方式將參數(shù)加入請(qǐng)求體。可以看到Observable中的是RecognitionResultBean類(lèi)型十性,我們可以從里面拿到服務(wù)器返回的文字識(shí)別信息叭莫。

??定義好這兩個(gè)方法之后,我們便可以構(gòu)造retrofit對(duì)象進(jìn)行調(diào)用

 Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://aip.baidubce.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

        baiduOCRService = retrofit.create(BaiduOCRService.class);

??我們來(lái)看一下 rxjava+retrofit 在接口方法中的具體實(shí)現(xiàn)

  @Override
    public void getRecognitionResultByImage(Bitmap bitmap) {

        String encodeResult = bitmapToString(bitmap);

        baiduOCRService.getRecognitionResultByImage(ACCESS_TOKEN,encodeResult)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<RecognitionResultBean>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(RecognitionResultBean recognitionResultBean) {
                        Log.e("onnext",recognitionResultBean.toString());
                        StringBuilder s = new StringBuilder();
                        List<RecognitionResultBean.WordsResultBean> wordsResult = recognitionResultBean.getWords_result();
                        for (RecognitionResultBean.WordsResultBean words:wordsResult) {
                            s.append(words.getWords());
                        }

                        mView.updateUI(s.toString());

                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e("onerror",e.toString());
                    }

                    @Override
                    public void onComplete() {

                    }
                });

    }

??可以看到傳入了一個(gè)Bitmap類(lèi)型的圖片參數(shù)烁试,這個(gè)參數(shù)經(jīng)過(guò) String encodeResult = bitmapToString(bitmap); 方法轉(zhuǎn)成了String類(lèi)型雇初。是因?yàn)榻涌谝蟮膮?shù)數(shù)據(jù)為String類(lèi)型,所以我們對(duì)圖片進(jìn)行了base64轉(zhuǎn)碼减响,具體的轉(zhuǎn)碼方法如下:

    private String bitmapToString(Bitmap bitmap){
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        byte[] bytes = baos.toByteArray();
        return Base64.encodeToString(bytes, Base64.DEFAULT);
    }

??調(diào)用此方法靖诗,便可以把圖片類(lèi)型轉(zhuǎn)化成字符串類(lèi)型,之后的操作便是對(duì)網(wǎng)路接口調(diào)用之后的回調(diào)方法進(jìn)行定義支示,我們?cè)谡{(diào)用成功后的onNext操作中刊橘,拿到了RecognitionResultBean類(lèi)型參數(shù),這個(gè)參數(shù)里含有圖片所包含文字的信息颂鸿,我們將所有的文字一一取出促绵,用StringBuilder連接成一個(gè)字符串,返回給View層嘴纺,調(diào)用View層的updateUI進(jìn)行UI界面的更新败晴,對(duì)于這個(gè)字符串我們?cè)谥筮€可以進(jìn)行進(jìn)一步的分析操作。

??以上栽渴,百度OCR接口請(qǐng)求模塊定義部分便已完成尖坤,接下來(lái),我們要做的就是調(diào)用系統(tǒng)的相機(jī)功能闲擦,拍照得到照片慢味,將照片傳遞給我們上面定義的請(qǐng)求接口,進(jìn)行文字識(shí)別墅冷。

相機(jī)功能調(diào)用

??首先纯路,由于要對(duì)相機(jī)功能進(jìn)行調(diào)用,我們需要在A(yíng)ndroidManifest清單文件中寫(xiě)明我們需要用到的權(quán)限

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.CAMERA"/>

??分別是網(wǎng)絡(luò)請(qǐng)求權(quán)限寞忿,數(shù)據(jù)的讀存取權(quán)限驰唬,以及相機(jī)權(quán)限。在A(yíng)ndroid 6.0 之前應(yīng)用的權(quán)限在安裝時(shí)全部授予罐脊,也就是說(shuō)只要在A(yíng)ndroidManifest中申請(qǐng)過(guò)的權(quán)限定嗓,都會(huì)給予蜕琴。而在 Android 6.0 或更高版本之后萍桌,對(duì)權(quán)限的管理作出了改變,對(duì)某些涉及到用戶(hù)隱私的權(quán)限可在運(yùn)行時(shí)根據(jù)用戶(hù)的需要?jiǎng)討B(tài)授予凌简,也就是說(shuō)上炎,在A(yíng)ndroidManifest中申請(qǐng)的權(quán)限,在用戶(hù)使用的過(guò)程中還得詢(xún)問(wèn)用戶(hù)是否給予,用戶(hù)給予權(quán)限了藕施,應(yīng)用才能進(jìn)行相關(guān)的權(quán)限操作寇损。因此我們需在代碼中增加動(dòng)態(tài)權(quán)限申請(qǐng)的模塊(對(duì)用戶(hù)安全性友好了,但是對(duì)開(kāi)發(fā)者增加了不友好度....)裳食,以下是動(dòng)態(tài)權(quán)限申請(qǐng)部分的代碼:

    private boolean hasPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, PERMISSIONS_REQUEST_CODE);
            return false;
        }else {
            return true;
        }
    }

??代碼的主要邏輯是矛市,在程序運(yùn)行的時(shí)候,檢查是否有相應(yīng)的權(quán)限诲祸,如果有權(quán)限浊吏,則可以進(jìn)行相關(guān)操作,如果沒(méi)有權(quán)限救氯,就調(diào)用申請(qǐng)權(quán)限的方法找田。在完成這部分代碼的編寫(xiě)之后還需重寫(xiě)onRequestPermissionsResult方法,對(duì)申請(qǐng)權(quán)限的結(jié)果進(jìn)行反應(yīng)着憨。

??接下來(lái)是調(diào)用相機(jī)功能的代碼

 private void takePhoto(){

        if (!hasPermission()) {
            return;
        }

        Intent intent = new Intent();
        intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/img";
        if (new File(path).exists()) {
            try {
                new File(path).createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        String filename = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        mTmpFile = new File(path, filename + ".jpg");
        mTmpFile.getParentFile().mkdirs();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            String authority = getPackageName() + ".provider";
            imageUri = FileProvider.getUriForFile(this, authority, mTmpFile);
        } else {
            imageUri = Uri.fromFile(mTmpFile);
        }
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);

        startActivityForResult(intent, CAMERA_REQUEST_CODE);

    }

??需要提到的是墩衙,在A(yíng)ndroid 7.0之后,如果你使用Intent攜帶這樣的上面的imageUri去打開(kāi)相機(jī)拍照甲抖,會(huì)拋出FileUriExposedException異常漆改。這時(shí)候就需要用到google官方的解決方案——FileProvider。使用的方法可以參照 Android 7.0適配-應(yīng)用之間共享文件

??調(diào)用相機(jī)之后我們需要重寫(xiě)onActivityResult方法准谚,對(duì)返回拍照結(jié)果進(jìn)行處理籽懦,

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == CAMERA_REQUEST_CODE) {
            if (requestCode == RESULT_OK){
                Bitmap photo = BitmapFactory.decodeFile(mTmpFile.getAbsolutePath());
                mPresenter.getRecognitionResultByImage(photo);
                imageView.setImageBitmap(photo);
            }
        }
    }

??如果拍照成功,我們就把照片作為參數(shù)傳遞給之前定義好的接口方法氛魁,調(diào)用進(jìn)行圖片文字識(shí)別暮顺。可以看到我還把照片放入imageview中方便與識(shí)別結(jié)果進(jìn)行對(duì)比秀存。等服務(wù)器成功返回識(shí)別結(jié)構(gòu)之后捶码,就會(huì)調(diào)用VIew層的updateUI,更新textview顯示識(shí)別結(jié)果或链。

??至此惫恼,我們就完成了從拍照到拿到識(shí)別結(jié)果的全部功能。再來(lái)識(shí)別一下公交卡


公交卡識(shí)別.jpg

??嗯澳盐,不錯(cuò)祈纯。

最后

??這個(gè)demo實(shí)現(xiàn)了單純的圖片文字識(shí)別,就是把圖片上的字讀取了出來(lái)叼耙,在此之上我們可以進(jìn)一步做很多有意思的事腕窥,比如用正則表達(dá)式把字符串里的一些字提取出來(lái)進(jìn)行操作,像食品的營(yíng)養(yǎng)成分表啊筛婉,發(fā)票單子啊簇爆,都可以拿來(lái)識(shí)別,將里面的有用信息提取出來(lái),進(jìn)行分析處理操作入蛆,將功能進(jìn)一步擴(kuò)展响蓉。
??當(dāng)然,在實(shí)現(xiàn)這些功能之后哨毁,我們最后還是得感謝為我們提供輪子的大佬枫甲,哈哈。
??貼上本項(xiàng)目的github地址 reggie1996/CharacterRecognition

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末扼褪,一起剝皮案震驚了整個(gè)濱河市言秸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌迎捺,老刑警劉巖举畸,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異凳枝,居然都是意外死亡抄沮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)岖瑰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)叛买,“玉大人,你說(shuō)我怎么就攤上這事蹋订÷收酰” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵露戒,是天一觀(guān)的道長(zhǎng)椒功。 經(jīng)常有香客問(wèn)我,道長(zhǎng)智什,這世上最難降的妖魔是什么动漾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮荠锭,結(jié)果婚禮上旱眯,老公的妹妹穿的比我還像新娘。我一直安慰自己证九,他們只是感情好删豺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著愧怜,像睡著了一般呀页。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叫搁,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天赔桌,我揣著相機(jī)與錄音,去河邊找鬼渴逻。 笑死疾党,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惨奕。 我是一名探鬼主播雪位,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼梨撞!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起卧波,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎港粱,沒(méi)想到半個(gè)月后螃成,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體查坪,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年氮凝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片望忆。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖启摄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鞋仍,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布威创,位于F島的核電站,受9級(jí)特大地震影響肚豺,放射性物質(zhì)發(fā)生泄漏溃斋。R本人自食惡果不足惜吸申,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一享甸、第九天 我趴在偏房一處隱蔽的房頂上張望梳侨。 院中可真熱鬧,春花似錦走哺、人聲如沸蚯嫌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)择示。三九已至,卻和暖如春晒旅,著一層夾襖步出監(jiān)牢的瞬間栅盲,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工废恋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剪菱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓拴签,卻偏偏與公主長(zhǎng)得像孝常,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚓哩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,116評(píng)論 25 707
  • 1构灸、通過(guò)CocoaPods安裝項(xiàng)目名稱(chēng)項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明先生_X自主閱讀 15,980評(píng)論 3 119
  • 2017.6.25 星期天 小雨轉(zhuǎn)晴 明天孩子們就考試了!今天在家休息岸梨,今天也停電喜颁,正好在家和孩子一起學(xué)...
    小小鄧媽咪閱讀 198評(píng)論 0 1
  • 最好的自己就是在當(dāng)下的物理環(huán)境中,用自己最好的內(nèi)在狀態(tài)存在著曹阔,內(nèi)在狀態(tài)的質(zhì)量可以經(jīng)由改善輸入來(lái)改進(jìn)半开。輸入可以是被動(dòng)...
    Marymlj閱讀 357評(píng)論 0 0
  • 這里沒(méi)有絢麗多彩的魔法,也沒(méi)有豪華的高樓大廈赃份,這里是斗氣大陸寂拆。斗者,斗師抓韩,斗靈纠永,斗王,斗宗谒拴,斗尊尝江,斗圣,鉆石斗...
    昭云冥丹閱讀 269評(píng)論 0 0