本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价。图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏。

本文需要OpenCV contrib库,OpenCV contrib库的编译安装见:

OpenCV_contrib库在windows下编译使用指南

本文所有代码见:

OpenCV-Practical-Exercise

1 OpenCV中图像质量评价算法介绍

1.1 相关背景

图像质量评价(IQA)算法以任意图像作为输入,输出质量分数作为输出。有三种类型的IQA:

  1. 全参考图像质量评价,适用情形:一个“干净”参考(非扭曲)图像以衡量扭曲图像的质量。此度量可用于评估图像压缩算法的质量。
  2. 半参考图像质量评价,适用情形:如果没有参考图像,而是具有一些选择性信息的图像(例如,水印图像)来比较和测量失真图像的质量。
  3. 无参考图像质量评价,适用情形:算法得到的唯一输入是要测量其质量的图像。

在OpenCV contrib的quality模块中一共有提供了5种图像质量评价算法,按上面的类别分仅提供全参考图像质量评价和无参考图像质量评价两种类别的算法,没有半参考图像质量评价算法。官方代码地址见quality,其中包含的5种图像质量评价算法具体如下:

  • 均方误差 Mean squared error (MSE)
  • 峰值信噪比 Peak signal-to-noise ratio (PSNR)
  • 结构相似性 Structural similarity (SSIM)
  • 梯度幅度相似性偏差 Gradient Magnitude Similarity Deviation (GMSD)
  • 盲/无参考图像空间质量评估器 Blind/Referenceless Image Spatial Quality Evaluation (BRISQUE)

这5种图像质量评价算法中,除了BRISQUE是无参考图像质量评价算法外,其他都是全参考图像质量评价。本文不具体介绍这些算法的原理,仅介绍这些算法的应用。想知道具体原理见链接:

事实上,各种图像质量评估算法都是寻找不同数学公式给出一个评判结果,差异并不那么大,仅知道使用即可。就全参考图像质量评价算法而言,一般情况下GMSD效果比其他全参考图像质量评价算法效果好。无参考图形质量评价以BRISQUE为代表。半参考图像质量评价更多用于发论文,实际应用不多。近年来也有深度学习应用于图像质量评估,但是效果还不错,但速度太慢。关于图像质量评估算法具体进一步研究可参考链接:图像质量评估指标(Image Quality Assessment,IQA)

1.2 OpenCV中图像质量评价算法接口介绍

OpenCV中图像质量评价算法接口分为静态方法和实例方法,静态方法固定快捷,实例方法灵活性强。其中全参考图像质量评价算法接口类似,只需要更改函数名即可,因为各种参考图像质量算法其实都数学公式应用变换数学公式即可。BRISQUE在[OpenCV实战]37 图像质量评价BRISQUE中已经提到如何使用,不过用起来相对opencv_contrib库中的quality模块麻烦,唯一好处[OpenCV实战]37 图像质量评价BRISQUE提到的方法不需要编译opencv_contrib库,但是实际建议使用opencv_contrib库的quality模块来实现图像质量评估算法。

1.2.1 opencv_contrib中全参考图像质量评价算法具体接口

C++/静态方法

// output quality map
// 质量结果图
// 质量结果图quality_map就是检测图像和基准图像各个像素点差值图像
cv::Mat quality_map;
// compute MSE via static method
// cv::noArray() if not interested in output quality maps
// 静态方法,一步到位
// 如果不想获得质量结果图,将quality_map替换为noArray()
cv::Scalar result_static = quality::QualityMSE::compute(img1, img2, quality_map);

C++/实例方法

// alternatively, compute MSE via instance
cv::Ptr<quality::QualityBase> ptr = quality::QualityMSE::create(img1);
// compute MSE, compare img1 vs img2
cv::Scalar result = ptr->compute(img2);
ptr->getQualityMap(quality_map);

Python/静态方法

# 静态方法,一步到位
# 质量结果图quality_map就是检测图像和基准图像各个像素点差值结果
result_static, quality_map = cv2.quality.QualityMSE_compute(img1, img2)

Python/实例方法

obj = cv2.quality.QualityMSE_create(img1)
result = obj.compute(img2)
quality_map = obj.getQualityMap()

