需求:將圖像變形,如矩形圖片變換成梯形的,圖素拉伸。
解決方案:目前找到有兩種。
- 使用EmguCV,它是.Net版的OpenCV。推薦直接在VS里的Nuget中搜索EmguCV進行下載。
- 使用第三方庫FreeImageTransformation。(網上搜YLScsFreeTransform關鍵字)
- 使用第三方庫MagickImage。(非常厲害的魔法~)
思路:首先一張圖片有四個點,給圖片實體類准備一個屬性,用於記錄變形前和變形后的四個點XY坐標值。
public float[] ProjectTransform { get; set; }
該數組有16個float型元素,按順序分別表示:
變形前左上角X值 變形前左上角Y值 變形后左上角X值 變形后左上角Y值
變形前右上角X值 變形前右上角Y值 變形后右上角X值 變形后右上角Y值
變形前左下角X值 變形前左下角Y值 變形后左下角X值 變形后左下角Y值
變形前右下角X值 變形前右下角Y值 變形后右下角X值 變形后右下角Y值
若使用方案一:EmguCV
- 將Emgu的dll引入到項目中。
- 實體類中定義一個Image屬性用於保存圖像,該屬性是UMat類型。這是OpenCV中的類型。
- 使用圖像變形函數 CvInvoke.WarpPerspective()
private UMat image;
public UMat Image
{
get
{
if (ProjectTransform != null)
{
PointF[] corner = new PointF[4];
PointF[] trans_corner = new PointF[4];
UMat trans_img = new UMat();
// 變換前的四個角的坐標
corner[0] = new PointF(ProjectTransform[0], ProjectTransform[1]);
corner[1] = new PointF(ProjectTransform[4], ProjectTransform[5]);
corner[2] = new PointF(ProjectTransform[8], ProjectTransform[9]);
corner[3] = new PointF(ProjectTransform[12], ProjectTransform[13]);
// 變換后的四個角的坐標
trans_corner[0] = new PointF(ProjectTransform[2], ProjectTransform[3]);
trans_corner[1] = new PointF(ProjectTransform[6], ProjectTransform[7]);
trans_corner[2] = new PointF(ProjectTransform[10], ProjectTransform[11]);
trans_corner[3] = new PointF(ProjectTransform[14], ProjectTransform[15]);
// 變形規則
Mat transform = CvInvoke.GetPerspectiveTransform(corner, trans_corner);
// 圖像變形
CvInvoke.WarpPerspective(image, trans_img, transform, new Size(image.Cols, trans_img.Rows));
return trans_img;
}
return image; // 如果該圖片沒有ProjectTransform屬性,說明該圖不需要變形,直接取出來使用即可。
}
set { image = value; }
}
若使用方案二:第三方庫YLScsFreeTransform
- 從下載的項目中,通過閱讀源碼,只取出我們需要用到的部分,做成工具類。(下載地址)
- 使用辦法如下:
private Bitmap image
public Bitmap Image
{
get
{
if (ProjectTransform != null)
{
PointF[] trans_corner = new PointF[4];
// 扭曲圖像
trans_corner[0] = new PointF(ProjectTransform[2], ProjectTransform[3]); // 變換后的左上角XY
trans_corner[1] = new PointF(ProjectTransform[6], ProjectTransform[7]); // 變換后的右上角XY
trans_corner[2] = new PointF(ProjectTransform[10], ProjectTransform[11]); // 變換后的左下角XY
trans_corner[3] = new PointF(ProjectTransform[14], ProjectTransform[15]); // 變換后的右下角XY
using (System.Drawing.Bitmap sourceImg = image.Bitmap)
{
YLScsDrawing.Imaging.Filters.FreeTransform filter = new YLScsDrawing.Imaging.Filters.FreeTransform();
filter.Bitmap = sourceImg;
// assign FourCorners (the four X/Y coords) of the new perspective shape
//filter.FourCorners = new System.Drawing.PointF[] { trans_corner[0], trans_corner[1], trans_corner[2], trans_corner[3] };
filter.VertexLeftTop = trans_corner[0];
filter.VertexTopRight = trans_corner[1];
filter.VertexBottomLeft = trans_corner[2];
filter.VertexRightBottom = trans_corner[3];
filter.IsBilinearInterpolation = true; // optional for higher quality
System.Drawing.Bitmap perspectiveImg = filter.Bitmap;
return perspectiveImg;
}
}
return image;
}
set { image = value; }
}
若使用方案三:第三方庫MagickImage
- 在NuGet中搜MagickImage,下載最高下載量那個,導入項目中。
public Bitmap Image
{
get
{
return image;
}
set
{
if (ProjectTransform != null)
{
using (ImageMagick.MagickImage magickImage = new ImageMagick.MagickImage(value))
{
magickImage.VirtualPixelMethod = ImageMagick.VirtualPixelMethod.Transparent;
magickImage.MatteColor = new ImageMagick.MagickColor(255, 255, 255, 0);
magickImage.FilterType = ImageMagick.FilterType.Point;
magickImage.Distort(ImageMagick.DistortMethod.Perspective, ProjectTransform);
image = magickImage.ToBitmap();
image.SetResolution(72, 72); // 坑點:因為WPF的默認DPI為96的問題,在圖像轉型Bitmap時DPI會改變,需要手動修改。
}
}
else
{
image = value;
}
}
}
- 小問題:注意這次圖片變形的過程寫在了Set()中而不是Get()中,因為寫Get中運行時偶爾會發生圖片未經過變形處理的情況,猜想可能是因為WPF的綁定時機及先后順序問題,寫到Set中因為能確保For循環是按順序執行的。
小結:
- 在項目中,由於有幾十張2400*1440的大圖需要扭曲變形(項目需求是用2D圖片的扭曲拉伸來模擬3D透視效果)。在切換圖素/重新加載場景時,使用EmguCV會導致程序直接崩潰(EmguCV報錯),可能是因為圖像沒有釋放干凈導致內存爆炸。所以最后改用了C#原生的YLScsFreeTransform庫。運行效率感覺比OpenCV的更快。。。。最重要的是,更多的圖片變形也沒有導致程序崩潰。
- 2017.5.24更新:最后選用了方案三MagickImage,是因為用YLScsFreeTransform變形的圖片時等比縮放的,因此2D圖像從矩形變形為梯形來模擬3D效果時,無法做到近大遠小的透視效果。而用MagickImage做變形能夠做到這一點!