0%

OpenCV是计算机视觉领域应用最广泛的开源工具包,基于C/C++,支持Linux/Windows/MacOS/Android/iOS,并提供了Python,Matlab和Java等语言的接口

  • core:核心模块,主要包含了OpenCV中最基本的结构(矩阵,点线和形状等),以及相关的基础运算/操作。
  • imgproc:图像处理模块,包含和图像相关的基础功能(滤波,梯度,改变大小等),以及一些衍生的高级功能(图像分割,直方图,形态分析和边缘/直线提取等)。
  • highgui:提供了用户界面和文件读取的基本函数,比如图像显示窗口的生成和控制,图像/视频文件的IO等。
    如果不考虑视频应用,以上三个就是最核心和常用的模块了。针对视频和一些特别的视觉应用,OpenCV也提供了强劲的支持:
  • video:用于视频分析的常用功能,比如光流法(Optical Flow)和目标跟踪等。
  • calib3d:三维重建,立体视觉和相机标定等的相关功能。
  • features2d:二维特征相关的功能,主要是一些不受专利保护的,商业友好的特征点检测和匹配等功能,比如ORB特征。
  • object:目标检测模块,包含级联分类和Latent SVM
  • ml:机器学习算法模块,包含一些视觉中最常用的传统机器学习算法。
  • flann:最近邻算法库,Fast Library for Approximate
  • Nearest Neighbors,用于在多维空间进行聚类和检索,经常和关键点匹配搭配使用。
  • gpu:包含了一些gpu加速的接口,底层的加速是CUDA实现。
  • photo:计算摄像学(Computational Photography)相关的接口,当然这只是个名字,其实只有图像修复和降噪而已。
  • stitching:图像拼接模块,有了它可以自己生成全景照片。
  • nonfree:受到专利保护的一些算法,其实就是SIFT和SURF。
  • contrib:一些实验性质的算法,考虑在未来版本中加入的。
  • legacy:字面是遗产,意思就是废弃的一些接口,保留是考虑到向下兼容。
  • ocl:利用OpenCL并行加速的一些接口。
  • superres:超分辨率模块,其实就是BTV-L1(Biliteral Total Variation – L1 regularization)算法
  • viz:基础的3D渲染模块,其实底层就是著名的3D工具包VTK(Visualization Toolkit)。

安装

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

读取显示图像

1
2
3
4
5
6
7
8
9
10
11
12
img = cv2.imread('singlemushroom.jpg', cv2.IMREAD_UNCHANGED)
cv2.imshow("origin image", img)

img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

cv2.imshow("gray image", img_gray)

cv2.waitKey(0)
cv2.destroyAllWindows()

# 写入图像
cv2.imwrite("originImage.jpg", img)

cv.imread的枚举参数

  • cv2.IMREAD_COLOR = 1 :读入一副彩色图像。透明度会被忽略,默认参数
  • cv2.IMREAD_GRAYSCALE = 0 :以灰度模式读入图像
  • cv2.IMREAD_UNCHANGED = -1:保留读取图片原有的颜色通道

缩放

1
result = cv2.resize(src, (200,100))

像素操作

1
2
3
4
5
#矩阵运算

#拷贝区域
ball=img[280:340,330:390]
img[273:333,100:160]=ball

通道

1
2
3
4
#通道顺序与R-G-B顺序相反
b,g,r=cv2.split(img)
#b=img[:,:,0]
img=cv2.merge(b,g,r)

阈值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#读取图片
src = cv2.imread('miao.jpg')

#灰度图像处理
GrayImage = cv2.cvtColor(src,cv2.COLOR_BGR2GRAY)

#二进制阈值化处理
r, b = cv2.threshold(GrayImage, 127, 255, cv2.THRESH_BINARY)
#THRESH_BINARY 超过阈值像素设为最大值 否则为0
#THRESH_BINARY_INV 超过阈值像素设为0 否则为最大值
#THRESH_TRUNC 大于阈值部分设为最大值 否则不变
#THRESH_TOZERO 大于阈值部分不变 否则设为0
#THRESH_TOZERO_INV 大于阈值部分设为0 否则不变
print(r)

