山东大学项目实训树莓派提升计划二期(4)计算机系统原理实验四

实验要求优化OpenCV中的图像旋转函数,实现数字图像90度顺时针旋转。学生需关注代码优化,如减少内存引用、循环展开等,使用GPROF工具评估性能。实验提供测试用例,通过比较旋转前后的图像像素来验证正确性。学生需修改main.cc中的函数并使用Makefile进行编译和性能测试。
摘要由CSDN通过智能技术生成

前言

本实验是基于 CS:APP 书中提供的 ICS+ 课程内容而成的,这门课程旨在培养能够理解硬件、操作系统、编译系统和系统编程对应用程序性能、正确性影响的程序员,不涉及低层次的处理器体系结构,也就是本实验不会涉及 CS:APP 第四章的内容。因此实验四直接指向第五章「优化程序性能」。

介绍

通常来说,需要优化程序性能的算法主要存在于「基础设施类」的代码,如标准函数库。因此,我们将本实验聚焦于 OpenCV 的算法。本实验包含 1 道有关数字图像处理的算法优化题目,即将数字图像顺时针旋转 90°。

数字图像,本质上是离散像素及像素属性的特定排列,简单来说就是「非常多不同颜色的方格」。因此将数字图像顺时针旋转 90°,只需要将每个「方格」逐个转移到正确的特定位置即可。

在本实验中,单个数字图像被定义为二维矩阵,其中每个单位元素为只带有 RGB 信息的像素。在实验指导书中,我将给出顺时针旋转 90° 的数学表达式,使学生在没有数字图像处理相关背景知识的情况下也可以完成实验。

具体内容

学生需要优化下面的函数,并使用 GPROF 工具来检查自己编写函数的性能。实验五的测试程序和测试用例集已经在文件中一并给出,学生只需简单地运行编译好的程序即可检查实验结果。

void clockwiseRotate(int dimension, pixel* src, pixel* dst) {
    for(int i = 0; i < dimension; i++) {
        for(int j = 0; j < dimension; j++) {
            dst[locate(j, dimension-1-i, dimension)] = src[locate(i, j, dimension)];
        }
    }
}

这个函数的作用是将分辨率为 dimension * dimension 的数字图像 src 顺时针旋转旋转 90° 并保存到 dst 中。函数原型及实现已经完成,学生要做的是优化这个程序使其运行速度更快。

文件内容

  • main.cc:clockwiseRotate() 函数所在位置,学生需要修改的文件
  • test.cc:测试驱动文件
  • driver.h:实验四的头文件
  • Makefile:简化编译过程的文件
  • README:实验指导书实验四部分节选

实验过程简介

如上文所介绍的,学生需要修改位于 main.cc 文件中的函数 clockwiseRotate(),并通过 Makefile 进行编译,然后通过 GPROF 工具检查程序的运行时间是否已经优化成功。优化的方式在 CS:APP 第五章中有大量的介绍,每个方式都可以或多或少地提高程序的运行速度,这里不展开解释:

  1. 代码移动
  2. 减少过程调用
  3. 消除不必要的内存引用
  4. 创建多个累积变量
  5. 循环展开
  6. 重新结合变换

在优化完函数后,使用 Make 工具进行编译,可以得到一个可执行文件 rotate。通过运行这个可执行文件,学生可以检查自己的函数是否「正确」。

unix> make
g++ -Wall -Og -pg main.cc test.cc -o rotate
unix> ./rotate
共测试 8 个图像尺寸数据,没有发现错误,
请使用 GPROF 进行性能测试。

运行后会生成一个 gmon.out 文件,使用 GPROF 工具可以生成一个包含性能分析信息的文本文件,通过该文件即可检查程序性能。

unix> gprof -b rotate gmon.out >> report
unix> vim report

文件的具体内容及其含义会在实验指导书中给出,这里不做介绍。

实验编写简介

  1. driver.h 文件

这个头文件中声明了所有需要用到的测试函数,以及下面两个重要的结构:

#define locate(i,j,n) ((i)*(n)+(j))

typedef struct {
    unsigned short red;
    unsigned short green;
    unsigned short blue;
} pixel;

因为本实验的二维矩阵是通过一维数组实现的(后文介绍),因此需要一个定位函数将二维坐标映射到一维坐标,于是便有了上述第一个结构。这个结构中的 i、j 指二维坐标的行和列,而 n 指当前数字图像的尺寸。

