利用矩阵分解实现图像压缩(实验)

news2024/11/27 2:34:52

机器学习的课程,老师布置了一个实验报告,当我看到实验内容,傻眼了,手写计算矩阵特征值和特征向量的函数,这给我整无语了,直接调用已有的不好吗,             我直接摆烂。

实验报告放这了,有哪个大佬会的评论区分享一下你的想法

实验图片  example.jpg

一、实验内容及要求

矩阵分解在机器学习领域有重要的地位。本实验通过动手编写程序,实现矩阵分解和图像压缩应用。要求如下:

1、利用numpy库编写两个函数,分别实现特征分解和奇异值分解。具体要求如下:

(1)输入为一个矩阵(ndarray格式),输出为分解后的2个(特征分解)或3个(SVD)矩阵。

(2)必须自己编写,不能调用numpy库或其他库中已有的矩阵分解方法/函数。

(3)自定义一个矩阵,作为参数传递给前面编写的函数,把结果和numpy库的线性代数包中的两个函数 numpy.linalg.eig()、numpy.linalg.svd()进行比较,观察结果是否一致。

2、利用前面编写的函数,分别对图像进行压缩并比较。具体要求如下:

(1)利用PIL库或其他库读入图像example.jgp,并转化为ndarray格式。

(2)利用前面编写的函数,对图像进行矩阵分解。

(3)取其TOP-K(K分别为10,50,100)个特征值/奇异值,和对应的特征向量/奇异向量,重建图像。

(4)把特征分解、奇异值分解两种方法得到的重建结果进行比较,画出图。

本实验旨在通过编写程序,实现矩阵分解和图像压缩应用

二.实验过程

1.求特征值和特征向量函数

eigensA

由于图像的矩阵是大型矩阵,使用 (入E -A)X=0 这种常规方法计算特征值和特征向量明显是不可靠的,这就要找一种能够快速计算大型矩阵的特征值和特征向量的方法,幂迭代和反迭代法是一个计算大矩阵的方法,该方法的基本思想是通过不断地将矩阵乘以一个向量,并对结果向量进行归一化,最终可以得到矩阵的最大特征值以及对应的特征向量。

eigensA函数通过调用power_iteration(A, num_iterations)函数进行幂迭代、通过调用inverse_iteration(A, mu, num_iterations)函数进行反迭代,完成矩阵A的特征值和特征向量的计算。

2.特征分解函数的实现

myEIG(A)

对于一个 n*n 的实对称矩阵 A , 其特征分解就是将 A 分解为下列形式:
A=QΛQ-1 。其中,Q是由特征向量构成的正交矩阵,Q-1 是其逆矩阵,Λ是对角矩阵,其对角线上的元素是矩阵A的特征值。

利用eigensA函数计算出矩阵的特征值和特征向量后,将特征值从大到小排序,可得出Q矩阵、Λ矩阵, 然后返回特征值向量Λ和特征向量矩阵Q

3.奇异值分解函数的实现:

mySVD(A)

任意一个矩阵都能写成 SVD 分解的形式。其形式如下:A=UΣV   
其中,U与V是正交矩阵,Σ是一个对角线上有奇异值的矩阵,它表示了矩阵 A“压缩”或“放大”的程度。

利用eigens()函数计算出A.T * A矩阵(左乘)的特征值和特征向量,则右奇异矩阵V为此矩阵的特征向量 ,奇异值为此矩阵的 特征值开根号

利用eigens()函数计算出A * A.T矩阵(右乘)的特征值和特征向量,则左奇异矩阵 U为此矩阵的特征向量。

推导过程如下:

 

通过前面函数的计算,我们先验证特征值分解和奇异值分解的计算结果对不对:

定义一个矩阵A为

A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

调用自写的特征值分解函数,则输出以下结果

自写evd的分解结果为:

t: [ 1.61168440e+01 -1.11684397e+00 -2.27320163e-10]

 vec: [[ 0.23197069 -0.02992263 -0.02992263]

 [ 0.52532209 -0.45894233 -0.45894233]

 [ 0.8186735  -0.88796203 -0.88796203]]

调用numpy库的特征值分解函数,则输出以下结果:

numpy库的evd分解结果为:

t: [ 1.61168440e+01 -1.11684397e+00 -4.22209278e-16]

 vec: [[-0.23197069 -0.78583024  0.40824829]

 [-0.52532209 -0.08675134 -0.81649658]

 [-0.8186735   0.61232756  0.40824829]]

