1.1前言
1.本文是博主本着虚心学习的态度
2.本文是针对对C语言以及图像放大的基础讨论,如是大牛们可以直接忽略本文;
3.运行环境:由于在不同电脑配置和系统上的运行时间有差异,本程序的测试平台:电脑CPU为Intel奔腾双核E6500,主频2.93GHZ内存4GB.虚拟机为10.0版的VMware,linux系统为Fedora16
要讲程序的运行速度,还是要从计算机的底层实现为出发点,以下一个图像的两倍线性放大论证此事实。刚刚接触图像处理,要完成图像的两倍线性放大,一想到图像有长与宽,最先想到用二维数组来实现,每次插值采用时间复杂度为O(n^2)的两个for循环来实现,插几次值就OK了。做完之后,大师说这个程序还有优化的空间,因为计算机的内存时一块一维连续的空间,如果这里出发采用一维数组去实现,效果会更好,插2次值就OK,而且每次插值采用的时间复杂度为O(n)。同时涉及到一维数组及二维数组的在堆的动态内存分配,以及以一位数组、二维数组作为函数的形参,进行参数传递。
主旨: 采用隔行隔列插入,左右及上下取均值把640x480的图像放大2倍,变成1280x960的图像,测试程序运行效率时选择了稍大点的数据,效果会比较明显。
2.1插值函数参数为二维数组
紫色框的圆代表原始数据,首先上下取均值插(√),再左右取均值插(×),再取四个均值插(※),最后插最后一列和最后一行。数据来源于计算机生成的伪随机数,为了方便测试该程序,改用较大分辨率2048x1536(现实没见到这种分辨率,按4:3的比例赋的值,测试用即可)程序从linux平台考过来的,格式可能会有一些乱。
源码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/time.h>
typedef unsigned short dbyte;//图像每个像素点为双字节(双char型),且都大于零;char为8bit,short为16bit,用short来表示双char
#define H 1536 //表示图像分辨率的高度
#define W 2048 //表示图像分辨率的宽度
int interpolation_expansion(dbyte **original_data, dbyte **processed_data);//插值函数声明
int main(int argc, int *argv[])
{
int i, j;
dbyte **input_data, **output_data;
//内存分配
input_data = (dbyte **)malloc(H*sizeof(dbyte *));//为二维数组H行分配dbyte*内存
for(i = 0; i < H; i++)
{
input_data[i] = (dbyte *)malloc(W*sizeof(dbyte));//为每行中应放存放多少个dbyte元素分配内存
memset(input_data[i],0,W);//初始化内存
}
output_data = (dbyte **)malloc(2*H*sizeof(dbyte *));
for(i = 0; i < 2*H; i++)
{
output_data[i] = (dbyte *)malloc(2*W*sizeof(dbyte));
memset(output_data[i],0,2*W);
}
for(i = 0;i < H; i++)
{
for(j = 0; j < W; j++)
{
input_data[i][j] = rand();//数据采用伪随机数
}
}
for(i = 238; i < 241; i++)
{
for(j = 320; j < 324; j++)
printf("[%d][%d]_ %d ",i , j, input_data[i][j]);
printf("\n");
}
struct timeval tpstart, tpend;
float timeuse;
gettimeofday(&tpstart, NULL);//获取函数调用之前的时间
interpolation_expansion(input_data, output_data);
gettimeofday(&tpend, NULL);//获取函数调用结束的时间
printf("_________________________<_________>_________________________\n");
for(i = 476; i < 481; i++)
{
for(j = 640; j < 648; j++)
printf("[%d][%d]_%d ", i, j, output_data[i][j]);
printf("\n");
}
//计算函数调用多花费的时间,单位为秒
timeuse = 1000000*(tpend.tv_sec - tpstart.tv_sec)+ tpend.tv_usec - tpstart.tv_usec;
timeuse /= 1000000;
printf("The usetime TWO-dimensional array is %fS\n",timeuse);
//释放内存,【好像不用循环直接free(input_data)也可以,前提是需要知道第二维,我不是很确定】
for(i = 0; i < H; i++)
{
free(input_data[i]);
}
for(i = 0; i < 2*H; i++)
{
free(output_data[i]);
}
}
int interpolation_expansion(dbyte **original_data, dbyte **processed_data)//插值函数的原型
{
int i, j;
int last_line = 2*H -1;
int last_column = 2*W -1;
short tmp_data;
//set values for expansion array 把原来图片的像素点数据插入到需要放大的图片上(下标[偶数][偶数]标示○)
for(i = 0;i < H; i++)
{
for(j = 0; j < W; j++)
{
processed_data[2*i][2*j] = original_data[i][j];
}
}
//insert values into that subscript is odd line caculating by up number and down number用已知值进行上下取均值插入(下标[偶数][奇数]标示√)
for(i = 1; i < 2*(H-1); i += 2)//height_line
{
for(j = 0;j < 2*W; j += 2)//width_column
{
processed_data[i][j] = (processed_data[i-1][j] + processed_data[i+1][j])/2;
}
}
//insert values into that subscript is odd column caculating by left number and right number左右取均值插入(下标[奇][偶]标示×)
for(i = 0; i < 2*H; i += 2)//height_line
{
for(j = 1;j < 2*(W-1); j += 2)//width_column
{
processed_data[i][j] = (processed_data[i][j-1] + processed_data[i][j+1])/2;
}
}
//insert values into that both weight and height are odd numbers 对周围四个取均值(下标[奇][奇]标示※)
for(i = 1; i < 2*(H-1); i += 2)//height_line
{
for(j = 1; j < 2*(W-1); j += 2)
{
processed_data[i][j] = (processed_data[i-1][j-1] + processed_data[i-1][j+1] + processed_data[i+1][j-1] + processed_data[i+1][j+1])/4;
}
}
//insert values into the last line and column对最后一行和最后一列进行插值
for(i = 0; i< 2*H-1; i++)//insert values into last colum
{
if((tmp_data = 2*processed_data[i][last_column-1]-processed_data[i][last_column-2])>0)
{
processed_data[i][last_column] = tmp_data;
}
else
{
processed_data[i][last_column] = processed_data[i][last_column-1];
}
}
for(j = 0; j< 2*W-1; j++)//insert values into last line
{
if((tmp_data = 2*processed_data[last_line-1][j] - processed_data[last_line-2][j]) > 0)
{
processed_data[last_line][j] = tmp_data;
}else
{
processed_data[last_line][j] = processed_data[last_line-1][j];
}
}
processed_data[last_line][last_column] = (processed_data[last_line-1][last_column] + processed_data[last_line][last_column-1] + processed_data[last_line-1][last_column-1])/3;
return 1;
}
本段程序实现了函数形参为二维数组的传递,以及二维数组在堆的动态内存分配。我当初直接为二维数组在栈上分配空间,也就是指定死二维数组的行和列,但是当分辨率较高时,在linux编译能通过,但是运行遇到段错误(吐核),如下
目前也没有找到其根本原因,后面用二维数组的动态内存分配解决了这个问题……
2.2程序的测试结果
截取的数据为放大前、后图片中间的某块数据,平均运行时间为0.098260秒
3.1插值函数参数为一维数组
紫色框的圆代表原始数据,2个for循环实现,首先左右取均值插值(×),再上下取均值插值(√);也可以在插值子函数里实现动态内存,但是为了方便管理,尽量在主函数分配,谁用谁分配和释放,只需要调用函数接口即可。
源码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/time.h>
typedef unsigned short dbyte;
#define M 1536 //原始图片高度
#define N 2048 //原始图片宽度
#define H 3072 //height 放大2倍图片的高度
#define W 4096 //weight 放大2倍图片的宽度
dbyte* insert_values(dbyte *original_data, dbyte *processed_data);
int main(int argc, int *argv[])
{
int i, count;
//内存分配
dbyte *original_data = (dbyte *)malloc(M*N*sizeof(dbyte));
dbyte *processed_data = (dbyte *)malloc(W*H*sizeof(dbyte));
if(original_data == NULL)
{
printf("please allocate the input memery again!\n");
}
if(processed_data == NULL)
{
printf("please allocate the output memery again!\n");
}
//初始化内存为零
memset(original_data,0,M*N);
memset(processed_data,0,W*H);
//the test program as the follows.
//set original data
for(i = 0; i < N*M; i++)
{
original_data[i] = rand();
}
//printf have not be to insert
for(i = 238*N + 320, count = 1; i < 324 + 241* N; )
{
printf(" %d__%ld ",i, original_data[i]);
if(count%4 == 0)
{
count = 1;
i += (N-3);
printf("\n");
}else{
count++;
i++;
}
}
printf("______________*****************_____________________________*****************__________\n");
struct timeval tpstart, tpend;
float timeuse;
gettimeofday(&tpstart, NULL);
insert_values(original_data,processed_data);//use of the function
gettimeofday(&tpend, NULL);
//printf insert have been down
for(i = 476*W + 640, count = 1; i < 648 + 481* W; )
{
printf(" %d__%ld ", i, processed_data[i]);
if(count%8 == 0)
{
count = 1;
i += (W-7);
printf("\n");
}else{
count++;
i++;
}
}
timeuse = 1000000*(tpend.tv_sec - tpstart.tv_sec)+ tpend.tv_usec - tpstart.tv_usec;
timeuse /= 1000000;
printf("The usetime of One-dimensional array is %fS\n",timeuse);
free(processed_data);
free(original_data);
}
dbyte* insert_values(dbyte *original_data, dbyte *processed_data)//The value of return can be define by yourself
{
int i, j;
short tmp;
//set_values from original_data to the processed_data
for(i = j = 0; i<W*(H-1); i += 2, j++)//i为循环要放大的图像,步长为2(隔列插入),j用来循环遍历原始数据
{
if((i!= 0) && (i%W) == 0)//判断是否把一个W宽度的数据插完,真就跳W个数据继续插值,注意:如果不添加i!=0,当i=0,只判断i%W==0,最开始的W个数据时没有的
{
i += W;
}
processed_data[i] = original_data[j];
}
//insert_values into the line caculating by left number and right number 左右均值插值
for(i = 1;i < W*(H-1); i += 2)
{
if((i+1)%W == 0)//最后一列的插值方法,图像由左向右延伸
{
if((tmp = processed_data[i-1]*2-processed_data[i-2]) > 0) //如果中间值的2倍大于两边值之和,就插2倍中间值减去已知的值
{
processed_data[i] = tmp;
}
else{
processed_data[i] = processed_data[i-1];//如果需要插的值是小于零的,直接复制前一个像素点数据
}
i += W;
}
else
{
processed_data[i] = (processed_data[i-1] + processed_data[i+1])/2;
}
}
//insert_values into the line caculating by up number and down number
for(i = W; i < W*H; i++)
{
if(i < W*(H-1))
{
processed_data[i] = (processed_data[i-W] + processed_data[i+W])/2;
if((i+1)%W == 0)
{
i += W;
}
}else//最后一行的插值方法,图像由上向下延伸
{
if((tmp = processed_data[i-W]*2-processed_data[i-2*W]) > 0)
{
processed_data[i] = tmp;
}else{
processed_data[i] = processed_data[i-W];
}
}
}
return processed_data;
}
3.2一维数组程序的测试结果
截取的数据为放大前、后图片中间的某块数据,平均运行时间为0.081015秒
4.1结论
(0.098260-0.081015)=0.17245秒
0.17245/0.09826*100=17.55%,效率提高了百分之17.55。可能有的人觉得没有必要,毕竟现在的CPU处理速度已经相当快了。但如果是在嵌入式设备上就不一定了,毕竟嵌入式设备的资源都是有限的,所以很宝贵,充分利用它才是王道。