实现OCR语言识别Demo(一)- ButtomSheet实现

  • 实现BottomSheet
    • BottomSheet布局实现
    • 假数据准备
    • BottomSheet代码实现

先来看看最终效果

正如你们看到的,这个Demo的功能是我们可以从手机里或者是拍照的方式获取到某张图片,然后经过OCR识别文字后,将识别出来的文字在图片上全部都框选出来,并且在底部以扩展界面的方式可以查看识别内容的列表,点击列表里的某一项识别项就会在图片上选中这一项识别项,反过来点击图片上的框选的识别项也会在列表中进行这一对应项的选中。

想要实现上面的这种效果,我们需要解决这几个技术点

  • 实现内嵌RecycleView的BottomSheet
  • 图片大小跟随BottomSheet展开折叠的缩放处理
  • 图片上展示识别内容的渲染方式
  • 图片上识别内容的点击行为实现

让我们一步一步来实现并且一步步的解决这些问题

实现BottomSheet

BottomSheet布局实现

首先我们为应用创建一个带BottomSheet的布局,实现main页面布局

activity_main.xml中

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/coordinator_Layout"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_height="match_parent"android:layout_width="match_parent"android:background="@color/white"><io.github.karl.ocrdemo.OcrImageViewandroid:id="@+id/image_preview"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center|top"android:scaleType="fitCenter"tools:src="@tools:sample/backgrounds/scenic" /><LinearLayoutandroid:id="@+id/custom_bottom_sheet"android:layout_width="match_parent"android:layout_height="210dp"app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"app:layout_anchorGravity="bottom|end"app:behavior_peekHeight="50dp"app:behavior_hideable="false"android:background="@drawable/bottom_sheet_layout_shape"android:paddingStart="6dp"android:paddingEnd="6dp"android:orientation="vertical"><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/image_view"android:layout_width="match_parent"android:layout_height="50dp"android:layout_marginTop="2dp"android:scaleType="center"android:src="@mipmap/round_bar_icon"app:layout_constraintTop_toTopOf="parent"android:clickable="true"android:focusable="true"/><ImageViewandroid:id="@+id/open_take_pic"android:layout_width="30dp"android:layout_height="30dp"android:alpha="0.25"android:src="@mipmap/icon_takepic"android:scaleType="fitCenter"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent"app:tint="@color/black" /><ImageViewandroid:id="@+id/open_select_image"android:layout_width="20dp"android:layout_height="20dp"android:layout_marginEnd="10dp"android:alpha="0.25"android:src="@mipmap/picture"android:scaleType="fitCenter"app:layout_constraintRight_toLeftOf="@id/open_take_pic"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent"app:tint="@color/black" /></androidx.constraintlayout.widget.ConstraintLayout><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recycle_view"android:layout_width="match_parent"android:layout_height="wrap_content"app:layout_constraintTop_toBottomOf="@id/image_view"android:nestedScrollingEnabled="true"/></LinearLayout></androidx.coordinatorlayout.widget.CoordinatorLayout>

这边的页面中,为了使用BottomSheet(对ButtonSheet不熟悉的可以看BottomSheet的使用),

  1. 我们使用CoordinatorLayout布局
  2. 我们会有个自定义的ImageView控件OcrImageView,在之后我们会去实现这个控件,然后为其设置android:scaleType="fitCenter"android:scaleType="fitCenter"android:scaleType="fitCenter"属性,使其缩放是等比例的,这一步很关键,只有这样设置后,才能使我们的图片可以自动的进行缩放来调整后续我们的识别内容位置
  3. 我们会有个LinearLayout来作为BottomSheet的整个布局载体,为其加入app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"app:layout\_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"属性,并使其在最底部位置,设置app:behavior_hideableapp:behavior\_hideableapp:behavior_hideable屬性使其不能被隐藏,设置behavior_peekHeightbehavior\_peekHeightbehavior_peekHeight属性使其折叠时候的高度为50dp,做这些属性设置是为了让我们的BottomSheet被当做一个抽屉的把手一样,我们可以靠拉动把手来展示出来我们的列表内容
  4. 使用一个ConstraintLayout来加入一个bar的图片,并加入选择照片按钮和选择图片按钮
  5. 使用了一个圆角布局使其更加的美观些android:background="@drawable/bottomsheetlayoutshape"android:background="@drawable/bottom_sheet_layout_shape"android:background="@drawable/bottoms​heetl​ayouts​hape"
  6. 同样将我们的RecyclerView放置在BottomSheet区域的最下面,完成布局

