C++图像处理 -- 平面几何变换

来源:互联网 发布:ios10.2.1无法连接网络 编辑:程序博客网 时间:2024/05/17 03:50

阅读提示

    《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。

    《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。

    尽可能保持二者内容一致,可相互对照。

    本文代码必须包括《C++图像处理 -- 数据类型及公用函数》文章中的BmpData.h头文件以及《C++图像处理 -- 平面几何变换类》TransformMatrix.h文件。

 

    在《C++图像处理 -- 平面几何变换类》一文中,介绍了图像平面几何变换类TransformMatrix,并写了一个简单的临近插值法图像几何变换函数Transform,用于测试。很显然,Transform函数产生的变换图像不仅质量较差,而且也不具备通用性,只能作为一个实现图像几何变换的框架。

    本文拟采用临近插值法、双线性插值法和双立方插值法等三种插值方式,来实现较完整、通用的图形图像平面几何变换。下面是除TransformMatrix类外的全部代码(TransformMatrix类代码在《C++图像处理 -- 平面几何变换类》中)。

//---------------------------------------------------------------------------#define GetPixel4096(data, x, y)\(PARGBQuad)((LPBYTE)data->Scan0 + (y >> 12) * data->Stride + ((x >> 12) << 2))//---------------------------------------------------------------------------// 获取临近插值颜色FORCEINLINEARGBQuad GetNearColor(CONST BitmapData *data, UINT x, UINT y){return *GetPixel4096(data, x, y);}//---------------------------------------------------------------------------// 获取线性插值颜色FORCEINLINEARGBQuad GetBilinearColor(CONST BitmapData *data, UINT x, UINT y){UINT u = (x & 0xfff) >> 4;// u = (x % 0x1000) / 16UINT v = (y & 0xfff) >> 4;// v = (y % 0x1000) / 16UINT u0 = u ^ 255;// u0 = 255 - uUINT v0 = v ^ 255;// v0 = 255 - vUINT m0 = v0 * u0;UINT m1 = v * u0;UINT m2 = v0 * u;UINT m3 = v * u;PARGBQuad p0 = GetPixel4096(data, x, y);PARGBQuad p1 = (PARGBQuad)((LPBYTE)p0 + data->Stride);PARGBQuad p2 = p0 + 1;PARGBQuad p3 = p1 + 1;ARGBQuad color;// 如果不要求很高精度,/ (255 * 255)可改为 >> 16,能提高速度color.Blue = (p0->Blue * m0 + p1->Blue * m1 + p2->Blue * m2 + p3->Blue * m3) / (255 * 255);color.Green = (p0->Green * m0 + p1->Green * m1 + p2->Green * m2 + p3->Green * m3) / (255 * 255);color.Red = (p0->Red * m0 + p1->Red * m1 + p2->Red * m2 + p3->Red * m3) / (255 * 255);color.Alpha = (p0->Alpha * m0 + p1->Alpha * m1 + p2->Alpha * m2 + p3->Alpha * m3) / (255 * 255);return color;}//---------------------------------------------------------------------------static INT uvTable[513];// 获取双立方插值颜色FORCEINLINEARGBQuad GetBicubicColor(CONST BitmapData *data, UINT x, UINT y){INT us[4], vs[4];UINT u = (x & 0xfff) >> 4;// u = (x % 0x1000) / 16UINT v = (y & 0xfff) >> 4;// v = (y % 0x1000) / 16us[0] = uvTable[256 + u];us[1] = uvTable[u];us[2] = uvTable[256 - u];us[3] = uvTable[512 - u];vs[0] = uvTable[256 + v];vs[1] = uvTable[v];vs[2] = uvTable[256 - v];vs[3] = uvTable[512 - v];PARGBQuad p = GetPixel4096(data, x, y);INT pixOffset = data->Stride >> 2;INT sA, sR, sG, sB;sA = sR = sG = sB = 0;for (INT i = 0; i < 4; i ++, p += pixOffset){sB += ((us[0] * p[0].Blue + us[1] * p[1].Blue +us[2] * p[2].Blue + us[3] * p[3].Blue) * vs[i]);sG += ((us[0] * p[0].Green + us[1] * p[1].Green +us[2] * p[2].Green + us[3] * p[3].Green) * vs[i]);sR += ((us[0] * p[0].Red + us[1] * p[1].Red +us[2] * p[2].Red + us[3] * p[3].Red) * vs[i]);sA += ((us[0] * p[0].Alpha + us[1] * p[1].Alpha +us[2] * p[2].Alpha + us[3] * p[3].Alpha) * vs[i]);}sB >>= 16;sG >>= 16;sR >>= 16;sA >>= 16;ARGBQuad color;sA = sA < 0? 0 : sA > 255? 255 : sA;// 因像素格式为PARGB,上限必须为sA(Alpha)而非255color.Blue = sB < 0? 0 : sB > sA? sA : sB;color.Green = sG < 0? 0 : sG > sA? sA : sG;color.Red = sR < 0? 0 : sR > sA? sA : sR;color.Alpha = sA;return color;}//---------------------------------------------------------------------------VOID InitBicubicUVTable(FLOAT slope = -0.75){static FLOAT _slope = 0;DOUBLE x, x2, x3;if (!(slope < 0)) slope = -0.75;if (_slope != slope){_slope = slope;for (INT i = 0; i <= 512; i ++){x = i * (1.0 / 256);x2 = x * x;x3 = x * x2;if (x > 2)uvTable[i] = 0;else if (x > 1)uvTable[i] = (INT)(slope * (x3 - 5 * x2 + 8 * x - 4) * 256);elseuvTable[i] = (INT)(((slope + 2) * x3 - (slope + 3) * x2 + 1) * 256);}}}//---------------------------------------------------------------------------// 目标像素pd和颜色cs合成函数。FORCEINLINEVOID MixerColor(PARGBQuad pd, ARGBQuad cs){if (cs.Alpha == 255)// 如果源像素不透明度为255,直接拷贝pd->Color = cs.Color;else if (cs.Alpha != 0)// 否则,如果源像素不透明度大于0{if (pd->Alpha == 255)// 如果目标像素不透明度为255,ARGB合成{pd->Blue += (cs.Blue - (pd->Blue * cs.Alpha + 127) / 255);pd->Green += (cs.Green - (pd->Green * cs.Alpha + 127) / 255);pd->Red += (cs.Red - (pd->Red * cs.Alpha + 127) / 255);}else  // 否则,PARGB合成{// pd转换为PARGB,cs已经是PARGB格式pd->Blue = (pd->Blue * pd->Alpha + 127) / 255;pd->Green = (pd->Green * pd->Alpha + 127) / 255;pd->Red = (pd->Red * pd->Alpha + 127) / 255;// pd与cs合成pd->Blue += (cs.Blue - (pd->Blue * cs.Alpha + 127) / 255);pd->Green += (cs.Green - (pd->Green * cs.Alpha + 127) / 255);pd->Red += (cs.Red - (pd->Red * cs.Alpha + 127) / 255);pd->Alpha += (cs.Alpha - (pd->Alpha * cs.Alpha + 127) / 255);// 重新转换为ARGBpd->Blue = pd->Blue * 255 / pd->Alpha;pd->Green = pd->Green * 255 / pd->Alpha;pd->Red = pd->Red * 255 / pd->Alpha;}}}//---------------------------------------------------------------------------typedef ARGBQuad (*InterpolateProc)(CONST BitmapData*, UINT, UINT);// 获取插值过程和扩展半径INT GetInterpolateProc(InterpolateMode mode, InterpolateProc &proc){INT radius[] = {2, 1, 2, 4};InterpolateProc procs[] = {GetBilinearColor, GetNearColor,   GetBilinearColor, GetBicubicColor};proc = procs[mode];return radius[mode];}//---------------------------------------------------------------------------VOID CopyInterpolateData(BitmapData *dest, CONST BitmapData *source, INT alpha){//SetAlphaFlag(dest, HasAlphaFlag(source));PARGBQuad pd, ps;UINT width, height;INT dstOffset, srcOffset;GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);UINT x, y;// 如果alpha < 255或者源数据含Alpha,转换为PARGB像素格式if (alpha < 255 || HasAlphaFlag(source)){for (y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset){for (x = 0; x < width; x ++, pd ++, ps ++){pd->Alpha = (alpha * ps->Alpha + 127) / 255;pd->Blue = (ps->Blue * pd->Alpha + 127) / 255;pd->Green = (ps->Green * pd->Alpha + 127) / 255;pd->Red = (ps->Red * pd->Alpha + 127) / 255;}}}// 否则, 直接像素拷贝else{for (y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset){for (x = 0; x < width; *pd ++ = *ps ++, x ++);}}}//---------------------------------------------------------------------------VOID FillBorder(BitmapData *data, UINT radius, BOOL fillX, BOOL fillY){UINT height = data->Height - (radius << 1);UINT x, y;PARGBQuad pd, ps;if (fillX){UINT width = data->Width - (radius << 1);pd = (PARGBQuad)data->Scan0 + radius * data->Width;for (y = 0; y < height; y ++){for (x = 0, ps = pd + radius; x < radius; *pd ++ = *ps, x ++);for (x = 0, pd += width, ps = pd - 1; x < radius; *pd ++ = *ps, x ++);        }}if (fillY){pd = (PARGBQuad)data->Scan0;ps = pd + radius * data->Width;PARGBQuad pd2 = ps + height * data->Width;PARGBQuad ps2 = pd2 - data->Width;for (y = 0; y < radius; y ++){for (x = 0; x < data->Width; *pd ++ = ps[x], *pd2 ++ = ps2[x], x ++);}    }}//---------------------------------------------------------------------------BOOL CanTransform(INT width, INT height, RECT &r){r.right += r.left;r.bottom += r.top;if (r.right > width) r.right = width;if (r.bottom > height) r.bottom = height;if (r.left > 0) r.right -= r.left; else r.left = 0;if (r.top > 0) r.bottom -= r.top; else r.top = 0;return r.right > 0 && r.bottom > 0;}BOOL GetTransformParams(INT dstWidth, INT dstHeight,INT srcWidth, INT srcHeight, TransformMatrix &matrix, RECT &dst, RECT &src){FLOAT fx, fy, fwidth, fheight;matrix.GetTransformSize(srcWidth, srcHeight, fx, fy, fwidth, fheight);matrix.Invert();dst.left = (LONG)fx;dst.top = (LONG)fy;dst.right = (LONG)(fwidth + fx + 0.999999f);dst.bottom = (LONG)(fheight + fy + 0.999999f);if (!CanTransform(dstWidth, dstHeight, dst))return FALSE;if (fx > 0 || fy > 0){if (fx < 0) fx = 0;else if (fy < 0) fy = 0;matrix.Translate(fx, fy);}matrix.GetTransformSize(dst.right, dst.bottom, fx, fy, fwidth, fheight);src.left = (LONG)fx;src.top = (LONG)fy;src.right = (LONG)(fwidth + fx + 0.999999f);src.bottom = (LONG)(fheight + fy + 0.999999f);if (!CanTransform(srcWidth, srcHeight, src))return FALSE;if (fx > 0) matrix.GetElements().dx -= fx;if (fy > 0) matrix.GetElements().dy -= fy;return TRUE;}//---------------------------------------------------------------------------// 执行图像数据几何变换VOID ImageTransform(BitmapData *dest, INT x, INT y,CONST BitmapData *source, TransformMatrix *matrix, FLOAT alpha = 1.0f){INT alphaI = (INT)(alpha * 255);if (alphaI <= 0) return;if (alphaI > 255) alphaI = 255;// 复制几何变换矩阵对象TransformMatrix m(matrix);// 几何变换矩阵绝对增加平移量x, ym.GetElements().dx += x;m.GetElements().dy += y;// 逆转几何变换矩阵,计算并分别返回目标和源图像实际大小dstR和srcRRECT dstR, srcR;if (GetTransformParams(dest->Width, dest->Height,source->Width, source->Height, m, dstR, srcR) == FALSE)return;// 将浮点数扩大4096倍,采用定点数运算INT im11 = (INT)(m.GetElements().m11 * 4096.0f);INT im12 = (INT)(m.GetElements().m12 * 4096.0f);INT im21 = (INT)(m.GetElements().m21 * 4096.0f);INT im22 = (INT)(m.GetElements().m22 * 4096.0f);// 根据mode获取插值过程及边框扩展半径InterpolateMode mode = GetInterpolateMode(source);InterpolateProc ColorProc;INT radius = GetInterpolateProc(mode, ColorProc);BitmapData dst, src, exp, tmp;// 按dstR和srcR分别获取目标和源图像子图到dst和srcGetBitmapData(dest, dstR.left, dstR.top, dstR.right, dstR.bottom, &dst);GetBitmapData(source, srcR.left, srcR.top, srcR.right, srcR.bottom, &src);// 建立扩展半径为radius新的图像数据对象expGetBitmapData(src.Width + radius * 2, src.Height + radius * 2, &exp);// src图像数据拷贝到expGetBitmapData(&exp, radius, radius, src.Width, src.Height, &tmp);CopyInterpolateData(&tmp, &src, alphaI);// 填充exp边框像素。如果im21或im12的12位尾数不为0,说明x或y向为斜边,不填充BOOL fillX = (im21 & 0xfff) == 0;BOOL fillY = (im12 & 0xfff) == 0;FillBorder(&exp, radius, fillX, fillY);if (fillX && fillY && alphaI == 255 && !HasAlphaFlag(source) &&dest->Width == dst.Width && dest->Height == dst.Height)SetAlphaFlag(dest, FALSE);// 确定源图像边界界限INT up = radius * 0x800;INT xDown = (exp.Width - radius) * 0x1000;INT yDown = (exp.Height - radius) * 0x1000;// 几何变换逆矩阵的平移量为与子图原点对应的源图起始坐标点INT xs = (INT)(m.GetElements().dx * 4096.0f) + up + 0x800;INT ys = (INT)(m.GetElements().dy * 4096.0f) + up + 0x800;INT width = (INT)dst.Width;INT height = (INT)dst.Height;PARGBQuad pd = (PARGBQuad)dst.Scan0;INT dstOffset = (dst.Stride >> 2) - dst.Width;// 如果插值方式为双立方卷积,初始化UV表if (mode == InterpolateModeBicubic)InitBicubicUVTable(-0.75);// 按目标子图逐点复制源子图几何变换后的数据for (y = 0; y < height; y ++, ys += im22, xs += im21, pd += dstOffset){INT y0 = ys;INT x0 = xs;for (x = 0; x < width; x ++, x0 += im11, y0 += im12, pd ++){if (y0 >= up && y0 < yDown && x0 >= up && x0 < xDown){MixerColor(pd, ColorProc(&exp, x0, y0));            }}}FreeBitmapData(&exp);}//---------------------------------------------------------------------------

    同《C++图像处理 -- 平面几何变换类》的临近插值图形图像平面几何变换函数相比,本文代码有以下特点:

    1、实现了邻近插值、双线性插值和双立方插值三种插值方式,具有很强的通用性和实用性。

    2、插值过程采用了定点数运算,比浮点数运算速度快。

    3、较好的实现了边界处理。边界处理是图形图像平面几何变换的一个难点,处理不好会出现难看的锯齿或者半透明的图像边缘。本文代码采用了扩展图像边框像素的方法,较好的解决了这个问题。当边界发生倾斜(变形)时,超出图像边界的像素值设置为0,通过插值后的半透明像素点能较好地解决边界锯齿;而边界不变形时,超出图像边界的像素值用临近的边界像素值替代,这样就不会出现一些不该出现的半透明像素,避免难看的半透明的图像边缘。下面是用本文代码和GDI+几何变换函数分别作1.2倍缩放处理加0.3剪切的双线性插值处理效果图:

    4、可处理含Alpha信息的图像,同时增加了图像数据不透明度的处理。对含Alpha信息的图像数据或者不透明度小于1的几何变换,对图像源作了自乘预处理,减少了原像素与变换后的像素值得差异,保证了较好的视觉效果。下面的PNG图片几何变换处理效果图中,左边是经过自乘预处理后的,而右边是未处理的:

    5、限于文章篇幅,本文代码以通用性和清晰度为主,没作过多的优化处理,有兴趣的朋友可根据自己需要进行改进。例如,可将缩放处理过程独立,以提高缩放处理处理速度。

    下面是用BCB2010和GDI+运用本文图形图像平面几何变换代码处理Alpha像素格式图像的例子代码:

