Friday, September 26, 2008

linux下查看硬盘参数及cpu温度

1)查看硬盘参数:(下面仅是速度)

 /sbin/hdparm -t /dev/hda

我的硬盘显示:

 Timing buffered disk reads:  64 MB in  1.77 seconds = 36.16 MB/sec

2)查看cpu温度:

  cat /proc/acpi/thermal_zone/THRM/temperature

但是有个前提条件是:

要有内核相关模块(比如I2C)支持,还要找出系统的传感器型号,然后在内核中打开相应选项。

我的可能是不满足这个前提条件,查看失败。

 

Wednesday, September 24, 2008

零拷贝技术研究与实现[转]

零拷贝技术研究与实现[转]
转的,有作者信息,虽然零拷贝技术需要修改网卡驱动,而我对这方面的知识为零,但是以后肯定会学而且要用这个,所以先转过来吧~
=====================================================================
作者:梁健(firstdot)
E-MAIL:firstdot@163.com

一.基本概念
零拷贝(zero-copy)基本思想是:数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现CPU的零参与,彻底消除 CPU在这方面的负载。实现零拷贝用到的最主要技术是DMA数据传输技术和内存区域映射技术。如图1所示,传统的网络数据报处理,需要经过网络设备到操作系统内存空间,系统内存空间到用户应用程序空间这两次拷贝,同时还需要经历用户向系统发出的系统调用。而零拷贝技术则首先利用DMA技术将网络数据报直接传递到系统内核预先分配的地址空间中,避免CPU的参与;同时,将系统内核中存储数据报的内存区域映射到检测程序的应用程序空间(还有一种方式是在用户空间建立一缓存,并将其映射到内核空间,类似于linux系统下的kiobuf技术),检测程序直接对这块内存进行访问,从而减少了系统内核向用户空间的内存拷贝,同时减少了系统调用的开销,实现了真正的"零拷贝"。


图1 传统数据处理与零拷贝技术之比较
二.实现
在 redhat7.3上通过修改其内核源码中附带8139too.c完成零拷贝的试验,主要想法是:在8139too网卡驱动模块启动时申请一内核缓存,并建立一数据结构对其进行管理,然后试验性的向该缓存写入多个字符串数据,最后通过proc文件系统将该缓存的地址传给用户进程;用户进程通过读proc文件系统取得缓存地址并对该缓存进行地址映射,从而可以从其中读取数据。哈哈,为了偷懒,本文只是对零拷贝思想中的地址映射部分进行试验,而没有实现DMA 数据传输(太麻烦了,还得了解硬件),本试验并不是一个IDS产品中抓包模块的一部分,要想真正在IDS中实现零拷贝,除了DMA外,还有一些问题需考虑,详见本文第三节的分析。以下为实现零拷贝的主要步骤,详细代码见附录。

步骤一:修改网卡驱动程序
a.在网卡驱动程序中申请一块缓存:由于在linux2.4.X内核中支持的最大可分配连续缓存大小为2M,所以如果需要存储更大量的网络数据报文,则需要分配多块非连续的缓存,并使用链表、数组或hash表来对这些缓存进行管理。

#define PAGES_ORDER 9
unsigned long su1_2
su1_2 = __get_free_pages(GFP_KERNEL,PAGES_ORDER);

b. 向缓存中写入数据:真正IDS产品中的零拷贝实现应该是使用DMA数据传输把网卡硬件接收到的包直接写入该缓存。作为试验,我只是向该缓存中写入几个任意的字符串,如果不考虑DMA而又想向缓存中写入真正的网络数据包,可以在8139too.c的rtl8139_rx_interrupt()中调用 netif_rx()后插入以下代码:

//put_pkt2mem_n++; //包个数
//put_mem(skb->data,pkt_size);
其中put_pkt2mem_n变量和put_mem函数见附录。

c. 把该缓存的物理地址传到用户空间:由于在内核中申请的缓存地址为虚拟地址,而在用户空间需要得到的是该缓存的物理地址,所以首先要进行虚拟地址到物理地址的转换,在linux系统中可以使用内核虚拟地址减3G来获得对应的物理地址。把缓存的地址传到用户空间需要在内核与用户空间进行少量数据传输,这可以使用字符驱动、proc文件系统等方式实现,在这里采用了proc文件系统方式。

int read_procaddr(char *buf,char **start,off_t offset,int count,int *eof,void *data)
{
   sprintf(buf,"%u\n",__pa(su1_2));
    *eof = 1;
    return 9;
}
create_proc_read_entry("nf_addr",0,NULL,read_procaddr,NULL);

步骤二:在用户程序中实现对共享缓存的访问
a.读取缓存地址:通过直接读取proc文件的方式便可获得。

char addr[9];
int fd_procaddr;
unsigned long ADDR;
fd_procaddr = open("/proc/nf_addr",O_RDONLY);
read(fd_procaddr,addr,9);
ADDR = atol(addr);

b.把缓存映射到用户进程空间中:在用户进程中打开/dev/mem设备(相当于物理内存),使用mmap把网卡驱动程序申请的缓存映射到自己的进程空间,然后就可以从中读取所需要的网络数据包了。

char *su1_2;
int fd;
fd=open("/dev/mem",O_RDWR);    
su1_2 = mmap(0,PAGES*4*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, ADDR);

三.分析
    零拷贝中存在的最关键问题是同步问题,一边是处于内核空间的网卡驱动向缓存中写入网络数据包,一边是用户进程直接对缓存中的数据包进行分析(注意,不是拷贝后再分析),由于两者处于不同的空间,这使得同步问题变得更加复杂。缓存被分成多个小块,每一块存储一个网络数据包并用一数据结构表示,本试验在包数据结构中使用标志位来标识什么时候可以进行读或写,当网卡驱动向包数据结构中填入真实的包数据后便标识该包为可读,当用户进程对包数据结构中的数据分析完后便标识该包为可写,这基本解决了同步问题。然而,由于IDS的分析进程需要直接对缓存中的数据进行入侵分析,而不是将数据拷贝到用户空间后再进行分析,这使得读操作要慢于写操作,有可能造成网卡驱动无缓存空间可以写,从而造成一定的丢包现象,解决这一问题的关键在于申请多大的缓存,太小的缓存容易造成丢包,太大的缓存则管理麻烦并且对系统性能会有比较大的影响。

四.附录
a.    8139too.c中加入的代码

/*add_by_liangjian for zero_copy*/
#include <linux/wrapper.h>
#include <asm/page.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#define PAGES_ORDER 9
#define PAGES 512
#define MEM_WIDTH    1500
/*added*/

/*add_by_liangjian for zero_copy*/
struct MEM_DATA
{
    //int key;
    unsigned short width;/*缓冲区宽度*/
    unsigned short length;/*缓冲区长度*/
    //unsigned short wtimes;/*写进程记数,预留,为以后可以多个进程写*/
    //unsigned short rtimes;/*读进程记数,预留,为以后可以多个进程读*/
    unsigned short wi;/*写指针*/
    unsigned short ri;/*读指针*/
} * mem_data;
struct MEM_PACKET
{
    unsigned int len;
    unsigned char packetp[MEM_WIDTH - 4];/*sizeof(unsigned int) == 4*/
};
unsigned long su1_2;/*缓存地址*/
/*added*/

