2024年1月

最好的中文TTS项目Bert-vits2更新了中文特化分支,但可能由于时间仓促,代码中存在不少的bug,作为普通用户,有的时候也想为自己喜欢的开源项目做一点点贡献,帮助作者修改一些简单的bug,那么该如何开始?

本次我们以Bert-vits2项目为例子,分享正确提交PR(Pull Requests)的方式。

FORK项目

首先面对一个开源项目,作为普通用户我们有三个选项:

点击Watch可以关注该项目,项目有了更新会提醒关注者,点击Star可以给作者鼓励,类似点赞之类的操作,这里最重要的是Fork,可以把项目拷贝一份到自己的项目库中:

在项目列表中可以看到明确标出了该项目是fork自fishaudio/Bert-VITS2项目:

Forked from fishaudio/Bert-VITS2

至此,我们对自己项目列表中的Bert-VITS2项目就有完整的读写和提交权限了。

修改代码

Github默认显示的都是主分支代码,直接修改主分支代码是非常不理智的行为,一般情况下,我们需要修改的是开发分支,这里先切换到中文特化分支上:

随后开始修改代码,这里的bug是2.3版本去掉了clap特征,而中文特化版本又加了回来,但是代码没有正确回退,导致中文特化版本训练的时候会报错:

修改成功后,提交到自己fork的分支上,注意写清楚提交理由。

提交PR

当我们提交了修改后,在项目上方会自动弹出PR窗口:

此时点击Compare & pull request,正式提交PR

提交PR的时候,也可以描述一下此次pull request的具体理由:

这里描述的越清楚,越容易被作者采纳。

作者合并PR的分支

最后,就是等待作者查看PR请求,如果代码修改的比较合理,那么作者就会考虑合并到目标分支:

如果作者认为代码有可取的地方,那么就会手动合并两个分支,也就是把fork的代码,合并到正式项目的指定分支上,如上图所示。

结语

GitHub 中的 Pull Request(PR)合并分支的原理是通过创建分支保护规则来实现的。分支保护规则定义了非项目小组的协作者在推送更改到分支之前必须满足的条件,例如需要通过状态检查或者需要有一个线性的提交历史。这样可以确保在合并 PR 到受保护的分支之前,必须满足一定的条件。

在 GitHub 中,你可以通过创建分支保护规则来强制执行特定的工作流程或要求。这些规则可以限制谁可以推送更改到受保护的分支,以及是否允许强制推送或删除分支。

当你创建一个分支保护规则后,协作者在推送更改或合并 PR 到受保护的分支时,必须满足规则中定义的条件。这有助于确保代码的质量和安全性,特别是在团队协作的情况下。

总而言之,GitHub 中的分支保护规则可以帮助开源团队管理代码的流程和质量,确保在合并非开发小组的代码到指定分支之前,必须满足一定的条件和要求。

在数据科学和分析的世界里,将数据可视化是至关重要的一步,它能帮助我们更好地理解数据,发现潜在的模式和关系。Python 提供了多种可视化工具,HvPlot 是其中一个出色的库,专为简单且高效的交互式可视化设计。

HvPlot 简介

HvPlot 是基于 HoloViews 的高层抽象绘图库,但它提供了更为直接和方便的API,使得创建复杂的交互式图表变得简单快捷。HvPlot 可以与 Pandas、xarray 等数据处理库无缝集成,并且支持 Bokeh、Matplotlib 等多种绘图后端,允许用户根据需要制作静态或交互式图表。

安装 HvPlot

在开始之前,需要安装 HvPlot,可以使用 pip 来安装:

pip install hvplot

使用示例

让我们通过几个简单的例子来展示 HvPlot 的基本用法。

示例 1:简单的线图

假设我们有一些时间序列数据,我们想要画出它的线图:

import hvplot.pandas  # 导入 hvplot 的 pandas 接口
import pandas as pd
import numpy as np

# 创建一个时间序列数据
idx = pd.date_range('2023-01-01', periods=100, freq='D')
data = np.random.randn(100).cumsum()
df = pd.DataFrame(data, index=idx, columns=['value'])

# 使用 hvplot 绘制线图
plot = df.hvplot.line(title="时间序列示例")
plot

这个例子将会产生一个带有标题的时间序列线图,你可以缩放、平移来交互地查看图表。

示例 2:散点图和直方图

