c#数字图像处理(十一)图像旋转
2021-04-19 17:25
如果平面上的点绕原点逆时针旋转θo,则其坐标变换公式为:
x‘=xcosθ+ysinθ y=-xsinθ+ycosθ
其中,(x, y)为原图坐标,(x’, y’)为旋转后的坐标。它的逆变换公式为:
x=x‘cosθ-y‘sinθ y=x‘sinθ+y‘cosθ
矩阵形式为:
和缩放类似,旋转后的图像的像素点也需要经过坐标转换为原始图像上的坐标来确定像素值,同样也可能找不到对应点,因此旋转也用到插值法。在此选用性能较好的双线性插值法。为提高速度,在处理旋转90o、-90o、±180o时使用了镜像来处理。
////// 图像旋转 /// /// 原始图像 /// 旋转角度 /// 目标图像 /// 处理成功 true 失败 false public static bool Rotation(Bitmap srcBmp, double degree, out Bitmap dstBmp) { if (srcBmp == null) { dstBmp = null; return false; } dstBmp = null; BitmapData srcBmpData = null; BitmapData dstBmpData = null; switch ((int)degree) { case 0: dstBmp = new Bitmap(srcBmp); break; case -90: dstBmp = new Bitmap(srcBmp.Height, srcBmp.Width); srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { byte* ptrSrc = (byte*)srcBmpData.Scan0; byte* ptrDst = (byte*)dstBmpData.Scan0; for (int i = 0; i ) { for (int j = 0; j ) { ptrDst[j * dstBmpData.Stride + (dstBmp.Height - i - 1) * 3] = ptrSrc[i * srcBmpData.Stride + j * 3]; ptrDst[j * dstBmpData.Stride + (dstBmp.Height - i - 1) * 3 + 1] = ptrSrc[i * srcBmpData.Stride + j * 3 + 1]; ptrDst[j * dstBmpData.Stride + (dstBmp.Height - i - 1) * 3 + 2] = ptrSrc[i * srcBmpData.Stride + j * 3 + 2]; } } } srcBmp.UnlockBits(srcBmpData); dstBmp.UnlockBits(dstBmpData); break; case 90: dstBmp = new Bitmap(srcBmp.Height, srcBmp.Width); srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { byte* ptrSrc = (byte*)srcBmpData.Scan0; byte* ptrDst = (byte*)dstBmpData.Scan0; for (int i = 0; i ) { for (int j = 0; j ) { ptrDst[(srcBmp.Width - j - 1) * dstBmpData.Stride + i * 3] = ptrSrc[i * srcBmpData.Stride + j * 3]; ptrDst[(srcBmp.Width - j - 1) * dstBmpData.Stride + i * 3 + 1] = ptrSrc[i * srcBmpData.Stride + j * 3 + 1]; ptrDst[(srcBmp.Width - j - 1) * dstBmpData.Stride + i * 3 + 2] = ptrSrc[i * srcBmpData.Stride + j * 3 + 2]; } } } srcBmp.UnlockBits(srcBmpData); dstBmp.UnlockBits(dstBmpData); break; case 180: case -180: dstBmp = new Bitmap(srcBmp.Width, srcBmp.Height); srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { byte* ptrSrc = (byte*)srcBmpData.Scan0; byte* ptrDst = (byte*)dstBmpData.Scan0; for (int i = 0; i ) { for (int j = 0; j ) { ptrDst[(srcBmp.Width - i - 1) * dstBmpData.Stride + (dstBmp.Height - j - 1) * 3] = ptrSrc[i * srcBmpData.Stride + j * 3]; ptrDst[(srcBmp.Width - i - 1) * dstBmpData.Stride + (dstBmp.Height - j - 1) * 3 + 1] = ptrSrc[i * srcBmpData.Stride + j * 3 + 1]; ptrDst[(srcBmp.Width - i - 1) * dstBmpData.Stride + (dstBmp.Height - j - 1) * 3 + 2] = ptrSrc[i * srcBmpData.Stride + j * 3 + 2]; } } } srcBmp.UnlockBits(srcBmpData); dstBmp.UnlockBits(dstBmpData); break; default://任意角度 double radian = degree * Math.PI / 180.0;//将角度转换为弧度 //计算正弦和余弦 double sin = Math.Sin(radian); double cos = Math.Cos(radian); //计算旋转后的图像大小 int widthDst = (int)(srcBmp.Height * Math.Abs(sin) + srcBmp.Width * Math.Abs(cos)); int heightDst = (int)(srcBmp.Width * Math.Abs(sin) + srcBmp.Height * Math.Abs(cos)); dstBmp = new Bitmap(widthDst, heightDst); //确定旋转点 int dx = (int)(srcBmp.Width / 2 * (1 - cos) + srcBmp.Height / 2 * sin); int dy = (int)(srcBmp.Width / 2 * (0 - sin) + srcBmp.Height / 2 * (1 - cos)); int insertBeginX = srcBmp.Width / 2 - widthDst / 2; int insertBeginY = srcBmp.Height / 2 - heightDst / 2; //插值公式所需参数 double ku = insertBeginX * cos - insertBeginY * sin + dx; double kv = insertBeginX * sin + insertBeginY * cos + dy; double cu1 = cos, cu2 = sin; double cv1 = sin, cv2 = cos; double fu, fv, a, b, F1, F2; int Iu, Iv; srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { byte* ptrSrc = (byte*)srcBmpData.Scan0; byte* ptrDst = (byte*)dstBmpData.Scan0; for (int i = 0; i ) { for (int j = 0; j ) { fu = j * cu1 - i * cu2 + ku; fv = j * cv1 + i * cv2 + kv; if ((fv 1) || (fv > srcBmp.Height - 1) || (fu 1) || (fu > srcBmp.Width - 1)) { ptrDst[i * dstBmpData.Stride + j * 3] = 150; ptrDst[i * dstBmpData.Stride + j * 3 + 1] = 150; ptrDst[i * dstBmpData.Stride + j * 3 + 2] = 150; } else {//双线性插值 Iu = (int)fu; Iv = (int)fv; a = fu - Iu; b = fv - Iv; for (int k = 0; k 3; k++) { F1 = (1 - b) * *(ptrSrc + Iv * srcBmpData.Stride + Iu * 3 + k) + b * *(ptrSrc + (Iv + 1) * srcBmpData.Stride + Iu * 3 + k); F2 = (1 - b) * *(ptrSrc + Iv * srcBmpData.Stride + (Iu + 1) * 3 + k) + b * *(ptrSrc + (Iv + 1) * srcBmpData.Stride + (Iu + 1) * 3 + k); *(ptrDst + i * dstBmpData.Stride + j * 3 + k) = (byte)((1 - a) * F1 + a * F2); } } } } } srcBmp.UnlockBits(srcBmpData); dstBmp.UnlockBits(dstBmpData); break; } return true; }