比较输出的结果后,可以发现得到的矩阵分解结果是基本相同,就是有些精度损失。

但是,当把图片的大矩阵代入计算,计算了很久,代码直接崩溃,

我一步步debug,查看每个变量的矩阵长什么样,查找原因,发现了有除0错误,不知怎么解决

计算的结果相差很多,我百思不得其解,说明上面那个函数的方法不适合计算大矩阵

同理,调用自写的奇异值分解函数,输出以下结果

自写svd的分解结果为:

u: [[ 0.21483724 -0.88723069  0.08677199]

 [ 0.52058739 -0.24964395  0.73492663]

 [ 0.82633754  0.38794278  0.67257228]]

 sigma: [1.68481034e+01 1.06836951e+00 1.19249288e-07]

 v: [[ 0.47967118 -0.77669099  0.62161018]

 [ 0.57236779 -0.07568647 -0.24462121]

 [ 0.66506441  0.62531805  0.74415136]]

调用numpy库的奇异值分解函数,输出以下结果

numpy库的svd分解结果为:

u: [[-0.21483724  0.88723069  0.40824829]

 [-0.52058739  0.24964395 -0.81649658]

 [-0.82633754 -0.38794278  0.40824829]]

 sigma: [1.68481034e+01 1.06836951e+00 4.41842475e-16]

 v: [[-0.47967118 -0.57236779 -0.66506441]

 [-0.77669099 -0.07568647  0.62531805]

 [-0.40824829  0.81649658 -0.40824829]]

比较输出的结果后,可以发现得到的矩阵分解结果是基本相同,就是有些精度损失。

但是,当把图片的大矩阵代入计算,计算了很久,都计算不出特征值,说明计算的方法有误或者代码有bug

计算的结果相差很多,我百思不得其解,说明上面那个函数的方法不适合计算大矩阵

4.evd分解重构函数的实现

eig_restore()

对于一个 n*n 的实对称矩阵 A , 其特征分解就是将 A 分解为下列形式:
A=QΛQ-1 

通过前面的函数,我们已经算出矩阵Q和矩阵Λ,此时我们利用v = np.linalg.inv(vec) 这个函数算Q-1逆矩阵

根据k 的值,压缩对应的矩阵:

对于Q矩阵 ,取矩阵所有的行和前K列   Vex_k = vec[:, :K]

对于Q-1矩阵,取矩阵前K行和所有的列  V_k = v[:K, :]

对应Λ矩阵,取前k个值,并构造对角矩阵   t_k = np.diag(t[:K])  

Q-1这三个矩阵相乘,可得重构的结果 A_k

A_k = np.dot(Vex_k, np.dot(t_k, V_k))  

A_k矩阵不能直接显示图片,需要转为uint8类型

img_k = A_k.astype('uint8')

5.svd分解重构函数的实现

svd_restore()

对于一个 m*n 的矩阵 A , 其奇异值分解就是将 A 分解为下列形式:
A=UΣV

通过前面的函数,我们已经算出矩阵U, 奇异值向量Σ和矩阵V

根据k 的值,压缩对应的矩阵:

对于U矩阵 ,取矩阵所有的行和前K列   u_k = u[:, :K]
对于V矩阵 , 取矩阵前K行和所有的列  V_k = v[:K, :]
对应Σ矩阵,取前k个值,并构造对角矩阵   s_k = np.diag(sigma[:K])  

UΣV这三个矩阵相乘,可得重构的结果

A_k = np.dot(u_k, np.dot(s_k, V_k))  

A_k矩阵不能直接显示图片,需要转为uint8类型

img_k = A_k.astype('uint8')

6.图像压缩函数的实现

main()

读取图像,并把它转换成矩阵类型

接下来,分别调用前面实现的特征分解和奇异值分解函数对图像进行矩阵分解

接着,我们通过取TOP-K(K=10、50、100)个奇异值/特征值和对应的奇异向量/特征向量, 对分解后的矩阵进行压缩

用plt.imshow(image) 显示图像

观察最终结果

三.对实验中参数和结果的分析

实验输入图片如下:

(由于自写计算特征值和特征向量的函数压缩不得最后的结果,此图片压缩为调用numpy库计算的结果)

实验的图片大小为  512 x 512 ,所以计算出的特征值或奇异值也有512个。