#显示图像
cv2.imshow("src", src)
cv2.imshow("result", b)

自适应阈值 adapativeThreshold

1
adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst])
  • maxValue 满足条件的最大值
  • adaptiveMethod 自适应方法 ADAPTIVE_THRESH_MEAN_C ADAPTIVE_THRESH_GAUSSIAN_C
    ADAPTIVE_THRESH_MEAN_C的计算方法是计算出领域的平均值再减去 C;ADAPTIVE_THRESH_GAUSSIAN_C的计算方法是计算出领域的高斯均值再减去C
  • thresholdType 阈值类型 THRESH_BINARY 或者 THRESH_BINARY_INV
  • blockSize 邻域大小 如 3,5,7
  • C 阈值偏移量
1
2
3
4
thresh1 = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
thresh2 = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 31, 3)
thresh3 = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
thresh4 = cv2.adaptiveThreshold(gray_image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 3)

卷积和滤波

设想3*3矩阵 对中心位置像素值做卷积运算 其作用即一种平滑滤波

1
2
#高斯滤波
gaussianBlur = cv2.GaussianBlur(greyImg, (3,3), 0)

膨胀和腐蚀,开运算和闭运算 顶帽和底帽

1
2
3
4
5
6
7
#腐蚀
kernel=np.ones((5,5),np.uint8)
erosion=cv2.erode(img,kernel,iterations=1)
#iterations 为重复次数
#膨胀
kernel=np.ones((5,5),np.uint8)
dilate=cv2.dilate(img,kernel,iterations=1)

开为先腐蚀再膨胀 闭为先膨胀后腐蚀

顶帽:原图像 - 开运算结果 顶帽得到的是开运算中断开的细节
底帽:原图像 - 闭运算结果 底帽得到的是闭运算中补上的细节

边缘检测 canny

  • 去噪 噪声会影响该算法进行边缘检测的准确性
  • 计算每个点的梯度大小和方向 例如用一对Sobel算子作为卷积核对图像滤波
  • 抑制非边缘点 边缘点应该满足:±梯度方向上的局部最大值,因此将不满足条件的点归零
  • 用两个阈值条件筛选剩余的边缘‘条纹’
1
2

edges = cv.Canny(blur, 50, 150, apertureSize)

FindContour

一种边缘提取方法

1
contours,_=cv2.findContours(img,RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
  • 参数1 二值图像
  • 参数2 轮廓返回模式(RETR_EXTERNAL: 表示只检测最外层轮廓;RETR_LIST: 提取所有轮廓,并放置在list中,检测的轮廓不建立等级关系; RETR_TREE: 提取所有轮廓并重新建立网状轮廓结构 )
  • 参数3 遍历发现方法(CHAIN_APPROX_NONE:获取每个轮廓的每个像素,相邻的两个点的像素位置差不超过1;CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素,值保留该方向的重点坐标,如果一个矩形轮廓只需4个点来保存轮廓信息 )
    用findcontour去孔洞

drawContours

cv2.drawContours()

1
cv2.drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None)
  • image 原图像
  • contours 轮廓集合 例如findContours获得的集合
  • contourIdx 指定绘制轮廓list中的哪条轮廓,如果是-1,则绘制其中的所有轮廓。
  • color 颜色
  • thickness 轮廓线的宽度,如果是-1(cv2.FILLED),则为填充模式。

外接矩形

1
2
3
x, y, w, h = cv2.boundingRect(contour)
img = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 1)
cv2.imshow(img)

区域质心位置

1
2
mu = cv2.moments(contour, False)
mc = [mu['m10']/mu['m00'], mu['m01']/mu['m00']]

联通区域计数

CSDN Blog:连通区域分析算法

floodFill填充孔洞

掩膜

