自从普通高校实行网上录取以来,高考、成考、中考、研究生招考、自学考试都采用了电子摄像的方式来采集考生的肖像信息即考生的数码证照。数码证照作为考生档案的重要组成部分,不仅保障了网上录取的顺利进行,也大大方便了考务管理。
现行数码证照拍摄,均采用视频捕捉技术,硬件上采用“普通电脑+内、外置视频捕捉卡+视频设备(如摄像机、摄像头)”的组合,软件以Video for windows 或最新的Direct show 为基础进行的开发。由于是联机拍摄,可以直接在电脑屏幕上看到被拍摄对象,并可以按任意设定的规则存储照片,所以使用简单,管理方便,有着以往用数码相机脱机拍摄无法比拟的优点。
由于输入的视频流一般为横向320*240~640*480,如果按照相馆的拍摄方式(即用整个的取景框来拍摄证件照),就需要翻转视频输入设备,并要在拍摄过程中根据被拍摄对象的不同而不断手工调整视频设备的高、低、远、近,这在大规模现场拍摄工作中,是完全行不通的,而且,从照片精度的角度上讲,也完全没有必要。所以在实际应用中采用了在视频流窗口上放一个取景框,通过手工中拖动取景框,来切割出所需要的内容作为数码证照。这样只要被拍摄对象还在视频流窗口中,就不必调整硬件设备的空间位置,使得拍摄非常灵活。如图1所示:
图1
但人工框选切割也存在着一些问题与不足,首先就是操作人员的劳动强度很大,在一天拍摄上千考生数码证照的工作量下,每拍摄一张就要拖动取景框,并判断空间效果的好外,这不是一件轻松的事。另外,由于操作人员并不具有摄影专业素养,人工取景的方式,很难保证每一张照片中头像的空间位置的合理与一致,所以拍摄出来的照片高高低低、左左右右的现象难以避免。
是否能够按设定大小与空间位置要求自动切割出所需要的照片呢?答案是肯定的,通过不断的分析与研究,笔者初步找到了可行的解决办法。
二、 头像自动切割的原理:
要自动切割出头像,就必须找到头像的位置,而要找到头像的位置,其实就是要求出头顶像素的坐标,这样就可以按所设定的要求(照片的大小,头顶上方留出的空白大小等等)切割出头像。要在一张图片上找到特定物体的位置,首先想到的就是投影法,其基本原理与图像处理过程如下:
1、 选择正确的图像二值处理阈值。要使用投影法,必须先进行图像二值处理,而阈值的选择是二值化图像的关键。阈值的选择主要可以分为两类:全局阈值和局部阈值。全局阈值是对整个图像采用一个阈值进行划分,例如固定阈值法二值化、判断分析法二值化、基于灰度差值方图的阈值二值化法等;局部阈值是将图像分为一些子块,对于每一个子块选定一个阈值分别进行二值化。针对数码证照中的背景是相对固定且只要结合考虑拍摄时光线影响这一实际情况,可以采用全局阈值对进二值化,而要得到这一全局阈值,就是拍摄一张背景照(也就是没有被拍摄人的空背景),计算出其平均灰度(nArgGray),这一平均灰度就是全局阈值;
2、 图像二值处理。将图像中每个像素点的灰度值与阈值进行比较,由于本例中的背景是浅白色的,所以若像素点灰度值大于阈值,则该像素点灰度值置为0,反之置为255。二值处理后的图像如图2-A所示;
图2
3、 对二值化后的图像作X与Y轴上的投影。所谓X与Y轴上的投影其实就是对二值处理后的图像分别以X方向与Y方向进行灰度值为255的点进行计数。如图2-B、2-C所示。如果图像宽度为nW个像素点,那么X轴投影图是由nW条垂直方向的黑色直线构成的,直线的高度就是二值化图像中该列灰度值为255的像素点的个数。同理,如果图像高度为nH个像素点,那么Y轴投影图是由nH条水平方向的黑色直线构成的,直线的长度就是二值化图像中该行灰度值为255的像素点的个数。
4、 设定X与Y轴判别阈值线,计算出头顶坐标。从X与Y轴投影图中可以清楚的看到:在X轴投影图上,设从左边原点到投影分布区域之间的距离为x1,设投影区域的长度为x2,那么被拍摄对象头顶的X轴坐标nX就等于x1+x2/2,因为在程序上不出现小数,所以nX=x1+round(x2/2)。注:round()为四舍五入函数。而被拍摄对象头顶的Y轴坐标nY就更一目了然了,就是在Y轴投影图上投影区域的高度。为了防止噪声象素点的干扰而造成所得到的nX与nY不正确,可以有两种办法:一种是先对二值化图像进行平滑与削波处理,另一种是设根据现场拍摄的实际情况设定X与Y轴的判别阈值线,只有在线上的投影区域才被计算,这样不但可以有效的去除噪声点所带来的干扰,计算也非常简单。如图2-B、2-C所示。
5、 根据得到的头顶坐标自动切割出头像。得到了头顶坐标(nX,nY)后,根据所给定的切割条件:头顶上方留多少空白,切割框的高度与宽度等,就可以非常方便的从原图中切割出符合要求的数码证照了,如图2-D所示。
通过以上分析,头像的自动切割在理论上完全可行,但直接根据以上方法来编制程序,虽然完全可行,但潜在的计算量太大,计算也比较复杂。根据以上原理,通过进一步的分析发现,完全可以用简化的处理办法,快速的得到头顶坐标。改进后的处理如图3所示:
图3
因为人的上半身图像有着非常重要的特点:“肩膀总是比头宽、头顶总是一个人的最高点、人是左右对称的”。虽然听比较可笑,但的确可以有效的简化处理过程。也就是说,只要在颈部以下的肩部或胸部做一条水平直线进行采样,就可以得到前面通过X轴投影才能得到的头顶X轴坐标,在这一点上作一条垂线进行采样,就可以得到头顶的Y轴坐标。以下就是改进后的图像处理流程简介:
1、 求出空背景的平均灰度值(nArgGray)。方法与前面相同,不再赘述。
2、 在图像的1/4高度处进行水平采样,并算出头顶的X轴坐标。与前面的全图二值化不同,这里只对1/4高度处的水平线进行二值化,得到图3-B的结果。然后从水平采样线的两端向中间方向找出有效投影区域的起止点,具体的办法是,如果某一点的灰度值为255,并且以它为起点有至少ndB个连续点的灰度值均为255,那么该点就是起止点,nDB为噪声点阈值。这样不但可以去掉噪声点,也可以快速找到头顶的X轴坐标nX=x1+round((x2-x1)/2)。
3、 在nX处作一垂直采样线,算出头顶的Y轴坐标。算法同第2点,只方向改成是从上至下而已。
4、 根据得到的头顶坐标自动切割出头像。方法与前面相同,不再赘述。只是需要注意的是,本文分析时是按平面坐标系的第一象限来计算坐标的,而计算机图像通常是按第四象限来计算坐标的,所以在实际编程中要注意转换。
简化后的处理方法,虽然未必精确,但在实际应用时非常有效,只要根据拍摄现场的光线设置好空背景的平均灰度和噪声点阈值,就可以有非常高的正确切割率。对于那些剃了光头、染了发或是穿了件比背景更白的衣服的拍摄对像,会出现不能正确切割的情况,但为数极少,而且在程序上照样保留手工切割的功能,不会影响使用,相反倒成了调节单调工作的开心果。
您有可能注意到了,本文所讨论的头像自动切割的背景是白色的,这倒不是为了自动切割算法的方便才使用白色背景的,只是现在招生考试中所有的照片都是用黑白激光打印机打印的,如果背景为其它颜色,如红色、蓝色等,黑白打印的效果很不好,而且非常费硒鼓(现在打印机便宜,硒鼓好贵!)。如果背景是其它颜色,如红色,本文方法依然可行,只是不用算灰度值进行比较了,只要对每个像素点的RGB的R分量进行比较处理就可以了,如果背景不是三元色,即是红、绿、蓝以外的颜色,那么还是用灰度值来进行比较,但可能效果会稍差一些。
三、 头像自动切割的核心算法:
所谓头像自动切割的核心算法,其实就是求出头顶坐标的过程或函数,下面给出在Delphi 7.0下编写的计算头顶坐标的函数nGetPatePosition():
{如果自动找到头顶则返回True和头顶坐标[X,Y],否则返回False和[-1,-1];
bitmap为所需查找头顶坐标的图片;AvgGray:为空背景平均灰度;dB:为噪声阈值;
CopyWriter PeiYun HangZhou Admissions Office 2005-8,All Rights reserved.}
type
pArr =^TRGBTripleArray;
TRGBTripleArray = array [Byte] of TRGBTriple;
var
tmpbmp:TBitmap;
xp:pArr;
gray,i,j,x1,x2:integer;
begin
tmpbmp:=TBitmap.Create;
tmpbmp.Assign(bitmap);
//取得水平采样线
xp:=tmpbmp.ScanLine[round(tmpbmp.Height*3/4)];
x1:=-1;
x2:=-1;
//从左往右求X1
for i:=0 to tmpbmp.Width-1-dB do
begin
gray:=round(xp[i].rgbtRed*0.3)+round(xp[i].rgbtGreen*0.59)+round(xp[i].rgbtBlue*0.11);
//取灰度
if gray<AvgGray then
begin
j:=1;
//排除噪声点
while j<dB do
begin
gray:=round(xp[i+j].rgbtRed*0.3)+round(xp[i+j].rgbtGreen*0.59)+round(xp[i+j].rgbtBlue*0.11);
if gray<AvgGray then
j:=j+1
else
break;
end;
if j=dB then x1:=i;
end;
if x1>=0 then break;
end;
//从右往左求X2
for i:=tmpbmp.Width-1 downto dB do
begin
//取灰度
gray:=round(xp[i].rgbtRed*0.3)+round(xp[i].rgbtGreen*0.59)+round(xp[i].rgbtBlue*0.11);
if gray<AvgGray then
begin
j:=1;
//排除噪声点
while j<dB do
begin
gray:=round(xp[i-j].rgbtRed*0.3)+round(xp[i-j].rgbtGreen*0.59)+round(xp[i-j].rgbtBlue*0.11);
if gray<AvgGray then
j:=j+1
else
break;
end;
if j=dB then x2:=i;
end;
if x2>=0 then break;
end;
if (x1>=0) and (x2>=0) and (x1<x2) then
begin
//得到头顶的X轴坐标
X:=x1+round((x2-x1)/2);
//求头顶的Y轴坐标
Y:=-1;
for i:=0 to tmpbmp.Height-1-dB do
begin
if getgray(tmpbmp.Canvas.Pixels[X,i])<AvgGray then
begin
j:=1;
//排除噪声点
while j<dB do
if getgray(tmpbmp.Canvas.Pixels[X,i+j])<AvgGray then
j:=j+1
else
break;
if j=dB then Y:=i
end;
if Y>=0 then break;
end;
if Y>=0 then
result:=true
else
result:=false;
end
else
begin
X:=-1;
Y:=-1;
result:=false;
end;
tmpbmp.Free;
end;
function getgray(color:Tcolor):integer;
{取某像素点的灰度值的自定义函数}
var
r,g,b:integer;
begin
r:=byte(color);
g:=byte(color shr 8);
b:=byte(color shr 16);
result:=round(r*0.3)+round(g*0.59)+round(b*0.11);
end;