Android开发:通过Tesseract第三方库实现OCR

news2024/9/22 5:37:20

一、引言

        什么是OCR?OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程。简单地说,OCR是一种技术,该项技术采用光学的方式将纸质文档中的文字转换为黑白点阵图像,然后通过识别软件将图像中的文字转换成文本格式,供文字处理软件进一步编辑加工。

        什么是Tesseract?Tesseract was originally developed at Hewlett-Packard Laboratories Bristol UK and at Hewlett-Packard Co, Greeley Colorado USA between 1985 and 1994, with some more changes made in 1996 to port to Windows, and some C++izing in 1998. In 2005 Tesseract was open sourced by HP. From 2006 until November 2018 it was developed by Google.Tesseract最初是在英国布里斯托尔的惠普实验室和美国科罗拉多州格里利的惠普公司于1985年至1994年间开发的,1996年做了一些更改以移植到Windows,并在1998年进行了一些c++化。2005年,Tesseract被惠普开源。从2006年到2018年11月,它由谷歌开发。简单地说,Tesseract 就是上面OCR所说的“识别软件”的具体实现。

        OCR的识别对象(输入)是一张图片,而识别结果(输出)是计算机文字。在Android手机端主要存在两种图片的获取方式,一种是从相册中选择一个,另一个是直接拍照获得。因此,本文将实现最简单的OCR思路:首先从手机中获得一张图片,然后将其输入到Tesseract库,最后通过该库输出识别结果。由于只是学习该库的使用方式,所以博主忽略了其它辅助性的功能,比如拍照识别。

二、Android通过Tesseract实现OCR

1、在Module的build.gradle文件中添加以下依赖

implementation 'com.rmtheis:tess-two:9.1.0'

2、在AndroidManifest.xml文件中添加以下权限

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

3、在MainActivity中申请权限

        建议在onCreate方法中执行下面的checkPermission方法

    // 检查应用所需的权限,如不满足则发出权限请求
    private void checkPermission() {
        if (ContextCompat.checkSelfPermission(getApplicationContext(),
                Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 120);
        }
        if (ContextCompat.checkSelfPermission(getApplicationContext(),
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 121);
        }
    }