或者叫蒙版 将感兴趣区域(Region of Interest, ROI)提取出来 掩盖其他区域.

1
2
3
4
5
h, w = gaussianBlur.shape
mask = np.zeros((h, w), np.uint8)
cv2.fillPoly(mask, [capContour], (255, 255, 255))
roi = cv2.bitwise_and(GrayImage, GrayImage, mask=mask)
cv2.imshow('roi', roi)

直方图

反映像素的值在图像中的分布,值范围分段,进行统计,值可以是亮度,或任意色彩通道的分量
直方图可以作为阈值分割的参数选择依据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#读取图像
src = cv2.imread('orign.bmp')

#绘制直方图
plt.hist(src.ravel(), bins=256, density=1, facecolor='green', alpha=0.75)
plt.xlabel("x")
plt.ylabel("y")
plt.show()

#显示原始图像
cv2.imshow("src", src)
cv2.waitKey(0)
cv2.destroyAllWindows()
  • src.ravel()将二维图像数据展成一维
  • BINS 分若干组
  • DENSITY 密度
  • FACECOLOR 直方图颜色
  • ALPHA

matchtemplate

opencv.js

OpenCV directly in the browser (webassembly + webworker)

PIL( Python Imaging Library)是 Python 的第三方图像处理库,由于其功能丰富,API 简洁易用,因此深受好评。由于 PIL 库更新缓慢,目前仅支持 Python 2.7 版本,这明显无法满足 Python3 版本的使用需求。于是一群 Python 社区的志愿者(主要贡献者:Alex Clark 和 Contributors)在 PIL 库的基础上开发了一个支持 Python3 版本的图像处理库,它就是 Pillow。

Pillow 提供了丰富的图像处理功能,可概括为两个方面:
图像归档
图像处理
图像归档,包括创建缩略图、生成预览图像、图像批量处理等;而图像处理,则包括调整图像大小、裁剪图像、像素点处理、添加滤镜、图像颜色处理等。

Pillow 库可以配合 Python GUI(图形用户界面)工具 Tkinter 一起使用。

输入棋盘图片路径 棋盘角点数(横纵方格数减1)

1
2
3
4
5
6
7
8
def calibratePos(imgPath, w, h):
chessboardImg = cv2.imread(imgPath)
chessboardImg = cv2.cvtColor(chessboardImg, cv2.COLOR_BGR2GRAY)
ret, chessboard = cv2.findChessboardCorners(chessboardImg, (w, h), None)
cv2.drawChessboardCorners(chessboardImg, (w, h), chessboard, ret)
# cv2.imwrite('calibrated_image.jpg', chessboardImg)
cv2.imshow('calibrated image', chessboardImg)
cv2.waitKey(0)

vue的状态管理库
其核心是管理一个store(仓库) store保存变量的状态 组件引用store 查询状态
以commit mutation的方式更新状态

状态驱动视图 UI=f(state)

state

使用单一状态树,即app只用一个对象管理所有变量

vuex通过vue插件系统将store注入到每一个组件,子组件通过this.$store.count访问

mapstate

1
comouted: mapState(['count'])

字符串为state的变量名

mutation

1
store.commit('increment', payload)
1
2
3
4
5
6
7
8
9
10
const store = createStore({
state: {
count: 1
},
mutations: {
increment (state, payload) {
state.count += payload
}
}
})

mutation的处理方法不能是异步的 对于异步的状态是无法追踪的

action

与mutation类似
分发action

1
store.dispatch('increment')

action通常传递一个mutation 其处理方法可以是异步的
1
2
3
4
5
6
7
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}

module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

实践

store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 数据
const state = {
count,
user:{name:''}
}

const getters = {
getUser(state){
return state.user
}
}

// 修改数据
const mutations = {
updateUser(state, user){
state.user = user
}
}

// 异步操作
const actions = {
delayUpdate(store, user){
setTimeout(()=>{
store.commit('updateUser', user)
})
}
}