K=10(大约占50分之一),取最大的前10个特征值或奇异值,从压缩的图片可以看出,svd压缩和eig压缩的图片都模糊,svd压缩的图片比eig压缩的图片清晰一点

K=50(大约占10分之一),取最大的前50个特征值或奇异值,从压缩的图片可以看出,svd压缩的图片有一些噪点,eig压缩有许多噪点,svd分解的图片比eig分解的图片清晰一些。

在k=50以前,图片都存在着相当多的噪点,基本属于不可用状态

K=100(大约占5分之一),取最大的前100个特征值或奇异值,从压缩的图片可以看出,svd压缩的图片几乎没有噪点,eig压缩的图片有一点噪点,svd压缩的图片和eig压缩的图片比较清晰

在 K 值较小的情况下,特征分解的重构效果与奇异值分解相差不大。而随着 K 值的增大,奇异值分解的重构效果越来越接近原图,而特征分解的重构效果则稍有下降。我们可以这样理解:K 值越大,保留的信息越多,因此奇异值分解的重构效果更好。

经过比较,两种分解方法得到的结果明显不一样,svd算法只取k=50进行压缩,图片的质量就比较好了,而eid算法要取100才得,所以svd算法分解矩阵压缩图片的性能要比eig要好。
 

四.总结

本实验使我了解到了矩阵分解在机器学习中的重要性,并通过编写函数实现了特征分解和奇异值分解,掌握了矩阵分解的实际应用,同时也加深了对图像压缩的理解。

通过这两种分解算法对图片压缩进行分析比较,svd算法能在保持图像表现的情况下将我们原图片压缩至一个非常小的水准,比较适用于图像压缩。而我们的evd不太适合直接使用,容易产生较多噪点影响观感。通过实验我们发现,在保留图像信息的同时,通过削减图像信息可以显著减少图像占用的存储空间,对于一些需要大量存储图片的应用具有很好的优化效果。同时,我们也认识到,在不同的应用场景下,对于 K 值的选择需要谨慎,需要在压缩率和图像重构质量之间进行平衡。

我认为这次的实验时非常有意义的,在检验了我们这一学期掌握的知识以外,还留给了我们很多的思考和精进的空间,虽然结果不尽人意,但也学到了很多东西。

代码

import numpy as np
import matplotlib.pyplot as plt


# ------------------求解特征值和特征向量---------------------
def power_iteration(A, num_iterations):
    n = A.shape[0]
    # 生成一个随机的初始向量
    x = np.random.rand(n)
    # 进行 num_iterations 次迭代
    for i in range(num_iterations):
        x = A @ x
        x = x / np.linalg.norm(x)
    # 计算特征值
    eigenvalue = x @ A @ x
    # 返回特征向量和特征值
    return x, eigenvalue


def inverse_iteration(A, mu, num_iterations):
    n = A.shape[0]
    # 生成一个随机的初始向量
    x = np.random.rand(n)
    # 进行平移变换
    A = A - mu * np.eye(n)
    # 进行 num_iterations 次迭代
    for i in range(num_iterations):
        x = np.linalg.solve(A, x)
        x = x / np.linalg.norm(x)
    # 计算特征值
    eigenvalue = 1 / x @ A @ x + mu
    # 返回特征向量和特征值
    return x, eigenvalue


def eigens(A):
    num_iterations = 200  # 迭代次数
    eps = 1e-10  # 迭代精度
    n = A.shape[0]
    # 初始化特征值和特征向量
    eigenvalues = np.zeros(n)
    eigenvectors = np.zeros((n, n))
    # 进行幂迭代
    for i in range(n):
        x, eigenvalue = power_iteration(A, num_iterations)
        eigenvalues[i] = eigenvalue
        eigenvectors[:, i] = x
        # 对 A 进行 Rayleigh 商的计算,用于反迭代
        mu = eigenvalue
        for k in range(num_iterations):
            x, eigenvalue = inverse_iteration(A, mu, num_iterations)
            if np.abs(eigenvalue - mu) < eps:
                break
            mu = eigenvalue
        # 将已经求解出来的特征值和特征向量从 A 中删除
        A = A - eigenvalue * np.outer(x, x)
    # 返回特征值和特征向量
    return eigenvalues, eigenvectors