继续利用 HvPlot,我们可以很容易地绘制散点图和直方图来查看变量之间的关系和分布:

# 创建一些随机数据
df = pd.DataFrame({
    'x': np.random.rand(100),
    'y': np.random.rand(100)
})

# 绘制散点图
scatter_plot = df.hvplot.scatter('x', 'y', title="散点图示例")
scatter_plot

# 绘制直方图
histogram = df.hvplot.hist('x', bins=20, title="直方图示例")
histogram

在散点图中,每个点的位置反映了数据表中的一行记录。直方图则显示了变量 'x' 的分布情况。

示例 3:交互式探索

HvPlot 支持通过交互式小部件来探索数据,例如选择不同的变量来绘图:

# 创建一些分类数据
df = pd.DataFrame({
    'variable': np.random.choice(['A', 'B', 'C'], 100),
    'value': np.random.randn(100)
})

# 交互式绘制箱型图
boxwhisker = df.hvplot.box(y='value', by='variable', title="箱型图示例", width=400)
boxwhisker


这个箱型图将会按照 'variable' 列的类别来显示 'value' 列的分布情况,并且你可以通过交互式图表来查看不同类别的具体统计信息。

示例 4:交互式探索

当然,HvPlot 不仅适用于基础绘图,还可以创建更高级和复杂的可视化,如动态交叉筛选、地理数据可视化以及使用数据流的实时数据可视化。下面是一个使用 HvPlot 进行动态交叉筛选的示例:

我们将使用汽车数据集,展示如何利用 HvPlot 进行动态交叉筛选。用户可以选择汽车的制造年份,动态地看到不同年份下汽车的马力与加速之间的关系。

import hvplot.pandas
import pandas as pd
import panel as pn
from bokeh.sampledata.autompg import autompg_clean as df

# 清洗数据,去除缺失值
df.dropna(inplace=True)

# 创建一个选择器,用于交互
year_slider = pn.widgets.IntSlider(name='yr', start=df.yr.min(), end=df.yr.max(), step=1)

# 定义一个动态更新的散点图函数
@pn.depends(year=year_slider.param.value)
def get_scatter_plot(year):
    current_df = df[df.yr == year]
    return current_df.hvplot.scatter(
        x='hp', 
        y='mpg', 
        xlim=(df.hp.min() - 10, df.hp.max() + 10), 
        ylim=(df.mpg.min() - 5, df.mpg.max() + 5),
        title=f'马力 vs. 每加仑英里数(年份:{year})',
        color='cyl', 
        size=10,
        hover_cols=['name']
    )

# 创建一个 Panel,包含选择器和绘图
dashboard = pn.Column(year_slider, get_scatter_plot)

# 服务化如果你在 Jupyter Notebook 上运行,使用以下命令来显示
# dashboard.servable()

# 如果你使用的是纯 Python 脚本,使用以下命令来启动服务器
dashboard.show()

在这个例子中,我们首先导入了必要的库,然后清洗了Bokeh库中的汽车数据集。

接着,我们创建了一个
IntSlider
控件用于选择年份。
get_scatter_plot
函数定义了如何根据选定的年份更新散点图。

最后,我们用
pn.Column
将滑块和绘图函数组合在一起,形成一个可交互的面板(
dashboard
)。

要注意的是,如果你在 Jupyter Notebook 上运行这段代码,需要调用
dashboard.servable()
来显示面板。如果是在纯 Python 环境中,需要使用
dashboard.show()
来启动一个服务器,并在浏览器中查看面板。

这只是 HvPlot 功能的冰山一角。HvPlot 结合其他库,如 Panel、Datashader 和 GeoViews,可以实现更加复杂和强大的数据可视化。

1、准备材料