4、从assets中读取一张读片

        既用于显示,又用于识别

    // 从assets中读取一张Bitmap类型的图片
    private Bitmap getBitmapFromAssets(Context context, String filename) {
        Bitmap bitmap = null;
        AssetManager assetManager = context.getAssets();
        try {
            InputStream is = assetManager.open(filename);
            bitmap = BitmapFactory.decodeStream(is);
            is.close();
            Log.i(TAG, "图片读取成功。");
            Toast.makeText(getApplicationContext(), "图片读取成功。", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            Log.i(TAG, "图片读取失败。");
            Toast.makeText(getApplicationContext(), "图片读取失败。", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
        return bitmap;
    }

5、做好OCR的准备工作

        去https://github.com/tesseract-ocr/下载.traineddata语言包,然后放在项目assets目录下,并通过以下代码复制到Android文件系统中

    // 为Tesserect复制(从assets中复制过去)所需的数据
    private void prepareTess() {
        try{
            // 先创建必须的目录
            File dir = getExternalFilesDir(TESS_DATA);
            if(!dir.exists()){
                if (!dir.mkdir()) {
                    Toast.makeText(getApplicationContext(), "目录" + dir.getPath() + "没有创建成功", Toast.LENGTH_SHORT).show();
                }
            }
            // 从assets中复制必须的数据
            String pathToDataFile = dir + "/" + DATA_FILENAME;
            if (!(new File(pathToDataFile)).exists()) {
                InputStream in = getAssets().open(DATA_FILENAME);
                OutputStream out = new FileOutputStream(pathToDataFile);
                byte[] buff = new byte[1024];
                int len;
                while ((len = in.read(buff)) > 0) {
                    out.write(buff, 0, len);
                }
                in.close();
                out.close();
            }
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
    }

6、点击按钮后调用以下方法,执行OCR识别

    // OCR识别的主程序
    private void mainProgram() {
        // 从assets中获取一张Bitmap图片
        Bitmap bitmap = getBitmapFromAssets(MainActivity.this, TARGET_FILENAME);
        // 同时显示在界面
        main_iv_image.setImageBitmap(bitmap);
        if (bitmap != null) {
            // 准备工作:创建路径和Tesserect的数据
            prepareTess();
            // 初始化Tesserect
            TessBaseAPI tessBaseAPI = new TessBaseAPI();
            String dataPath = getExternalFilesDir("/").getPath() + "/";
            tessBaseAPI.init(dataPath, "eng");
            // 识别并显示结果
            String result = getOCRResult(tessBaseAPI, bitmap);
            main_tv_result.setText(result);
        }
    }

    // 进行OCR并返回识别结果
    private String getOCRResult(TessBaseAPI tessBaseAPI, Bitmap bitmap) {
        tessBaseAPI.setImage(bitmap);
        String result = "-";
        try{
            result = tessBaseAPI.getUTF8Text();
        }catch (Exception e){
            Log.e(TAG, e.getMessage());
        }
        tessBaseAPI.end();
        return result;
    }

7、编译运行,效果如图

        个人感觉识别率不是很准,一般般。

8、源代码贴一下

        MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.googlecode.tesseract.android.TessBaseAPI;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class MainActivity extends AppCompatActivity {

    public static final String TESS_DATA = "/tessdata";
    private static final String TARGET_FILENAME = "vin_demo.png";
    private static final String DATA_FILENAME = "eng.traineddata";
    private static final String TAG = MainActivity.class.getSimpleName();

    private Button main_bt_recognize;
    private TextView main_tv_result;
    private ImageView main_iv_image;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置布局文件
        setContentView(R.layout.activity_main);
        // 检查并请求应用所需权限
        checkPermission();
        // 获取控件对象
        initView();
        // 设置控件的监听器
        setListener();
    }

    private void setListener() {
        // 设置识别按钮的监听器
        main_bt_recognize.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 识别之前需要再次检查一遍权限
                checkPermission();
                // 点击后的主程序
                mainProgram();
            }
        });
    }

    // 获得界面需要交互的控件
    private void initView() {
        main_bt_recognize = findViewById(R.id.main_bt_recognize);
        main_tv_result = findViewById(R.id.main_tv_result);
        main_iv_image = findViewById(R.id.main_iv_image);
    }

    // 检查应用所需的权限,如不满足则发出权限请求
    private void checkPermission() {
        if (ContextCompat.checkSelfPermission(getApplicationContext(),
                Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 120);
        }
        if (ContextCompat.checkSelfPermission(getApplicationContext(),
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 121);
        }
    }

    // OCR识别的主程序
    private void mainProgram() {
        // 从assets中获取一张Bitmap图片
        Bitmap bitmap = getBitmapFromAssets(MainActivity.this, TARGET_FILENAME);
        // 同时显示在界面
        main_iv_image.setImageBitmap(bitmap);
        if (bitmap != null) {
            // 准备工作:创建路径和Tesserect的数据
            prepareTess();
            // 初始化Tesserect
            TessBaseAPI tessBaseAPI = new TessBaseAPI();
            String dataPath = getExternalFilesDir("/").getPath() + "/";
            tessBaseAPI.init(dataPath, "eng");
            // 识别并显示结果
            String result = getOCRResult(tessBaseAPI, bitmap);
            main_tv_result.setText(result);
        }
    }

    // 从assets中读取一张Bitmap类型的图片
    private Bitmap getBitmapFromAssets(Context context, String filename) {
        Bitmap bitmap = null;
        AssetManager assetManager = context.getAssets();
        try {
            InputStream is = assetManager.open(filename);
            bitmap = BitmapFactory.decodeStream(is);
            is.close();
            Log.i(TAG, "图片读取成功。");
            Toast.makeText(getApplicationContext(), "图片读取成功。", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            Log.i(TAG, "图片读取失败。");
            Toast.makeText(getApplicationContext(), "图片读取失败。", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
        return bitmap;
    }

    // 为Tesserect复制(从assets中复制过去)所需的数据
    private void prepareTess() {
        try{
            // 先创建必须的目录
            File dir = getExternalFilesDir(TESS_DATA);
            if(!dir.exists()){
                if (!dir.mkdir()) {
                    Toast.makeText(getApplicationContext(), "目录" + dir.getPath() + "没有创建成功", Toast.LENGTH_SHORT).show();
                }
            }
            // 从assets中复制必须的数据
            String pathToDataFile = dir + "/" + DATA_FILENAME;
            if (!(new File(pathToDataFile)).exists()) {
                InputStream in = getAssets().open(DATA_FILENAME);
                OutputStream out = new FileOutputStream(pathToDataFile);
                byte[] buff = new byte[1024];
                int len;
                while ((len = in.read(buff)) > 0) {
                    out.write(buff, 0, len);
                }
                in.close();
                out.close();
            }
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
    }

    // 进行OCR并返回识别结果
    private String getOCRResult(TessBaseAPI tessBaseAPI, Bitmap bitmap) {
        tessBaseAPI.setImage(bitmap);
        String result = "-";
        try{
            result = tessBaseAPI.getUTF8Text();
        }catch (Exception e){
            Log.e(TAG, e.getMessage());
        }
        tessBaseAPI.end();
        return result;
    }
}

        layout_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >

            <ImageView
                android:id="@+id/main_iv_image"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginLeft="5dp"
                android:layout_marginRight="5dp"/>

            <Button
                android:id="@+id/main_bt_recognize"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_marginRight="5dp"
                android:layout_gravity="center_horizontal"
                android:text="读取一张图片并识别" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_marginRight="5dp"
                android:layout_gravity="center_horizontal"
                android:text="识别结果:" />

            <TextView
                android:id="@+id/main_tv_result"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_marginRight="5dp"
                android:layout_gravity="center_horizontal" />
        </LinearLayout>
    </ScrollView>
</LinearLayout>

        AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cs.ocrdemo4csdn">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.OCRDemo4CSDN">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

        build.gradle(Module)

plugins {
    id 'com.android.application'
}

android {
    compileSdk 34

    defaultConfig {
        applicationId "com.cs.ocrdemo4csdn"
        minSdk 21
        targetSdk 34
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    implementation 'com.rmtheis:tess-two:9.1.0'
}

        vin_demo.png

随便从网上下的图片(保证存在光学字符即可)。

        eng.traineddata

从https://github.com/tesseract-ocr/下载的,如果不行从https://github.com/raykibul/Android-OCR-Testing/tree/main下载。

三、参考资料

        1、光学字符识别

        2、GitHub - tesseract-ocr/tesseract

        3、GitHub - raykibul/Android-OCR-Testing

四、总结语

        1、跟着别人的CSDN博客捣鼓了一天多,但是没能调通,一直在报错,比如遇到“Could not initialize Tesseract API with language=eng”、“getUTF8Text导致android tesseract崩溃”等等问题。后边看了GitHub上边比较新的代码(诸位如果代码参考了我的博客还是没能调通,建议看看这份代码),然后就跑通了。目前还不知道原因是啥。

        2、我看Tesseract这个库的识别结果并不是十分准确,尤其是对于拍照出来的结果识别率很低,再从这个库的发展历史来看,好像现在都没有什么人再维护它了(最后的维护者是谷歌,而且停留在2018年),所以,对于现在(今年是2023年)而言,我感觉它已经有点属于是过时技术。大家可以寻求一些比较新的技术方案,毕竟现在大模型都搞得这么牛了,OCR这种应该搞得更好才是。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/802091.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YAML+PyYAML笔记 6 | PyYAML源码之yaml.scan(),yaml.parse(),yaml.compose()

6 | PyYAML源码之yaml.scan&#xff0c;yaml.parse, yaml.compose 0 yaml文档1 yaml.scan()2 yaml.parse()3 yaml.compose() 0 yaml文档 以下示例来源于网络&#xff0c;便于后续学习用, 文档为config_yaml.yaml。 {name: John Doe,age: 28,hobbies: [hiking, cooking, fishi…

生信分析案例 Python简明教程 | 视频15

开源生信 Python教程 生信专用简明 Python 文字和视频教程 源码在&#xff1a;https://github.com/Tong-Chen/Bioinfo_course_python 目录 背景介绍 编程开篇为什么学习Python如何安装Python如何运行Python命令和脚本使用什么编辑器写Python脚本Python程序事例Python基本语法 数…

怎样修改LED显示屏的显示内容

每个LED显示屏通常都附带配套的控制卡和相应的管理软件。安装完LED显示屏后&#xff0c;只需连接控制卡和电源&#xff0c;并通过运行管理软件&#xff0c;便能轻松地更换LED显示屏的显示内容。 更改LED显示屏内容非常便捷&#xff0c;我们可以使用电脑、手机或者U盘等设备进行…

html实现蜂窝菜单

效果图 CSS样式 keyframes _fade-in_mkmxd_1 {0% {filter: blur(20px);opacity: 0}to {filter: none;opacity: 1} } keyframes _drop-in_mkmxd_1 {0% {transform: var(--transform) translateY(-100px) translateZ(400px)}to {transform: var(--transform)} } ._examples_mkmx…

layui各种事件无效(例如表格重载或 分页插件按钮失效)的解决方法

下图是我一个系统的操作日志&#xff0c;在分页插件右下角嵌入了一个导出所有数据的按钮 &#xff0c;代码没有任何问题&#xff0c;点击导出按钮却失效 排查之后&#xff0c;发现表格标签table定义了ID又定义了lay-filter&#xff0c;因我使用的layui从2.7.6升级到2.8.11&…

JMeter接口测试:BeanShell实现接口的加密和解密

前些天用JMeter写了一个接口的自动化脚本&#xff0c;请求参数加密和响应数据解密都覆盖到了&#xff0c;中间涉及了BeanShell脚本编写和导入jar包的一些方法&#xff0c;想着挺有代表性的&#xff0c;分享给大家&#xff0c;希望对大家的接口自动化测试有所启发。 这是一个注册…

简化Java单元测试数据

用EasyModeling简化Java单元测试 EasyModeling 是我在2021年圣诞假期期间开发的一个 Java 注解处理器&#xff0c;采用 Apache-2.0 开源协议。它可以帮助 Java 单元测试的编写者快速构造用于测试的数据模型实例&#xff0c;简化 Java 项目在单元测试中准备测试数据的工作&…

半路杀出个“程咬金”,谁在吹响智能化供应链重构号角

汽车智能化的竞争&#xff0c;不再是平行模式&#xff08;车企与车企、Tier1与Tier1&#xff09;&#xff0c;也不再是一边倒的车企自研模式&#xff0c;更不是纯粹的B2C模式。 随着昨天大众集团对外官宣与小鹏、上汽的深度合作启动&#xff0c;围绕电动化、智能化的竞争无疑进…

PHP注册、登陆、6套主页-带Thinkphp目录解析-【白嫖项目】

强撸项目系列总目录在000集 PHP要怎么学–【思维导图知识范围】 文章目录 本系列校训本项目使用技术 上效果图主页注册&#xff0c;登陆 phpStudy 设置导数据库项目目录如图&#xff1a;代码部分&#xff1a;控制器前台的首页 其它配套页面展示直接给第二套方案的页面吧第三套…

Talk | 南洋理工大学博士后研究员李祥泰:基于Transformer的视觉分割模型总结、回顾与展望

​ 本期为TechBeat人工智能社区第517期线上Talk&#xff01; 北京时间7月27日(周四)20:00&#xff0c;南洋理工大学博士后研究员—李祥泰的Talk已经准时在TechBeat人工智能社区开播了&#xff01; 他与大家分享的主题是: “基于Transformer的视觉分割模型总结、回顾与展望”&am…

无涯教程-jQuery - animate()方法函数

animate()方法执行一组CSS属性的自定义动画。 animate( params, [duration, easing, callback] ) - 语法 selector.animate( params, [duration, easing, callback] ); 这是此方法使用的所有参数的说明 params - 动画将朝其移动的CSS属性图。duration - 这是可选…

idea常用技巧/idea常见问题

idea常见问题 idea全局搜索默认只显示100条解决方案 如上图&#xff0c;每次搜索时只显示100条&#xff0c;没法展示全。因版本的不同&#xff0c;配置也有些差异&#xff0c;以下也是经过各种搜索整理出了两个方案来解决这个问题。 方案一&#xff1a; 快捷键Ctrl shift a…

windows环境启动redis-server.exe出现闪退问题解决方案(亲测有效)

现象 windows环境下&#xff0c;启动redis-server.exe&#xff0c;出现闪退现象 解决方案 在你的redis解压目录下&#xff0c;新建一个start.bat文件 在start.bat文件里面写上这一句话&#xff1a; redis-server.exe redis.windows.conf然后保存&#xff0c;后面启动redis…

网络安全/信息安全—学习笔记

一、网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

【暑期每日一练】 day7

目录 选择题 &#xff08;1&#xff09; 解析&#xff1a; &#xff08;2&#xff09; 解析&#xff1a; &#xff08;3&#xff09; 解析&#xff1a; &#xff08;4&#xff09; 解析&#xff1a; &#xff08;5&#xff09; 解析&#xff1a; 编程题 题一…

​LeetCode解法汇总2500. 删除每行中的最大值

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣 描述&#xff1a; 给你一个 m x n 大小的矩阵 grid &#xff0c;由若干正整数组成。 执行下述操作…

方差分析学习

有时候能看的下去&#xff0c;有时候看书看不下去。 为了加深理解和记忆&#xff0c;先把基础的内容记忆一下。 组内方差&#xff08;误差平方和&#xff09;和组间方差&#xff08;效应平方和&#xff09;的定义&#xff1a; 注意&#xff1a;图中红线部分是一个整体。 误差…

基于java SpringBoot和HTML的博客系统

随着网络技术渗透到社会生活的各个方面&#xff0c;传统的交流方式也面临着变化。互联网是一个非常重要的方向。基于Web技术的网络考试系统可以在全球范围内使用互联网&#xff0c;可以在本地或异地进行通信&#xff0c;大大提高了通信和交换的灵活性。在当今高速发展的互联网时…

Quartz实战:基于Quartz实现定时任务的动态调度,实现定时任务的增删改查

文章目录 一、Quartz基础二、使用Quartz实现定时任务的动态调度1、使用Quartz-jobStore 持久化2、前端页面实现效果图3、自定义job表4、增删改查Controller5、Quartz工具类6、测试任务类7、springboot启动初始化定时任务8、自定义JobFactory&#xff0c;使Task注册为Bean9、省略…

PoseiSwap 即将开启质押,利好刺激下 POSE通证短时涨超 30%

随着Nautilus Chain主网的上线&#xff0c;预示着Web3世界迎来全新的模块化、Layer3时代&#xff0c;为Web3世界与Web2世界的深入融合构建基础。而PoseiSwap作为Nautilus Chain上的首个DEX&#xff0c;也成为了加密行业首个以模块化为基础构建的DEX。 基于Nautilus Chain&#…