void __fastcall TForm1::Button1Click(TObject *Sender){// 获取源图像扫描线数据Gdiplus::Bitmap *bmp =  new Gdiplus::Bitmap(L"..\\..\\media\\IMG_9440_mf.jpg");BitmapData source, dest;LockBitmap(bmp, &source);// 设置几何变换TransformMatrix matrix;matrix.Scale(1.2, 1.2);matrix.Shear(0.3, 0.3);// 建立目标位图并获取其扫描线数据RECT r;matrix.GetTransformRect(source.Width, source.Height, r);Gdiplus::Bitmap *newBmp = new Gdiplus::Bitmap(r.right - r.left, r.bottom - r.top, PixelFormat32bppARGB);LockBitmap(newBmp, &dest);// 设置双立方插值方式SetInterpolateMode(&source, InterpolateModeBicubic);// 执行图像几何变换。// 注意这里使用-r.left, -r.top为坐标,使得变换后的图像完全可见ImageTransform(&dest, -r.left, -r.top, &source, &matrix, 1);// 释放图像扫描线数据(位图解锁)UnlockBitmap(newBmp, &dest);UnlockBitmap(bmp, &source);// 画几何变换后的图像Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);g->DrawImage(newBmp, 0, 0);delete g;delete newBmp;delete bmp;}//---------------------------------------------------------------------------

    因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com

    这里可访问《C++图像处理 -- 文章索引