1.2.2 opencv_contrib中无参考图像质量评价算法具体接口

C++/静态方法

// path to the trained model
cv::String model_path = "./model/brisque_model_live.yml";
// path to range file
cv::String range_path = "./model/brisque_range_live.yml";
// 静态计算方法
cv::Scalar result_static = quality::QualityBRISQUE::compute(img, model_path, range_path);

C++/实例方法

cv::Ptr<quality::QualityBase> ptr = quality::QualityBRISQUE::create(model_path, range_path);
// computes BRISQUE score for img
cv::Scalar result = ptr->compute(img)

Python/静态方法

# path to the trained model
model_path = "./model/brisque_model_live.yml"
# path to range file
range_path = "./model/brisque_range_live.yml"
# 静态计算方法
result_static = cv2.quality.QualityBRISQUE_compute(img, model_path, range_path)

Python/实例方法

obj = cv2.quality.QualityBRISQUE_create(model_path, range_path)
result = obj.compute(img)

1.2.3 opencv_contrib中图像质量评价算法输出参数介绍

对于静态方法和实例方法输出结果一样的,都是输出在不同颜色通道下的结果,比如对于全参考图像质量评价算法而言RGB图就是分别输出R、G、B三个通道的结果,所以最后需要求均值。对BRISQUE而言不管是彩色图还是灰度图都只输出一个0到100之间的数。各个算法的结果特点如下表所示:

算法 输出结果特点
MSE 结果越小,检测图像和基准图像的差距越小
PSNR 结果越小,检测图像和基准图像的差距越大
GMSD 结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越大
SSIM 结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越小
BRISQUE 结果为一个0到100之间的数,越小表示检测图像质量越好

2 代码实现与结果分析

2.1 代码实现

本文所提供的代码可以对者图像进行质量评价。本文提供C++和Python代码实现,但是MSE的Python实例计算代码可能有问题,可以用Python静态方法代替,所有代码如下:

C++