这边我们要注意,我们的BottomSheet即我们的LinearLayout可以给其一个高度,不然图片都被其展开的时候都遮住了也就没什么意义了

假数据准备

需要说明的是,这边我使用的数据是通过服务端接口返回的,这个接口是内部使用的,而我这边为了方便起见,拿来借用了一下,下面会贴出来其数据格式,数据格式字段都很清晰一看就知道了,这边的数据只是图个方便,当然OCR识别这块的API也有很多厂商都提供的,所以数据格式的话,需要自行解析,我这边就写死一个数据内容来模拟我从我们内部使用的接口获取的数据来进行说明,数据如下:

private val mockResponseJsonStr = """{"errId": 0,"errMsg": "","result": [{"boxes": [[71, 1798],[264, 1806],[262, 1854],[69, 1846]],"text": "Authority","score": 0.9999019}, {"boxes": [[75, 1742],[716, 1748],[714, 1794],[73, 1788]],"text": "Government Root Certification","score": 0.9929104}, {"boxes": [[77, 1552],[714, 1552],[714, 1586],[77, 1586]],"text": "Go Daddy Root Certificate Authority-G2","score": 0.99476784}, {"boxes": [[79, 1484],[470, 1490],[468, 1536],[77, 1530]],"text": "saacammonne","score": 0.55913043}, {"boxes": [[71, 1284],[387, 1288],[385, 1328],[69, 1324]],"text": "GlobalSign Root CA","score": 0.9982361}, {"boxes": [[73, 1222],[432, 1230],[430, 1282],[71, 1274]],"text": "GlobalSign nv-sa","score": 0.9990079}, {"boxes": [[73, 1026],[250, 1034],[248, 1074],[71, 1066]],"text": "GlobalSign","score": 0.99623775}, {"boxes": [[73, 966],[301, 974],[299, 1022],[71, 1014]],"text": "GlobalSign","score": 0.98376197}, {"boxes": [[73, 768],[250, 776],[248, 816],[71, 808]],"text": "Gamasaspe","score": 0.59074664}, {"boxes": [[73, 704],[305, 714],[303, 768],[71, 758]],"text": "GlobalSign","score": 0.9983882}, {"boxes": [[71, 510],[252, 518],[250, 558],[69, 550]],"text": "GlobalSign","score": 0.99850523}, {"boxes": [[73, 444],[305, 454],[303, 508],[71, 498]],"text": "swdsasignGd","score": 0.6600375}, {"boxes": [[665, 232],[752, 232],[752, 286],[665, 286]],"text": "电电电","score": 0.68177694}, {"boxes": [[329, 230],[418, 230],[418, 284],[329, 284]],"text": "杂东景","score": 0.28567907}, {"boxes": [[73, 122],[450, 122],[450, 176],[73, 176]],"text": "个一信任的证书","score": 0.9976823}, {"boxes": [[889, 32],[1038, 28],[1040, 76],[891, 80]],"text": "物电区A","score": 0.0875141}, {"boxes": [[41, 34],[297, 28],[299, 74],[43, 80]],"text": "17:030Ri08","score": 0.73744255}]}""".trimIndent()

这个数据对应的图片是这张,接下来我都会使用这张图片以及这个数据进行说明演示

()

原图大小1080*1920,boxes中存放了对应于上传解析的原图的OCR识别的坐标点信息,是一个闭合的四边形,4个数组分别是四边形左上,右上,右下,左下四个角的坐标点,每个坐标点数组中的2个值代表了其X轴和Y轴坐标,text为识别出来的文本内容,score为识别可行度评分

BottomSheet代码实现

数据有了,下来我们就来实现代码

在mainActivity中,我们初始化我们的BottomSheet控件,进行一些设置