# --------------------evd分解----------------------------
def myEIG(A):
    # 计算A的特征值和特征向量
    # eig_vals, eig_vecs = np.linalg.eig(A)   #调用numpy库的函数
    eig_vals, eig_vecs = eigens(A)
    """
    # 对特征值进行降序排序,并获取排序后的索引
    idx = np.argsort(eig_vals)[::-1]
    # 根据索引重新排列特征值和特征向量
    eig_vals = eig_vals[idx]
    eig_vecs = eig_vecs[:, idx]
    """
    # 返回特征分解后的两个矩阵
    return eig_vals, eig_vecs


# -------------svd分解------------------
def mySVD(A):
    # 计算A的转置矩阵
    A_T = A.T
    # 计算A_T * A的特征值和特征向量
    # eig_vals, eig_vecs = np.linalg.eig(A_T @ A)  #调用numpy库的函数
    eig_vals, eig_vecs = eigens(A_T @ A)
    eig_vals = np.abs(eig_vals)  # 取绝对值
    # 对特征值进行降序排序,并获取排序后的索引
    idx = np.argsort(eig_vals)[::-1]
    # 根据索引重新排列特征值和特征向量
    eig_vals = eig_vals[idx]
    eig_vecs = eig_vecs[:, idx]
    # 构造对角矩阵,存放奇异值(特征值的平方根)
    sigma = np.sqrt(eig_vals)

    # 计算右奇异向量矩阵V(就是特征向量矩阵)
    V = eig_vecs
    # t, U = np.linalg.eig(A @ A_T)    #调用numpy库的函数
    t, U = eigens(A @ A_T)
    U = U[:, idx]
    # 返回奇异值分解后的三个矩阵
    return U, sigma, V


# -------------------evd重建图像矩阵--------------
def eig_restore(t, vec, K):
    K = min(len(t) - 1, K)  # 当K超过sigma的长度时会造成越界
    v = np.linalg.inv(vec)  # 求特征向量逆矩阵
    Vex_k = vec[:, :K]  # 取矩阵所有的行和前K列
    V_k = v[:K, :]  # 取矩阵前K行和所有的列
    # 构造对角矩阵
    t_k = np.diag(t[:K])  # 取前K个特征值
    # 压缩后的图像矩阵
    A_k = np.dot(Vex_k, np.dot(t_k, V_k))  # A=V*T* V^-1
    img_k = A_k.astype('uint8')
    return img_k


# ----------------------svd重建图像矩阵----------------
def svd_restore(sigma, u, v, K):
    K = min(len(sigma) - 1, K)  # 当K超过sigma的长度时会造成越界
    u_k = u[:, :K]  # 取矩阵所有的行和前K列
    V_k = v[:K, :]  # 取矩阵前K行和所有的列
    # 构造对角矩阵
    s_k = np.diag(sigma[:K])  # 取前K个特征值
    # 压缩后的图像矩阵
    A_k = np.dot(u_k, np.dot(s_k, V_k))
    SigRecon = A_k.astype('uint8')
    return SigRecon


# -----------------------比较自写的两种算法-----------------
def compare():
    # 自定义一个矩阵,作为参数传递给前面编写的函数,测试
    A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    # A = np.array([[1, 2, 3,4], [5, 6, 7,8], [9,10,11,12],[13,14,15,16]])

    # ----------------evd-----------------
    t, vec = myEIG(A)
    print("自写evd的分解结果为:")
    print('t:', t, '\n', 'vec:', vec)
    T, Vec = np.linalg.eig(A)
    print("numpy库的evd分解结果为:")
    print('t:', T, '\n', 'vec:', Vec, '\n')

    # -------------------svd--------------
    u, s, v = mySVD(A)
    print("自写svd的分解结果为:")
    print('u:', u, '\n', 'sigma:', s, '\n', 'v:', v)
    U, Sigma, V = np.linalg.svd(A)
    print("numpy库的svd分解结果为:")
    print('u:', U, '\n', 'sigma:', Sigma, '\n', 'v:', V, '\n')


def main():
    # 导入灰度图片,单矩阵
    img = plt.imread('./example.jpg')
    print('图像矩阵大小为::', img.shape)
    img = np.array(img)

    # ------分解算法-----------
    # evd
    T, Vec = np.linalg.eig(img)
    # T, Vec = myEIG(img)
    # svd
    u, sigma, v = np.linalg.svd(img)
    # u, sigma, v = mySVD(img)

    i = 0
    for k in [10, 50, 100]:
        image = eig_restore(T, Vec, k)
        i += 1
        plt.subplot(3, 3, i)  # 表示第i张图片
        plt.imshow(image)
        plt.xlabel(f'eig decomposition-{k}')
        plt.xticks([])
        plt.yticks([])

    i = 3
    for k in [10, 50, 100]:
        image = svd_restore(sigma, u, v, k)
        i += 1
        plt.subplot(3, 3, i)
        plt.imshow(image)
        plt.xlabel(f'svd decomposition-{k}')
        plt.xticks([])  # 两行是消除每张图片自己单独的横纵坐标,不然每张图片会有单独的横纵坐标,影响美观
        plt.yticks([])

    plt.show()


