C++程序设计(三:可视化)

任务 3

一家生产纽扣的工厂与您签订了合同。 重要的是工厂标识损坏的按钮,以便它们不会提供给商店。 工厂有一台照相机按钮的照片。 相机只能黑白(无彩色),分辨率不是很高很好,但这不是问题。您的工作是编写一个 C++ 程序来识别照片中任何损坏的按钮。 你需要生成一个图像,在每个按钮周围显示一个框。 如果按钮损坏,您必须显示一个红色框,如果按钮没有损坏,您必须显示一个绿色框。 请务必仔细阅读通过以下所有部分。

PS:Buttons.ppm and Ass3-start.cpp
链接:https://pan.baidu.com/s/1CmnuvId2ky2n84AN-Qihxg
提取码:k6gn

Section A - input

程序的输入是出厂时相机拍摄的照片。 这在 .ppm 文件中可用称为 Buttons.ppm。 不要以任何方式编辑此文件。
如果您不小心修改了文件,请从 Stream 下载文件的新副本。您的程序必须能够处理任何此类照片。 不要假设特定数量的按钮。不要假设按钮将始终位于照片中的同一位置。
(Hints:在开始您的程序之前,请检查 .ppm 文件是否没有错误。下载 Buttons.ppm
并将其转换为 .bmp(或其他格式)并查看它。 显示应如下所示:

在这里插入图片描述

只是出于兴趣 - 您可以看出相机的分辨率较低,因为图像中按钮的“阶梯”边缘。 实际上,对于许多此类问题(即识别产品缺陷),通常最好使用黑白照片,因为缺陷更明显。

Section B – understanding the problem

像许多“现实生活”系统一样,这种类型的项目永远不可能完美(这就是让现实生活变得完美的原因)
有趣的项目),但我们尽我们所能。
Notes the following:

  1. 按钮在深色背景上显示为白色(或浅灰色)物体。这是一张黑白照片,这意味着每个像素都是一个灰色阴影(即每个像素的 R、G 和 B 值都相同)。如果 R(或 G 或 B)值大于 128,我们将像素定义为按钮的一部分。
  2. 按钮边缘周围总会有几个像素(取决于阴影)比这更暗,因此不会算作按钮的一部分。这没关系。
  3. 我们需要知道如何“识别”一个按钮。基本上我们会寻找 R 值大于 128 的像素。但我们需要的不止这些——见下一节。
  4. 要在按钮周围绘制一个框,您需要知道按钮中所有像素的最小和最大 x 值以及所有像素的最小和最大 y 值。然后,该框的左上角为 (xmin, ymin),右下角为 (xmax, ymax),依此类推。
  5. 需要考虑如何定义“损坏”按钮。这完全取决于你。

Hints:损坏的按钮比未损坏的按钮具有更少的总像素。

Section C - the algorithm to identify a button in the image

一个按钮由 R 值大于 128 的像素组成,并且这些像素必须相互接触。 如果我们处理每个像素,我们可以通过以下方式识别按钮:
a) 找到一个 R 值大于 128 的像素
b) 找到连接到该像素的所有其他像素(并且 R 值大于 128)
c) 回到我们在 (a) 中的位置并继续
下面的图片试图展示这一点。
步骤 (a) 以黄色显示。 我们从左上角开始,在图像上下稳定工作,直到找到合适的像素。
步骤 (b) 以红色显示——我们找到所有像素连接到第一个像素。
步骤(c)显示为绿色——我们回到我们在步骤(a)的位置,继续寻找 R 值大于 128 的像素。注意这个图是为了得到这个想法——绘制并不完美。

