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