开发板(
STM32F407G-DISC1

ST-LINK/V2驱动
STM32CubeMX软件(
Version 6.10.0

keil µVision5 IDE(
MDK-Arm

XCOM V2.6串口助手
杜邦线一根

2、实验目标

使用STM32CubeMX软件配置STM32F407
通用定时器TIM9为输入捕获模式,捕获由上一实验输出的可变占空比的PWM波型周期和占空比

3、实验流程

3.0、前提知识

该实验需要串口输出PWM波形的周期和占空比信息,因此需要你知道如何简单使用串口,所以如果不知道如何使用请阅读本系列教程“
STM32CubeMX教程9 USART/UART 异步通信
”实验

输入捕捉的功能是记录下要捕捉的边沿出现的时刻,如果你仅仅捕捉下降沿,那么两次捕捉的差表示输入信号的周期,即两次下降沿之间的时间。如果要测量低电平的宽度,你应该在捕捉到下降沿的中断处理中把捕捉边沿改变为上升沿,然后把两次捕捉的数值相减就得到了需要测量的低电平宽度(注释1)

输入捕获有①Input Capture direct mode和②Input Capture indirect mode两种模式可选,但是在没有通道 选择 直接模式输入捕获时,非直接模式的输入捕获是不可选择的,这个也很好理解,直接模式输入捕获会将捕获通道设置到某一个具体的引脚上,而非直接模式的输入捕获不会将捕获通道设置到某一个具体的引脚上,而是使用和直接模式输入捕获同一引脚作为捕获输入

所有拥有2/4个捕获/比较通道的通用定时器以Channel1/2、Channel3/4为两组,每一组的两个通道可以设置为①全部使用直接输入捕获模式、②一个通道直接输入捕获另一个通道非直接输入捕获 两种模式,而剩下的只有1个捕获/比较通道的通用定时器只能使用直接输入捕获模式

输入捕获中还有一个特殊模式,即PWM输入模式,利用联合通道 Combined Channels 可以直接测量出PWM波形的周期和占空比,非常方便,另外Combined Channels还可以选择①编码器模式、②通道1PWM输入、③通道2PWM输入和④传感器XOR功能四种模式,模式②③均是使用的通用定时器的Channel1/2

3.1、CubeMX相关配置

请先阅读“
STM32CubeMX 工程建立
”实验3.4.1小节配置RCC和SYS

3.1.1、时钟树配置

系统时钟树配置与上一实验一致,均设置为STM32F407总线能达到的最高时钟频率,具体如下图所示

3.1.2、外设参数配置

本实验需要需要初始化USART2作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信”

在Pinout & Configuration页面右边芯片引脚预览Pinout view中找到PD12引脚,左键单击将其设定为TIM4_CH1

然后在页面左侧功能分类栏目中点开Timers栏目,单击栏目下的TIM4,按照上一个实验的配置对通道1配置为PWM输出

具体配置如下图所示

然后单击Timers栏目中的TIM9,配置其 Combined Channels 为通道1输入捕获/通道2输入捕获,这两个选项除了通道对应的引脚不同外,其余均一致,这里通道1对应的是PE5,而通道2对应的是PE6

接下来对页面中间的 Combined Channels 两个通道参数进行配置,首先对于计数器参数的设置,如PSC、计数模式、ARR等之前的实验已有讲解,在此不再说明

然后对于通道1PWM输入,其Input Trigger和从机模式控制器两个参数选项唯一,不可设置

最后设置通道1的参数极性选择上升沿,直接模式,通道2的参数极性只能选择与通道1设置的相反,非直接模式,其他分频和滤波不做设置,具体配置如下图所示

为什么要这么设置?

当配置为Combined Channels的PWM输入模式时,如果通道1设置为上升沿,则在PWM输入引脚PE5检测到输入的PWM波第一个上升沿时,会将当前计数器CNT的值锁存在TIM9_CCR1寄存器中,并且将寄存器复位,使CNT为0从这个上升沿重新开始计数,然后当检测到下降沿时,会将当前计数器CNT的值锁存在TIM9_CCR2寄存器中,因此TIM9_CCR2寄存器中的值即为PWM波高电平持续的计数频率次数,然后再次遇到上升沿时重复上述过程,因此TIM9_CCR1寄存器中的值即为PWM波的周期

如下图举例所示

3.1.3、外设中断配置

在Pinout & Configuration页面左边System Core/NVIC中勾选TIM9全局中断,然后选择合适的中断优先级即可

3.2、生成代码

请先阅读“
STM32CubeMX 工程建立
”实验3.4.3小节配置Project Manager

单击页面右上角GENERATE CODE生成工程

3.2.1、外设初始化函数调用流程

首先在生成的工程代码主函数main()中调用了MX_TIM9_Init()函数完成了对TIM9基本定时器参数,输入捕获通道1/2参数的配置

然后在HAL_TIM_IC_Init()函数中调用了HAL_TIM_IC_MspInit()函数对TIM9时钟和中断进行了使能,并对中断优先级进行了配置,最后对TIM9的两个输入捕获通道引脚进行了复用设置,从而完成了整个初始化过程

如下图所示为TIM4输出比较初始化的具体函数调用流程

3.2.2、外设中断函数调用流程

使能定时器全局中断后在stm32f4xx_it.c中自动生成了TIM9的中断处理函数TIM1_BRK_TIM9_IRQHandler()

TIM1_BRK_TIM9_IRQHandler()函数调用了HAL库的定时器中断处理函数HAL_TIM_IRQHandler(),这个函数负责处理所有的定时器相关中断

通过判断中断来源及相关寄存器,最终调用输入捕获事件完成的回调函数为HAL_TIM_IC_CaptureCallback(),该函数为虚函数,需要用户重新实现

如下图所示为TIM9输入捕获中断回调的具体函数调用流程

3.2.3、添加其他必要代码

在tim.c中重新实现输入捕获中断回调函数HAL_TIM_IC_CaptureCallback(),当捕获到上升沿/下降沿时会进入该中断回调函数中,然后读取TIM9_CCR1寄存器的值,该值即为PWM波形周期,然后读取TIM9_CCR2寄存器的值,该值即为PWM波形高电平持续计数值,具体代码如下图所示

源代码如下所示

/*输入捕获中断回调函数*/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    uint16_t IC1_Width = __HAL_TIM_GET_COMPARE(&htim9,TIM_CHANNEL_1);
    uint16_t IC2_Pulse = __HAL_TIM_GET_COMPARE(&htim9,TIM_CHANNEL_2);
    if((IC1_Width==0) || (IC2_Pulse==0))
        return;
 
    printf("IC1_Width:%d,IC2_Pulse:%d\r\n", IC1_Width, IC2_Pulse);
}

在主函数中启动通用定时器TIM4的PWM输出,以中断的方式启动通用定时器TIM9,并启动其通道1/2的输入捕获,最后在主函数主循环中轮询按键,当按键按下时更改通用定时器TIM4的PWM输出占空比即可,如下图所示为具体代码

源代码如下所示

/*主函数外变量定义*/
uint16_t pulseWidth=0;
uint8_t dirInc=1;
 
/*主循环外启动代码*/
HAL_TIM_Base_Start(&htim4);
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);
HAL_TIM_Base_Start_IT(&htim9);
HAL_TIM_IC_Start_IT(&htim9, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim9, TIM_CHANNEL_2);
 
/*主循环内代码*/
while(1)
{
    /* USER CODE BEGIN 3 */
    if(htim->Instance != TIM4)
        return;
    
    if(dirInc == 1)	
    {
        pulseWidth ++;
        if(pulseWidth >= 195)
        {
            pulseWidth = 195;
            dirInc = 0;	
        }
    }
    else
    {
        pulseWidth --;
        if(pulseWidth <= 5)
        {
            pulseWidth = 5;
            dirInc = 1;	
        }
    }
    __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, pulseWidth);
}