private lateinit var behavior: BottomSheetBehavior<View>//获取状态栏的高度
private fun getStatusBarHeight(activity: Activity): Int {val resourceId = activity.resources.getIdentifier("status_bar_height","dimen","android")return if (resourceId > 0) {activity.resources.getDimensionPixelSize(resourceId)} else 0
}private val displayWidth: Int by lazy {resources.displayMetrics.widthPixels
}private val displayHeight: Int by lazy {resources.displayMetrics.heightPixels - behavior.peekHeight -  getStatusBarHeight(this)
}override fun onCreate(savedInstanceState: Bundle?) {...behavior = BottomSheetBehavior.from(findViewById(R.id.custom_bottom_sheet))behavior.addBottomSheetCallback(object :BottomSheetBehavior.BottomSheetCallback() {override fun onStateChanged(bottomSheet: View, newState: Int) {}override fun onSlide(bottomSheet: View, slideOffset: Float) {val bottomSheetHeightOffset =(bottomSheet.height - behavior.peekHeight) * slideOffsetval layoutParams = image_preview.layoutParamsval height = displayHeightlayoutParams.height =(height - bottomSheetHeightOffset).toInt()image_preview.layoutParams = layoutParams}})...
}

我们为BottomSheet设置了一个callback监听,监听其折叠和展开的事件,我们在onSlide中得到其slideOffset的值(关于这个值的详细描述可以看BottomSheet的使用),并根据这个值计算出我们的图片的高度要如何调整,首先BottomSheet高度偏移量=(bottomSheet的总高度−bottomSheet的peekHeight折叠高度)∗offset比例值BottomSheet高度偏移量 = (bottomSheet的总高度 - bottomSheet的peekHeight折叠高度) * offset比例值BottomSheet高度偏移量=(bottomSheet的总高度−bottomSheet的peekHeight折叠高度)∗offset比例值,算出高度偏移量后,我们改变ImageView的高度值,使其等于ImageView控件高度−BottomSheet高度偏移量ImageView控件高度 - BottomSheet高度偏移量ImageView控件高度−BottomSheet高度偏移量,控件的高度我们给个固定值,即屏幕高度−BottomSheet折叠时候的高度−状态栏的高度屏幕高度 - BottomSheet折叠时候的高度 - 状态栏的高度屏幕高度−BottomSheet折叠时候的高度−状态栏的高度,这样,当我们展开或折叠BottomSheet的时候,ImageView会被重新计算其大小,并会进行与调节BottomSheet一样高度的值来调节其高度值

完成上一步后,接下来我们的BottomSheet会展现一个识别内容的列表,我们已经在xml中定义了RecycleView控件,那么我们为这个控件进行一些初始化,使其能展现我们上面提供的数据出来

MainActivity.kt

private val adapter =OcrListAdapter(OcrItemListener { _, item ->image_preview.selectOcrBox(ocrItem = item)
})override fun onCreate(savedInstanceState: Bundle?) {...recycle_view.adapter = adapterrecycle_view.layoutManager = LinearLayoutManager(this)recycle_view.addItemDecoration(DividerItemDecoration(this,LinearLayoutManager.VERTICAL))...
}

上面代码简单的进行了初始化的工作,我们会自定义一个Adapter,并且我们会有一个点击列表中某一Item的回调,回调中我们会使其与我们的imageView进行交互,使得我们点击列表中的某一项的时候,也会同时在图片中显示选中效果,我们先将回调接口方法全都定义好

OcrItemListener.kt

class OcrItemListener(private val clickListener: (position: Int, ocrItem : OcrItem) -> Unit) {fun onClick(position: Int, ocrItem: OcrItem) = clickListener(position, ocrItem)
}

然后我们来实现我们的Adapter并简单实现一下RecycleView布局

ocr_row_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="wrap_content"android:clickable="true"android:background="@drawable/ocr_list_bg_no_selector"><TextViewandroid:id="@+id/ocr_result_text"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"/><TextViewandroid:id="@+id/ocr_result_score"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="10dp"app:layout_constraintStart_toEndOf="@id/ocr_result_text"app:layout_constraintTop_toTopOf="parent"/></androidx.constraintlayout.widget.ConstraintLayout>