// 分装
const modules = {
moduleA:{
state,
mutations,
actions
},
moduleB:{
state,
mutations,
actions
}
}

子组件中 调用mutation用commit方法 调用action用dispatch方法
1
2
3
this.$store.commit('updateUser', user)

this.$store.dispatch('delayUpdate', user)

troubleshooting

TypeError: Cannot read properties of undefined (reading ‘commit’)

vuex@4 不兼容vue2 改为vuex@3
定义store时 Vue.use(Vuex)

vtk.js widgets

vtk Widget是官方提供的常用小工具,如LineWidgetAngleWidgetPaitWidget

vtk Widget架构遵循MVC,分为三个组件

  • vtkWidgetState (model)
  • vtkWidgetRepresentation (view)
  • vtkAbstractWidget (control)

下图示意调用widget的不同组件的通信关系

Widget 工厂

Widget 工厂用于组装Widget及其state、representations

构建vtkWidgetState
调用getWidgetForView 工厂new一个widget对象 将state赋给该对象 创建并设置representations即SetRepresentation

开发者应继承vtkAbstractWidgetFactory开发widget

与InteractorStyle相比

VTK的交互器样式(vtkInteractorStyle)通常只是控制相机以及提供一些简单的键盘和鼠标事件的交互技术。交互器样式在渲染场景中并没有一种表达形式,也就是说,在交互时我们看不见交互器样式到底是什么样子的,用户在使用这些交互器样式时,必须事先知道哪些键盘和鼠标事件是控制哪些操作的。FromCSDN: Widgets简介

vtkWidget同为vtkInteractorObserver子类,监听并响应交互器事件,又添加可视化的representation

开发Widgets

Developing Widgets
vtkWidgetManager 是管理widgets创建、抑制(suppression)及聚焦(focus)的对象,每个render中唯一

1
2
3
4
5
6
7
8
widget = vtkWidget.newInstance()
handle = widgetManager.addWidget(widget, viewType)
widgetManager.setRenderer(renderer)
widgetManager.grabFocus(widget)
widgetManager.enablePicking()

widgetManager.removeWidget(widget)
widget.delete()

focus至多一个widget 激活并使能其响应事件(其实behaviour也可以在unfocus的状态下响应)

使用widget的newInstance方法创建widget对象 这时widget state被创建 用于在不同组件间同步状态 比如工具栏和canvas的相互交互

viewType用于指示widget manager该使用的representation。

创建子状态

1
2
3
4
5
6
7
8
9
10
11
12
vtkStateBuilder
.createBuilder()
.addStateFromMixin({
labels: ['{LABEL0}'],
mixins: ['origin', 'color', 'scale1', 'visible'],
name: '{NAME}',
initialValues: {
scale1: 0.1,
origin: [1, 2, 3],
visible: false,
}
})

  • name是子状态唯一标识 调用state.get{NAME}()从widget state中读取子状态
  • labels决定哪些representation可以用来渲染该子状态
  • mixins存放子状态有效数据 representation会使用到这些数据 因而是有限且标准的 get/set方法:subState.get{NAME}(), subState.set{NAME}() 修改子状态触发场景渲染
  • initialValues子状态初始值 非必须的

动态子状态
Mixins

调用widgetManager.getRepresentationsForViewType(viewType)返回含representation的集合 参数viewType是addWidget时指定的参数
返回各项 {builder, labels} 前者是Representation类 后者是representation对象要用到的子状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
switch (viewType) {
case ViewTypes.DEFAULT:
case ViewTypes.GEOMETRY:
case ViewTypes.SLICE:
return [
{
builder: vtkCircleContextRepresentation,
labels: ['handle', 'trail'],
},
{
builder: vtkPolyLineRepresentation,
labels: ['trail'],
},
];
case ViewTypes.VOLUME:
return [
{
builder: vtkSphereHandleRepresentation,
labels: ['handles'],
initialValues: {
scaleInPixels: true,
},
},
{
builder: vtkSphereHandleRepresentation,
labels: ['moveHandle'],
initialValues: {
scaleInPixels: true,
},
},
{
builder: vtkSVGCircleHandleRepresentation,
labels: ['handles', 'moveHandle'],
}
];
default:
return [{ builder: vtkSphereHandleRepresentation, labels: ['handle'] }];
}