值得提醒的是我们的PWM波输出引脚为PD12,而我们PWM输入捕获引脚为PE5,因此需要一根杜邦线将开发板上的PD12和PE5短接

4、常用函数

/*启动输入捕获*/
HAL_StatusTypeDef HAL_TIM_IC_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
 
/*停止输入捕获*/
HAL_StatusTypeDef HAL_TIM_IC_Stop(TIM_HandleTypeDef *htim, uint32_t Channel)
 
/*以中断方式启动输入捕获*/
HAL_StatusTypeDef HAL_TIM_IC_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel)
 
/*停止以中断方式的输入捕获*/
HAL_StatusTypeDef HAL_TIM_IC_Stop_IT(TIM_HandleTypeDef *htim, uint32_t Channel)
 
/*输入捕获回调函数*/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)

5、烧录验证

5.1、具体步骤

“配置TIM4定时器通道1输出PWM波 -> 配置TIM9定时器Combined Channels为PWM输入在通道1 -> 设置PWM输入相关参数 -> 勾选启动TIM9输入PWM捕获中断并设置合适的 -> 重新实现输入捕获中断回调函数HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) -> 使用__HAL_TIM_GET_COMPARE(&htim9,TIM_CHANNEL_1/2)获取捕获到的PWM波形的周期和占空比 -> 主函数中使用HAL_TIM_Base_Start_IT(&htim9)以中断方式启动定时器 -> 然后使用HAL_TIM_IC_Start_IT(&htim9, TIM_CHANNEL_1/2)以中断方式启动输入捕获 -> 主循环中实现按键设置PWM输出占空比”