if __name__ == '__main__':
    compare()  # 比较两种算法

    main()  # 压缩图片

参考资料:微信公众平台 (qq.com)

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

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

相关文章

【C#图解教程】 第六章 方法(上)

方法的结构 方法是一块具有名称的代码&#xff0c;在类和结构中都经常用到 局部变量 局部变量位于方法内部&#xff0c;在方法中声明时产生&#xff0c;在方法执行结束时结束 类型推断与var关键字 某些情况下&#xff0c;在声明的开始部分包含类型名是多余的&#xff0c;因为…

Python-python判断语句:布尔类型和比较运算符、if语句的基本格式、if else语句、if el if else语句、判断语句的嵌套、实战案例

版本说明 当前版本号[20230601]。 版本修改说明20230601初版 知识总览图 目录 文章目录 版本说明知识总览图目录Python判断语句布尔类型和比较运算符布尔类型布尔类型的定义 比较运算符 if语句的基本格式if判断语句if语句的注意点 if else 语句语句注意点 if elif else语句注…

7min 到 40s:SpringBoot 优化居然可以玩出这么多花样!

0 背景 公司 SpringBoot 项目在日常开发过程中发现服务启动过程异常缓慢&#xff0c;常常需要6-7分钟才能暴露端口&#xff0c;严重降低开发效率。通过 SpringBoot 的 SpringApplicationRunListener 、BeanPostProcessor 原理和源码调试等手段排查发现&#xff0c;在 Bean 扫描…

“灵巧小工具”一个将图片处理成打印纸尺寸的丰富功能完全免费无水印的图片处理工具

今天推荐一款微信小程序“灵巧小工具”&#xff0c;经常有打印图片需求的用户赶紧收藏了。 可以先扫码体验一番&#xff1a; 下面介绍一下它的主要功能&#xff1a; 1.照片&#xff08;1寸、2寸、5寸、6寸&#xff09; 支持1寸&#xff0c;2寸照片自动排版&#xff0c;生成相纸…

JavaSE】Java基础语法(四十):UDP通信程序

文章目录 1. UDP发送数据2. UDP接收数据【应用】3. UDP通信程序练习【应用】4. UDP三种通讯方式 1. UDP发送数据 Java中的UDP通信 UDP协议是一种不可靠的网络协议&#xff0c;它在通信的两端各建立一个Socket对象&#xff0c;但是这两个 Socket只是发送&#xff0c;接收数据的对…

IPv6 6to4隧道配置和验证实验

IPv6 6to4隧道配置和验证实验 【实验目的】 熟悉IPv6 6to4隧道的概念。 掌握IPv6和IPv4共存的实现方法。 掌握IPv6 6to4地址编址规则。 掌握IPv6 6to4隧道的配置。 验证配置。 【实验拓扑】 设备参数如下表所示。 设备 接口 IP地址 子网掩码 默认网关 R1 S0/0 19…

cuda编程学习——CUDA共享内存性能优化(九)

前言 参考资料&#xff1a; 高升博客 《CUDA C编程权威指南》 以及 CUDA官方文档 CUDA编程&#xff1a;基础与实践 樊哲勇 文章所有代码可在我的GitHub获得&#xff0c;后续会慢慢更新 文章、讲解视频同步更新公众《AI知识物语》&#xff0c;B站&#xff1a;出门吃三碗饭 …

绝不可错过!R语言与ggplot2实现SCI论文数据分析神器

一、介绍 1.1 R语言和ggplot2 语言是一种强大的数据分析和统计建模工具&#xff0c;具有广泛的应用领域。 ggplot2是基于R语言的数据可视化工具&#xff0c;具有强大的绘图功能和灵活性。 1.2 数据分析中的重要性 R语言和ggplot2在数据分析中具有广泛的应用&#xff0c;能够帮助…

有些香港云主机为啥更容易遭遇停机风险?