/*add_by_liangjian for zero_copy*/
//删除缓存
void del_mem()
{
    int pages = 0;
    char *addr;
    addr = (char *)su1_2;
    while (pages <=PAGES -1)
    {
        mem_map_unreserve(virt_to_page(addr));
        addr = addr + PAGE_SIZE;
        pages++;
    }
    free_pages(su1_2,PAGES_ORDER);    
}    
void init_mem()
/********************************************************
*                  初始化缓存
*       输入:   aMode:    缓冲区读写模式:  r,w        *
*       返回:   00:     失败                        *
*               >0:     缓冲区地址                  *
********************************************************/
{
    int i;
    int pages = 0;
    char *addr;
    char *buf;
    struct MEM_PACKET * curr_pack;
    
    su1_2 = __get_free_pages(GFP_KERNEL,PAGES_ORDER);
    printk("[%x]\n",su1_2);
    addr = (char *)su1_2;
    while (pages <= PAGES -1)
    {
        mem_map_reserve(virt_to_page(addr));//需使缓存的页面常驻内存
        addr = addr + PAGE_SIZE;
        pages++;
    }
    mem_data = (struct MEM_DATA *)su1_2;
    mem_data[0].ri = 1;
          mem_data[0].wi = 1;
          mem_data[0].length = PAGES*4*1024 / MEM_WIDTH;
          mem_data[0].width = MEM_WIDTH;
    /* initial su1_2 */
    for(i=1;i<=mem_data[0].length;i++)
    {
        buf = (void *)((char *)su1_2 + MEM_WIDTH * i);
        curr_pack = (struct MEM_PACKET *)buf;
        curr_pack->len = 0;
    }    
}
int put_mem(char *aBuf,unsigned int pack_size)
/****************************************************************
*                 写缓冲区子程序                                *
*       输入参数    :   aMem:   缓冲区地址                      *
*                       aBuf:   写数据地址                      *
*       输出参数    :   <=00 :  错误                            *
*                       XXXX :  数据项序号                      *
*****************************************************************/
{
    register int s,i,width,length,mem_i;
    char *buf;
    struct MEM_PACKET * curr_pack;

    s = 0;
    mem_data = (struct MEM_DATA *)su1_2;
    width  = mem_data[0].width;
    length = mem_data[0].length;
    mem_i  = mem_data[0].wi;
    buf = (void *)((char *)su1_2 + width * mem_i);

    for (i=1;i<length;i++){
        curr_pack = (struct MEM_PACKET *)buf;
            if  (curr_pack->len == 0){
                    memcpy(curr_pack->packetp,aBuf,pack_size);
                    curr_pack->len = pack_size;;
                s = mem_i;
            mem_i++;
                    if  (mem_i >= length)
                        mem_i = 1;
                mem_data[0].wi = mem_i;
                break;
            }
            mem_i++;
            if  (mem_i >= length){
                    mem_i = 1;
                    buf = (void *)((char *)su1_2 + width);
            }
            else buf = (char *)su1_2 + width*mem_i;
        }

    if(i >= length)
            s = 0;
    return s;
}
// proc文件读函数
int read_procaddr(char *buf,char **start,off_t offset,int count,int *eof,void *data)
{
    sprintf(buf,"%u\n",__pa(su1_2));
    *eof = 1;
    return 9;
}
/*added*/

在8139too.c的rtl8139_init_module()函数中加入以下代码:
/*add_by_liangjian for zero_copy*/
    put_pkt2mem_n = 0;
    init_mem();
    put_mem("data1dfadfaserty",16);
    put_mem("data2zcvbnm",11);
    put_mem("data39876543210poiuyt",21);
    create_proc_read_entry("nf_addr",0,NULL,read_procaddr,NULL);
/*added */    

在8139too.c的rtl8139_cleanup_module()函数中加入以下代码:
/*add_by_liangjian for zero_copy*/
    del_mem();
    remove_proc_entry("nf_addr",NULL);
/*added*/    

b.用户空间读取缓存代码

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#define PAGES 512
#define MEM_WIDTH 1500
struct MEM_DATA
{
    //int key;
    unsigned short width;/*缓冲区宽度*/
    unsigned short length;/*缓冲区长度*/
    //unsigned short wtimes;/*写进程记数,预留,为以后可以多个进程写*/
    //unsigned short rtimes;/*读进程记数,预留,为以后可以多个进程读*/
    unsigned short wi;/*写指针*/
    unsigned short ri;/*读指针*/
} * mem_data;

struct MEM_PACKET
{
    unsigned int len;
    unsigned char packetp[MEM_WIDTH - 4];/*sizeof(unsigned int) == 4*/
};

int get_mem(char *aMem,char *aBuf,unsigned int *size)
/****************************************************************
*                 读缓冲区子程序                                *
*       输入参数    :   aMem:   缓冲区地址                      *
*                       aBuf:   返回数据地址, 其数据区长度应大于*
*                               缓冲区宽度                      *
*       输出参数    :   <=00 :  错误                            *
*                       XXXX :  数据项序号                      *
*****************************************************************/
{
    register int i,s,width,length,mem_i;
    char     *buf;
    struct MEM_PACKET * curr_pack;

    s = 0;
    mem_data = (void *)aMem;
    width  = mem_data[0].width;
    length = mem_data[0].length;
    mem_i  = mem_data[0].ri;
    buf = (void *)(aMem + width * mem_i);

    curr_pack = (struct MEM_PACKET *)buf;
    if  (curr_pack->len != 0){/*第一个字节为0说明该部分为空*/
            memcpy(aBuf,curr_pack->packetp,curr_pack->len);
            *size = curr_pack->len;
            curr_pack->len = 0;
            s = mem_data[0].ri;
            mem_data[0].ri++;
            if(mem_data[0].ri >= length)
                    mem_data[0].ri = 1;
            goto ret;
        }
    
    for (i=1;i<length;i++){
            mem_i++;/*继续向后找,最糟糕的情况是把整个缓冲区都找一遍*/
            if  (mem_i >= length)
                mem_i = 1;
            buf = (void *)(aMem + width*mem_i);
            curr_pack = (struct MEM_PACKET *)buf;
            if  (curr_pack->len == 0)
                    continue;
            memcpy(aBuf,curr_pack->packetp,curr_pack->len);
            *size = curr_pack->len;
            curr_pack->len = 0;
            s = mem_data[0].ri = mem_i;
            mem_data[0].ri++;
            if(mem_data[0].ri >= length)
            mem_data[0].ri = 1;
            break;
        }

    ret:
    return s;
}

int main()
{
    char *su1_2;
    char receive[1500];
    int i,j;
    int fd;
    int fd_procaddr;
    unsigned int size;
    char addr[9];
    unsigned long ADDR;
    
    j = 0;
    /*open device 'mem' as a media to access the RAM*/
    fd=open("/dev/mem",O_RDWR);    
    fd_procaddr = open("/proc/nf_addr",O_RDONLY);
    read(fd_procaddr,addr,9);
    ADDR = atol(addr);
    close(fd_procaddr);
    printf("%u[%8lx]\n",ADDR,ADDR);
    /*Map the address in kernel to user space, use mmap function*/
    su1_2 = mmap(0,PAGES*4*1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, ADDR);
    perror("mmap");
    while(1)
    {
        bzero(receive,1500);
        i = get_mem(su1_2,receive,&size);
        if (i != 0)
        {
            j++;
            printf("%d:%s[size = %d]\n",j,receive,size);
        }    
        else
        {
            printf("there have no data\n");
            munmap(su1_2,PAGES*4*1024);
            close(fd);
            break;
        }
    }
    while(1);
}

五.参考文献
1.CHRISTIAN KURMANN, FELIX RAUCH ,THOMAS M. STRICKER.
Speculative Defragmentation - Leading Gigabit Ethernet to True Zero-Copy Communication
2.ALESSANDRO RUBINI,JONATHAN CORBET.《LINUX DEVICE DRIVERS 2》,O'Reilly & Associates 2002.
3.胡希明,毛德操.《LINUX 内核源代码情景分析》,浙江大学出版社 2001


关于作者:梁健,华北计算技术研究所在读硕士研究生,研究方向:信息安全。论文开题为《基于系统调用分析的主机异常入侵检测与防御》。对IDS有两年多的研究经验,熟悉linux内核,熟悉linux c/c++编程、win32 API编程,对网络和操作系统安全感兴趣。

Monday, September 08, 2008

通过命令行处理图形

 

原文链接: http://www-900.ibm.com/developerWorks/cn/linux/l-graf/index.shtml