OcrListAdapter.kt

class OcrListAdapter(private val ocrItemListener: OcrItemListener
) : ListAdapter<OcrItem, OcrListAdapter.ViewHolder>(OcrDiffCallBack()) {class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {val textView: TextView = view.findViewById(R.id.ocr_result_text)val scoreView: TextView = view.findViewById(R.id.ocr_result_score)}var selectPosition = -1var isClick: Boolean = falseoverride fun onCreateViewHolder(parent: ViewGroup,viewType: Int): ViewHolder {val view = LayoutInflater.from(parent.context).inflate(R.layout.ocr_row_item, parent, false)return ViewHolder(view).apply {view.setOnClickListener {refreshClickItem(this.adapterPosition)onClick(this.adapterPosition)}}}override fun onBindViewHolder(holder: ViewHolder, position: Int) {val ocrItem = getItem(position)holder.textView.text = ocrItem.textholder.textView.setTextColor(ocrItem.color.toArgb())holder.scoreView.text = ocrItem.score.toString()holder.scoreView.setTextColor(ocrItem.color.toArgb())if (selectPosition == position && isClick) {holder.itemView.setBackgroundResource(R.color.select_background)} else {holder.itemView.setBackgroundResource(R.color.white)}}private fun onClick(position: Int) =ocrItemListener.onClick(position, getItem(position))fun linkageClick(position: Int) {refreshClickItem(position)}private fun refreshClickItem(position: Int) {isClick = if (!isClick) {true} else {selectPosition != position}notifyItemChanged(selectPosition)selectPosition = positionnotifyItemChanged(selectPosition)}class OcrDiffCallBack : DiffUtil.ItemCallback<OcrItem>() {override fun areItemsTheSame(oldItem: OcrItem,newItem: OcrItem): Boolean {return oldItem.text == newItem.text}override fun areContentsTheSame(oldItem: OcrItem,newItem: OcrItem): Boolean {return oldItem == newItem}}
}

我们来解释一下比较特殊的代码部分

  • 我们为我们的数据格式定义了一个OcrItem类,用来封装我们的数据格式,并且我们定义了一个color属性,用来使得我们的识别内容可以显示不同颜色的边框

​ OcrItem.kt

data class OcrItem(val boxes: List<List<Int>> = listOf(),val text: String,val color: Color,val score: Float
)
  • 定义了optionPosition来让RecycleView知道当前想要进行操作的是哪一个Item
  • 定义isClick来使其支持取消选中状态
  • 在onCreateViewHolder中为Item项目设置onClick事件,并通过ocrItemListener回调给外层
  • refreshClickItem方法中,我们会先将原先的列表选中状态刷新掉然后再进行新的选择项的状态的更新
  • 预留linkageClick方法,使得外层能够控制我们的列表进行选中,用于在图片上点击某个识别内容的时候,也进行Recycle列表Item的选中效果

至此,BottomSheet已经实现了,接下来我们会实现图片的展示以及识别内容的展示和交互功能,在这之前,还是建议先把这一部分的文章理解下,因为下一篇文章是在这一篇基础上来实现的,另,先附上完整的项目大家可以到github去查看:https://github.com/xiaozeiqwe8/OCRDemo,这样也能更加好的结合下一篇内容来看

最后还望各位兄弟姐妹们点个赞,关个注,更多的我理解的内容我还会陆续和大家分享的,谢谢大家!