​对于搭建过外贸网站的站长们来说&#xff0c;在面对香港云主机的选择时&#xff0c;往往遇到且出现较为频繁的两个词便是&#xff1a;免费香港云主机和付费香港云主机。其中&#xff0c;一些所谓的免费香港云主机&#xff0c;尤其是长久免费使用&#xff0c;恐怕用户们就要承…

ES6中函数新增了哪些扩展?

参数 &#x1f355;&#x1f355;&#x1f355;ES6允许为函数的参数设置默认值 函数的形参是默认声明的&#xff0c;不能使用let或const再次声明 function foo(x5){let x 1;//errconst x 2;//err }参数默认值可以与解构赋值的默认值结合起来使用 function foo({x,y 5}){co…

华为OD机试真题B卷 Java 实现【分奖金】,附详细解题思路

一、题目描述 公司老板做了一笔大生意&#xff0c;想要给每位员工分配一些奖金&#xff0c;想通过游戏的方式来决定每个人分多少钱。按照员工的工号顺序&#xff0c;每个人随机抽取一个数字。按照工号的顺序往后排列&#xff0c;遇到第一个数字比自己数字大的&#xff0c;那么…

OpenGL超级宝典第八章学习笔记:基元处理之曲面细分

前言 本篇在讲什么 OpenGL蓝宝书第八章学习笔记之曲面细分 本篇适合什么 适合初学OpenGL的小白 本篇需要什么 对C语法有简单认知 对OpenGL有简单认知 最好是有OpenGL超级宝典蓝宝书 依赖Visual Studio编辑器 本篇的特色 具有全流程的图文教学 重实践&#xff0c;轻…

CI/CD:如何使用 GitLab 执行 SpringBoot 前后端分离项目的持续集成与持续交付(持续部署)?

一、GitLab Runner 安装 官网各系统 & 各方式安装说明&#xff1a;https://docs.gitlab.com/runner/install/ 本文基于阿里云 CentOS 安装 GitLab Runner &#xff08;Linux Shell 方式&#xff0c;非 Docker 方式&#xff09; 1.1 GitLab Runner 介绍 GitLab Runner 是…

GPT面试知识点

0.GPT的模型结构 GPT是一个基于Transformer的生成式预训练模型。使用Transformer中的解码器部分 它由一系列的模块化的Transformer Blocks组成。每一个Block包含一个多头自注意力机制(Multi-Head Self-Attention mechanism)以及一个位置前馈网络(position-wise feedforward n…

Redis - 原理篇

✨作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github、语雀 &#x1f389;公众号&#xff1a;猫十二懿 Redis(原理篇) 一、数据结构 1.1 动态字符串SDS 我们都知道Redis中保存的Key是字符串&#xff0c;value往往是字符串或者字…

Spring Boot如何实现分布式锁的自动释放

Spring Boot如何实现分布式锁的自动释放 在分布式系统中&#xff0c;为了保证数据的一致性和可靠性&#xff0c;常常需要使用分布式锁。在实际开发中&#xff0c;我们可以使用 Redis、Zookeeper 等分布式系统来实现分布式锁。本文将介绍如何使用 Spring Boot 来实现分布式锁的…

Android Input子系统 - kernel

目录 前言 数据结构 输入子系统流程 前言 上一节有展示Android Input子系统的架构图,这里我们关心Linux kernel层 可以看到kernel层分为三层: 输入子系统设备驱动:处理与硬件相关的信息,调用input API注册输入设备,并把数据往上报 输入子系统核心层:为事件处理层和设…

关于QGroundControl的软件架构的理解

首先QGC是基于QT平台开发&#xff0c;个人理解软件架构即为项目前后端结构&#xff0c;以及前后端数据交互的逻辑。下面是对QGroundControl源码的一些个人理解&#xff0c;写这个博客只是为了记录下来&#xff0c;防止时间久了忘记&#xff0c;过程中看了一些大佬的博客来帮助理…

服务运营 |摘要: Healthcare Management Science 近期论文汇总

推文作者&#xff1a;李舒湉 罗毓灵 编者按 Healthcare Management Science 近期论文汇总 Healthcare Management Science 论文精选&#xff08;三月下&#xff09; 1Monitoring policy in the context of preventive treatment of cardiovascular disease https://link.sprin…

SQL——视图检查选项 local

create table stue ( id int auto_increment primary key comment 客户编号, name varchar(20) comment 客户名称, mima varchar(100) comment客户密码 , phonr varchar(20) comment 客户电话, xb char(2) …