#include <opencv2/opencv.hpp>
#include <opencv2/quality.hpp> using namespace std;
using namespace cv; // 计算结果均值
double calMEAN(Scalar result)
{
int i = 0;
double sum = 0;
// 计算总和
for (auto val : result.val)
{
if (0 == val || isinf(val))
{
break;
}
sum += val;
i++;
}
return sum / i;
} // 均方误差 MSE
double MSE(Mat img1, Mat img2)
{
// output quality map
// 质量结果图
// 质量结果图quality_map就是检测图像和基准图像各个像素点差值图像
cv::Mat quality_map;
// compute MSE via static method
// cv::noArray() if not interested in output quality maps
// 静态方法,一步到位
// 如果不想获得质量结果图,将quality_map替换为noArray()
cv::Scalar result_static = quality::QualityMSE::compute(img1, img2, quality_map); /* 另外一种动态计算的方法
// alternatively, compute MSE via instance
cv::Ptr<quality::QualityBase> ptr = quality::QualityMSE::create(img1);
// compute MSE, compare img1 vs img2
cv::Scalar result = ptr->compute(img2);
ptr->getQualityMap(quality_map);
*/ return calMEAN(result_static);
} // 峰值信噪比 PSNR
double PSNR(Mat img1, Mat img2)
{
// 质量结果图
// 质量结果图quality_map就是检测图像和基准图像各个像素点差值图像
cv::Mat quality_map;
// 静态方法,一步到位
// 如果不想获得质量结果图,将quality_map替换为noArray()
// 第四个参数为PSNR计算公式中的MAX,即图片可能的最大像素值,通常为255
cv::Scalar result_static = quality::QualityPSNR::compute(img1, img2, quality_map, 255.0); /* 另外一种动态计算的方法
cv::Ptr<quality::QualityBase> ptr = quality::QualityPSNR::create(img1, 255.0);
cv::Scalar result = ptr->compute(img2);
ptr->getQualityMap(quality_map);*/ return calMEAN(result_static);
} // 梯度幅度相似性偏差 GMSD
double GMSD(Mat img1, Mat img2)
{
// 质量结果图
// 质量结果图quality_map就是检测图像和基准图像各个像素点差值图像
cv::Mat quality_map;
// 静态方法,一步到位
// 如果不想获得质量结果图,将quality_map替换为noArray()
cv::Scalar result_static = quality::QualityGMSD::compute(img1, img2, quality_map);
/* 另外一种动态计算的方法
cv::Ptr<quality::QualityBase> ptr = quality::QualityGMSD::create(img1);
cv::Scalar result = ptr->compute(img2);
ptr->getQualityMap(quality_map);*/
return calMEAN(result_static);
} // 结构相似性 SSIM
double SSIM(Mat img1, Mat img2)
{
// 质量结果图
// 质量结果图quality_map就是检测图像和基准图像各个像素点差值图像
cv::Mat quality_map;
// 静态方法,一步到位
// 如果不想获得质量结果图,将quality_map替换为noArray()
cv::Scalar result_static = quality::QualitySSIM::compute(img1, img2, quality_map);
/* 另外一种动态计算的方法
cv::Ptr<quality::QualityBase> ptr = quality::QualitySSIM::create(img1);
cv::Scalar result = ptr->compute(img2);
ptr->getQualityMap(quality_map);*/
return calMEAN(result_static);
} // 盲/无参考图像空间质量评估器 BRISQUE
double BRISQUE(Mat img)
{
// path to the trained model
cv::String model_path = "./model/brisque_model_live.yml";
// path to range file
cv::String range_path = "./model/brisque_range_live.yml";
// 静态计算方法
cv::Scalar result_static = quality::QualityBRISQUE::compute(img, model_path, range_path);
/* 另外一种动态计算的方法
cv::Ptr<quality::QualityBase> ptr = quality::QualityBRISQUE::create(model_path, range_path);
// computes BRISQUE score for img
cv::Scalar result = ptr->compute(img);*/
return calMEAN(result_static);
} void qualityCompute(String methodType, Mat img1, Mat img2)
{
// 算法结果和算法耗时
double result;
TickMeter costTime; costTime.start();
if ("MSE" == methodType)
result = MSE(img1, img2);
else if ("PSNR" == methodType)
result = PSNR(img1, img2);
else if ("PSNR" == methodType)
result = PSNR(img1, img2);
else if ("GMSD" == methodType)
result = GMSD(img1, img2);
else if ("SSIM" == methodType)
result = SSIM(img1, img2);
else if ("BRISQUE" == methodType)
result = BRISQUE(img2);
costTime.stop();
cout << methodType << "_result is: " << result << endl;
cout << methodType << "_cost time is: " << costTime.getTimeSec() / costTime.getCounter() << " s" << endl;
} int main()
{
// img1为基准图像,img2为检测图像
cv::Mat img1, img2;
img1 = cv::imread("image/original-rotated-image.jpg");
img2 = cv::imread("image/noise-version.jpg"); if (img1.empty() || img2.empty())
{
cout << "img empty" << endl;
return 0;
} // 结果越小,检测图像和基准图像的差距越小
qualityCompute("MSE", img1, img2);
// 结果越小,检测图像和基准图像的差距越大
qualityCompute("PSNR", img1, img2);
// 结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越大
qualityCompute("GMSD", img1, img2);
// 结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越小
qualityCompute("SSIM", img1, img2);
// BRISQUE不需要基准图像
// 结果为一个0到100之间的数,越小表示检测图像质量越好
qualityCompute("BRISQUE", cv::Mat{}, img2);
system("pause");
return 0;
}

Python