第二个结构是像素,每个像素由 3 个无符号短整型组成 24 位 RGB 三原色构成,是数字图像的基本单位。

  1. test.cc
constexpr int MAX_DIMENSION = 8192;

pixel data[MAX_DIMENSION * MAX_DIMENSION * 3];

pixel *origin = NULL;
pixel *copyOfOrigin = NULL;
pixel *result = NULL;

int testCases[] = {27, 256, 81, 1024, 243, 4096, 729, 8192};

test.cc 最上方的全局变量,包括一个 MAX_DIMENSION 图像最大尺寸、由 MAX_DIMENSION 生成的一维数组 data、3 个指向特定存储位置的指针和 8 个测试用例。

data 的大小为 MAX_DIMENSION * MAX_DIMENSION * 3,包含原始图像、原始图像的副本和旋转后的图像。原始图像指针指向数组相对地址 0,原始图像副本指针指向数组相对地址 MAX_DIMENSION * MAX_DIMENSION,而旋转后的图像指针指向数组相对地址 2 * MAX_DIMENSION * MAX_DIMENSION。

int main() {

    int size = sizeof(testCases) / sizeof(testCases[0]);
    int totalErrors = 0;

    for(int i = 0; i < size; i++) {
        int currentTestCase = testCases[i];
        createImage(currentTestCase);
        clockwiseRotate(currentTestCase, origin, result);
        totalErrors += testRotate(currentTestCase);
    }

    ......
}

void createImage(int dimension) {

    origin = data;
    copyOfOrigin = origin + dimension * dimension;
    result = copyOfOrigin + dimension * dimension;

    for(int i = 0; i < dimension; i++) {
        for(int j = 0; j < dimension; j++) {
            origin[locate(i,j,dimension)].red = randomValueOfPixel(0, 65536);
            origin[locate(i,j,dimension)].green = randomValueOfPixel(0, 65536);
            origin[locate(i,j,dimension)].blue = randomValueOfPixel(0, 65536);

            copyOfOrigin[locate(i,j,dimension)].red = randomValueOfPixel(0, 65536);
            copyOfOrigin[locate(i,j,dimension)].green = randomValueOfPixel(0, 65536);
            copyOfOrigin[locate(i,j,dimension)].blue = randomValueOfPixel(0, 65536);

            result[locate(i,j,dimension)].red = randomValueOfPixel(0, 65536);
            result[locate(i,j,dimension)].green = randomValueOfPixel(0, 65536);
            result[locate(i,j,dimension)].blue = randomValueOfPixel(0, 65536);
        }
    }
}

main 函数中,针对每一个测试点,首先生成一个随机图像,这个生成方式由上述的 createImage() 生成,针对每个像素给出一个随机值,并初始化指针;接着运行学生提供的旋转函数,最后通过测试函数 testRotate() 检查旋转函数是否正确。

int testRotate(int dimension) {
    if(!checkOriginImg(dimension)) {
        std::cout << "原始图像数据被修改过!" << std::endl;
        return 0;
    }

    int error = 0;
    int badi = 0, badj = 0;
    pixel badOrigin, badResult;

    for(int i = 0; i < dimension; i++) {
        for(int j = 0; j < dimension; j++) {
            if(!comparePixel(origin[locate(i,j,dimension)], result[locate(j,dimension-1-i,dimension)])) {
                error++;
                badi = i;
                badj = j;
                badOrigin = origin[locate(i,j,dimension)];
                badResult = result[locate(j,dimension-1-i,dimension)];
            }
        }
    }
}

而 testRotate() 函数中,则首先检查原始图像是否被修改过,接着逐像素检查学生提供的函数是否正确进行顺时针旋转 90° 操作,若存在错误则给出错误提示。

  1. Makefile
CC = g++
CPPFLAGS = -Wall -Og -pg

lock: main.cc test.cc driver.h
	$(CC) $(CPPFLAGS) main.cc test.cc -o rotate

clean:
	rm -f *.out rotate report
  • 本实验由 C++ 写成,故使用 g++ 编译器进行编译
  • 因为本实验最终是为了调试程序,故使用 -Og 参数表示在 -O1 的基础上,去掉影响调试的优化
  • 参数 -pg 会生成可供 GPROF 剖析用的可执行文件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值