除了PaddleOCR之外,之前还介绍过一些其它好玩的开源项目,例如老照片修复Bringing-Old-Photos-Back-to-Life
、黑白照片上色DeOldify
。因此,最近准备启动一个项目,做一个在线网站,将之前一些好玩的功能都陆续集成在这个网站中
本篇文章将介绍网站第一个功能模块:图片OCR识别,识别功能借助于PaddleOCR,后端使用Django框架,前端主要借助Element-PLus+Vue实现,这个模块虽然没有用到数据库存储功能,但由于是Django框架需要借助MySQL实现项目初始化。
OCR识别整体流程:网站提供一个图片上传入口,用户将识别后的图片上传后,网站后台在1~2秒后返回图片的识别结果,效果如下:
页面整体布局
文字识别中页面:
OCR识别完成:
一,Django项目初始化
- 1.1,检查Django环境
python -m django --version
若终端显示版本号,未出现Nomodulenameddjango
,即可证明已安装成功~,Django安装方法参考以下连接
https://docs.djangoproject.com/zh-hans/4.0/
- 1.2创建Django项目
在Django框架中,项目中每个模块叫app,每个app负责一个模块功能,例如博客中的评论模块;所有app组合在一起形成整个项目project
,因此首先,创建一个项目 dlIntegrated(命名随意)
django-admin startproject dlIntegrated
- 创建后目录树形结构如下
dlIntegrated │ manage.py │ └─dlIntegrated asgi.py settings.py urls.py wsgi.py __init__.py
cd到manage.py
同级的目录下,终端运行
python manage.py runserver
终端无报错,浏览器输入http:\localhost:8080
,进入django初始化页面,即可代表django启动成功
- 1.3,创建app
cd到与manage.py同级的目录下,输入以下命令:
python manage.py startapp paddleApp
运行成功后,目录树结构如下:
│ manage.py │ ├─dlIntegrated │ │ asgi.py │ │ settings.py │ │ urls.py │ │ wsgi.py │ │ __init__.py │ │ │ └─__pycache__ │ settings.cpython-37.pyc │ __init__.cpython-37.pyc │ └─paddleApp │ admin.py │ apps.py │ models.py │ tests.py │ views.py │ __init__.py │ └─migrations __init__.py
- 1.4,配置
dlIntegrated/setting.py
配置项,连接数据库,连接App等
找到DATABASES
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'USER':'root', 'HOST':'localhost', 'NAME':'dl_intergate', 'PASSWORD':'root', 'TIME_ZONE':'Asia/Shanghai' } }
找到INSTALLED_APPS
,加入app选项paddleApp.apps.PaddleappConfig
(根据自己项目调整)
INSTALLED_APPS = [ 'paddleApp.apps.PaddleappConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
二,安装PaddleOCR
之前介绍PaddleOCR时,需要经过Github代码仓库拉项目、配环境、写demo一系列步骤,然后才能正常使用;PaddleOCR项目源于百度,经过多次迭代后目前已经被封装为一个Python库,安装时借助pip工具即可
- 2.1,pip安装PaddleOCR:
# GPU版本 python3 -m pip install paddlepaddle-gpu # CPU版本 python3 -m pip install paddlepaddle
需要注意的是,如果项目用GPU版本时,需要保证GPU环境配置好,例如CUDA、Cudnn等
- 2.2,安装PaddleOCRWhl包
pip install <span data-raw-text="" "="" data-textnode-index-1652755033716="149" data-index-1652755033716="2372" class="character">"paddleocr>=2.0.1<span data-raw-text="" "="" data-textnode-index-1652755033716="149" data-index-1652755033716="2389" class="character">" # Recommend to use version 2.0.1+
安装paddleocr时可能会报错,这里我记录了我配置时遇到的一些错误(Windows环境),有遇到相同错误的可以参考下:https://blog.csdn.net/weixin_42512684/article/details/124783499?spm=1001.2014.3001.5501
三,Django完善视图,url模块
- 3.1,views视图中实现识别核心模块:
def ocr_detect(filePath:str): ''' orc识别 ''' print(<span data-raw-text="" "="" data-textnode-index-1652755033716="165" data-index-1652755033716="2672" class="character">"filePath is <span data-raw-text="" "="" data-textnode-index-1652755033716="165" data-index-1652755033716="2685" class="character">",filePath) ocr = PaddleOCR(use_angle_cls=True, lang='ch') # need to run only once to download and load model into memory result = ocr.ocr(filePath, cls=True) result_list = result (path,fileName) = os.path.split(filePath) image = Image.open(filePath).convert('RGB') boxes = [line[0] for line in result] txts = [line[1][0] for line in result] scores = [line[1][1] for line in result] im_show = draw_ocr(image, boxes, txts, scores, font_path='./template/fonts/Deng.ttf') im_show = Image.fromarray(im_show) im_show.save(os.path.join(path,<span data-raw-text="" "="" data-textnode-index-1652755033716="211" data-index-1652755033716="3254" class="character">"draw-<span data-raw-text="" "="" data-textnode-index-1652755033716="211" data-index-1652755033716="3260" class="character">"+fileName))
- 3.2,在app中的urls.py中定义url接口
# ProjectName/App/urls.py from django.urls import path from . import views urlpatterns = [ path('upload-image',views.paddle_image_upload) ]
- 3.3将app中API接口与项目链接
# ProjectName/urls.py urlpatterns = [ path('admin/', admin.site.urls), path('paddleApp/',include('paddleApp.urls')) ]
四,借助Vue对前端项目初始化
- 4.1,这里借助的是Vue脚手架,为了提交开发效率借助Element-Plus组件库,项目的结构树如下:
│ .gitignore │ babel.config.js │ jsconfig.json │ list.txt │ package-lock.json │ package.json │ README.md │ tsconfig.json │ vue.config.js ├─public │ favicon.ico │ index.html │ └─src │ App.vue │ main.ts │ router.ts │ shims-vue.d.ts │ ├─assets │ logo.png │ ├─components │ HelloWorld.vue │ └─page └─paddle textOcrPage.vue
- 4.2,router.ts定义前端路由
import {createRouter,createWebHashHistory} from 'vue-router' const routes = [ { path: '/paddle-ocr', component:()=> import('@/page/paddle/textOcrPage.vue') } ] const router = createRouter({ history:createWebHashHistory(), routes }) export default router;
- 4.3,在配置文件
vue.conf.js
中定义方向代理,解决跨域问题
module.exports = defineConfig({ transpileDependencies: true, devServer: { proxy:{ '/api':{ target: 'http://localhost:8089', pathRewrite: { '^/api':'', changeOrigin: true } } }, port: 8083 }, // chainWebpack: config => { // // 处理ts文件 (新增loader) // config.module.rule('ts').use('te-loader').end() // } })
- 4.4,在vue脚本实现页面布局,上传组件实现,上传逻辑编写(以下为部分代码)
<div v-loading=<span data-raw-text="" "="" data-textnode-index-1652755033716="356" data-index-1652755033716="4821" class="character">"loading<span data-raw-text="" "="" data-textnode-index-1652755033716="356" data-index-1652755033716="4829" class="character">" :element-loading-text=<span data-raw-text="" "="" data-textnode-index-1652755033716="358" data-index-1652755033716="4853" class="character">"loadingText<span data-raw-text="" "="" data-textnode-index-1652755033716="358" data-index-1652755033716="4865" class="character">" element-loading-background=<span data-raw-text="" "="" data-textnode-index-1652755033716="360" data-index-1652755033716="4894" class="character">"rgba(122, 122, 122, 0.8)<span data-raw-text="" "="" data-textnode-index-1652755033716="360" data-index-1652755033716="4919" class="character">"> <div style=<span data-raw-text="" "="" data-textnode-index-1652755033716="368" data-index-1652755033716="4941" class="character">"border:1px solid black<span data-raw-text="" "="" data-textnode-index-1652755033716="368" data-index-1652755033716="4964" class="character">"> <h1> 文本 OCR 在线识别接口 </h1> </div> <div style=<span data-raw-text="" "="" data-textnode-index-1652755033716="389" data-index-1652755033716="5061" class="character">"margin-top:100px<span data-raw-text="" "="" data-textnode-index-1652755033716="389" data-index-1652755033716="5078" class="character">"> <el-upload class=<span data-raw-text="" "="" data-textnode-index-1652755033716="397" data-index-1652755033716="5124" class="character">"avatar-uploader<span data-raw-text="" "="" data-textnode-index-1652755033716="397" data-index-1652755033716="5140" class="character">" action=<span data-raw-text="" "="" data-textnode-index-1652755033716="401" data-index-1652755033716="5164" class="character">"/api/paddleApp/upload-image<span data-raw-text="" "="" data-textnode-index-1652755033716="401" data-index-1652755033716="5192" class="character">" :show-file-list=<span data-raw-text="" "="" data-textnode-index-1652755033716="405" data-index-1652755033716="5225" class="character">"false<span data-raw-text="" "="" data-textnode-index-1652755033716="405" data-index-1652755033716="5231" class="character">" :on-success=<span data-raw-text="" "="" data-textnode-index-1652755033716="409" data-index-1652755033716="5260" class="character">"handleAvatarSuccess<span data-raw-text="" "="" data-textnode-index-1652755033716="409" data-index-1652755033716="5280" class="character">" :before-upload=<span data-raw-text="" "="" data-textnode-index-1652755033716="413" data-index-1652755033716="5312" class="character">"beforeAvatarUpload<span data-raw-text="" "="" data-textnode-index-1652755033716="413" data-index-1652755033716="5331" class="character">"> <img v-if=<span data-raw-text="" "="" data-textnode-index-1652755033716="421" data-index-1652755033716="5355" class="character">"imageUrl<span data-raw-text="" "="" data-textnode-index-1652755033716="421" data-index-1652755033716="5364" class="character">" :src=<span data-raw-text="" "="" data-textnode-index-1652755033716="425" data-index-1652755033716="5371" class="character">"imageUrl<span data-raw-text="" "="" data-textnode-index-1652755033716="425" data-index-1652755033716="5380" class="character">" class=<span data-raw-text="" "="" data-textnode-index-1652755033716="429" data-index-1652755033716="5388" class="character">"avatar<span data-raw-text="" "="" data-textnode-index-1652755033716="429" data-index-1652755033716="5395" class="character">" /> <el-icon v-else class=<span data-raw-text="" "="" data-textnode-index-1652755033716="439" data-index-1652755033716="5433" class="character">"avatar-uploader-icon<span data-raw-text="" "="" data-textnode-index-1652755033716="439" data-index-1652755033716="5454" class="character">"><Plus></Plus></el-icon> </el-upload> <el-card shadow=<span data-raw-text="" "="" data-textnode-index-1652755033716="460" data-index-1652755033716="5531" class="character">"never<span data-raw-text="" "="" data-textnode-index-1652755033716="460" data-index-1652755033716="5537" class="character">"> <template #header> <div class=<span data-raw-text="" "="" data-textnode-index-1652755033716="474" data-index-1652755033716="5605" class="character">"card-header<span data-raw-text="" "="" data-textnode-index-1652755033716="474" data-index-1652755033716="5617" class="character">"> <span>OCR 识别结果</span> </div> </template> <div style=<span data-raw-text="" "="" data-textnode-index-1652755033716="498" data-index-1652755033716="5746" class="character">"text-align: center;font-size: 20px;color:royalblue<span data-raw-text="" "="" data-textnode-index-1652755033716="498" data-index-1652755033716="5797" class="character">" v-for=<span data-raw-text="" "="" data-textnode-index-1652755033716="502" data-index-1652755033716="5805" class="character">"o in resultText<span data-raw-text="" "="" data-textnode-index-1652755033716="502" data-index-1652755033716="5821" class="character">" :key=<span data-raw-text="" "="" data-textnode-index-1652755033716="506" data-index-1652755033716="5828" class="character">"o<span data-raw-text="" "="" data-textnode-index-1652755033716="506" data-index-1652755033716="5830" class="character">" class=<span data-raw-text="" "="" data-textnode-index-1652755033716="510" data-index-1652755033716="5838" class="character">"text item<span data-raw-text="" "="" data-textnode-index-1652755033716="510" data-index-1652755033716="5848" class="character">">{{o[1][0] ?o[1][0]:'' }}</div> </el-card> </div> </div> </template> <script lang=<span data-raw-text="" "="" data-textnode-index-1652755033716="533" data-index-1652755033716="5948" class="character">"ts<span data-raw-text="" "="" data-textnode-index-1652755033716="533" data-index-1652755033716="5951" class="character">" setup> import { ref } from 'vue' import { ElMessage } from 'element-plus' import { Plus } from '@element-plus/icons-vue' import type { UploadProps } from 'element-plus' const imageUrl = ref('') const resultText = ref('') const loading = ref(false) const loadingText = ref('识别中,请耐心等待') const handleAvatarSuccess: UploadProps['onSuccess'] = ( response, uploadFile ) => { const resultData = response.data resultText.value = resultData.result imageUrl.value = URL.createObjectURL(uploadFile.raw!) loading.value =false } const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => { if (!(rawFile.type === 'image/jpeg' || rawFile.type === 'image/png')) { ElMessage.error('Avatar picture must be JPG format!') return false } else if (rawFile.size / 1024 / 1024 > 2) { ElMessage.error('Avatar picture size can not exceed 2MB!') return false } loading.value = true return true } </script>
五,小结
好了,以上就是本篇文章的全部内容了,本文涉及的内容知识较多,适合收藏下来慢慢:😛,由于Python语言也有自身的局限性,因此在项目中引入Vue、JS等作为前端界面实现,搭建一个前后端分离网站
这里在项目中引入了OCR图片识别功能,后面将会计划把一些其它功能加进来,例如Excel上传,读写;图像分割,经纬度转换等,带大家一起来完善这个项目