本文最后更新于 2025-03-29,文章内容可能已经过时。

创建PaddleRSC文件夹

Vue环境搭建

概览

1. 创建项目文件夹并初始化Vue项目

- 操作:创建一个项目文件夹(如 PaddleRSC),并在其中使用 npm create vite 命令初始化一个Vue项目(命名为 RSfront)。

- 作用

- 创建项目的基础目录结构。

- 初始化一个Vue项目,设置项目的基本配置和依赖。

2. 选择项目架构和脚本语言

- 操作:在初始化过程中,选择Vue作为项目架构,TypeScript作为脚本语言。

- 作用

- Vue是一个渐进式JavaScript框架,适合构建用户界面和单页应用(SPA)。

- TypeScript是JavaScript的超集,增加了类型系统和面向对象的特性,有助于提高代码的可维护性和开发效率。

3. 安装项目依赖

- 操作:在项目目录下运行 npm install 命令。

- 作用:安装项目所需的依赖包,确保项目能够正常运行。

4. 启动开发服务器

- 操作:运行 npm run dev 命令。

- 作用:启动Vite开发服务器,提供热模块替换(HMR)功能,方便开发时实时预览代码更改。

5. 安装Vue Router并配置路由

- 操作:安装 vue-router,并在项目中配置路由。

- 作用

- vue-router 是Vue.js的官方路由管理器,用于构建单页应用(SPA)。

- 配置路由可以实现页面之间的导航,支持嵌套路由、动态路由等功能。

6. 创建视图和组件

- 操作:在项目中创建 views 文件夹,分别创建不同功能模块的视图和组件。

- 作用

- 组织项目结构,将不同的功能模块分别放在不同的文件夹中。

- 创建视图和组件,便于复用和管理。

7. 配置Pinia状态管理库

- 操作:安装 pinia,并在项目中配置状态管理。

- 作用

- Pinia是Vue.js的专属状态管理库,允许跨组件或页面共享状态。

- 提供更简洁的API和TypeScript支持,是Vuex的轻量级替代品。

8. 安装并配置axios

- 操作:安装 axios,并在项目中配置HTTP请求。

- 作用

- axios 是一个基于Promise的HTTP客户端,用于发送HTTP请求。

- 支持请求和响应拦截、超时设置等功能,方便与后端接口交互。

9. 安装并配置Element Plus

- 操作:安装 element-plus,并在项目中引入。

- 作用

- Element Plus是一个基于Vue.js 3.0的UI库,提供了一套漂亮、易于使用和自定义的组件。

- 支持响应式设计,所有组件都可以自适应不同屏幕大小。

10. 安装并配置Sass预处理器

- 操作:安装 sasssass-loader,并在项目中使用。

- 作用

- Sass是一款强化CSS的辅助工具,增加了变量、嵌套、混合等功能。

- 使用Sass可以更好地组织管理样式文件,提高开发效率。

11. 安装并配置Remix Icon

- 操作:安装 remixicon,并在项目中引入。

- 作用

- Remix Icon是一个开源的SVG图标库,提供高质量、可定制的图标。

- 所有图标均可免费用于个人和商业用途。

12. 配置路径别名

- 操作:在 vite.config.tstsconfig.json 中配置路径别名。

- 作用

- 使用路径别名(如 @/ 代表 src/)可以简化文件路径的书写,提高开发效率。

- 便于管理和维护项目中的文件路径。

通过以上步骤,完成了一个Vue项目的环境搭建,为后续的开发工作奠定了基础。

进入PaddleRSC文件夹创建Vue前端

node.js安装略过

cd PaddleRSC
npm create vite RSfront

选择项目架构为Vue

选择TypeScript作为脚本语言

此时我们已经创建了RSfront

目录结构

RSfront/
│
├── .vscode/                      # VS Code编辑器配置
│
├── public/                       # 公共静态资源目录
│
├── src/                          # 源代码目录
│   ├── assets/                   # 静态资源(图片、字体等)
│   │   └── vue.svg              # Vue logo
│   │
│   ├── components/               # Vue组件
│   │   └── HelloWorld.vue       # 示例组件
│   │
│   ├── App.vue                   # 根组件
│   ├── main.ts                   # 应用入口文件
│   ├── style.css                 # 全局样式
│   └── vite-env.d.ts            # Vite类型声明
│
├── .gitignore                    # Git忽略配置
├── index.html                    # HTML入口文件
├── package.json                  # 项目配置和依赖
├── README.md                     # 项目说明文档
├── tsconfig.app.json             # 应用TS配置
├── tsconfig.json                 # 基础TS配置
├── tsconfig.node.json            # Node环境TS配置
└── vite.config.ts                # Vite构建工具配置

启动示例项目

cd RSfront
npm install
npm run dev

随后在浏览器输入http://localhost:5173/ 即可打开页面(或者Ctrl+左键点击蓝色链接)

安装vue组件

在开发中我们有一下常见的组件需要安装

注意,安装组件时都需要在RSfront文件夹下!因为组件会安装到node_modules中!如果不在RSfront 将无法识别。

Vue Router 路由管理

Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。功能包括:

  • 嵌套路由映射

  • 动态路由选择

  • 模块化、基于组件的路由配置

  • 路由参数、查询、通配符

  • 展示由 Vue.js 的过渡系统提供的过渡效果

  • 细致的导航控制

  • 自动激活 CSS 类的链接

  • HTML5 history 模式或 hash 模式

  • 可定制的滚动行为

  • URL 的正确编码

在终端执行命令:

npm install vue-router

先在项目的src文件下创建views文件夹,新建四个文件夹RSChangeDetectRSLandClassificRSObjectDetectRSRoadExtract,后面可以在这文件夹下分别存放遥感目标检测、变化检测、道路提取、地物分类的项目内容,再在每个遥感子文件夹下创建测试页面HomePage/index.vueDetect/index.vue

整个的目录结构如下所示,当然可以根据自己实际项目进行增删,这里设计的HomePage下可以放置相关模型介绍页面,Detect下可以放置相关模型的实现效果。

在每个文件夹下的HomePage 添加index.vue

<template>
  <div class="mainbody">
    <h1 class="headtitle">xxxx首页</h1>
  </div>
</template>
<script setup lang="ts">

</script>