使用 ImageMagick 进行翻转、缩放大小、旋转以及更多操作
Michael Still(mikal@stillhq.com
高级软件工程师,Tower Software
2003 年 9 月

没有什么能象命令行工具这样适合于处理大批量的任务,对于图像操作也不例外。Web 开发人员和管理员会喜欢轻松处理大量文件的能力,无论是使用命令行还是脚本。程序员 Michael Still 介绍了 ImageMagick 套件,这是一个用于以多种格式对图像进行缩放大小、旋转、转换和其它操作的 Linux 工具箱,无论图像的数量是一个还是上百个,它都可以一次处理掉。

本文展示了如何使用命令行工具执行图像操作。这种任务对我而言相当常见,因为我拥有几部数码相机,并且现在管理着一个拥有几千张精彩照片的资料库。对于经常需要对大量图像进行批处理的 Web 开发人员和管理员而言,命令行工具是特别具有吸引力的选择,因为开发人员可以将它们合并成脚本。但即使您只想执行一两次操作,选择命令行也可以节省时间。

本文中讨论的命令行工具是优秀的 ImageMagick 套件的一部分,该套件是随 Red Hat Linux 一起提供的,并可免费在线下载(请参阅参考资料)。也可以通过 C、C++、Perl、Python、Java 和其它几种语言使用 ImageMagick,Linux 程序员会喜欢这样做。

请注意,有许多种方法可以完成本文中所讨论的任务。我讨论了我所使用的方法,它对我确实很有效。这并不意味着除此之外的其它工具就很差劲;仅仅表示我对于现在所使用的工具很满意。

本文采用了以具体问题作为示例进行讨论这种形式,但其思想应该也适用于其它问题领域。

生成缩略图
我对照片集所执行的第一个操作是生成缩略图。我还想减少图像的大小,以适用于网站版本因为许多人实际上并不想看到我儿子的 1920 x 1440 像素的照片。

########################################
ImageMagick 是如何工作的
ImageMagick 是作为包含了一大堆不同的图像处理库的包装器实现的,这些图像处理库包括 libtiff 和 libpng(请参阅参考资料以获取我先前所著的关于 libtiff 文章的链接)。在 ImageMagick 术语中,这些库称为代表(delegate)。这是 ImageMagick 不如定制应用程序执行得那么快的原因之一;它必须以一种通用方式编写,该方式可以处理这些库表示图像数据的不同方法。
########################################

我使用 convert 工具,它是 ImageMagick 套件的一部分。convert 确实很酷。除了图像缩放,它还具有平滑处理、均衡图像组、模糊化、在图像格式间进行转换、修剪、去斑滤镜、抖动、绘制边界、翻转、连接、重新采样、调整大小以及许多功能。查看联机帮助页以获取关于它的各种命令行选项的更多信息。本文稍后还讨论了 convert 所提供的许多在视觉上比较有趣的效果。

让我们假定我希望为这幅非常美丽的玫瑰图像制作缩略图:
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/rose.jpg
要用 convert 调整图像的大小,只要使用 -sample 命令行选项。例如,让我们假定我希望得到 80 x 40 像素的缩略图。则命令行将是:

# convert -sample 80x40 input.jpg output.jpg
这生成了如下的缩略图:
图 2. 生成缩略图的第一次尝试
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/convert-sample-8040.jpg
ImageMagick 会自动地考虑在缩放图像大小时图像的两条邻边所产生的比例。这意味着新的图像的高宽比与原图相同。在上面的示例中,这意味着缩放后的图像实际上是 53 x 40 像素,而不是所要求的 80 x 40 像素。指定输出图像大小的另一种方法是使用百分数。如果您不能确定输入图像的大小,或者并不是刻意地要得到确切大小的新图像,那么这种方法比较方便。以下是关于如何使用百分数的示例:

# convert -sample 25%x25% input.jpg output.jpg
现在,我们得到了如下所示的缩略图:

图 3. 生成缩略图的第二次尝试
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/convert-sample-25pct.jpg

使用该命令,您可以在某个目录中生成图像的缩略图。虽然本文并不是关于 shell 脚本编制的,但我会迅速地向您展示一个示例,以说明如何生成当前目录中每个 JPEG 的缩略图:

清单 1. 为当前目录中的所有 JPEG 生成缩略图

for img in `ls *.jpg`
do
convert -sample 25%x25% $img thumb-$img
done



这会生成一系列大小为实际图像 25% 的缩略图,其文件名称是在 JPEG 文件名的前面加上 thumb- 前缀。

获取关于图像文件的信息
另一个常见任务是确定图像文件的尺寸(dimension)。例如,您可能需要知道前一个示例中的缩略图有多大。

许多图像处理库提供了用于此用途的优秀工具。例如,libtiff(TIFF 库,我先前曾写过有关这个库的文章;请参阅参考资料)提供了 tiffinfo,tiffinfo 显示了关于 TIFF 文件的下列种类的信息:

# tiffinfo sample.tif
清单 2. tiffinfo 的样本输出

TIFF Directory at offset 0x146
Image Width: 352 Image Length: 288
Bits/Sample: 8
Compression Scheme: Deflate
Photometric Interpretation: RGB color
Samples/Pixel: 3
Planar Configuration: single image plane



这并不是一个关于如何使用 tiffinfo 的详尽示例,但您可以看到它返回了诸如图像大小、像素深度(每个样本的位数和每个像素的样本数的组合)以及所用的压缩方案之类的有用信息。

类似地,有一个返回 PNG 文件的类似信息的 pnginfo 命令:

# pnginfo sample.png
清单 3. pnginfo 的样本输出

sample.png...
Image Width: 640 Image Length: 480
Bitdepth (Bits/Sample): 8
Channels (Samples/Pixel): 3
Pixel depth (Pixel Depth): 24
Colour Type (Photometric Interpretation): RGB
Image filter: Single row per byte filter
Interlacing: No interlacing
Compression Scheme: Deflate method 8, 32k window
Resolution: 0, 0 (unit unknown)
FillOrder: msb-to-lsb
Byte Order: Network (Big Endian)
Number of text strings: 0 of 0



我不清楚是否存在用于其它格式(诸如 BMP、GIF 和 JPEG)功能相当的单个工具。但是,ImageMagick 又一次帮了我们的忙,这次是使用名为 identify 的工具。

# identify -verbose sample.png
清单 4. identify 的样本输出

Image: sample.png
Format: PNG (Portable Network Graphics)
Geometry: 640x480
Class: DirectClass
Type: true color
Depth: 8 bits-per-pixel component
Colors: 142360
Filesize: 555.6k
Interlace: None
Background Color: grey100
Border Color: #DFDFDF
Matte Color: grey74
Dispose: Undefined
Iterations: 0
Compression: Zip
signature: 361fe70ae623ef6f1fca44e0d29d157c2d701039fcf0f8625862925d881e13a4
Tainted: False
User Time: 0.190u
Elapsed Time: 0:01



您可以看到 identify 显示了一串关于图像文件的有用信息,如以像素为单位的图像大小、图像的色深以及图像的格式。

################################
pnginfo
当我开始使用 PNG 时,我成为 libtiff 用户已经有相当长一段时间了。当时,没有与 tiffinfo 功能相当的工具用于 PNG 文件,因此我编写了 pnginfo。(您可以通过参考资料中的链接下载 pnginfo)。
################################

identify 还有 -format 命令行标志,它允许您仅指定想要输出的信息。例如,如果您只对图像尺寸感兴趣,则可以使用如下命令:

# identify -format "%wx%h" sample.png
输出类似于:

640x480
此处,%w 表示图像宽度,而 %h 表示图像高度。有关可以与该选项一起使用的格式化字符的更多信息,请查看 identify 联机帮助页。

旋转图像
另一种经常需要用到的图像操作是旋转图像。例如,我用数码相机所拍摄的许多照片都旋转了九十度,因为我是把相机竖过来拍摄它们的。相机不会为我旋转这些照片,因此我编写了一个脚本,以便在从相机下载这些图像之后完成旋转任务。

例如,这是我在最近去塔斯马尼亚岛的亚瑟港旅游时拍摄的一幅照片:

图 4. 横卧的亚瑟港
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/portarthur.jpg
为了旋转该照片,我们再次求助于 convert 命令:

# convert -rotate 90 input.jpg output.jpg
该命令生成了如下所示的图像:

图 5. 亚瑟港
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/portarthur-rot.jpg
请注意 -rotate 选项的参数是将图像向右旋转的度数。要向左旋转,请使用负数。

更改图像的格式
convert 命令还能够转换图像文件的格式。这包括图像格式之间的转换(如将 JPEG 图像转换成 PNG)以及从颜色到灰度之间的转换、抖动和类似操作。

convert 根据命令行中给定的文件扩展名来了解输入和输出的图像格式分别是什么。因此,要将 JPEG 转换成 PNG,使用如下所示的命令行:

# convert input.jpg output.png
在我撰写本文时,ImageMagick 支持 89 种图像格式。查看 ImageMagick 网站(请参阅参考资料)以获取更多信息。

向图像添加文本注释
有时您需要向图像添加文本注释。例如,假设您的公司拥有标准的名片图像,并希望在将名片发送到打印机之前将每个雇员的详细信息都添加到名片上面。另一个示例是为通过您网站上的在线课程的用户生成表示证书(presentation certificate)。

让我们假定您从这幅图入手:

图 6. 2002 年芙萝莉雅蝶园艺博览会(FLORIADE 2002)照片
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/floriade.jpg
您可以使用下列命令行,为该图注释一些标识信息:

# convert -font helvetica -fill white -pointsize 36 \
-draw 'text 10,50 "Floriade 2002, Canberra, Australia"' \
floriade.jpg comment.jpg
这里是结果:

图 7. 加注释的 2002 年芙萝莉雅蝶园艺博览会照片
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/comment.jpg

迄今为止,这是我在本文中所展示的最复杂的 convert 命令行了,因此我将花些时间来解释它。

-font helvetica 将注释的字体设置为 Helvetica。也可以在此处指定字体文件的路径。这个示例给图像添加了标记,这样未经许可其它网站就不能再使用该图像了,但它是使用位于非标准位置的字体来完成该任务的:

# convert -font fonts/1900805.ttf -fill white -pointsize 36 \
-draw 'text 10,475 "stillhq.com"' \
floriade.jpg stillhq.jpg
以下是结果:

图 8. 带标记的图像
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/stillhq.jpg

-fill white 用白色而不是标准的黑色来填充字母。

-pointsize 36 以点为单位指定字母的大小。一英寸等于 72 点。

-draw 'text 10,50 "..."' 是一组绘图命令,在本例中是移动到位置 10, 50,然后绘制出双引号中的文本。使用单引号是因为如果需要绘制多个字,则绘图命令中需要使用双引号,而您不能在双引号中再用双引号。

其它更富艺术性的转换
convert 还实现了一系列相当艺术性的转换。我将在此处演示一些在视觉上比较有趣的转换。如果您对此感兴趣,应该查看 ImageMagick 的联机帮助页和网站以获取更多信息。这是我将用于该演示的输入图像:

图 9. 乌奴奴(Uluru)夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/uluru.jpg
这张乌奴奴(以前称为艾尔斯岩(Ayers Rock))的照片是在日落时分照的。

炭笔
炭笔效果模拟出该图像的素描画。

# convert -charcoal 2 input.jpg output.jpg
其结果如下所示:

图 10. 应用炭笔效果之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/charcoal.jpg
增加 -charcoal 选项的参数的数值会增加应用于该图像的"炭笔"数量,但也会延缓生成图像的过程。下面是一个使用稍多炭笔的示例。

# convert -charcoal 10 input.jpg output.jpg
它产生了如下结果:

图 11. 应用更多炭笔效果之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/charcoal-medium.jpg
如果您真的想近乎疯狂地使用大量炭笔:

# convert -charcoal 200 input.jpg output.jpg
您会得到这样的结果:

图 12. 应用过量炭笔效果之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/charcoal-intense.jpg

着色
着色是将每个像素的颜色与指定颜色混合的过程。该效果的参数就是要用来混合的颜色。可以用一个百分数(它将分别用于红色、绿色和蓝色),也可以用三个百分数来指定这个参数。也可以提供三个实际值中的一个。

# convert -colorize 255 input.jpg output.jpg
以下是着色之后的乌奴奴:

图 13. 应用着色效果之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/colorize.jpg

##################################
指定三个值
要指定三个值,每个值分别代表红色、绿色和蓝色三个采样,使用 red/green/blue 形式的参数。例如,10/20/30 意味着红色的值是 10、绿色值为 20 而蓝色值为 30。您也可以在这个构造中使用百分数。
##################################

内爆(Implode)
内爆效果模拟了您图像的中心被吸入虚拟黑洞的情形。所用的参数是您所期望的内爆效果量。

# convert -implode 4 input.jpg output.jpg
内爆的乌奴奴如下所示:

图 14. 内爆之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/implode.jpg
曝光
曝光是在相片冲洗过程中把底片暴露在光线中所发生的效果。这里,输入参数是应用于该效果的亮度,可以指定为绝对值,也可以是可用于像素的最大可能值的百分数。如果像素超过阈值,则对它求反。

# convert -solarize 42 input.jpg output.jpg
在曝光之后,我们的图像如下所示:

图 15. 曝光之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/solarize.jpg

################################
solarize 参数
ImageMagick 文档声明 solarize 的参数总是百分数。严格地说,这并不正确。如果参数以百分号结尾,那么就把它当作百分数。否则,就将它当作一个字面值。
################################

发散
spread 在图像之内以随机的数量移动像素。所用的参数是被移到新选择的位置的像素区域的大小。所以它指定了输出和输入的相似程度:

# convert -spread 5 input.jpg output.jpg
再次使用乌奴奴,这次是发散之后的情形:

图 16. 发散之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/spread.jpg
在一次 ImageMagick 调用中执行多条命令
您已经看到了将命令与注释示例联系起来的示例。但是,可以将本文中提到的任意 ImageMagick 命令链接起来。例如,也许我们希望制作某图像的缩略图,然后对它应用发散。在发散发生之后,我们将应用炭笔效果:

# convert -sample 25%x25% -spread 4 \ -charcoal 4 input.jpg output.jpg
这会产生:

图 17. 应用了一系列效果之后的乌奴奴
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/chained.jpg
图像操作技巧
在迫不及待地动手修改您所拥有的每一幅图像之前,您应该记住一些关于图像操作的事情。首先,您应该考虑一下打算长期使用什么图像格式,免得最终有了一大堆某种格式的图像之后再来后悔就晚了。这一点特别容易做到,因为正如本文先前所讨论的,您可以使用 convert 来更改图像的格式。

JPEG 压缩对于诸如照片之类的大图像很适合。但其压缩通常是有损的(换句话说,在压缩过程中会丢弃图像数据)。这使得 JPEG 非常不适于压缩需要保持字迹清晰的文本。另一件需要牢记的事情是:损失是累积的。

如果您不希望在一系列操作之后产生的累积损失会影响到彩色图像的图像品质,通常 PNG 是个不错的选择。

有关该主题的更多信息,请参阅我所著的关于用 libtiff 处理彩色图像的文章" Graphics programming with libtiff,Part 2"(请参阅下面的参考资料中的链接)。

您还应该记住本文中所展示的大多数操作是单向的。例如,如果您将图像缩小了,则图像数据就丢失了。如果此后您再放大该图像,则输出将很粗糙。例如,让我们拍一幅照片,制作缩略图,然后再扩大该图像。为了节省一些篇幅,在此,我将只包括最初和最后的图像,而省略中间的缩略图。

图 18. 凯恩斯瀑布
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/waterfall.jpg
现在,我们将把缩小和放大操作链接在一起:

# convert -sample 10% -sample 1000% input.jpg output.jpg
这产生了如下所示的图像:

图 19. 斑驳的瀑布
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/blocky.jpg
很难在最终的图像中看出瀑布来,尽管分散您的眼光似乎有些帮助。

结束语
在本文中我们讨论了一些有趣的事情,ImageMagick 可以完成这些任务以满足您的命令行图像处理需求。这里所描述的工具并不能解决所有问题,有时您会需要定制代码段,但是通用的命令行图像处理工具通常可以大幅度地减轻工作负担。

通过命令行处理图形

原文链接: http://www-900.ibm.com/developerWorks/cn/linux/l-graf/index.shtml


使用 ImageMagick 进行翻转、缩放大小、旋转以及更多操作
Michael Still(mikal@stillhq.com)
高级软件工程师,Tower Software
2003 年 9 月

没有什么能象命令行工具这样适合于处理大批量的任务,对于图像操作也不例外。Web 开发人员和管理员会喜欢轻松处理大量文件的能力,无论是使用命令行还是脚本。程序员 Michael Still 介绍了 ImageMagick 套件,这是一个用于以多种格式对图像进行缩放大小、旋转、转换和其它操作的 Linux 工具箱,无论图像的数量是一个还是上百个,它都可以一次处理掉。

本文展示了如何使用命令行工具执行图像操作。这种任务对我而言相当常见,因为我拥有几部数码相机,并且现在管理着一个拥有几千张精彩照片的资料库。对于经常需要对大量图像进行批处理的 Web 开发人员和管理员而言,命令行工具是特别具有吸引力的选择,因为开发人员可以将它们合并成脚本。但即使您只想执行一两次操作,选择命令行也可以节省时间。

本文中讨论的命令行工具是优秀的 ImageMagick 套件的一部分,该套件是随 Red Hat Linux 一起提供的,并可免费在线下载(请参阅参考资料)。也可以通过 C、C++、Perl、Python、Java 和其它几种语言使用 ImageMagick,Linux 程序员会喜欢这样做。

请注意,有许多种方法可以完成本文中所讨论的任务。我讨论了我所使用的方法,它对我确实很有效。这并不意味着除此之外的其它工具就很差劲;仅仅表示我对于现在所使用的工具很满意。

本文采用了以具体问题作为示例进行讨论这种形式,但其思想应该也适用于其它问题领域。

生成缩略图
我对照片集所执行的第一个操作是生成缩略图。我还想减少图像的大小,以适用于网站版本因为许多人实际上并不想看到我儿子的 1920 x 1440 像素的照片。

########################################
ImageMagick 是如何工作的
ImageMagick 是作为包含了一大堆不同的图像处理库的包装器实现的,这些图像处理库包括 libtiff 和 libpng(请参阅参考资料以获取我先前所著的关于 libtiff 文章的链接)。在 ImageMagick 术语中,这些库称为代表(delegate)。这是 ImageMagick 不如定制应用程序执行得那么快的原因之一;它必须以一种通用方式编写,该方式可以处理这些库表示图像数据的不同方法。
########################################

我使用 convert 工具,它是 ImageMagick 套件的一部分。convert 确实很酷。除了图像缩放,它还具有平滑处理、均衡图像组、模糊化、在图像格式间进行转换、修剪、去斑滤镜、抖动、绘制边界、翻转、连接、重新采样、调整大小以及许多功能。查看联机帮助页以获取关于它的各种命令行选项的更多信息。本文稍后还讨论了 convert 所提供的许多在视觉上比较有趣的效果。

让我们假定我希望为这幅非常美丽的玫瑰图像制作缩略图:
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/rose.jpg
要用 convert 调整图像的大小,只要使用 -sample 命令行选项。例如,让我们假定我希望得到 80 x 40 像素的缩略图。则命令行将是:

# convert -sample 80x40 input.jpg output.jpg
这生成了如下的缩略图:
图 2. 生成缩略图的第一次尝试
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/convert-sample-8040.jpg
ImageMagick 会自动地考虑在缩放图像大小时图像的两条邻边所产生的比例。这意味着新的图像的高宽比与原图相同。在上面的示例中,这意味着缩放后的图像实际上是 53 x 40 像素,而不是所要求的 80 x 40 像素。指定输出图像大小的另一种方法是使用百分数。如果您不能确定输入图像的大小,或者并不是刻意地要得到确切大小的新图像,那么这种方法比较方便。以下是关于如何使用百分数的示例:

# convert -sample 25%x25% input.jpg output.jpg
现在,我们得到了如下所示的缩略图:

图 3. 生成缩略图的第二次尝试
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/convert-sample-25pct.jpg

使用该命令,您可以在某个目录中生成图像的缩略图。虽然本文并不是关于 shell 脚本编制的,但我会迅速地向您展示一个示例,以说明如何生成当前目录中每个 JPEG 的缩略图:

清单 1. 为当前目录中的所有 JPEG 生成缩略图

for img in `ls *.jpg`
do
convert -sample 25%x25% $img thumb-$img
done



这会生成一系列大小为实际图像 25% 的缩略图,其文件名称是在 JPEG 文件名的前面加上 thumb- 前缀。

获取关于图像文件的信息
另一个常见任务是确定图像文件的尺寸(dimension)。例如,您可能需要知道前一个示例中的缩略图有多大。

许多图像处理库提供了用于此用途的优秀工具。例如,libtiff(TIFF 库,我先前曾写过有关这个库的文章;请参阅参考资料)提供了 tiffinfo,tiffinfo 显示了关于 TIFF 文件的下列种类的信息:

# tiffinfo sample.tif
清单 2. tiffinfo 的样本输出

TIFF Directory at offset 0x146
Image Width: 352 Image Length: 288
Bits/Sample: 8
Compression Scheme: Deflate
Photometric Interpretation: RGB color
Samples/Pixel: 3
Planar Configuration: single image plane



这并不是一个关于如何使用 tiffinfo 的详尽示例,但您可以看到它返回了诸如图像大小、像素深度(每个样本的位数和每个像素的样本数的组合)以及所用的压缩方案之类的有用信息。

类似地,有一个返回 PNG 文件的类似信息的 pnginfo 命令:

# pnginfo sample.png
清单 3. pnginfo 的样本输出

sample.png...
Image Width: 640 Image Length: 480
Bitdepth (Bits/Sample): 8
Channels (Samples/Pixel): 3
Pixel depth (Pixel Depth): 24
Colour Type (Photometric Interpretation): RGB
Image filter: Single row per byte filter
Interlacing: No interlacing
Compression Scheme: Deflate method 8, 32k window
Resolution: 0, 0 (unit unknown)
FillOrder: msb-to-lsb
Byte Order: Network (Big Endian)
Number of text strings: 0 of 0



我不清楚是否存在用于其它格式(诸如 BMP、GIF 和 JPEG)功能相当的单个工具。但是,ImageMagick 又一次帮了我们的忙,这次是使用名为 identify 的工具。

# identify -verbose sample.png
清单 4. identify 的样本输出

Image: sample.png
Format: PNG (Portable Network Graphics)
Geometry: 640x480
Class: DirectClass
Type: true color
Depth: 8 bits-per-pixel component
Colors: 142360
Filesize: 555.6k
Interlace: None
Background Color: grey100
Border Color: #DFDFDF
Matte Color: grey74
Dispose: Undefined
Iterations: 0
Compression: Zip
signature: 361fe70ae623ef6f1fca44e0d29d157c2d701039fcf0f8625862925d881e13a4
Tainted: False
User Time: 0.190u
Elapsed Time: 0:01



您可以看到 identify 显示了一串关于图像文件的有用信息,如以像素为单位的图像大小、图像的色深以及图像的格式。

################################
pnginfo
当我开始使用 PNG 时,我成为 libtiff 用户已经有相当长一段时间了。当时,没有与 tiffinfo 功能相当的工具用于 PNG 文件,因此我编写了 pnginfo。(您可以通过参考资料中的链接下载 pnginfo)。
################################

identify 还有 -format 命令行标志,它允许您仅指定想要输出的信息。例如,如果您只对图像尺寸感兴趣,则可以使用如下命令:

# identify -format "%wx%h" sample.png
输出类似于:

640x480
此处,%w 表示图像宽度,而 %h 表示图像高度。有关可以与该选项一起使用的格式化字符的更多信息,请查看 identify 联机帮助页。

旋转图像
另一种经常需要用到的图像操作是旋转图像。例如,我用数码相机所拍摄的许多照片都旋转了九十度,因为我是把相机竖过来拍摄它们的。相机不会为我旋转这些照片,因此我编写了一个脚本,以便在从相机下载这些图像之后完成旋转任务。

例如,这是我在最近去塔斯马尼亚岛的亚瑟港旅游时拍摄的一幅照片:

图 4. 横卧的亚瑟港
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/portarthur.jpg
为了旋转该照片,我们再次求助于 convert 命令:

# convert -rotate 90 input.jpg output.jpg
该命令生成了如下所示的图像:

图 5. 亚瑟港
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/portarthur-rot.jpg
请注意 -rotate 选项的参数是将图像向右旋转的度数。要向左旋转,请使用负数。

更改图像的格式
convert 命令还能够转换图像文件的格式。这包括图像格式之间的转换(如将 JPEG 图像转换成 PNG)以及从颜色到灰度之间的转换、抖动和类似操作。

convert 根据命令行中给定的文件扩展名来了解输入和输出的图像格式分别是什么。因此,要将 JPEG 转换成 PNG,使用如下所示的命令行:

# convert input.jpg output.png
在我撰写本文时,ImageMagick 支持 89 种图像格式。查看 ImageMagick 网站(请参阅参考资料)以获取更多信息。

向图像添加文本注释
有时您需要向图像添加文本注释。例如,假设您的公司拥有标准的名片图像,并希望在将名片发送到打印机之前将每个雇员的详细信息都添加到名片上面。另一个示例是为通过您网站上的在线课程的用户生成表示证书(presentation certificate)。

让我们假定您从这幅图入手:

图 6. 2002 年芙萝莉雅蝶园艺博览会(FLORIADE 2002)照片
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/floriade.jpg
您可以使用下列命令行,为该图注释一些标识信息:

# convert -font helvetica -fill white -pointsize 36 \
-draw 'text 10,50 "Floriade 2002, Canberra, Australia"' \
floriade.jpg comment.jpg
这里是结果:

图 7. 加注释的 2002 年芙萝莉雅蝶园艺博览会照片
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/comment.jpg

迄今为止,这是我在本文中所展示的最复杂的 convert 命令行了,因此我将花些时间来解释它。

-font helvetica 将注释的字体设置为 Helvetica。也可以在此处指定字体文件的路径。这个示例给图像添加了标记,这样未经许可其它网站就不能再使用该图像了,但它是使用位于非标准位置的字体来完成该任务的:

# convert -font fonts/1900805.ttf -fill white -pointsize 36 \
-draw 'text 10,475 "stillhq.com"' \
floriade.jpg stillhq.jpg
以下是结果:

图 8. 带标记的图像
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/stillhq.jpg

-fill white 用白色而不是标准的黑色来填充字母。

-pointsize 36 以点为单位指定字母的大小。一英寸等于 72 点。

-draw 'text 10,50 "..."' 是一组绘图命令,在本例中是移动到位置 10, 50,然后绘制出双引号中的文本。使用单引号是因为如果需要绘制多个字,则绘图命令中需要使用双引号,而您不能在双引号中再用双引号。

其它更富艺术性的转换
convert 还实现了一系列相当艺术性的转换。我将在此处演示一些在视觉上比较有趣的转换。如果您对此感兴趣,应该查看 ImageMagick 的联机帮助页和网站以获取更多信息。这是我将用于该演示的输入图像:

图 9. 乌奴奴(Uluru)夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/uluru.jpg
这张乌奴奴(以前称为艾尔斯岩(Ayers Rock))的照片是在日落时分照的。

炭笔
炭笔效果模拟出该图像的素描画。

# convert -charcoal 2 input.jpg output.jpg
其结果如下所示:

图 10. 应用炭笔效果之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/charcoal.jpg
增加 -charcoal 选项的参数的数值会增加应用于该图像的"炭笔"数量,但也会延缓生成图像的过程。下面是一个使用稍多炭笔的示例。

# convert -charcoal 10 input.jpg output.jpg
它产生了如下结果:

图 11. 应用更多炭笔效果之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/charcoal-medium.jpg
如果您真的想近乎疯狂地使用大量炭笔:

# convert -charcoal 200 input.jpg output.jpg
您会得到这样的结果:

图 12. 应用过量炭笔效果之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/charcoal-intense.jpg

着色
着色是将每个像素的颜色与指定颜色混合的过程。该效果的参数就是要用来混合的颜色。可以用一个百分数(它将分别用于红色、绿色和蓝色),也可以用三个百分数来指定这个参数。也可以提供三个实际值中的一个。

# convert -colorize 255 input.jpg output.jpg
以下是着色之后的乌奴奴:

图 13. 应用着色效果之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/colorize.jpg

##################################
指定三个值
要指定三个值,每个值分别代表红色、绿色和蓝色三个采样,使用 red/green/blue 形式的参数。例如,10/20/30 意味着红色的值是 10、绿色值为 20 而蓝色值为 30。您也可以在这个构造中使用百分数。
##################################

内爆(Implode)
内爆效果模拟了您图像的中心被吸入虚拟黑洞的情形。所用的参数是您所期望的内爆效果量。

# convert -implode 4 input.jpg output.jpg
内爆的乌奴奴如下所示:

图 14. 内爆之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/implode.jpg
曝光
曝光是在相片冲洗过程中把底片暴露在光线中所发生的效果。这里,输入参数是应用于该效果的亮度,可以指定为绝对值,也可以是可用于像素的最大可能值的百分数。如果像素超过阈值,则对它求反。

# convert -solarize 42 input.jpg output.jpg
在曝光之后,我们的图像如下所示:

图 15. 曝光之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/solarize.jpg

################################
solarize 参数
ImageMagick 文档声明 solarize 的参数总是百分数。严格地说,这并不正确。如果参数以百分号结尾,那么就把它当作百分数。否则,就将它当作一个字面值。
################################

发散
spread 在图像之内以随机的数量移动像素。所用的参数是被移到新选择的位置的像素区域的大小。所以它指定了输出和输入的相似程度:

# convert -spread 5 input.jpg output.jpg
再次使用乌奴奴,这次是发散之后的情形:

图 16. 发散之后的乌奴奴夕照
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/spread.jpg
在一次 ImageMagick 调用中执行多条命令
您已经看到了将命令与注释示例联系起来的示例。但是,可以将本文中提到的任意 ImageMagick 命令链接起来。例如,也许我们希望制作某图像的缩略图,然后对它应用发散。在发散发生之后,我们将应用炭笔效果:

# convert -sample 25%x25% -spread 4 \ -charcoal 4 input.jpg output.jpg
这会产生:

图 17. 应用了一系列效果之后的乌奴奴
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/chained.jpg
图像操作技巧
在迫不及待地动手修改您所拥有的每一幅图像之前,您应该记住一些关于图像操作的事情。首先,您应该考虑一下打算长期使用什么图像格式,免得最终有了一大堆某种格式的图像之后再来后悔就晚了。这一点特别容易做到,因为正如本文先前所讨论的,您可以使用 convert 来更改图像的格式。

JPEG 压缩对于诸如照片之类的大图像很适合。但其压缩通常是有损的(换句话说,在压缩过程中会丢弃图像数据)。这使得 JPEG 非常不适于压缩需要保持字迹清晰的文本。另一件需要牢记的事情是:损失是累积的。

如果您不希望在一系列操作之后产生的累积损失会影响到彩色图像的图像品质,通常 PNG 是个不错的选择。

有关该主题的更多信息,请参阅我所著的关于用 libtiff 处理彩色图像的文章" Graphics programming with libtiff,Part 2"(请参阅下面的参考资料中的链接)。

您还应该记住本文中所展示的大多数操作是单向的。例如,如果您将图像缩小了,则图像数据就丢失了。如果此后您再放大该图像,则输出将很粗糙。例如,让我们拍一幅照片,制作缩略图,然后再扩大该图像。为了节省一些篇幅,在此,我将只包括最初和最后的图像,而省略中间的缩略图。

图 18. 凯恩斯瀑布
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/waterfall.jpg
现在,我们将把缩小和放大操作链接在一起:

# convert -sample 10% -sample 1000% input.jpg output.jpg
这产生了如下所示的图像:

图 19. 斑驳的瀑布
http://www-900.ibm.com/developerWorks/cn/linux/l-graf/blocky.jpg
很难在最终的图像中看出瀑布来,尽管分散您的眼光似乎有些帮助。

结束语
在本文中我们讨论了一些有趣的事情,ImageMagick 可以完成这些任务以满足您的命令行图像处理需求。这里所描述的工具并不能解决所有问题,有时您会需要定制代码段,但是通用的命令行图像处理工具通常可以大幅度地减轻工作负担。

Sunday, September 07, 2008

压缩技术

一、压缩技术  
    尽管所有的压缩技术都是针对扫描行而言,但他们之间还是有明显的区别:  
    1、MH和MR  
    修正霍夫曼编码(MH)每次只对一扫描行进行压缩,它把各行看成互不相关,对前面的扫描而改进的像素相对地址编码(MR)殷前一扫描行作为参考行,因为一页纸上的信息在垂直方向上也有很大的相关性,换句话说,不管是一幅画还是一封信,他们除了水平方向外,在垂直方向上也有连续性。因此,前一行可作为后一行的参考行,MR只需考虑前后行间的变化,增减量即可,这样,其压缩效率比MH提高了35%。  
    MR的执行看起来很复杂,实际上却很简单,假设有一幅画:一页白纸中间画一个黑色的圆。对没有圆的部分压缩,很简单:在压缩了第一行后,由于以后各行都没有变化,因此,根据MR算法,只需重复第一行,当MR扫描到圆时,黑色游程就开始出现,随着扫描下移,圆越来越大,但是由于采用前一行作为参考,因此不需要像MH那样记录所有的黑色游程,进行编码,而只需记录前后扫描行的变化率。  
    可见,MH和MR最大的区别在于,后者利用了前一行的参考信息,在垂直方向上进行了压缩。由于MR在水平方向和垂直方向都进行了压缩,因此也叫"二维压缩技术"。  
    2、MMR和MR  
    MMR与MR的唯一不同在于差错控制方法:  
    在电话线传输中,由于电子接口或其他原因引入了噪声就会导致误码,在接收不能正确的加以恢复,为了防止产生误码,就需要引入差错控制模式(ECM)。当初,在电话网传真发生比特丢失或误码时,还没有关于恢复技术方面的协议。最初的三类机压缩技术就没有ECM功能。这就需要采用一种新技术,以便在误码发生时,保持复制文件与原稿的同步,   这就是MR和MH都有线终码(EOL)这一特殊信号的原因(一般在每一扫描行结束后添加一12-24位EOL码)。当所收的传真发生误码时,接收端对此码不作处理,直至收到一EOL码,打印机只是重复上一扫描行,通常裸眼对此操作并不敏感。  
    3、MH和MMR  
    MH和MMR的区别在于MMR最初是为四类机设计的,因此不采用EOL码:四类机运行于数据网,有误码时,此数据包会被要求重发,因此本身就有纠错功能。由于没有数据丢失,因此就不需要EOL码对它进行压缩,从而提供更高的压缩效率。    
    压缩技术中,一个最重要的问题是参考行(MH代码行)的发送频率为多少,即所谓的系统数K。在MR中,采用标准清晰度时,K=2,即每两线发送一参考行,采用最佳清晰度时,K=4,即每四线发一行,而MMR不采用MH代码行,因此,其系统数K为无穷大。MMR只在每页图文的第一行发一MH代码,然后依次往下采用二维编码,当然其中需纠错信号,假如在MR中这样做,但没有差错检测,则任一扫描行上发生的误码都将影响下面的每一行,从而破坏了整页信息。  
    到1992年,TSS的三类机研究小组允许三类机选用四类机的编码技术。由于MMR只在那些支持ECM的三类机上进行发送,因此收发双方必须对任何类型数据都有ECM功能。GammaLink支持MMR技术,它是第一个支持三类机ECM,提供基于计算机的传真(CBF)卡生产厂商。  
    有趣的是,有时采用这些压缩编码的技术会产生相反的作用,产生比实际像点还多的数据流,这主要发生在灰度等级图像中。因为在这种情况下许多变化信息(黑或白,水平或垂直)要求发送许多附加的信息代码。这也是计算机发送灰度信息时,速度非常慢的原因。  
  二、压缩、分辨率和速度  
    压缩和分辨率之间没有一点联系:MH、MR和MMR仅仅是一些无损压缩技术,即压缩和解压缩算法,只对给定方向的像素数进行处理,它们对分辨率没有任何影响。  
    如上所述,压缩与速度也是不相干的,压缩技术是对位图进行处理,而不受扫描器和调制器的影响,有一点可以说明:我们可以对硬盘上的位图进行压缩,而获得一幅压缩图像,而这中间未涉及任何传输技术或传真。  
    任何人如果想对一个公司的传真费用进行估计和预算,就必须明白一点,由于采用不同的压缩技术,同一文件压缩后的尺寸和所需存储空间都不同。一文件经充分压缩后其尺寸会变得很小,所需传输时间也大大减少。  
    利用更快速度的调制器,如发送速度达14.4Kbps的调制器,在单位时间内就能通过电话线发送更多的信息,但这仅仅是在电话线上传输更快,而信息的压缩率没变,不管用的是MH、MR、还是MMR,即与图像、扫描系统和压缩无关。另外,发送速度不依赖于发射机,而主要与接收机有关。  
    传统的传真机是一种机械机制,在它接收完一行信息后,必须重新设置扫描头并进纸。现在的打印机扫描一行只需10   ms,这在5年前是不可想象的。  
    在握手期间,发送正文信息前,收发传真设备间必须交换一些有关扫描性能的消息。如果接收机速度为每行20   ms,而发送机速度比它快,发送端将增一些填充码,这些附加的比特,占据了一定的传输时间,使远端的接收机在接收下一扫描行前有时间重新设置,其数目由接收机性能决定。但是,假如一高速发送机向一低速接收机发送信息,增加了许多填充码,这就会使发送机原有的高速传输、省时优点没有得到应有的体现。  
    当前,传真正朝着高速化发展。GammaLink的14.4Kbps调制器的芯片已引入GammaFaxCPi和MLCP4传真卡中,它大大减少了发送时间,使低费用、高速传真成为现实,满足了用户的需要,并赢得了良好的国际信誉,使其在传真业的竞争中独领风骚。  
    有一点用户必须记住:由于传真中一种压缩技术,在CBF中用户必须拥有一些软件工具,才能对所接收的图像进行充分的解压缩,文件的尺寸决定了占用电话线的时间,而这直接决定了费用的多少。  

Sunday, May 11, 2008

gdb remote debug

1. gdbserver 192.168.0.230:4321  mytstprog
2. gdb
         target remote 192.168.0.230:4321
         symbol-file mytstprog
 
  (or    gdb -q -s mytstprog )
 
ok,
  

Wednesday, May 07, 2008

AMR音频编码器概述及文件格式分析

 
AMR音频编码器概述及文件格式分析

全称Adaptive Multi-Rate,自适应多速率编码,主要用于移动设备的音频压缩比比较大但相对其他的压缩格式质量比较差由于多用于人声通话效果还是很不错的。

一、分类

1. AMR: 又称为AMR-NB,相对于下面的WB而言,

语音带宽范围:3003400Hz

8KHz抽样

2. AMR-WB:AMR WideBand

      语音带宽范围: 507000Hz

      16KHz抽样

"AMR-WB"全称为"Adaptive Multi-rate - Wideband",即"自适应多速率宽带编码",采样频率为16kHz,是一种同时被国际标准化组织ITU-T3GPP采用的宽带语音编码标准,也称 G722.2标准。AMR-WB提供语音带宽范围达到507000Hz,用户可主观感受到话音比以前更加自然、舒适和易于分辨。

  与之作比较,现在GSM用的EFR(Enhenced Full Rate,增强型全速率编码)采样频率为8kHz,语音带宽为2003400Hz

  AMR-WB应用于窄带GSM(全速信道16kGMSK)的优势在于其可采用从6.6kb/s, 8.85kb/s12.65kb/s三种编码,当网络繁忙时C/I恶化,编码器可以自动调整编码模式,从而增强QoS。在这种应用中,AMR-WB抗扰 度优于AMR-NB

AMR-WB应用于EDGE3G可充分体现其优势。足够的传输带宽保证AMR-WB可采用从6.6kb/s23.85kb/s共九种编码,语音质量超越PSTN固定电话。

 

二、编码方式

1. AMR-NB:

AMR 一共有16种编码方式, 0-7对应8种不同的编码方式, 8-15 用于噪音或者保留用。

Frame Type

Mode Indication

Mode Request

Frame content (AMR mode, comfort noise, or other)

0

0

0

AMR 4,75 kbit/s

1

1

1

AMR 5,15 kbit/s

2

2

2

AMR 5,90 kbit/s

3

3

3

AMR 6,70 kbit/s (PDC-EFR)

4

4

4

AMR 7,40 kbit/s (TDMA-EFR)

5

5

5

AMR 7,95 kbit/s

6

6

6

AMR 10,2 kbit/s

7

7

7

AMR 12,2 kbit/s (GSM-EFR)

8

-

-

AMR SID

9

-

-

GSM-EFR SID

10

-

-

TDMA-EFR SID

11

-

-

PDC-EFR SID

12-14

-

-

For future use

15

-

-

No Data (No transmission/No reception)

 

2. AMR-WB:

Frame Type Index

Mode Indication

Mode Request

Frame content (AMR-WB mode, comfort noise, or other)

0

0

0

AMR-WB 6.60 kbit/s

1

1

1

AMR-WB 8.85 kbit/s

2

2

2

AMR-WB 12.65 kbit/s

3

3

3

AMR-WB 14.25 kbit/s

4

4

4

AMR-WB 15.85 kbit/s

5

5

5

AMR-WB 18.25 kbit/s

6

6

6

AMR-WB 19.85 kbit/s

7

7

7

AMR-WB 23.05 kbit/s

8

8

8

AMR-WB 23.85 kbit/s

9

-

-

AMR-WB SID (Comfort Noise Frame)

10-13

-

-

For future use

14

-

-

speech lost

15

-

-

No Data (No transmission/No reception)

 

-

-

 

 

二、AMR 帧格式:

AMR 有两种类型的帧格式:AMR IF1 AMR IF2

1. AMR IF1:

  IF1 的帧格式如下图所示:

FrameType, Mode Indication, Mode Request 对应上面两个表格里的数。从上面的表格里我们可以看出,这三个域的值是相同的。所以在IF2中省略了Mode Indication, Mode Request 两个域。

Frame Quality Indicator: 0表示bad frame 或者corrupted frame 1表示 good frame

每一帧的数据有分为三个部分:Class A/B/C

Class A:一帧中最敏感、最重要的数据。一旦这一部份数据有损坏,整个帧就无法解码,就损坏了。所以,一般在无线传输的时候要使用各种冗余的方式对这部分数据加以保护。

Class B:相对于Class A不那么重要的数据。

Class C:比Class B还不重要的数据。

 

2. AMR IF2:

  IF2的帧格式如下图所示:

相对于IF1, IF2 省去了Frame Quality Indicator, Mode Indication, Mode Request CRC 校验。但是增加了bit 填充。因为AMR帧中数据的长度并不是字节(8bit)的整数倍,所以在有些帧的末尾需要增加bit填充,以使整个帧的长度达到字节的整数倍。

有关IF2帧中各个域的信息请参考下面的帧大小节的表格。

 

三、帧大小

1. AMR-NB

Frame Type Index

Frame content

Number of bits in Frame Type

Number of Bits in AMR Core Frame

Number of Bits in
Bit Stuffing

Number of octets (N)

0

AMR 4,75 

4

95

5

13

1

AMR 5,15

4

103

5

14

2

AMR 5,90

4

118

6

16

3

AMR 6,70

4

134

6

18

4

AMR 7,40

4

148

0

19

5

AMR 7,95

4

159

5

21

6

AMR 10,2

4

204

0

26

7

AMR 12,2

4

244

0

31

8

AMR SID

4

39

5

6

9

GSM-EFR SID

4

43

1

6

10

TDMA-EFR SID

4

38

6

6

11

PDC-EFR SID

4

37

7

6

12-14

For future use

-

-

-

-

15

No Data

4

0

4

1

 

Number of bits in Classes A, B, and C for each AMR codec mode

Frame Type

AMR
codec mode

Total number of bits

Class A

Class B

Class C

0

4,75

95

42

53

0

1

5,15

103

49

54

0

2

5,90

118

55

63

0

3

6,70

134

58

76

0

4

7,40

148

61

87

0

5

7,95

159

75

84

0

6

10,2

204

65

99

40

7

12,2

244

81

103

60

 

2. AMR-WB:

Composition of AMR-WB IF2 Frames for all Frame Types

 

 

 

Frame Type Index

Frame content

Number of bits in Frame Type

Number of bits in Frame Quality Indicator

Number of Bits in AMR-WB Core Frame

Number of Bits in Bit Stuffing

Number of octets (N)

0

AMR-WB 6.60 kbit/s

4

1

132

7

18

1

AMR-WB 8.85 kbit/s

4

1

177

2

23

2

AMR-WB 12.65 kbit/s

4

1

253

6

33

3

AMR-WB 14.25 kbit/s

4

1

285

6

37

4

AMR-WB 15.85 kbit/s

4

1

317

6

41

5

AMR-WB 18.25 kbit/s

4

1

365

6

47

6

AMR-WB 19.85 kbit/s

4

1

397

6

51

7

AMR-WB 23.05 kbit/s

4

1

461

6

59

8

AMR-WB 23.85 kbit/s

4

1

477

6

61

9

AMR-WB SID (Comfort Noise Frame)

4

1

40

3

6

10-13

For future use

-

-

-

-

-

14

speech lost

4

1

0

3

1

15

No Data (No transmission/No reception)

4

1

0

3

1

 

Frame Type

AMR-WB
codec mode

Total number of bits

Class A

Class B

Class C

0

6.60

132

54

78

0

1

8.85

177

64

113

0

2

12.65

253

72

181

0

3

14.25

285

72

213

0

4

15.85

317

72

245

0

5

18.25

365

72

293

0

6

19.85

397

72

325

0

7

23.05

461

72

389

0

8

23.85

477

72

405

0

 

 

四、PCM16AMR之间的转换
Amr
一帧为20毫秒
AMR 4.75Kbits/s为例:

每秒产生的声音位数 = 4750bits/s
20ms帧占用的位数 = 4750bits/s / 50frames/s = 95bits
20ms帧占用的字节数 = 95bits / 8bits/byte = 11.875bytes - 圆整到12字节不足的补0
加上一个字节的帧头所以20ms一帧的AMR: 12-bytes + 1-byte = 13-bytes

相反转换回来就成了
13-bytes * 50frames/s * 8bits/byte = 5200bits/s

注意这里两个数值并不对应是由于圆整的原因

 

五、 AMR 文件的存储格式(RFC 3267):

AMR IF1, IF2定义了 AMR的帧格式, 用于无线传输用。 RFC 3267定义了把AMR数据存成文件的文件格式。

AMR的文件格式如下图1所示:

它包含一个文件头,然后就是一帧一帧的AMR数据了。

 

<!--[if !supportLists]-->1.       <!--[endif]-->文件头格式:

 AMR 文件支持单声道和多声道。单声道和多声道的文件头是不同的。

 单声道:

 AMR-NB文件头: "#!AMR\n" (or 0x2321414d520a in hexadecimal)(引号内的部分)

 AMR-WB 文件头:"#!AMR-WB\n" (or 0x2321414d522d57420a in hexadecimal).(引号内)

多声道:

多声道的文件头包含一个magic number32bit channle description域。

AMR-NB magic number"#!AMR_MC1.0\n"

(or 0x2321414d525F4D43312E300a in hexadecimal).

AMR-WBmagic number"#!AMR-WB_MC1.0\n"

                         (or 0x2321414d522d57425F4D43312E300a in hexadecimal).

32bitchannel description域的定义如下:

其中 reserved bits必须为0 CHAN:表示当前文件中含有几个声道。

 

帧头的格式:

帧头的格式如图2 所示, 它占1个字节(8bit

P为填充为设置为0

FT为编码模式, 即上面提到的16中编码模式。
Q
为帧质量指示器,如果为0表明帧被损坏。

 

3 列举了AMR-NB 5.9Kbit的一个帧的格式,

对于5.9kbit一帧的有118bit的数据,15*8=120=118+2, 所以在最后有2bit的填充位。

 

 

参考文献:

RFC3267 RTP Payload Format for AMR and AMR-WB

3GPP TS 26.201 V6.0.0

3GPP TS 26.101 V6.0.0