在这里插入图片描述
现在让我们更详细地看一下步骤 (b)——如果按钮中有一个像素,我们如何找到其他像素?
假设我们在位置 (x, y) 发现像素 A 的 R 值大于 128。因此我们知道像素 A 在按钮内。 然后我们可以处理接触像素 A 的像素(它们是像素 B、C、D 和 E)。 注意这些像素的位置。 像素 B 与像素 A 位于图像的同一行,因此具有相同的 y 值。 但是像素 B 位于左侧一个位置,因此它的 x 值为 x – 1。像素 E 与像素 A 在图像的同一垂直列中,因此具有相同的 x 值。 但像素 E 位于屏幕下方,因此它的 y 值为 y + 1。其他像素也是如此。

现在我们知道如何识别像素 A 的“next door”像素,我们有一个算法如下:

  • 在位置 (x, y) 处找到像素 A 并通过以下方式查找所有连接的像素:
  • 在位置 (x – 1, y) 找到像素 B 并查找所有连接的像素;
  • 在位置 (x + 1, y) 处找到像素 C 并查找所有连接的像素;
  • 在位置 (x, y – 1) 处找到像素 D 并查找所有连接的像素;
  • 在位置 (x, y + 1) 处找到像素 E 并查找所有连接的像素;
    这是一个递归算法——通过找到连接到 B 的像素来找到连接到 A 的像素,等等。

虽然这可能看起来不像著名的“caves”plan,但本质上是相同的情况。我们有同样的问题。我们可以开发一个无限循环,其中像素 A 检查像素 B,检查像素 A,检查像素 B,等等。我们以相同的方式解决问题,即我们在每个像素中放入一个布尔值,一旦我们检查了一个像素我们将其从搜索中排除。不要再次检查该像素。

每个递归函数都需要一个基本情况。在这种情况下,有两个是:

  • 如果您正在检查的像素的 R 值等于或小于 128,则返回
  • 如果这个像素被排除在搜索之外(即如果这个像素之前已经被检查过),则返回

一些精明的读者可能已经注意到,我们将它们视为与像素 A 相邻的四个像素,而实际上有八个相邻的像素。我们省略了对角线像素。这样做的原因是递归最终会遍历所有相邻像素。例如。 A 上方和左侧的像素也在 B 上方,因此将在检查 B 时进行检查.

Section D

还有一个程序员曾经在工厂工作。 不幸的是,该程序员并未在清北学习 C++,因此无法完成该项目。 你可能会发现一些
部分完成的程序中有趣的想法,称为 Ass3-start.cpp
下载名为 Ass3-start.cpp 的程序并研究它。
您必须使用名为 pixel_class 的类。 请注意,其中两个方法位于程序的末尾。
这个类在注释中讨论过,但有一个名为 exclude 的额外布尔变量来辅助递归函数。 此排除变量在程序开始时设置为 false,如果已检查此特定像素,则设置为 true。
您必须使用以下全局变量:

int screenx, screeny, maxcolours;
pixel_class picture[600][600];

强烈建议您还使用全局变量:

int total, xmin, xmax, ymin, ymax; // these MUST be global

但是,如果您让程序在没有这些变量的情况下工作,那么您就不需要使用它们。

您必须完全按照程序中的方式使用名为 loadButtons 的函数。

剩下的就看你了。 只要您使用上面列出的强制性代码部分,您就可以保留程序中当前的所有内容或替换其中的一些内容。

主程序的基本概要如下:
• 使用函数loadButtons 将照片数据加载到图片中
• 通过所有像素识别按钮并将框放入图片中
• 将图片数据写入新的 .ppm 文件
•(在程序之外)将新的 .ppm 文件转换为 .bmp 并查看它
Extra notes on drawing a box:
您可以通过将特定颜色的像素放入图片中来绘制一个框(或实际上是任何东西)。
一个盒子需要四个值,分别是 xmin、xmax、ymin、ymax。
盒子的左上角是(xmin, ymin),盒子的右上角是(xmax, ymin)。
盒子的左下角是(xmin, ymax),盒子的右下角是(xmax, ymax)。
要绘制框的顶线,请使用以下循环(或类似循环):

for (x = xmin; x <= xmax; x++) {
 picture[x][ymin].loaddata(R, G, B);
 picture[x][ymin].setexclude(true);
}