# -*- coding: utf-8 -*-
"""
Created on Fri Oct 9 05:27:28 2020 @author: luohenyueji
""" import cv2
import numpy as np
import time # ----- 时间装饰器,打印运行结果和运行时间
def usetime(func):
def inner(*args, **kwargs):
time_start = time.time()
# 装饰的函数在此运行
result = func(*args, **kwargs)
time_run = time.time() - time_start
# 打印结果
print(func.__name__ + '_result is: {:.3f}'.format(result))
# 打印运行时间
print(func.__name__ + '_cost time is: {:.3f} s'.format(time_run)) return inner # ----- 均方误差 MSE
@usetime
def MSE(img1, img2):
# 静态方法,一步到位
# 质量结果图quality_map就是检测图像和基准图像各个像素点差值结果
result_static, quality_map = cv2.quality.QualityMSE_compute(img1, img2)
# 另外一种动态计算的方法,但是MSE的计算可能有问题
# obj = cv2.quality.QualityMSE_create(img1)
# result = obj.compute(img2)
# quality_map = obj.getQualityMap()
# 计算均值
score = np.mean([i for i in result_static if (i != 0 and not np.isinf(i))])
score = 0 if np.isnan(score) else score
return score # ----- 峰值信噪比 PSNR
@usetime
def PSNR(img1, img2):
# 静态方法,一步到位
# 质量结果图quality_map就是检测图像和基准图像各个像素点差值结果
# maxPixelValue参数为PSNR计算公式中的MAX,即图片可能的最大像素值,通常为255
result_static, quality_map = cv2.quality.QualityPSNR_compute(img1, img2, maxPixelValue=255)
# 另外一种动态计算的方法
# obj = cv2.quality.QualityPSNR_create(img1, maxPixelValue=255)
# result = obj.compute(img2)
# quality_map = obj.getQualityMap()
# 计算均值
score = np.mean([i for i in result_static if (i != 0 and not np.isinf(i))])
return score # ----- 梯度幅度相似性偏差 GMSD
@usetime
def GMSD(img1, img2):
# 静态方法,一步到位
# 质量结果图quality_map就是检测图像和基准图像各个像素点差值结果
result_static, quality_map = cv2.quality.QualityGMSD_compute(img1, img2)
# 另外一种动态计算的方法
# obj = cv2.quality.QualityGMSD_create(img1)
# result = obj.compute(img2)
# quality_map = obj.getQualityMap()
# 计算均值
score = np.mean([i for i in result_static if (i != 0 and not np.isinf(i))])
score = 0 if np.isnan(score) else score
return score # ----- 结构相似性 SSIM
@usetime
def SSIM(img1, img2):
# 静态方法,一步到位
# 质量结果图quality_map就是检测图像和基准图像各个像素点差值结果
result_static, quality_map = cv2.quality.QualitySSIM_compute(img1, img2)
# 另外一种动态计算的方法
# obj = cv2.quality.QualitySSIM_create(img1)
# result = obj.compute(img2)
# quality_map = obj.getQualityMap()
# 计算均值
score = np.mean([i for i in result_static if (i != 0 and not np.isinf(i))])
score = 0 if np.isnan(score) else score
return score # ----- 盲/无参考图像空间质量评估器 BRISQUE
@usetime
def BRISQUE(img):
# path to the trained model
model_path = "./model/brisque_model_live.yml"
# path to range file
range_path = "./model/brisque_range_live.yml"
# 静态计算方法
result_static = cv2.quality.QualityBRISQUE_compute(img, model_path, range_path)
# # 另外一种动态计算的方法
# obj = cv2.quality.QualityBRISQUE_create(model_path, range_path)
# result = obj.compute(img)
# 计算均值
score = np.mean([i for i in result_static if (i != 0 and not np.isinf(i))])
score = 0 if np.isnan(score) else score
return score def main():
# img1为基准图像,img2为检测图像
img1 = cv2.imread("image/cut-original-rotated-image.jpg")
img2 = cv2.imread("image/cut-noise-version.jpg")
if img1 is None or img2 is None:
print("img empty")
return
# 结果越小,检测图像和基准图像的差距越小
MSE(img1, img2)
# 结果越小,检测图像和基准图像的差距越大
PSNR(img1, img2)
# 结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越大
GMSD(img1, img2)
# 结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越小
SSIM(img1, img2)
# 结果为一个0到100之间的数,越小表示检测图像质量越好
BRISQUE(img2) if __name__ == '__main__':
main()

2.2 结果分析

上面的代码实现了对不同图片的图像质量诊断,并输出各种方法在不同图像下的评分和方法检测速度。速度计算主要基于C++代码。
具体检测结果如下表所示,其中-nan(ind)表示结果出错,通常是两张图像一样。原图下的结果是原图和原图比,模糊图片和噪声图片是与原图为基准图片比较的结果。按清晰度而言,原图>模糊图片>噪声图片。
下面分别显示分辨率为612x816和中心裁剪分辨率305x305的结果。