<style scoped>
  .mainbody {
    height: 6em;
    padding: 1.5em;
    will-change: filter;
    transition: filter 300ms;
  }
  .headtitle {
    filter: drop-shadow(0 0 2em #646cffaa);
  }
</style>

在每个文件夹下的Detect添加index.vue

<template>
  <div class="mainbody">
    <h1 class="headtitle">xxxx功能页面</h1>
  </div>
</template>
<script setup lang="ts">

</script>

<style scoped>
  .mainbody {
    height: 6em;
    padding: 1.5em;
    will-change: filter;
    transition: filter 300ms;
  }
  .headtitle {
    filter: drop-shadow(0 0 2em #646cffaa);
  }
</style>

完成后目录结构如下:

views/
│
├── main.vue                          # 主视图文件
│
├── RSChangeDetect/                   # 变化检测模块
│   ├── HomePage/                     # 首页组件
│   │   └── index.vue                 # 首页视图
│   │
│   └── Detect/                       # 检测组件
│       └── index.vue                 # 检测视图
│
├── RSLandClassific/                  # 土地分类模块
│   ├── HomePage/                     # 首页组件
│   │   └── index.vue                 # 首页视图
│   │
│   └── Detect/                       # 检测组件
│       └── index.vue                 # 检测视图
│
├── RSObjectDetect/                   # 目标检测模块
│   ├── HomePage/                     # 首页组件
│   │   └── index.vue                 # 首页视图
│   │
│   └── Detect/                       # 检测组件
│       └── index.vue                 # 检测视图
│
└── RSRodeExtract/                    # 道路提取模块
    ├── HomePage/                     # 首页组件
    │   └── index.vue                 # 首页视图
    │
    └── Detect/                       # 检测组件
        └── index.vue                 # 检测视图

在项目的src文件下创建router/index.ts文件:

import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'

// createRouter 创建路由实例,===> new VueRouter()
// history 是路由模式,hash模式,history模式
// createWebHistory() 是开启history模块 
// createWebHashHistory() 是开启hash模式   

const routes = [
  {
    path: '/',
    name: 'HomePage',
    component: () => import('../views/main.vue')
  },
  {
    path: '/changedetectHome',
    name: 'changedetectHome',
    component: () => import('../views/RSChangeDetect/HomePage/index.vue')
  },
  {
    path: '/changedetectMain',
    name: 'changedetectMain',
    component: () => import('../views/RSChangeDetect/Detect/index.vue')
  },
  {
    path: '/landclassHome',
    name: 'landclassHome',
    component: () => import('../views/RSLandClassific/HomePage/index.vue')
  },
  {
    path: '/landclassMain',
    name: 'landclassMain',
    component: () => import('../views/RSLandClassific/Detect/index.vue')
  },
  {
    path: '/objectdetectHome',
    name: 'objectdetectHome',
    component: () => import('../views/RSObjectDetect/HomePage/index.vue')
  },
  {
    path: '/objectdetectMain',
    name: 'objectdetectMain',
    component: () => import('../views/RSObjectDetect/Detect/index.vue')
  },
  {
    path: '/roadextractHome',
    name: 'roadextractHome',
    component: () => import('../views/RSRoadExtract/HomePage/index.vue')
  },
  {
    path: '/roadextractMain',
    name: 'roadextractMain',
    component: () => import('../views/RSRoadExtract/Detect/index.vue')
  }
] as RouteRecordRaw[]
const router = createRouter({
  history: createWebHistory(),
  routes: routes
})

export default router

main.ts中引入router

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')

初始化页面的样式,打开style.css

修改#app的样式如下:

#app {
  margin: 0;
  padding: 0;
  position: relative;
  width: 100vw;
  height: 100vh;
  display: grid;
  grid-template-rows: 8% 92%;
}

修改App.vue,代码如下:

<template>
  <header class="navbar">
    <img src="./assets/vue.svg" alt="Logo" class="logo">
    <nav class="nav-center">
      <RouterLink to="/" class="nav-link" v-slot="{ isActive }">
        <span :class="{ 'active': isActive }">首页</span>
      </RouterLink>
      <RouterLink to="/changedetectHome" class="nav-link" v-slot="{ isActive }">
        <span :class="{ 'active': isActive }">变化检测</span>
      </RouterLink>
      <RouterLink to="/objectdetectHome" class="nav-link" v-slot="{ isActive }">
        <span :class="{ 'active': isActive }">目标检测</span>
      </RouterLink>
      <RouterLink to="/roadextractHome" class="nav-link" v-slot="{ isActive }">
        <span :class="{ 'active': isActive }">道路提取</span>
      </RouterLink>
      <RouterLink to="/landclassHome" class="nav-link" v-slot="{ isActive }">
        <span :class="{ 'active': isActive }">地物分类</span>
      </RouterLink>
    </nav>
  </header>
  <main class="main-content">
    <RouterView />
  </main>
</template>

<style scoped>
.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background-color: #f8f9fa;
  padding: 1rem;
  box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
}

.logo {
  height: 90%;
  padding: 0.5em;
  will-change: filter;
  transition: filter 300ms;
}

.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}

.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}

.nav-center {
  display: flex;
  justify-content: center;
  gap: 1rem; /* 添加间距 */
}

.nav-link {
  text-decoration: none;
  color: #343a40;
  transition: color 0.3s ease;
}

.active { /* 高亮当前激活的路由 */
  color: #007bff;
  font-weight: bold;
}

.nav-link:hover {
  color: #007bff;
}
</style>

创建一个空的类型声明文件shims-vue.d.ts来解决TypeScript类型错误

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

重新运行项目后,进行测试几个页面:

至此,路由配置完成。

Pinia 状态管理库

PiniaVue.js的专属状态管理库,允许跨组件或页面共享状态,提供了更简洁的API和TypeScript支持,是Vuex的轻量级替代品,Vuex对TS的语法支持不是完整的。

安装 Pinia,在终端执行如下安装指令:

npm install pinia

安装完成后修改main.ts如下,引入Pinia:

import { createApp } from 'vue'
import { createPinia } from 'pinia'

import './style.css'
import App from './App.vue'
import router from './router'

const pinia=createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)
app.mount('#app')

src目录下,新建一个store文件夹。在此文件夹下再创建一个index.ts文件。

index文件下主要进行三项操作:

  1. 定义状态容器(仓库)

  2. 修改容器(仓库)中的 state

  3. 仓库中的 action 的使用

Store里定义一个State,这里就编写"你好 Pinia!",代码如下:

import { defineStore} from 'pinia'

export const mainStore = defineStore('main',{
  state:()=>{
    return {
      helloPinia:'你好 Pinia!'
    }
  },
  getters:{},
  actions:{}
})

接下来就可以在vue3组件里读取Store数据了。

在之前准备好的views/main.vue文件中编写如下代码:

<template>
  <div class="mainbody">
    <h1 class="headtitle">遥感解译平台首页</h1>
    <h2 class="">{{ demoStore.helloPinia}}</h2>
  </div>