将每个像素中的 exclude 变量设置为 true 非常重要。 这些像素现在是框的一部分,不再是按钮图像的一部分。 他们必须从任何未来的按钮搜索中排除。

Section E - output

程序的输出是存储在 .ppm 文件中的图像。 为了查看图像,您可能需要将其转换为不同的格式,例如 .bmp 文件。
输出图像必须显示按钮,每个按钮周围都显示框。 盒子必须是红色的
如果按钮损坏,如果按钮可以接受,则为绿色。 它应该是这样的:
在这里插入图片描述
然而,此图像仅显示绿色框。 这不是正确的结果。
Note1:如果您仔细观察,您可能会发现有些框并没有完美地位于按钮周围。 绿线的“反面”可能有一个或两个像素。 不要担心这个。 不要浪费数小时的时间试图让您的盒子比上面显示的更好。 这些框完全足以显示正在引用哪个按钮。
Note 2:您需要决定“损坏”按钮的定义。 有些按钮明显损坏,其他按钮可能没问题,对此不太确定。 欢迎来到现实世界中的编程! 只要明显损坏的按钮被归类为损坏,没关系。 可能有一两个按钮有些人可能认为已损坏,而其他人则可能不会。 在真实工厂中,“损坏”按钮在被丢弃之前由人类专家检查

Extra ideas

递归很难。

如果你发现很难弄清楚你需要做什么,那是完全正常的。

即使是最好的程序员通常也不会从头开始发明递归函数。

这里有一些关于如何处理任务 3的提示。

1.递归算法(和函数):
用于检查像素的递归算法如下所示:
在 location(x,y) 处找到像素 A 并通过以下方式查找所有连接的素:

  • 转到位置 (x–1,y) 处的像素 B 并查找所有连接的像素;
  • 转到位置 (x+1,y) 处的像素 C 并查找所有连接的像素;
  • 转到位置 (x,y-1) 处的像素 D 并查找所有连接的像素;
  • 转到位置 (x,y+1) 处的像素 E 并查找所有连接的像素;

并且这里有两个基本情况。
首先要注意 - 这是一个空函数。它做了一些事情,但它不返回任何值。参数应该
是 x 和 y(像素的位置)。
此算法的代码的基本方法是:

void findConnectedPixels(int x, int y) {
	if (picture[x][y].getexclude() == true{ return; }// base case one
	if (picture[x][y].getR() <= 128) { return; }// base case two
	// use x and y to assist in calculating xmax, xmin, ymax, ymin
	picture[x][y].setexclude(true);// do not look at this pixel again
	findConnectedPixels(x-1,y);
	findConnectedPixels(x,y-1);
	etc...
)

2.在按钮周围绘制一个框要绘制一个框
需要值 xmin、xmax、ymin、ymax。

这是递归函数的主要工作。

它会根据 x(或 y)检查 xmin(等)并根据需要进行调整。在 main 中初始化 xmin(等)——不要在函数内部初始化它们。在 main 中绘制框——不要在函数内部绘制框。

Answer

#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;

class pixel_class {
private:
  int red, green, blue;
  bool exclude;  // do not check this pixel
public:
  void loaddata(int v1, int v2, int v3);
  void datatofile(fstream & ppmfile);
  int getR() { return red; }
  int getG() { return green; }
  int getB() { return blue; }
  void setexclude(bool ex) { exclude = ex; }
  bool getexclude() { return exclude; }
};

void loadButtons();
void checkpixel(int x, int y);
void DrawBox(int R, int G, int B);

int total, xmin, xmax, ymin, ymax;  // these MUST be global

int screenx, screeny, maxcolours;
pixel_class picture[600][600];

int main() {
  int x, y, grey;
  string outfilename;
  fstream outfile;
  bool excluded;

  // Step 1 : read in the image from Buttons.ppm
  loadButtons();

  // Step 2 : draw a box around each button
  for (y = 0; y < screeny; y++) {
    for (x = 0; x < screenx; x++) {
      grey = picture[x][y].getR();
      excluded = picture[x][y].getexclude();
      if ((grey > 128) && (excluded == false)) {
        total = 0;
        xmin = x;
        xmax = x;
        ymin = y;
        ymax = y;
        checkpixel(x, y);
        cout << "Total pixels = " << total << endl;
        if (total < 7700) {
          DrawBox(255, 0, 0);  // draw a red box
        } else {
          DrawBox(0, 255, 0);  // draw a green box
        }
      }
    }
  }

  // Step 3 : output the final .ppm file
  outfilename = "myImage.ppm";
  outfile.open(outfilename.c_str(), fstream::out);
  outfile << "P3\n";
  outfile << "# " << outfilename << endl;
  outfile << screenx << " " << screeny << endl;
  outfile << maxcolours << endl;
  for (y = 0; y < screeny; y++) {
    for (x = 0; x < screenx; x++) {
      picture[x][y].datatofile(outfile);
    }
    outfile << endl;
  }
  outfile.close();
}

void loadButtons() {
  // load the picture from Buttons.ppm
  int x, y, R, G, B;
  fstream infile;
  string infilename, line;
  infilename = "Buttons.ppm";
  infile.open(infilename.c_str(), fstream::in);
  if (infile.is_open() == false) {
    cout << "ERROR: not able to open " << infilename << endl;
    exit(2);
  }
  getline(infile, line);  // this line is "P3"
  getline(infile, line);  // this line is "# filename"
  infile >> screenx >> screeny;  // this line is the size
  infile >> maxcolours;  // this line is 256
  for (y = 0; y < screeny; y++) {
    for (x = 0; x < screenx; x++) {
      infile >> R >> G >> B;
      picture[x][y].loaddata(R, G, B);
      picture[x][y].setexclude(false);
    }
  }
  infile.close();
}

void checkpixel(int x, int y) {
  // recursively check a pixel and its neighbours
  int grey;
  bool excluded;
  grey = picture[x][y].getR();
  if (grey <= 128) { return; }
  excluded = picture[x][y].getexclude();
  if (excluded == true) { return; }  // do not check this
  total++;           // add another pixel
  if (x < xmin) { xmin = x; }
  if (x > xmax) { xmax = x; }
  if (y < ymin) { ymin = y; }
  if (y > ymax) { ymax = y; }
  picture[x][y].setexclude(true);  // do not check again
  if (x > 0) { checkpixel(x - 1, y); }
  if (y > 0) { checkpixel(x, y - 1); }
  if (x < screenx - 1) { checkpixel(x + 1, y); }
  if (y < screeny - 1) { checkpixel(x, y + 1); }
}

void DrawBox(int R, int G, int B) {
  // do not check the pixels of the box - use exclude
  int x, y;
  for (x = xmin; x <= xmax; x++) {
    picture[x][ymin].loaddata(R, G, B);
    picture[x][ymin].setexclude(true);
    picture[x][ymax].loaddata(R, G, B);
    picture[x][ymax].setexclude(true);
  }
  for (y = ymin; y <= ymax; y++) {
    picture[xmin][y].loaddata(R, G, B);
    picture[xmin][y].setexclude(true);
    picture[xmax][y].loaddata(R, G, B);
    picture[xmax][y].setexclude(true);
  }
}

//--------------- methods for the pixel_class ------------
void pixel_class::loaddata(int v1, int v2, int v3) {
  red = v1;
  green = v2;
  blue = v3;
}

void pixel_class::datatofile(fstream & ppmfile) {
  // write the data for one pixel to the ppm file
  ppmfile << red << " " << green;
  ppmfile << " " << blue << "  ";
}
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第一课 Windows编程和面向对象技术   1.1 Windows发展历史   1.2 Windows操作系统特点   1.3 Windows应用程序设计的特点   1.4 Windows应用程序的开发工具   1.5 面向对象和Windows编程 第二课 使用Visual C++ 5.0   2.1 Visual C++可视化集成开发环境   2.2 创建、组织文件、工程和工作区   2.3 WIN32开发   2.4 MFC编程   2.5 移植C Windows程序到MFC   2.6 Visual C++5.0新特性 第课 窗口、菜单与消息框   3.1 编写第一个窗口程序   3.2 AppWizard所创建的文件   3.3 编译和链接Hello程序   3.4 应用程序执行机制   3.5几种窗口类型   3.6 使用菜单   3.7 更新命令用户接口(UI)消息   3.8 快捷菜单 第四课 工具条和状态栏   4.1 工具条的可视化设计   4.2 工具条的编程技术   4.3 状态栏的设计与实现 第五课 对话框   5.1对话框和控件的基本概念   5.2 对话框模板的设计   5.3 对话框类的设计   5.4 非模态对话框   5.5 标签式对话框   5.6 公用对话框   5.7 小结 第六课 控件 6.1 传统控件   6.2 新型Win32控件   6.3 控件的技术总结   6.4 在非对话框窗口中使用控件   6.5 如何设计新的控件   6.6 小结 第七课 文档视结构   7.1 文档/视图概念   7.2 文档视结构程序实例   7.3 让文档视结构程序支持卷滚   7.4 定制串行化   7.5 不使用串行化的文档视结构程序   7.6 小 结 第八课 多文档界面MDI   8.1多文档界面窗口   8.2图形设备接口(GDI)   8.3 绘图程序   8.4访问当前活动视图和活动文档   8.5分隔视图   8.6打印和打印预览   8.7支持多个文档类型的文档视结构程序   8.8防止应用程序运行时创建空白窗口   8.9小结 第九课 创建用户模块   9.1用户模块   9.2静态连接库   9.3创建动态库   9.4小结 第十课 数据库编程   10.1 数据库的基本概念   10.2 ODBC基本概念   10.3 MFC的ODBC类简介   10.4 CDatabase类   10.5 CRecordset类   10.6 CRecordView类   10.7 编写Enroll数据库应用例程   10.8 DAO和DAO类   10.9 自动注册DSN和创建表   10.10 小结 第十一课 多媒体编程   11.1 调色板   11.2位图   11.3依赖于设备的位图(DDB)   1.4与设备无关的位图(DIB)   11.5动画控件   11.6媒体控制接口(MCI).   11,7小结  第十二章 多线程与串行通信   12.1 多任务、进程和线程   12.2 线程的同步   12.3 串行通信与重叠I/O   12.4 一个通信演示程序   12.5 小结
C++可视化计算器是一种使用C++编程语言开发的计算器软件,可以通过图形界面来实现计算功能。这种计算器软件通常具有美观、易用、可扩展性强等优点,并且可以广泛应用于科学、工程、经济等领域。 实现一个C++可视化计算器需要具备一定的编程能力和计算机科学知识。首先,需要掌握C++基础语法知识及其面向对象的编程思想,如类、成员函数、继承等。其次,需要掌握相关图形界面开发框架的使用,如Qt、MFC、WXWidgets等,以便轻松地完成用户界面的设计和实现。另外,还需要掌握算法和数学知识,以实现计算器所需要的各种功能,如加减乘除、开方、角函数、指数函数等。 C++可视化计算器的实现可以分为两部分,一部分是用户界面的设计和实现,另一部分是计算功能的实现。对于用户界面的设计和实现,开发者可以借助各种图形界面开发框架进行快速开发,通过设计按钮、文本框、菜单等控件,实现计算器的各个功能界面。计算器的另一部分功能实现则需要通过C++语言的算法和数学库,来设计并实现各种数学计算式和运算符的计算过程。 总之,C++可视化计算器是一种非常实用的软件开发工具,可以为人们提供一个快速、精准的数学计算平台。通过了解C++编程语言及其相关知识,我们可以实现高效的计算器应用程序,提升人们的生产、学习和研究效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值