结果 原图/分辨率612x816 模糊图片/分辨率612x816 噪声图片/分辨率612x816
方法
MSE -nan(ind) 1490.28 1734.03
PSNR -nan(ind) 16.3989 15.7454
GMSD -nan(ind) 0.209512 0.199491
SSIM 1 0.30256 0.482258
BRISQUE 53.3901 63.4859 71.2059
结果 原图/分辨率305x305 模糊图片/分辨率305x305 噪声图片/分辨率305x305
方法
MSE -nan(ind) 1303.96 984.486
PSNR -nan(ind) 16.9784 18.2243
GMSD -nan(ind) 0.111176 0.113035
SSIM 1 0.30256 0.687856
BRISQUE 56.1736 42.0616 73.3258

各个方法具体检测速度如下表所示:

速度/s 原图/分辨率612x816 模糊图片/分辨率612x816 噪声图片/分辨率612x816
方法
MSE 0.029 0.021 0.020
PSNR 0.017 0.019 0.019
GMSD 0.032 0.031 0.032
SSIM 0.084 0.086 0.084
BRISQUE 0.068 0.073 0.071
速度/s 原图/分辨率305x305 模糊图片/分辨率305x305 噪声图片/分辨率305x305
方法
MSE 0.006 0.005 0.005
PSNR 0.004 0.005 0.004
GMSD 0.012 0.011 0.012
SSIM 0.025 0.031 0.033
BRISQUE 0.027 0.028 0.028

从上面的结果可以得到如下分析:

  1. 对于612x816分辨率图片,结果正确的有MSE,GMSD,BRISQUE;对于305x305分辨率图片,如果从局部上来看,噪声图片和模糊图片清晰图差不太多,结果正确的有PSNR,GMSD。然而对于BRISQUE模糊图片的清晰度评分比原图高。所以通常情况下,有参考图片,GMSD准确率最高,其他方法并不靠谱,BRISQUE需要更加完整的大图才有好的效果。
  2. 就速度而言,图像分辨率越高,各个方法耗时也越多,毕竟都是靠图像像素点差值公式计算的,不过都能在1s以内获得结果。
  3. 如果有有参图像,最好用GMSD。BRISQUE更适合高分辨率图片,如果要低分辨率使用,建议自己重新训练模型,毕竟BRISQUE的模型太老了。关于BRISQUE模型训练见:quality/samples

总而言之,现在图像质量评价算法都只能针对某种特定环境使用,在实际最好针对每一种图像噪声情况设定一种判定算法,现在各个视频检测平台也都是这样做的。如果普通使用看看GMSD和BRISQUE即可。

3 参考

3.1 参考代码

3.2 参考文章