5.2、实验现象

烧录程序,开发板上电后打开串口,串口会不断的输出当前测量到的PWM波形周期为固定的199,然后按下用户按键USER_KEY会发现,输出的PWM波形的占空比不停变化,变化规律和上一个实验中LED呼吸灯变化情况一致,同时还可以观察到GREEN_LED的亮度不断地变化,下图为串口输出的详细信息

6、注释详解

注释1
:请阅读
在针对计数功能中,Input Capture direct mode和IO中断有什么区别 (stmicroelectronics.cn)

参考资料

STM32Cube高效开发教程(基础篇)

更多内容请浏览
OSnotes的CSDN博客

大家好,我是老章

最近在折腾之前搞的R-bookdown网站、Jekyll博客,还有刚上了Astro做的周刊、Astro Starlight做的文档站

还有正在建设的这个:

但是前端我是一窍不通,只能在现成的主题上修修改改,即便如此还是遇到了很多问题。好在有GPT这个打工仔帮忙,进度还行。

这里就把GPT-4干的活儿展示一下:

先干点粗活

我的提问

我的文件夹下有很多.mdx文件,文中有若干段落:

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/speecht5.png" alt="SpeechT5 outputs a spectrogram and uses a vocoder to create the waveform">
</div>

我需要把文章div格式改为

![]()

这种格式,比如

![](https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/speecht5.png)

GPT直接给了我代码,毫无bug

import re
import glob

# 匹配HTML中的图片标签
img_tag_regex = re.compile(r'<div class="flex justify-center">\s*<img src="(.*?)" alt=".*?"\s*/?>\s*</div>')

# 用于替换的函数
def replace_with_markdown_img(match):
    img_url = match.group(1)
    return f"![]({img_url})"

# 遍历所有的.mdx文件
for file_path in glob.glob('path_to_your_folder/*.mdx'):
    with open(file_path, 'r', encoding='utf-8') as file:
        content = file.read()
        
    # 替换所有匹配的图片标签
    new_content = img_tag_regex.sub(replace_with_markdown_img, content)
    
    with open(file_path, 'w', encoding='utf-8') as file:
        file.write(new_content)

print("Finished replacing all div image tags with markdown image syntax.")

还有别的粗活,比如

再来个cosplay

然后解决具体问题:调整页面跳转方式

我的提问:可以帮我调整一下astro页面吗,我想换个跳转方式,不要用点击详情来跳转到blog

GPT-4的回答

如果你想要换一种跳转到博客的方式,而不是通过点击一个“详情”按钮,你可以考虑将整个 `SummaryCard` 作为一个可点击的区域来实现跳转。这通常通过将 `SummaryCard` 包裹在一个 `<a>` 标签中来完成。

让我们进行一些调整,使整个卡片都是可点击的,并且在用户点击时将他们重定向到博客页面。我们将移除 `Button` 组件,并将 `SummaryCard` 包裹在一个 `<a>` 标签中。以下是更新后的代码:

```astro
代码太长了,此处略
```

在这个例子中:

- 我们移除了原有的 `Button` 组件。
- 我们添加了一个 `<a>` 标签来包裹整个 `SummaryCard`,将 `href` 设置为博客的 URL。
- 我们给 `<a>` 标签添加了类 `block text-inherit no-underline`,这些类分别用于让链接占据整个卡片区域、继承文本颜色、移除下划线样式,这样可以保持卡片的视觉一致性。
- 为了防止在点击标签链接时也触发卡片链接,我们在标签的 `<a>` 标签上添加了 `onClick` 属性并调用了 `e.stopPropagation()`,这样点击标签时不会触发卡片的跳转。

请根据你的项目需求和样式框架进行必要的调整。

还可以让他继续完善

再解决一个问题:修改页面展现方式

GPT的回答毫无bug:

要让标签(tags)和“阅读文章”(Read post)按钮并排显示,你需要将它们放在相同的容器中,并使用`flex`布局来使它们水平排列。此外,还需要确保这个容器在不同屏幕尺寸下都能适应布局。以下是调整后的代码:

```astro
代码太长了,此处略
```