</template>
<script setup lang="ts">
  import { mainStore} from '../store/index'
  /* 引入storeToRefs */
  import { storeToRefs } from 'pinia'
  //storeToRefs只会关注sotre中数据,不会对方法进行ref包裹

  /* 得到countStore */
  const demoStore =mainStore()
</script>

<style scoped>
  .mainbody {
    height: 6em;
    padding: 1.5em;
    will-change: filter;
    transition: filter 300ms;
  }
  .headtitle {
    filter: drop-shadow(0 0 2em #646cffaa);
  }
</style>

重新运行后,页面执行效果如下:

至此,Pinia配置完成。

axios HTTP客户端

axios是一个基于PromiseHTTP客户端,用于浏览器和Node.js中发送HTTP请求。它支持各种HTTP请求方法,如GET、POST、PUT、DELETE等,并且可以拦截请求和响应,设置请求超时等功能。它可以与现代前端框架(如Vue.js、React、Angular等)进行集成。

先安装axios,在终端进行安装。

npm install axios

可以直接使用axios进行请求,在需要使用的页面引入axios模块:

并使用类似下面的代码直接请求后端:

const adduser=()=>{
    let formData = new FormData();
    formData.append("username","12345");
    formData.append("password","54321");
    let url = 'http://127.0.0.1:5000/adduser' //访问后端接口的url
    let method = 'post'
    axios({
      method,
      url,
      data: formData,
    }).then(res => {
      alert(res.data)
    });
}
adduser()

不过,axios.get().then() 这样的书写,会有缺陷,在以下缺点:

①、请求头能不能统一处理

②、不便于接口的统一管理

大家可以搜寻相关资料进行优化。

需要注意的是,需要解决跨域问题,修改vite.config.ts代理配置。

跨域是指在浏览器中,当前网页的协议、域名或端口与请求目标的协议、域名或端口不相同,从而导致浏览器出于安全考虑限制某些操作。具体来说,当一个请求的协议、域名、端口三者之间任意一个与当前页面的URL不同,即为跨域

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
  ],
  resolve: {
  },
  css: {
  },
  server: {
    proxy: {
      '/api': {
        // '/api'是代理标识,用于告诉node,url前面是/api的就是使用代理的
        ws: true,
        target: 'http://127.0.0.1:8000', //这里填入你要请求的接口的前缀
        changeOrigin: true,   //是否跨域
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

至此,网络请求配置完成。

element-plus UI

Element Plus是一个基于Vue.js 3.0的UI库,是Element UI的升级版。它提供了一套漂亮、易于使用和自定义的组件,如按钮、输入框、表格、弹窗、日期选择器等。Element Plus的设计理念注重用户体验和响应式设计,所有组件都可以自适应不同屏幕大小以提供良好的用户体验。

npm install element-plus

main.ts中引入,修改代码如下:

import { createApp } from 'vue'
import { createPinia } from 'pinia'

import './style.css'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

const pinia=createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)
app.use(ElementPlus)
app.mount('#app')

views/main.vue中进行测试,修改代码如下:

<template>
  <div class="mainbody">
    <h1 class="headtitle">遥感解译平台首页</h1>
    <div>
      <h2 class="">{{ demoStore.helloPinia}}</h2>
    </div>
    <el-button type="primary">你好,这里使用Elemet-Plus</el-button>
  </div>
</template>
<script setup lang="ts">
  import { mainStore} from '../store/index'
  /* 引入storeToRefs */
  import { storeToRefs } from 'pinia'
  //storeToRefs只会关注sotre中数据,不会对方法进行ref包裹

  /* 得到countStore */
  const demoStore =mainStore()
</script>

<style scoped>
.mainbody {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.headtitle {
  filter: drop-shadow(0 0 2em #646cffaa);
}
</style>

修改保存后查看页面,如下效果:

Sass预处理器

Sass是一款强化CSS的辅助工具,它在CSS语法的基础上增加了变量(variables)、嵌套(nested rules)、混合(mixins)、导入(inline imports)等高级功能,这些拓展令CSS更加强大与优雅。使用Sass以及Sass的样式库有助于更好地组织管理样式文件,以及更高效地开发项目。

在终端执行如下安装语句:

npm install -D sass sass-loader

-D 是一个选项,它的全称是 --save-dev。这个选项的作用是将安装的包添加到项目的 package.json 文件中的 devDependencies 字段中,而不是添加到 dependencies 字段中。

dependenciesdevDependencies 的区别

  1. dependencies

    • 用于指定项目运行时必须的依赖包。

    • 这些依赖包在生产环境中也是必需的。

    • 例如,express(一个Node.js的Web框架)或axios(一个HTTP客户端)通常会被添加到dependencies中。

  2. devDependencies

    • 用于指定开发过程中需要的依赖包,但这些包在生产环境中通常不需要。

    • 例如,sass(一个CSS预处理器)和sass-loader(一个Webpack插件,用于处理Sass文件)通常会被添加到devDependencies中,因为它们主要用于开发过程中的样式文件编译,而在生产环境中,编译后的CSS文件可以直接使用,不需要这些工具。

vue文件的style部分使用,lang=“scss”,表示使用的是sass样式,一般还在style标签添加scoped关键字,目的是避免样式污染,让标签内的样式只生效于当前的vue文件。
使用方式,引入标签,
views/main.vue中进行测试,修改代码如下:

<template>
  <div class="mainbody">
    <h1 class="headtitle">遥感解译平台首页</h1>
    <div>
      <h2 class="">{{ demoStore.helloPinia}}</h2>
    </div>
    <el-button type="primary">你好,这里使用Elemet-Plus</el-button>
  </div>
</template>
<script setup lang="ts">
  import { mainStore} from '../store/index'
  /* 引入storeToRefs */
  import { storeToRefs } from 'pinia'
  //storeToRefs只会关注sotre中数据,不会对方法进行ref包裹

  /* 得到countStore */
  const demoStore =mainStore()
</script>

<style scoped>
.mainbody {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.headtitle {
  filter: drop-shadow(0 0 2em #646cffaa);
}
</style>

<style lang="scss" scoped>
/* #在此处编写代码 */
.mainbody{
  background: #76cfe5;
}
</style>

打开页面进行测试,保存后不报错,且页面实现对应效果,代表安装完成。

Remix Icon

Remix Icon是一个开源的图标库,旨在提供一套统一、高质量、可定制的SVG图标,适用于Web和移动应用设计,所有图标均可免费用于个人和商业用途。

安装方式:在终端执行如下安装指令:

npm install remixicon

使用方式:在需要使用到图标的vue文件中进行引入:

import 'remixicon/fonts/remixicon.css'

路径别名

安装node.js的类型声明包

npm install --save-dev @types/node

vite.config.ts中加入如下代码:

alias: {
      '@': path.resolve(__dirname, './src')
  }

完整代码为:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  },
  css: {
  },
  server: {
    proxy: {
      '/api': {
        // '/api'是代理标识,用于告诉node,url前面是/api的就是使用代理的
        ws: true,
        target: 'http://127.0.0.1:8000', //这里填入你要请求的接口的前缀
        changeOrigin: true,   //是否跨域
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

编辑tsconfig.json,加入如下代码:

    "baseUrl": ".",
    "paths": {
       "@/*": ["src/*"]
    },

意味着@/代表src/,后面加入对应文件地址即可。tsconfig.json完整代码如下:

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    
    "baseUrl": ".",
    "paths": {
       "@/*": ["src/*"]
    },

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

修改tfconfig.node.json 添加"composite": true并确保没有禁用输出。

完整代码如下

{
  "compilerOptions": {
    "composite": true,
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
    "target": "ES2022",
    "lib": ["ES2023"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "emitDeclarationOnly": true,

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true
  },
  "include": ["vite.config.ts"]
}

其他一些相关库也可以上面类似方法进行引入,至此,基本的一些Vue项目配置完成了。

Conda

Conda 安装请自行完成

安装Conda后,创建虚拟环境

conda create -n PaddleRS python=3.10

进入虚拟环境,后续所有python环境都在此完成

conda activate PaddleRS

PaddlePaddle

飞桨PaddlePaddle-源于产业实践的开源深度学习平台

PaddlePaddle(飞桨)是由百度主导开发并开源的深度学习框架,于2016年9月正式开源。它是一个功能全面、易用、高效、灵活且可扩展的深度学习平台,支持大规模数据的多GPU和多机器并行训练。

主要特点

- 易用性:提供简洁的API,减少学习成本,适合初学者和开发者快速上手。

- 动态图和静态图支持:同时支持动态图(灵活性高)和静态图(性能优)两种编程范式。

- 高性能:优化计算资源利用,支持多卡并行训练和分布式训练。

- 丰富的模型库和工具:涵盖计算机视觉、自然语言处理、语音识别等多个领域的模型库,如PaddleDetection(目标检测)、PaddleSeg(图像分割)、PaddleNLP(自然语言处理)等。

- 模型优化和部署:提供模型压缩、量化等优化工具,支持多端部署(云端、移动端、边缘端)。

应用场景

PaddlePaddle广泛应用于多个领域,包括但不限于:

- 计算机视觉:图像分类、目标检测、图像分割、视频分析等。

- 自然语言处理:文本分类、情感分析、机器翻译、对话系统等。

- 语音技术:语音识别、语音合成等。

- 推荐系统:个性化推荐、内容推荐等。

- 工业和医疗:智能制造、智能农业、医学影像分析等。

PaddlePaddle还提供了完整的开发和部署工具链,如PaddleHub(预训练模型库)、PaddleSlim(模型压缩工具)等。这些特性使得PaddlePaddle成为适合国内开发者使用的深度学习框架,尤其在中文场景下表现优异。

安装

在命令行查看Cuda版本

nvidia-smi

因此我们在官网选择安装对应版本,我选择的是paddlepaddle2.6版本

注意需要在conda环境执行命令

python -m pip install paddlepaddle-gpu==2.6.2.post120 -i https://www.paddlepaddle.org.cn/packages/stable/cu120/

PaddleRS套件

检测/分割套件安装与测试示例

在项目中创建RSbackend文件夹

环境要求

PaddlePaddle>=2.5.0

本地配置

注意需要在conda环境中安装!

路径定位到RSbackend下,Github下载PaddleRS代码或者从Gitee下载PaddleRS代码,但是Gitee上代码可能不是最新。当然也可以直接下载下来,解压到本地。

git clone https://github.com/PaddlePaddle/PaddleRS

cdPaddleRS路径下。

cd .\PaddleRS

develop版本执行下述命令再安装

git checkout develop

安装到python

pip install .

测试是否安装完成,可以执行如下指令进行训练模型的测试,跑通就行,数据集还未配置,后续会报错。

python tutorials/train/semantic_segmentation/deeplabv3p.py

跑通即可,因为还有数据集等都没配置,至此,PaddleRS本地环境配置完成。

出现的错误

  1. numpy版本不匹配

众所周知,由于PaddlePaddle的依赖经常不指定具体的版本,导致很多时候复现包的版本不匹配,因此只能根据错误降版本,这已经是必须要经过的步骤了。

显示numpy需要降版本到1.x,经过测试1.24.3是不会报错的

pip install numpy==1.24.3

线上/本地训练变化检测训练CDNet

PaddleRS/tutorials/train/change_detection/cdnet.py at develop · PaddlePaddle/PaddleRS

由于我们的数据集不是默认的数据集,因此需要修改代码tutorials/train/change_detection/cdnet.py

数据准备

切换到PaddleRS 目录下

mkdir data/
python tools/prepare_dataset/prepare_levircd.py \
    --in_dataset_dir "{LEVIR-CD数据集存放目录路径}" \
    --out_dataset_dir "data/levircd" \
    --crop_size 256 \
    --crop_stride 256

例如:

mkdir data/

python tools/prepare_dataset/prepare_levircd.py --in_dataset_dir "../datasets/LEVIR-CD" --out_dataset_dir "data/levircd" --crop_size 256  --crop_stride 256

修改配置

我们需要修改CDNet的配置在tutorials/train/change_detection/cdnet.py

#!/usr/bin/env python

# 变化检测模型CDNet训练示例脚本
# 执行此脚本前,请确认已正确安装PaddleRS库

import paddlers as pdrs
from paddlers import transforms as T

# 数据集存放目录
DATA_DIR = './data/levircd/'
# 训练集`file_list`文件路径
TRAIN_FILE_LIST_PATH = './data/levircd/train.txt'
# 验证集`file_list`文件路径
EVAL_FILE_LIST_PATH = './data/levircd/val.txt'
# 实验目录,保存输出的模型权重和结果
EXP_DIR = './output/cdnet/'

# 下载和解压AirChange数据集
# pdrs.utils.download_and_decompress(
#     'https://paddlers.bj.bcebos.com/datasets/airchange.zip', path='./data/')

# 定义训练和验证时使用的数据变换(数据增强、预处理等)
# 使用Compose组合多种变换方式。Compose中包含的变换将按顺序串行执行
# API说明:https://github.com/PaddlePaddle/PaddleRS/blob/develop/docs/apis/data.md
train_transforms = [
    # 随机裁剪
    T.RandomCrop(
        # 裁剪区域将被缩放到256x256
        crop_size=256,
        # 裁剪区域的横纵比在0.5-2之间变动
        aspect_ratio=[0.5, 2.0],
        # 裁剪区域相对原始影像长宽比例在一定范围内变动,最小不低于原始长宽的1/5
        scaling=[0.2, 1.0]),
    # 以50%的概率实施随机水平翻转
    T.RandomHorizontalFlip(prob=0.5),
    # 将数据归一化到[-1,1]
    T.Normalize(
        mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
]

eval_transforms = [
    # 验证阶段与训练阶段的数据归一化方式必须相同
    T.Normalize(
        mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]),
    T.ReloadMask()
]

# 分别构建训练和验证所用的数据集
train_dataset = pdrs.datasets.CDDataset(
    data_dir=DATA_DIR,
    file_list=TRAIN_FILE_LIST_PATH,
    label_list=None,
    transforms=train_transforms,
    num_workers=0,
    shuffle=True,
    with_seg_labels=False,
    binarize_labels=True)

eval_dataset = pdrs.datasets.CDDataset(
    data_dir=DATA_DIR,
    file_list=EVAL_FILE_LIST_PATH,
    label_list=None,
    transforms=eval_transforms,
    num_workers=0,
    shuffle=False,
    with_seg_labels=False,
    binarize_labels=True)

# 使用默认参数构建CDNet模型
# 目前已支持的模型请参考:https://github.com/PaddlePaddle/PaddleRS/blob/develop/docs/intro/model_zoo.md
# 模型输入参数请参考:https://github.com/PaddlePaddle/PaddleRS/blob/develop/paddlers/tasks/change_detector.py
model = pdrs.tasks.cd.CDNet()

# 执行模型训练
model.train(
    num_epochs=50,
    train_dataset=train_dataset,
    train_batch_size=4,
    eval_dataset=eval_dataset,
    save_interval_epochs=3,
    # 每多少次迭代记录一次日志
    log_interval_steps=50,
    save_dir=EXP_DIR,
    # 是否使用early stopping策略,当精度不再改善时提前终止训练
    early_stop=False,
    # 是否启用VisualDL日志功能
    use_vdl=True,
    # 指定从某个检查点继续训练
    resume_checkpoint=None)

开始训练

python tutorials/train/change_detection/cdnet.py

如果中途训练中断可以在修改配置中将resume_checkpoint=None 修改为对应的checkpoint 文件夹,例如resume_checkpoint='output/cdnet/epoch_36'

预测

PaddleRS/deploy/export/README.md at develop · PaddlePaddle/PaddleRS

examples中有写好的CDNet预测案例,直接使用即可

python examples/rs_research/predict_cd.py --model_dir "output/cdnet/best_model" --data_dir "data/levircd" --file_list "data/levircd/test.txt" --save_dir "output/predict/levircd/cdnet"

预测结果如上

导出模型

PaddleRS/deploy/export/README.md at develop · PaddlePaddle/PaddleRS

导出模型是将动态图模型转为静态图,提升部署时的推理性能,注意我们需要指定fixed_input_shape 保证部署时input_shape与训练时的图像尺寸一致

python deploy/export/export_model.py --model_dir=./output/cdnet/best_model/ --save_dir=./inference_model/ --fixed_input_shape=[256,256]

线上/本地训练变化检测训练自定义模型(可选)

PaddleRS 手把手教你PaddleRS实现变化检测 - 飞桨AI Studio星河社区

训练自定义模型:PaddleRS/examples/rs_research at develop · PaddlePaddle/PaddleRS

数据准备

配置好环境后,在PaddleRS仓库根目录中执行如下指令切换到本案例所在目录:

cd examples/rs_research

本案例在LEVIR-CD数据集[1]上开展实验。请在LEVIR-CD数据集下载链接下载数据集,解压至本地目录,并执行如下指令:

mkdir data/
python ../../tools/prepare_dataset/prepare_levircd.py \
    --in_dataset_dir "{LEVIR-CD数据集存放目录路径}" \
    --out_dataset_dir "data/levircd" \
    --crop_size 256 \
    --crop_stride 256

例如:

python ../../tools/prepare_dataset/prepare_levircd.py --in_dataset_dir "D:\project\PaddleRSC\RSbackend\datasets\LEVIR-CD" --out_dataset_dir "data/levircd" --crop_size 256 --crop_stride 256

以上指令利用PaddleRS提供的数据集准备工具完成数据集切分、file list创建等操作。具体而言,使用LEVIR-CD数据集官方的训练/验证/测试集划分,并将原始的1024x1024大小的影像切分为无重叠的256x256的小块(参考[2]中的做法)。

开始训练

如果没有自定义配置,使用如下代码训练即可

train_cd.py脚本对模型进行训练和验证,并汇报验证集上最优模型在测试集上的精度。

python train_cd.py

预测

在训练和精度指标验证完成后,可以通过如下指令保存模型输出的二值变化图:

python predict_cd.py --model_dir "exp/levircd/{模型名称}/best_model" --data_dir "data/levircd" --file_list "data/levircd/test.txt" --save_dir "exp/predict/levircd/{模型名称}"

例如:

python predict_cd.py --model_dir "exp/levircd/custom_model/best_model" --data_dir "data/levircd" --file_list "data/levircd/test.txt" --save_dir "exp/predict/levircd/custom_model"

随后可以前往目录查看输出

注意是将每张大图切割为16(4*4)的小图块进行预测,因此例如test_2_2.png指第二张测试图片的第3块(分块下标从0开始)

至此我们的训练就完成了

导出模型

PaddleRS/deploy/README.md at develop · PaddlePaddle/PaddleRS

自定义模型导出需要更改源码,不做赘述

前端完善

在前端src\views\RSChangeDetect\HomePage\index.vue下编写如下代码,用来配置前端基本框架样式。

后端配置

前往LEVIR-CD_数据集-飞桨AI Studio星河社区下载数据集

RSbackend文件夹下创建datasets文件夹

将下载好的数据集解压到此处,如下图所示:

将训练脚本中生成的txt文件下载下来放置至此。

在后端项目文件夹创建static/images文件夹,用户上传的图片可以存储在此处

终端执行下面安装语句安装opencv-python:

pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple

笔者是已经安装过了

需要编写后端接口接收前端传过来的前后时相图,创建app.py代码如下:

from flask import Flask, json, request, jsonify
from flask_cors import CORS
import pymysql
import cv2
import numpy as np
import os
DEBUG = True
app = Flask(__name__)
app.config.from_object(__name__)
CORS(app, resource={r'/*': {'origins': '*'}})

@app.route('/adduser', methods=['get', 'post'])
def adduser():
    username = request.form.get("username")
    password = request.form.get("password")
    print(username)
    print(password)
    return "已接收用户信息"

@app.route('/uploadbefore', methods=['get', 'post'])
def uploadbefore():
    username = request.form.get("username")
    img = request.files['file']
    picname=img.filename
    file = img.read()

    file = cv2.imdecode(np.frombuffer(file, np.uint8), cv2.IMREAD_COLOR)  # 解码为ndarray
    imgfile1_path = "./static/images/"+username+"/B/"
    if not os.path.exists(imgfile1_path):
        os.makedirs(imgfile1_path)
    img1_path = os.path.join(imgfile1_path, picname)
    cv2.imwrite(filename=img1_path, img=file)
    url = "http://127.0.0.1:5000/static/images/"+username+"/B/" + picname
    print(url)

    tempmap = {"url": url}
    return jsonify(tempmap)


@app.route('/uploadafter', methods=['get', 'post'])
def uploadafter():
    username = request.form.get("username")
    img = request.files['file']
    picname=img.filename
    file = img.read()

    file = cv2.imdecode(np.frombuffer(file, np.uint8), cv2.IMREAD_COLOR)  # 解码为ndarray
    imgfile1_path = "./static/images/"+username+"/A/"
    if not os.path.exists(imgfile1_path):
        os.makedirs(imgfile1_path)
    img1_path = os.path.join(imgfile1_path, picname)
    cv2.imwrite(filename=img1_path, img=file)
    url = "http://127.0.0.1:5000/static/images/"+username+"/A/" + picname
    print(url)

    tempmap = {"url": url}
    return jsonify(tempmap)



if __name__ == '__main__':
    app.run(host="127.0.0.1", port=5000, debug=True)

至此,基本的影像上传功能就完成了,接下来测试前后端交互。

前后端交互

前端上传前后时相的影像,可以从数据集中选择。

启动后端

python app.py

如果端口权限有问题,可以修改为其他端口启动,不过代码中的端口也需要相应修改

模型本地验证

前面训练过程生成的是训练模型格式。

输出目录中主要包含四个文件:

  • model.pdopt,包含训练过程中使用到的优化器的状态参数;

  • model.pdparams,包含模型的权重参数;

  • model.yml,模型的配置文件(包括预处理参数、模型规格参数等);

  • eval_details.json,包含验证阶段模型取得的指标。

由于训练阶段使用模型的动态图版本,因此将上述格式的模型权重参数和配置文件直接用于部署往往效率不高。建议将模型导出为专用的部署格式,在部署阶段使用静态图版本的模型以达到更高的推理效率。

部署模型格式如下:

  • model.pdmodel,记录模型的网络结构;

  • model.pdiparams,包含模型权重参数;

  • model.pdiparams.info,包含模型权重名称;

  • model.yml,模型的配置文件(包括预处理参数、模型规格参数等);

  • pipeline.yml,流程配置文件

在前面,我们已经实现了模型的导出,在后端建立models/rscd 目录,将导出的模型放入

着测试生成检测结果,先新建RSbackend/output文件夹,用来存放输出结果,创建test.py代码如下并执行。我们需要按照训练时的策略,先裁剪后拼接,可以用到Paddle已经提供的predictor.slider_predict

import numpy as np
from PIL import Image
import os
from paddlers.deploy import Predictor
import paddlers.transforms as T
import rasterio
import glob

# 实验路径和数据集路径
EXP_DIR = './models/rscd/'
DATA_DIR = './datasets/LEVIR-CD/'
OUTPUT_DIR = './output/'

# 确保输出目录存在
os.makedirs(OUTPUT_DIR, exist_ok=True)

# 定义与训练阶段相同的预处理transform
transforms = T.Compose([
    # 使用与训练时相同的归一化参数
    T.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])

predictor = Predictor("./models/rscd/", use_gpu=True)

fileA = "./datasets/LEVIR-CD/test/A/test_20.png"
fileB = "./datasets/LEVIR-CD/test/B/test_20.png"

# 获取输入文件名(不含路径和扩展名)
filename = os.path.basename(fileA).split('.')[0]

# 使用滑动窗口预测
# 参数说明:
# - block_size: 滑动窗口大小,与训练时的crop_size一致
# - overlap: 重叠区域大小,防止边界效应
# - merge_strategy: 图块合并策略
#   - 'keep_first': 保留首次经过的像素值
#   - 'keep_last': 保留最后经过的像素值
#   - 'accum': 根据累积概率确定像素类别
predictor.slider_predict(
    (fileA, fileB),                  # 输入图像路径元组
    save_dir=OUTPUT_DIR,            # 结果保存目录
    transforms=transforms,           # 预处理
    block_size=256,                  # 窗口大小,与训练时crop_size一致
    overlap=32,                      # 重叠像素
    merge_strategy='accum'           # 合并策略
)

# 查找生成的预测结果文件(通常是GeoTIFF格式)
prediction_files = glob.glob(os.path.join(OUTPUT_DIR, "*.tif"))
if not prediction_files:
    # 如果没有找到tif文件,尝试找其他可能的格式
    prediction_files = glob.glob(os.path.join(OUTPUT_DIR, "*"))

if prediction_files:
    # 对于每个预测结果文件
    for pred_file in prediction_files:
        # 读取预测结果(通常是GeoTIFF格式)
        try:
            # 尝试使用rasterio读取
            with rasterio.open(pred_file) as src:
                # 读取第一个波段
                pred_result = src.read(1)
        except:
            # 如果无法使用rasterio读取,尝试直接用numpy加载
            try:
                pred_result = np.load(pred_file)
            except:
                print(f"无法读取文件: {pred_file}")
                continue

        # 转换为二值图像:大于0的设置为255(白色),否则为0(黑色)
        binary_map = (pred_result > 0).astype(np.uint8) * 255

        # 将numpy数组转换为PIL图像
        binary_image = Image.fromarray(binary_map)
        
        # 生成PNG输出文件名
        output_png = os.path.join(OUTPUT_DIR, f"{os.path.basename(pred_file).split('.')[0]}.png")
        
        # 保存为PNG
        binary_image.save(output_png)
        print(f"预测结果已保存为PNG: {output_png}")
else:
    print("未找到预测结果文件!")

print("处理完成!")

得到的结果如图所示:

模型本地部署

上述方法每次都需要加载模型,使用flask框架时,可以全局加载一次,后续调用检测接口即可。接着修改app.py文件如下:

from flask import Flask, json, request, jsonify
from flask_cors import CORS
import pymysql
import cv2
import numpy as np
import os
DEBUG = True
from PIL import Image
from paddlers.deploy import Predictor
app = Flask(__name__)
app.config.from_object(__name__)
CORS(app, resource={r'/*': {'origins': '*'}})


@app.route('/adduser', methods=['get', 'post'])
def adduser():
    username = request.form.get("username")
    password = request.form.get("password")
    print(username)
    print(password)
    return "已接收用户信息"

@app.route('/uploadbefore', methods=['get', 'post'])
def uploadbefore():
    username = request.form.get("username")
    img = request.files['file']
    picname=img.filename
    file = img.read()

    file = cv2.imdecode(np.frombuffer(file, np.uint8), cv2.IMREAD_COLOR)  # 解码为ndarray
    imgfile1_path = "./static/images/"+username+"/A/"
    if not os.path.exists(imgfile1_path):
        os.makedirs(imgfile1_path)
    img1_path = os.path.join(imgfile1_path, picname)
    cv2.imwrite(filename=img1_path, img=file)
    url = "http://127.0.0.1:8000/static/images/"+username+"/A/" + picname
    print(url)
    picpath = "./static/images/" + username + "/A/" + picname

    tempmap = {"url": url,"picpath":picpath}
    return jsonify(tempmap)


predictor = Predictor("./models/rscd/", use_gpu=True)

@app.route('/uploadafter', methods=['get', 'post'])
def uploadafter():
    username = request.form.get("username")
    img = request.files['file']
    picname=img.filename
    file = img.read()

    file = cv2.imdecode(np.frombuffer(file, np.uint8), cv2.IMREAD_COLOR)  # 解码为ndarray
    imgfile1_path = "./static/images/"+username+"/B/"
    if not os.path.exists(imgfile1_path):
        os.makedirs(imgfile1_path)
    img1_path = os.path.join(imgfile1_path, picname)
    cv2.imwrite(filename=img1_path, img=file)
    url = "http://127.0.0.1:8000/static/images/"+username+"/B/" + picname
    print(url)
    picpath="./static/images/"+username+"/B/" + picname

    tempmap = {"url": url,"picpath":picpath}
    return jsonify(tempmap)

@app.route('/detectrscd', methods=['get', 'post'])
def detectrscd():
    username = request.form.get("username")

    # modelrscd.net.eval()
    fileA = request.form.get("imgA")
    fileB = request.form.get("imgB")
    print(fileA)
    print(fileB)

    res = predictor.predict((fileA, fileB))

    label_map = res['label_map']
    # 将numpy数组转换为PIL图像以便保存
    print(label_map)
    # 转换为二值图像:大于0.5的设置为255,否则为0
    binary_map = (label_map > 0.5).astype(np.uint8) * 255
    binary_image = Image.fromarray(binary_map)
    # 保存二值图像到本地,这里以'binary_image.png'为例,您可以自行修改文件名
    picname=fileA.split('/')[-1]
    outpath = "./static/images/" + username + "/res/"
    if not os.path.exists(outpath):
        os.makedirs(outpath)
    outpic = "./static/images/" + username + "/res/" + picname
    binary_image.save(outpic)
    print("二值图像已成功保存至本地。")
    url = "http://127.0.0.1:8000/static/images/" + username + "/res/" + picname
    tempmap = {"url": url}
    return jsonify(tempmap)



if __name__ == '__main__':
    app.run(host="127.0.0.1", port=8000, debug=True)

修改前端代码src\views\RSChangeDetect\HomePage\index.vue如下:

<template>  
  <div class="container">  
    <div class="content">  
      <div class="leftlist"></div>
      <div class="maindetect">
        <div class="toptool"> 
          <div class="upload-area before">  
            <h4 class="h4t">前时相遥感影像</h4>  
            <div class="uploadimage">
                <p class="commodity_img">
                      <!-- 上传图片
                        :class="boxdisplay"
                      -->
                      <el-upload
                        :on-success="handleSuccessbefore"
                        list-type="picture-card"
                        :auto-upload="false"
                        :on-change="handleChangesbefore"
                        :before-remove="beforeRemovebefore"
                        :on-preview="handlePictureCardPreviewbefore"
                        :file-list="fileListbefore"
                        multiple
                        limit="1"
                        name="img"
                      >
                        <el-icon class="avatar-uploader-icon">
                          <Plus />
                        </el-icon>
                      </el-upload>
                  </p>

              <el-dialog v-model="dialogVisible">
                <img w-full class="imageshow" :src="dialogImageUrl" alt="Preview Image" />
              </el-dialog>
            </div>
          </div>  
          <div class="upload-area after">  
            <h4 class="h4t">后时相遥感影像</h4>  
            <div class="uploadimage">
                <p class="commodity_img">
                      <!-- 上传图片
                        :class="boxdisplay"
                      -->
                      <el-upload
                        :on-success="handleSuccessafter"
                        list-type="picture-card"
                        :auto-upload="false"
                        :on-change="handleChangesafter"
                        :before-remove="beforeRemoveafter"
                        :on-preview="handlePictureCardPreviewafter"
                        :file-list="fileListafter "
                        multiple
                        limit="1"
                        name="img"
                      >
                        <el-icon class="avatar-uploader-icon">
                          <Plus />
                        </el-icon>
                      </el-upload>
                  </p>

              <el-dialog v-model="dialogVisible">
                <img w-full class="imageshow" :src="dialogImageUrl" alt="Preview Image" />
              </el-dialog>
            </div>
          </div>
          <div class="detectchange">
            <el-button class="uploadimg" @click="uploadimg">开始检测</el-button>
          </div>
        </div> 
        <div class="detectmain">
          <div>
            <el-image class="showimg" :src="srcbefore">

            </el-image>
          </div>
          <div>
            <el-image class="showimg" :src="srcafter">
            </el-image>
          </div>
          <div>
            <el-image  class="showimg" :src="srcdetect">
            </el-image>
          </div>
        </div>
      </div>
      <div class="rightlist"></div>
    </div>  
  </div>  
</template>  
  
<script setup lang="ts">  
 import { ref } from 'vue'
  import { ElMessage } from 'element-plus';
  import { Picture as IconPicture,Delete, Download, Plus, ZoomIn } from '@element-plus/icons-vue'

  import type { UploadFile } from 'element-plus'
  import axios from 'axios'
  const dialogImageUrl = ref('')
  const dialogVisible = ref(false)
  const disabled = ref(false)
  
  const srcbefore =ref('test')
  
  const srcafter =ref('test')

  const srcdetect =ref('test')
  

  const handleRemove = (file: UploadFile) => {
    file.url = '';
    file=null;
  }
  

  const handlePictureCardPreviewbefore = (file: UploadFile) => {
    dialogImageUrl.value = file.url!
    dialogVisible.value = true
  }
  const handlePictureCardPreviewafter = (file: UploadFile) => {
    dialogImageUrl.value = file.url!
    dialogVisible.value = true
  }


  const boxdisplay = ref("show_box");

  const upload_btn = ref(false);
  const fileListbefore = ref([]);
  const fileListafter = ref([]);

  const handleSuccessbefore = () => {

  };
  const handleSuccessafter = () => {

};

  const handleChangesbefore = img => {
    fileListbefore.value.push(img);
    boxdisplay.value = "hide_box";
    srcbefore.value = "";  
    srcafter.value = "";  
    srcdetect.value = "";  
  };
  const handleChangesafter = img => {
    fileListafter.value.push(img);
    boxdisplay.value = "hide_box";
    srcbefore.value = "";  
    srcafter.value = "";  
    srcdetect.value = ""; 
  };
  import {ElMessageBox} from 'element-plus'
  // 删除
  const beforeRemovebefore = () => {
    const result = new Promise((resolve, reject) => {
      ElMessageBox.confirm("此操作将删除该图片, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(() => {
          boxdisplay.value = "show_box";
          resolve();
          fileListbefore.value = [];
          upload_btn.value = false;
        })
        .catch(() => {
          reject(false);
        });
    });
    return result;
  };
  const beforeRemoveafter = () => {
    const result = new Promise((resolve, reject) => {
      ElMessageBox.confirm("此操作将删除该图片, 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(() => {
          boxdisplay.value = "show_box";
          resolve();
          fileListafter.value = [];
          upload_btn.value = false;
        })
        .catch(() => {
          reject(false);
        });
    });
    return result;
  };
  const uploadimg = async () => {  
    try {  
        var imga = await uploadimgbefore();  
        var imgb = await uploadimgafter();  
        let formData = new FormData();  
        formData.append("username", "12345");  
        formData.append("imgA", imga);  
        formData.append("imgB", imgb);  
        let url = 'http://127.0.0.1:8000/detectrscd'; //访问后端接口的url  
        let method = 'post';  
        axios({  
            method,  
            url,  
            data: formData,  
            headers: {  
                'Content-Type': 'multipart/form-data' // 确保设置了正确的Content-Type  
            }  
        }).then(res => {  
            srcdetect.value = res.data.url;  
            ElMessage({ message: '变化检测完成', type: 'success' });  
        }).catch(() => {  
            // 处理错误  
        });  
    } catch (error) {  
        console.error("Error uploading images:", error);  
    }  
};  
  
const uploadimgbefore = () => {  
    return new Promise((resolve, reject) => {  
        let formData = new FormData();  
        formData.append("username", "12345");  
        fileListbefore.value.forEach((file, index) => {  
            formData.append("file", file.raw);  
        });  
  
        let url = 'http://127.0.0.1:8000/uploadbefore'; //访问后端接口的url  
        let method = 'post';  
        axios({  
            method,  
            url,  
            data: formData,  
            headers: {  
                'Content-Type': 'multipart/form-data' // 确保设置了正确的Content-Type  
            }  
        }).then(res => {  
            fileListbefore.value = [];  
            upload_btn.value = false;  
            srcbefore.value = res.data.url;  
            ElMessage({ message: '前时相影像上传成功', type: 'success' });  
            resolve(res.data.picpath);  
        }).catch(() => {  
            reject("Failed to upload before image");  
        });  
    });  
};  
  
const uploadimgafter = () => {  
    return new Promise((resolve, reject) => {  
        let formData = new FormData();  
        formData.append("username", "12345");  
        fileListafter.value.forEach((file, index) => {  
            formData.append("file", file.raw);  
        });  
  
        let url = 'http://127.0.0.1:8000/uploadafter'; //访问后端接口的url  
        let method = 'post';  
        axios({  
            method,  
            url,  
            data: formData,  
            headers: {  
                'Content-Type': 'multipart/form-data' // 确保设置了正确的Content-Type  
            }  
        }).then(res => {  
            fileListafter.value = [];  
            upload_btn.value = false;  
            srcafter.value = res.data.url;  
            ElMessage({ message: '后时相影像上传成功', type: 'success' });  
            resolve(res.data.picpath);  
        }).catch(() => {  
            reject("Failed to upload after image");  
        });  
    });  
};
</script>  
  
<style scoped>  
.container {  
  display: grid;  
  grid-template-rows: 100%;
  height: 92vh;
}  
  
  
.content {  
  display: grid;  
  place-items: center;  
  padding: 0em;  
  background-color: #f0f0f0;  
  grid-template-columns: 15% 70% 15%;
}  
  
.upload-area, .result-area {  
  border: 1px dashed #4f4949;
    position: relative;
    padding: 1em;
    height: 69%;
    margin: 1em;
}  
  
.before {  

}  
  
.after {  

}  
  
.result-area {  
  background-color: #aaf;  
}  
  
.result-image {  
  height: 200px;  
  background-color: #eee;  
  display: flex;  
  align-items: center;  
  justify-content: center;  
} 
.leftlist{
  position: relative;
  width: 100%;
  height: 100%;
  background-color: #aac1ff63;
} 
.rightlist{
  position: relative;
  width: 100%;
  height: 100%;
  background-color: #aac1ff63;
}
.maindetect{
  position: relative;
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-rows: 30% 70%;
}
.toptool{
  position: relative;
    width: 100%;
    height: 100%;
    background-color: rgb(201 218 222);
    display: grid;
    grid-template-columns: 40% 40% 20%;
}
.upimg{
  position: relative;
  left: 5%;
  height: 4em;
}
.detectcd{
  position: relative;
  left: 5%;
  height: 4em;
}
.imageshow{
  width: 100%;  
  height: 100%;  
  object-fit: fill; 
}
.detectmain{
  position: relative;
  display: grid;
  grid-template-columns: 33% 33% 33%;
}
.h4t{
  margin: 0;

}
.uploadimg{
  position: relative;
    height: 4em;
    top: 35%;
    left: 10%;
}
.demo-image__error .block {
  padding: 30px 0;
  text-align: center;
  border-right: solid 1px var(--el-border-color);
  display: inline-block;
  width: 49%;
  box-sizing: border-box;
  vertical-align: top;
}
.demo-image__error .demonstration {
  display: block;
  color: var(--el-text-color-secondary);
  font-size: 14px;
  margin-bottom: 20px;
}
.demo-image__error .el-image {
  padding: 0 5px;
  max-width: 300px;
  max-height: 200px;
  width: 100%;
  height: 200px;
}

.demo-image__error .image-slot {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background: var(--el-fill-color-light);
  color: var(--el-text-color-secondary);
  font-size: 30px;
}
.demo-image__error .image-slot .el-icon {
  font-size: 30px;
}
.showimg{
  position: relative;
    width: 20vw;
    height: 20vw;
    left: 5%;
    top: 15%;
    background-color:#d7e6f570;
}
</style>

至此,模型本地部署完成。