[OpenCV实战]48 基于OpenCV实现图像质量评价的更多相关文章

  1. [OpenCV实战]47 基于OpenCV实现视觉显著性检测

    人类具有一种视觉注意机制,即当面对一个场景时,会选择性地忽略不感兴趣的区域,聚焦于感兴趣的区域.这些感兴趣的区域称为显著性区域.视觉显著性检测(Visual Saliency Detection,VS ...

  2. [OpenCV实战]45 基于OpenCV实现图像哈希算法

    目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash).图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅 ...

  3. [OpenCV实战]28 基于OpenCV的GUI库cvui

    目录 1 cvui的使用 1.1 如何在您的应用程序中添加cvui 1.2 基本的"hello world"应用程序 2 更高级的应用 3 代码 4 参考 有很多很棒的GUI库,例 ...

  4. [OpenCV实战]38 基于OpenCV的相机标定

    文章目录 1 什么是相机标定? 2 图像形成几何学 2.1 设定 2.1.1 世界坐标系 2.1.2 相机坐标系 2.1.3 图像坐标系 2.2 图像形成方法总结 3 基于OpenCV的相机标定原理 ...

  5. [OpenCV实战]26 基于OpenCV实现选择性搜索算法

    目录 1 背景 1.1 目标检测与目标识别 1.2 滑动窗口算法 1.3 候选区域选择算法 2 选择性搜索算法 2.1 什么是选择性搜索? 2.2 选择性搜索相似性度量 2.3 结果 3 代码 4 参 ...

  6. [OpenCV实战]51 基于OpenCV实现图像极坐标变换与逆变换

    在图像处理领域中,经常通过极坐标与笛卡尔直角坐标的互转来实现图像中圆形转为方形,或者通过极坐标反变换实现方形转圆形.例如钟表的表盘,人眼虹膜,医学血管断层都需要用到极坐标变换来实现圆转方. 文章目录 ...

  7. [OpenCV实战]11 基于OpenCV的二维码扫描器

    目录 1 二维码(QRCode)扫描 2 结果 3 参考 在这篇文章中,我们将看到如何使用OpenCV扫描二维码.您将需要OpenCV3.4.4或4.0.0及更高版本来运行代码. 1 二维码(QRCo ...

  8. [OpenCV实战]15 基于深度学习的目标跟踪算法GOTURN

    目录 1 什么是对象跟踪和GOTURN 2 在OpenCV中使用GOTURN 3 GOTURN优缺点 4 参考 在这篇文章中,我们将学习一种基于深度学习的目标跟踪算法GOTURN.GOTURN在Caf ...

  9. [OpenCV实战]5 基于深度学习的文本检测

    目录 1 网络加载 2 读取图像 3 前向传播 4 处理输出 3结果和代码 3.1结果 3.2 代码 参考 在这篇文章中,我们将逐字逐句地尝试找到图片中的单词!基于最近的一篇论文进行文字检测. EAS ...

随机推荐

  1. 工厂方法在Spring源码中的运用

    我们都知道Spring中IOC是使用的工厂模式,但是对于实现细节就一知半解了,今天这篇文章就带大家解读Spring中是如何使用工厂模式的. 在上篇文章中我们懂了什么是工厂模式,这篇文章就带着学过的概念 ...

  2. 如何去了解Spring

    对于你想了解的技术 官方总是一个合适的选择 首先,我们所指的Spring 一般指的是Spring Framework,伴随着的时代的进步,Spring全家桶也逐渐完善起来 Spring 1.Why S ...

  3. OCI runtime exec failed: exec failed: unable to start container process: exec: "mongo": executable file not found in $PATH: unknown

    前言: 今天按照以往在Docker安装MongoDB的方式安装,但是到最后使用mongo命令执行mongodb命令的时候一直执行不成功,最后还是按照官网的Issues解决了. 创建并运行一个Mongo ...

  4. 使用LEFT JOIN 统计左右存在的数据

    最近做了一个数据模块的统计,统计企业收款.发票相关的数据,开始统计是比较简单,后面再拆分账套统计就有点小复杂,本文做一个简单的记录. 需求 企业表 企业表t_company有如下字段:标识id.企业名 ...

  5. 消息队列之RabbitMQ介绍与运用

    RabbitMQ 说明 本章,我们主要从RabbitMQ简介.RabbitMQ安装.RabbitMQ常用命令.RabbitMQ架构模式.RabbitMQ使用.Quick.RabbitMQPlus的使用 ...

  6. Python数据分析:实用向

    文件处理 导包 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns ...

  7. Linux--多线程(二)

    线程的同步和互斥 基本概念 概述:现在操作系统基本都是多任务的操作系统,同时有大量可以调度的实体在运行.在多任务操作系统当中,同时运行的多个任务可能: 都需要访问/使用同一种资源 多个任务之间有依赖关 ...

  8. zk系列三:zookeeper实战之分布式锁实现

    一.分布式锁的通用实现思路 分布式锁的概念以及常规解决方案可以参考之前的博客:聊聊分布式锁的解决方案:今天我们先分析下分布式锁的实现思路: 首先,需要保证唯一性,即某一时点只能有一个线程访问某一资源: ...

  9. 在 Spring 生态中玩转 RocketMQ

    本文作者:饶子昊 - Spring Cloud Alibaba Committer,阿里云智能开发工程师. 01 Spring 生态介绍 根据 JVM EcoSystem Report 2021 最新 ...

  10. ARM MMU架构 -- CPU如何访问MMU及DRAM

    <ARM Architecture Reference Manual ARMv8-A>里面有Memory层级框架图,从中可以看出L1.L2.DRAM.Disk.MMU之间的关系,以及他们在 ...