更改后的代码中,我创建了一个新的父级`div`元素,其类为`flex flex-col sm:flex-row justify-between items-start sm:items-center my-4`,这样在小屏幕上,标签和按钮会堆叠排列(`flex-col`),而在大屏幕上则会并排排列(`flex-row`)。我还调整了margin和padding,以便在不同屏幕大小上看起来都合适。

请确保你的CSS框架(如Tailwind CSS)已经包含了以上用到的类,否则你可能需要添加或修改相应的CSS样式。

解决若干bug

代码解释工作和翻译

还有大量代码解释工作和翻译工作就不一一展示了

真的无法想象,没有机必替帮忙,我要花费几倍的时间才能搞定这些事情。

我太喜欢大模型时代了

准备工作

在开发应用前:
1、需要在AppGallery Connect中配置相关信息,包括:注册成为开发者和创建应用。
2、使用ML Kit云侧服务(端侧服务可不开通)需要开发者在AppGallery Connect上打开ML Kit服务开关。

集成HMS Core SDK

工程根目录build.gradle文件

buildscript {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
        // 配置HMS Core SDK的Maven仓地址。
        maven {url 'https://developer.huawei.com/repo/'}
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.0.4'
        classpath 'com.huawei.agconnect:agcp:1.6.2.300'
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
        // 配置HMS Core SDK的Maven仓地址。
        maven {url 'https://developer.huawei.com/repo/'}
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

app module下的build.gradle依赖华为基础SDK包与语言识别模型包:

// 引入基础SDK
implementation 'com.huawei.hms:ml-computer-vision-ocr:3.11.0.301'
// 引入拉丁语文字识别模型包
implementation 'com.huawei.hms:ml-computer-vision-ocr-latin-model:3.11.0.301'
// 引入日韩语文字识别模型包
implementation 'com.huawei.hms:ml-computer-vision-ocr-jk-model:3.11.0.301'
// 引入中英文文字识别模型包
implementation 'com.huawei.hms:ml-computer-vision-ocr-cn-model:3.11.0.301'

配置混淆脚本

-dontwarn com.huawei.**
-keep class com.huawei.** {*;}
-dontwarn org.slf4j.**
-keep class org.slf4j.** {*;}
-dontwarn org.springframework.**
-keep class org.springframework.** {*;}
-dontwarn com.fasterxml.jackson.**
-keep class com.fasterxml.jackson.** {*;}
-keep class com.huawei.noah.bolttranslator.**{*;}
-dontwarn com.huawei.hisi.**
-keep class com.huawei.hisi.** {*;}

添加权限

    <!--相机权限-->
    <uses-permission android:name="android.permission.CAMERA" />
    <!--使用网络权限-->
    <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.RECORD_AUDIO" />
    <!--获取网络状态权限-->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!--获取wifi状态权限-->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

端侧识别

    /**
     * 端侧文本识别
     */
    private void textAnalyzer() {
        long startTime =  (new Date()).getTime();
        Log.d("textAnalyzer", "start: " + startTime);
        MLLocalTextSetting setting = new MLLocalTextSetting.Factory()
                .setOCRMode(MLLocalTextSetting.OCR_DETECT_MODE)
                // 设置识别语种。
                .setLanguage("zh")
                .create();
        MLTextAnalyzer analyzer = MLAnalyzerFactory.getInstance().getLocalTextAnalyzer(setting);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test2);
        // 通过bitmap创建MLFrame,bitmap为输入的Bitmap格式图片数据。
        MLFrame frame = MLFrame.fromBitmap(bitmap);
        Task<MLText> task = analyzer.asyncAnalyseFrame(frame);
        task.addOnSuccessListener(new OnSuccessListener<MLText>() {
            @Override
            public void onSuccess(MLText text) {
                List<MLText.Block> blocks = text.getBlocks();
                StringBuilder sb = new StringBuilder();
                for (MLText.Block block : blocks) {
                    sb.append(block.getStringValue());
                }
                // 识别成功处理。
                tv.setText("识别成功: " + sb.toString());
                Log.d("textAnalyzer", "识别成功:\n " + sb.toString());
                long endTime =  (new Date()).getTime();
                Log.d("textAnalyzer", "end: " + endTime);
                Log.d("textAnalyzer", "耗时: " + (endTime-startTime));
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(Exception e) {
                // 识别失败处理。
                tv.setText("识别失败");
            }
        });
    }

端侧识别测试

原图:test2.png

识别结果:

云侧文本识别

注意:此功能收费,但是精确度更高。

配置应用的鉴权信息

先申请apikey:
https://developer.huawei.com/consumer/cn/console/api/credentials/dev388421841221889538
然后配置apikey

云侧文本识别实现

    MLApplicationInit.init();

    /**
     * 云侧文本识别
     */
    private void textAnalyzerNet() {
        long startTime =  (new Date()).getTime();
        Log.d("textAnalyzerNet", "start: " + startTime);
        // 方式一:使用自定义参数配置。
        // 创建语言集合。
        List<String> languageList = new ArrayList();
        languageList.add("zh");
        languageList.add("en");
        // 设置参数。
        MLRemoteTextSetting setting = new MLRemoteTextSetting.Factory()
                // 设置云侧文本字体模式:
                // 若选择手写体格式,文本检测模式仅支持稀疏文本,语言列表仅支持中文(zh),边界框格式仅支持NGON四顶点坐标。
                // setTextDensityScene、setLnaguageList、setBorderType等方法设置均不生效。
                // 若选择印刷体格式,则需要手动设置语言列表,检测模式和边框样式,或采取默认配置。
                // MLRemoteTextSetting.OCR_HANDWRITTENFONT_SCENE:手写体。
                // MLRemoteTextSetting.OCR_PRINTFONT_SCENE:印刷体。
                .setTextFontScene(MLRemoteTextSetting.OCR_HANDWRITTENFONT_SCENE)
                // 设置云侧文本检测模式:
                // MLRemoteTextSetting.OCR_COMPACT_SCENE:文本密集场景的文本识别。
                // MLRemoteTextSetting.OCR_LOOSE_SCENE:文本稀疏场景的文本识别。
                .setTextDensityScene(MLRemoteTextSetting.OCR_LOOSE_SCENE)
                // 设置识别语言列表,使用ISO 639-1标准。
                .setLanguageList(languageList)
                // 设置文本边界框返回格式。
                // MLRemoteTextSetting.NGON:返回四边形的四个顶点坐标。
                // MLRemoteTextSetting.ARC:返回文本排列为弧形的多边形边界的顶点,最多可返回多达72个顶点的坐标。
                .setBorderType(MLRemoteTextSetting.ARC)
                .create();
        MLTextAnalyzer analyzer = MLAnalyzerFactory.getInstance().getRemoteTextAnalyzer(setting);
        // 方式二:使用默认参数配置,自动检测语种进行识别,适用于文本稀疏场景,文本框返回格式为:MLRemoteTextSetting.NGON。
        //MLTextAnalyzer analyzer = MLAnalyzerFactory.getInstance().getRemoteTextAnalyzer();
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test2);
        // 通过bitmap创建MLFrame,bitmap为输入的Bitmap格式图片数据。
        MLFrame frame = MLFrame.fromBitmap(bitmap);
        Task<MLText> task = analyzer.asyncAnalyseFrame(frame);
        task.addOnSuccessListener(new OnSuccessListener<MLText>() {
            @Override
            public void onSuccess(MLText text) {
                // 识别成功。
                List<MLText.Block> blocks = text.getBlocks();
                StringBuilder sb = new StringBuilder();
                for (MLText.Block block : blocks) {
                    sb.append(block.getStringValue());
                    sb.append("\n");
                }
                // 识别成功处理。
                tv.setText("识别成功: " + sb.toString());
                Log.d("textAnalyzerNet", "识别成功:\n " + sb.toString());
                long endTime =  (new Date()).getTime();
                Log.d("textAnalyzerNet", "end: " +endTime);
                Log.d("textAnalyzerNet", "耗时: " + (endTime-startTime));
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(Exception e) {
                // 识别失败,获取相关异常信息。
                try {
                    MLException mlException = (MLException) e;
                    // 获取错误码,开发者可以对错误码进行处理,根据错误码进行差异化的页面提示。
                    int errorCode = mlException.getErrCode();
                    // 获取报错信息,开发者可以结合错误码,快速定位问题。
                    String errorMessage = mlException.getMessage();
                } catch (Exception error) {
                    // 转换错误处理。
                }
            }
        });
    }

云侧文本识别测试

原图:test2.png

识别结果:

官网参考

更多内容,请参考官网:
https://developer.huawei.com/consumer/cn/doc/hiai-Guides/text-recognition-0000001050040053