实现OCR语言识别Demo(一)- BottomSheet实现相关推荐

  1. OpenVINO——3. OpenVINO文字识别OCR运行demo

    1. 简介 搜索了一波关于openVINO的使用,竟然也有很多资料,看来是自己不关注这方面,井底之蛙了. 这里有个问题,按照上面安装步骤安装的仅仅是OpenVINO,但是查看OpenvinoTookl ...

  2. 百度文字识别官方Demo

    百度OCR 官方Demo 百度文字识别官网 OCR Android SDK 开发者文档 百度sdk下载 OCR: Optical Character Recognition 光学字符识别 一. 管理控 ...

  3. 【python】OCR

    先看看百度百科对 OCR 的定义: OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗.亮的模式确定 ...

  4. 图像识别,ocr 技术,有兴趣的可以了解一下

    我现在的项目有一个需求,就是把拍照的照片上面的文字识别出来,然后上传到服务器,录入数据,其实图像识别技术是很难的一个技术. (这是我的github里面的地址,关于ocr 的demo~>  htt ...

  5. Android Studio中快速接入百度OCR遇到的问题解决方法

    直接进入正题,我android 接入第三方插件,一般习惯直接运行demo,然后根据demo的功能,再考虑是把demo导入项目,还是把项目需要的功能,相对应接入项目. 现在开始说说百度OCR,这个百度确 ...

  6. Python使用PaddleOCR本地进行视频字幕识别

    本文简述了利用OpenCV库以及PaddleOCR库对视频预定位置进行字幕提取并整合识别,在实际工程中,可以调用OCR的识别输出接口进行识别内容的批量保存. 后续改进方向参考: 1.PaddleNLP ...

  7. QT+opencv【opencv学习篇】OpenCV 读取、显示和保存图像

    目录 一.OpenCV 读取图像 OpenCV 读取函数 参数: 二.OpenCV 显示图像 imshow函数 imshow函数功能 imshow函数原型 三.OpenCV 保存图像 四.结果和代码 ...

  8. 天花板级前端工程师才能玩转的工具?前端AI应用集合重磅开源

    [导读] 在前端应用人脸识别.人像分割等 AI 能力已经广泛分布于各类场景中,其在低延迟.数据隐私保护.服务资源节省等方面都有明显的应用优势.随着人工智能技术的不断发展,越来越多的深度学习模型在保持超 ...

  9. JS前端AI应用集合重磅开源,PP-OCRv3 JS版模型速度提升87.5%

    在前端应用中,人脸识别.人像分割等AI能力已经广泛分布于各类场景中,其在低延迟.数据隐私保护.服务资源节省等方面都有明显的应用优势.随着人工智能技术的不断发展,越来越多的深度学习模型在保持超轻量的同时 ...

最新文章

  1. Oracle PL/SQL编程学习笔记:Merge方法的使用
  2. 软工实践原型设计——PaperRepositories
  3. 轻松使用OpenCV Python控制Webcam,读取Barcode
  4. QUIC 是如何解决TCP 性能瓶颈的?
  5. 利用水的浮力测量物体的重量,这个方法称象可靠吗?
  6. php计算经纬度距离,php经纬度计算距离
  7. 图标代码_通过两行代码即可调整苹果电脑 Launchpad 图标大小!
  8. mycat数据库中间件透明实现MYSQL读写分离
  9. 纽微特成立起因:申某账务有鬼,张某不干活怎么不说
  10. 标准输入输出流OutputStreamWriter:将字节输出流转换为字符输出流InputStreamReader:将字节输入流转换为字符输入流打印流添加输出数据的功能ObjectInputStrea
  11. linux部署moodle
  12. Moodle中的session用法
  13. pdf文件过大怎样压缩?pdf文件如何压缩到指定大小?
  14. RAID磁盘阵列与磁盘阵列卡
  15. 服务器数据盘不显示,云服务器不显示数据盘
  16. 量子笔记:单比特量子门、泡利矩阵
  17. PHP开发网易云FM音乐试听程序源码+支持下载功能
  18. Linux基础命令----tailf 跟踪文件输出
  19. 机考怎么作弊_电脑上考试如何作弊 电脑上考试不能复制粘贴怎么办
  20. springboot和mybatis 多数据源

热门文章

  1. 2022.11.15【bug笔记】|Error in FASTQ file at line 55: Line expected to start with ‘+‘, but found ‘G‘
  2. 怎么去面试测试工程师?
  3. safe mode bypass and rooting
  4. excel表格两个表格合并
  5. 在C#中设置打印机纸张大小
  6. 深入理解文字高度和行高的设置
  7. 好程序员Java培训分享20个Java程序员基础题
  8. Thinking in java生词
  9. 处理器最新排行_CPU跑分工具CINBENCH R23排行榜出炉:AMD锐龙单核、多核均屠榜
  10. zepto和jquery