Representation托管自身actors和mappers, actor在Representation创建时创建,推入model.actors中进而渲染
Representation应继承vtkHandleRepresentation 或 vtkContextRepresentation

Widget behavior
widgetManager.addWidget返回的handle就是widget behavior对象 它控制这widget的行为:接收并响应鼠标、键盘事件 见其定义的方法形如 PublicAPI.handle{XXX}(callData)
widget behavior也可以访问renderer rendererWindow 和 interactor

程序入口

1
2
3
4
5
6
7
8
9
10
11
// main.js
import Vue from 'vue'
import App from './App.vue'

// 生产环境中不提示 正在使用开发板警告
Vue.config.productionTip = false

new Vue({
render: h => h(App),
}).$mount('#app')

h 是 createElement 的别名
$mount(‘#app’) 表示将该组件挂在到id=”app”的dom上,这个#app在public/index.html上

provider & inject

类似于React的 Context Provider, 避免父组件向孙子后代组件传参时逐层引用,而以依赖注入的形式暴露给其他组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const app = Vue.createApp({})

app.component('todo-list', {
data() {
return {
todos: ['Feed a cat', 'Buy tickets']
}
},
provide: {
user: 'John Doe'
},
template: `
<div>
{{ todos.length }}
<!-- 模板的其余部分 -->
</div>
`
})

app.component('todo-list-statistics', {
inject: ['user'],
created() {
console.log(`Injected property: ${this.user}` // > 注入的 property: John Doe
}
})

注意 provide 组件实例property,需要用返回函数的形式
1
2
3
4
5
provide() {
return {
todoLength: this.todos.length
}
}

生命周期

  • created vs mounted
  • beforeUpdete updated
  • beforeDestroy destroyed
  • activated deactivated (only for keep-alive)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@startuml
actor User

User -> FatherComponent: beforeCreate
User -> FatherComponent: created
User -> FatherComponent: beforeMount

FatherComponent -> ChildComponent: beforeCreate
FatherComponent -> ChildComponent: created
FatherComponent -> ChildComponent: beforeMount
FatherComponent -> ChildComponent: mounted

User -> FatherComponent: mounted

@enduml

组件通信

  • props/$emit
  • event bus
  • vuex
  • provide/inject
  • ref 由生命周期图示可知 获取子组件引用必须要在mounted后
  • $parent $children jquery的回忆浮现脑海
  • attrs/listeners

Options API vs Composition API

指的是生命周期的选项 类似React class组件中‘选择’恰当的生命周期钩子嵌入业务逻辑。 created, mounted 等生命周期钩子,直接对应 componentDidMount, componentDidUpdate 等

Composition API则React function组件中的hooks

ref() 和 reactive() 对应 useState。
computed() 对应 useMemo。
onMounted -> useEffect(…, [])
onUpdated -> useEffect(…, […]) (不指定依赖)
onUnmounted -> useEffect(() => { return () => { … } }, [])

组合式函数

defineProperty

自定义指令

defineConponent

vue.config.js

vue cli 项目配置包含项目基本配置和对webpack的封装

基本配置:

  • 构建路径如 publicPath outputDir indexPath
  • 构建设置 transpileDependencies(false to disable sourcemap) …
  • devServer 配置webpack-dev-server选项 包括 host proxy等
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    module.exports = defineConfig({
    transpileDependencies: true,
    devServer: {
    port: 8081,
    headers: {
    // 微前端需要跨域
    'Access-Control-Allow-Origin': '*'
    }
    },
    configureWebpack: {
    output: {
    library: `${packageName}-[name]`,
    libraryTarget: 'umd',
    chunkLoadingGlobal: `webpackJsonp_${packageName}`,
    }
    }
    })
    webpack配置:
    调整 webpack 配置最简单的方式就是在 vue.config.js 中的 configureWebpack 选项提供一个对象 该对象将会被 webpack-merge 合并入最终的 webpack 配置 (注意与vue.config.js重叠的配置项会被vue.config.js覆盖)

面试补充(2026-07-22)

本节为后续补充,用于资深软件工程师基础面试复习;上文保留原始笔记。上文用 React Hooks 类比 Composition API 便于入门,但两者的响应式模型、依赖声明和更新机制并不等价。

先建立 Vue 3 的运行模型

回答 Vue 原理问题时,可以先建立一条完整链路:

  1. 模板由编译器转换为渲染函数,并附带静态提升、Patch Flag 等优化信息。
  2. 组件渲染函数执行时读取响应式数据,Vue 将当前渲染 effect 记录为这些数据的依赖。
  3. 写入响应式数据会触发相关 effect,但组件更新通常先进入调度队列,同一轮同步修改会被去重和批处理。
  4. 更新阶段生成新的虚拟节点树,renderer 根据新旧节点的类型、key 和编译器提示执行 patch,最终修改必要的 DOM。

所以“数据变化就重新渲染整个页面”并不准确。变化触发的是依赖该数据的 effect;组件可能重新执行渲染函数,但真实 DOM 只提交比较后需要变更的部分。

refreactive 与解构边界

  • ref(value) 用带 .value 的对象保存值,适合基本类型、需要整体替换的对象以及 composable 的返回值。
  • reactive(object) 返回 Proxy,适合围绕同一对象组织多个相关字段。它依赖代理访问完成追踪,不能替换成一个全新的普通对象后还期待原引用继续响应。
  • 模板会自动解包常见位置上的 ref;JavaScript 中仍要显式访问 .value
  • 直接解构 reactive 对象得到的是当前属性值,后续读取不再经过原 Proxy。可用 toRef()toRefs() 保留属性级响应性。
  • 对大型第三方对象、Three.js 场景对象或采用不可变更新的数据,先判断是否真的需要深层代理;shallowRef()markRaw() 有时比默认深响应更合适。
1
2
3
4
5
6
7
8
9
10
import { reactive, toRefs, watchEffect } from 'vue'

const state = reactive({ online: 0, alarm: 0 })
const { online, alarm } = toRefs(state)

watchEffect(() => {
console.log(`online=${online.value}, alarm=${alarm.value}`)
})

state.online += 1

这里的 onlinealarm 是指向原对象属性的 ref。若写成 const { online } = state,得到的只是解构当时的数字。

computedwatchwatchEffect

API 主要职责 选择依据
computed 声明有缓存的派生状态 结果可以由其他响应式状态纯计算得到
watch 监听明确的数据源并执行副作用 需要新旧值、精确控制触发源或控制执行时机
watchEffect 自动收集同步执行阶段读取的依赖 副作用依赖较直观,希望立即运行且不想重复列依赖

不要用 watch 维护本可由 computed 得到的第二份状态,否则容易产生同步顺序和一致性问题。异步监听还要处理过期请求:在回调中注册 cleanup,取消旧请求或让旧响应失效,避免后发请求被先发请求的迟到结果覆盖。

1
2
3
4
5
6
7
8
9
watch(deviceId, async (id, _oldId, onCleanup) => {
const controller = new AbortController()
onCleanup(() => controller.abort())

const response = await fetch(`/api/devices/${id}`, {
signal: controller.signal
})
device.value = await response.json()
})

从模板到 DOM 更新

Vue 3 并非只依靠运行时逐节点比较。编译器可以识别静态节点和动态绑定,运行时据此减少不必要的比较。理解这一点比背诵某个 diff 实现细节更重要。

连续的同步状态修改通常会被批量处理:

1
2
3
4
5
count.value += 1
count.value += 1
// 此处响应式值已是新值,但依赖它的 DOM 通常还在等待本轮队列刷新。
await nextTick()
// 此处可以读取本次 Vue 更新后的 DOM。

nextTick() 的含义是等待当前 Vue 更新队列刷新,不是通用延时器,也不保证图片加载、网络请求、浏览器绘制或第三方库的异步任务已经完成。

key 表达的是节点身份

key 首先回答“新旧两次渲染中的节点是不是同一个实体”。稳定、唯一并来自业务实体的 key,可以让 Vue 正确复用或销毁组件实例和 DOM 状态。

用数组下标作为会插入、删除或重排列表的 key,可能让输入框状态、组件局部状态和实体错位。刻意改变组件 key 则可要求 Vue 销毁旧实例并创建新实例,但不应把它当作掩盖状态设计问题的常规刷新手段。

因此,“key 只是为了提升 diff 性能”是不完整的回答;正确性和状态身份比性能更先。

组件通信也是边界设计

  • props 是父组件到子组件的只读输入;子组件通过 emit 表达事件,不应直接修改父级拥有的状态。
  • 插槽把内容结构的控制权留给父组件,适合可复用容器和布局能力。
  • provide/inject 适合跨层提供稳定依赖,例如表单上下文、主题或领域服务;它不是所有共享状态的默认替代品。
  • composable 复用的是有状态逻辑。每次调用是否共享状态取决于状态定义在函数内部还是模块作用域,不能仅凭 useXxx 命名判断。
  • 全局状态应保存跨页面、跨组件且具有明确生命周期的业务状态;临时表单输入和局部 UI 状态通常仍应就近管理。

资深工程师还应说明组件边界:输入输出是否清晰、状态由谁拥有、副作用在哪里释放、组件能否独立测试,以及领域状态是否被 UI 框架细节绑死。

高频数据与大列表的性能诊断

假设工业状态看板持续接收数千台设备的遥测,页面出现卡顿。不要一开始就回答“上虚拟列表”或“换 Web Worker”,而应分层定位:

  1. 用 Vue Devtools、浏览器 Performance 和 Long Tasks 判断时间消耗在消息解析、响应式更新、组件渲染、布局还是绘制。
  2. 区分数据接收频率和人眼需要的 UI 刷新频率。例如遥测可按原频率入缓冲区,而界面按 100 至 250 ms 合并发布一次快照。
  3. 批量修改状态,避免每条消息触发独立的派生计算;稳定对象身份,减少无意义的 props 变化。
  4. 对只读快照或大型外部对象考虑 shallowRef();不要为了“响应式”深代理整棵设备对象图。
  5. 列表确实很大且 DOM 成本占主导时再使用虚拟滚动;图表和 3D 场景也应控制绘制频率和对象分配。
  6. JSON 解码、几何计算或数据聚合确实占用主线程时,再评估 Web Worker,并计入序列化和线程通信成本。

这类回答体现的是测量、分层和取舍,而不是背出更多 Vue API。

常见追问与回答边界

为什么修改状态后马上读取 DOM 还是旧值?

响应式值已同步改变,但组件 DOM 更新被调度和批处理。需要读取 Vue 本轮提交后的 DOM 时使用 await nextTick()

reactiveref 哪个性能更好?

通常不应先按微小性能差异选择。应先看状态形态、是否整体替换、是否需要解构和 composable API 的稳定性;大型数据再结合测量选择浅响应或原始对象。

为什么不能在子组件里直接修改 prop?

prop 的所有权属于父组件。子组件修改会破坏单向数据流,并可能在父组件重新渲染时被覆盖。可通过 emit 请求父级更新,或在边界清晰时使用 v-model 协议。

Composition API 是否天然比 Options API 更快?

二者主要是代码组织方式,不应笼统宣称 Composition API 运行更快。Composition API 的主要价值是按业务关注点组合逻辑、增强类型推导和逻辑复用;性能仍取决于实际依赖、渲染和数据结构。

深度学习网络对输入的数据进行多层的非线性变换操作, 不断耦合低层特征, 层层往上, 最后得到高层的语义表达

卷积神经网络

模型

gpt 175 billion netro cell

资源

白嫖限制试用的api
使用cloud function实现外网gpt代理访问

  • 前向传播
  • 反向传播 梯度
  • 损失函数 mae
  • 梯度下降

  • relu dropout

tianchi.aliyun

vibe coding 写深度学习: 我要做二手车价格预测,使用神经网络 tensorflow,搭建一个二层的神经网络,神经元的数量分别为 128 和 64 训练这个神经网络,使用 10%验证集进行验证,通过MAE进行验证,使用早停法,可以learning_rate稍微小一些,训练轮数大一些

  • 过拟合
  • 池化

创建新解决方案 使用webapi模板 new -> Project -> ASP.NET Core Web API template
命令行工具参考Microsoft Doc: dotnet new

1
2
3
4
5
6
mkdir MyServices
cd MyServices
dotnet new sln
mkdir src
dotnet new webapi -lang C# -o src/MyServices.API
dotnet sln add src/MyServices.API/MyServices.API.csproj

添加class library项目
1
2
3
4
dotnet new classlib -lang C# -o src/MyServices.EntityManagers
dotnet new classlib -lang C# -o src/MyServices.Models
dotnet sln add src/MyServices.EntityManagers/MyServices.EntityManagers.csproj
dotnet sln add src/MyServices.Models/MyServices.Models.csproj

创建详细目录,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
─src
├───MyServices.API
│ ├───Controllers
│ ├───Infrastructure
│ │ ├───ActionFilters
│ │ ├───DbContext
│ │ ├───Helpers
│ │ ├───OData
│ │ └───Security
│ └───Properties
├───MyServices.EntityManagers
│ ├───interfaces
│ ├───Managers
│ └───Utilities
└───MyServices.Models
├───Contexts
├───DTO
├───Enums
└───Migrations

安装依赖
API layer

  • Microsoft.AspNetCore.Mvc.NewtonsoftJson
  • Microsoft.AspNetCore.OData √
  • Microsoft.OData.ModelBuilder
  • Microsoft.EntityFrameworkCore √
  • Microsoft.EntityFrameworkCore.Design √
  • Microsoft.EntityFrameworkCore.SqlServer √
  • Microsoft.EntityFrameworkCore.Tools √
  • Swashbuckle.AspNetCore √
    EntityManager layer
    None
    Model layer
  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Design
  • Microsoft.EntityFrameworkCore.SqlServer

Microsoft DOc: Swagger/OpenAPI Swagger 项目已于 2015 年捐赠给 OpenAPI 计划,自此它被称为 OpenAPI。 是一个与语言无关的规范,用于描述 REST API。 它使计算机和用户无需直接访问源代码即可了解 REST API 的功能。

visual stuadio创建ASP.NET Core Web API项目模板,包含Enable OpenAPI support选项 即swagger页面
swagger
初始startup.cs的配置是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void ConfigureServices(IServiceCollection services)
{

services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1"));
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

配置加载名为swagger.json的接口描述,记作WebApplication1 v1的接口文档

swagger.json不会自动生成 从controller获取接口文档由下述swagger的实现包完成

Swashbuckle.AspNetCore

启用xml注释
右键API项目 -> Properties -> Build -> Output
Output path 为空 即项目根目录
勾选XML documentation file 并命名
xmlswagger

1
2
3
4
5
6
7
8
9
10
11
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
c.DocInclusionPredicate((name, api) => api.HttpMethod != null);

// Locate the XML file being generated by ASP.NET
var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "WebApplication1.API.xml", SearchOption.TopDirectoryOnly);

// Tell Swagger to use those XML comments.
xmlFiles.ToList().ForEach(xmlFile => c.IncludeXmlComments(xmlFile));
});

详情待续 参考Microsoft Doc:Swashbuckle入门

OData API的接口文档(存目)