如何优化图像像素化程序
Posted
技术标签:
【中文标题】如何优化图像像素化程序【英文标题】:How to optimise an image pixelation program 【发布时间】:2019-04-16 19:23:30 【问题描述】:我已经编写了一些串行代码,我希望在使用 OpenMP 对其进行并行化之前尽可能地对其进行优化。程序通过遍历 4x4 单元格(变量c
)中的像素数据读取 PPM 文件,然后找到每个 4x4 单元格的平均 RGB 值,最后通过输出平均颜色写入新文件值,再次为每个 4x4 单元格。这会产生一种马赛克/像素化效果。
对我的代码进行性能分析后,主要瓶颈是fscanf
和fprintf
。我忽略了读/写磁盘的执行时间,所以这两个函数无关紧要。
到目前为止我的优化工作:
循环阻塞:代码中有两个嵌套的 FOR 循环,它们具有完全相同的循环条件。但是,第二组嵌套 FOR 循环要求计算平均 RGB 值所需的函数保留在该特定组之外。然后需要在第三组嵌套 FOR 循环(与第二组具有相同的循环条件)中使用平均 RGB 值计算。因此,尽管第二组和第三组嵌套 FOR 循环很相似,但我一直在努力组合它们。
循环不变计算:我已尝试在可能的情况下将某些操作移出循环,但事实证明这很困难。
总结:如何优化这个程序以尽可能减少执行时间?
我的代码:
typedef struct //struct holding RGB type int
int r, g, b; //12 bytes
pixelInt;
typedef struct //struct holding RGB type unsigned char
unsigned char r, g, b; //3 bytes
pixel;
int c = 4; // Variable of 4x4 grids
int width, height; //image variable declarations
//Raw 1 dimensional store of pixel data - will contain all the data for each pixel in the image
pixel *data = (pixel *)calloc(width * height, sizeof(pixelInt));
//Loop through entire input image
for (int yy = 0; yy < height; yy += c)
for (int xx = 0; xx < width; xx += c)
//the total colour of cell of size 'c'
pixelInt cell_tot = 0, 0, 0 ; //zero initialize struct containing mosaic cell pixel totals.
unsigned int counter = 0; //the counter for how many pixels are in a 4x4 cell
int bx = xx + c; //used in loop conditions
int by = yy + c;
// Store each color from the cell into cell_tot struct
for (int y = yy; y < by; y++)
for (int x = xx; x < bx; x++)
unsigned int index_1d = x + y * width; //calculate 1d index from x-index (x), y-index(y) and width;
unsigned char r, g, b; //maximum vales are 255, i.e. unsigned char data type
fscanf(f, "%hhu %hhu %hhu", &r, &g, &b); //%hhu is unsigned char specifier
//store the pixel value into data array
data[index_1d].r = r;
data[index_1d].g = g;
data[index_1d].b = b;
counter++; //increment counter
//average pixel color of cell
cell_tot.r += r;
cell_tot.g += g;
cell_tot.b += b;
//average colour of cell found by dividing cell total by loop counter
pixel cell_average;
cell_average.r = cell_tot.r / counter;
cell_average.g = cell_tot.g / counter;
cell_average.b = cell_tot.b / counter;
//Loop through the new image in cells of size c
for (int y = yy; y < by; y++)
for (int x = xx; x < bx; x++)
unsigned int index_1d = x + y * width; //calculate 1d index from x-index (x), y-index(y) and width;
//Assign average cell value to the pixels in the cell
data[index_1d].r = cell_average.r;
data[index_1d].g = cell_average.g;
data[index_1d].b = cell_average.b;
//Output the average colour value for the image
fprintf(f_output, "%hhu %hhu %hhu \t", data[index_1d].r, data[index_1d].g, data[index_1d].b);
fprintf(f_output, "\n"); //Prints new line
【问题讨论】:
尝试将循环前的完整文件读取到缓冲区中并从缓冲区中读取sscanf
,然后将结果写入单独的缓冲区并在最后将结果写入文件。
OT:关于:pixel *data = (pixel *)calloc(width * height, sizeof(pixelInt))
1) 在 C 中,返回的类型是 void*
,可以分配给任何指针。强制转换只会使代码混乱,使其更难以理解、调试,建议删除“强制转换”
发布的代码没有考虑到像素行必须包含 2 的倍数的像素数。所以总是必须将“宽度”四舍五入为 2 的倍数跨度>
当问一个关于运行时问题的问题时,就像这个问题一样,发布minimal reproducible example以便我们可以重现问题并帮助您调试它。
关于:typedef struct //struct holding RGB type unsigned char unsigned char r, g, b; //3 bytes pixel;
图像标题中的一个字段说明了每个像素中有多少位。根据具体的图像,可以是每像素 1 字节、2 字节、3 字节、4 字节
【参考方案1】:
在我机器上的 1024x1024 图像上,您的代码在 0.325s
中执行。以下代码在0.182s
中执行:
unsigned w = width/c, h = height/c;
unsigned *accum = (unsigned*)malloc(3*sizeof(unsigned)*w);
char *line = (char*)malloc(12*w);
unsigned denom = c*c;
//Loop through entire input image
for (int yy = 0; yy < h; ++yy)
memset(accum, 0, 3*sizeof(unsigned)*w);
// read and accumulate c lines
for(int y = 0; y < c; ++y)
for (int xx = 0; xx < w; ++xx)
for (int x = 0; x < c; ++x)
unsigned char r, g, b;
fscanf(f, "%hhu %hhu %hhu", &r, &g, &b);
accum[3*xx+0] += r;
accum[3*xx+1] += g;
accum[3*xx+2] += b;
// format a line
for(int xx = 0; xx < w; ++xx)
char *cell = line + 12*c*xx;
snprintf(cell, 12, "%3u%4u%4u", accum[3*xx]/denom, accum[3*xx+1]/denom, accum[3*xx+2]/denom);
cell[11] = '\t';
for(int x = 1; x < c; ++x)
memcpy(cell + 12*x, cell, 12);
// write it out times c
line[12*w-1] = '\n';
for(int y = 0; y < c; ++y)
fwrite(line, 12*w, 1, f_output);
这里的技巧是只格式化一次平均值,然后复制格式化的字符串。此外,通过一次处理一行,我有更好的机会利用内存缓存。
要超越这一点,您需要重新实现 fscanf
以更快地解析整数。
【讨论】:
感谢您发布优化的解决方案。请问您在代码中声明变量c
的数据类型是什么?如果我将其定义为 unsigned int c = 0
我得到这个异常:Unhandled exception at 0x008D1169 in Assignment.exe: 0xC0000094: Integer division by zero
在这一行:unsigned *accum = (unsigned*)malloc(6 * sizeof(unsigned)*w);
@p.luck: c
是您代码中的变量,在您的示例中等于 4
。
当然——抱歉,我没有想清楚。然而,我在fwrite(line, 12 * w, 1, f_output);
线上得到了Assignment.exe has triggered a breakpoint.
。知道是什么原因造成的吗?
@p.luck:嗯,这真的很难说。确保w
和width
是正确的,并且width
可以被c
整除。仔细检查您是否正确复制了所有内容。以上是关于如何优化图像像素化程序的主要内容,如果未能解决你的问题,请参考以下文章