(12条消息) C#+OpenCvSharp实现图片显示控件(可缩放显示像元)

之前实现过随意缩放的图片查看控件,利用picturebox,通过改变picturebox的Size和Location进行缩放和移动,效果不好,图片放大后没有显示像元(缩放的算法不同),而且放大倍数过大会导致绘图错误且很卡,因此,从而改变思路,重新做一个图片查看器。

最近正在学习OpenCvSharp,于是就利用OpenCvSharp实现一个图片查看器(支持图片随意缩放不卡顿且能显示图片像元、鼠标集中图片某点缩放),看网上关于这一块的资源蛮少的,有的都是跟我之前做的那个差不多,因此把思路和过程放上来,大家一起交流。

以下是效果图:

思路如下:

以控件的原点建立坐标系,根据横纵像元尺寸(PixcelSize)计算实际图片需要显示的大小,MatDisplayRect.Size =(Image.Width*PixelSize.Width,Image.Height*PixelSize.Height),MatDisplayRect.Location控制图片显示的位置。

每次重绘的时候根据MatDisplayRect.Location跟坐标轴原点(0,0)的距离和横纵像元尺寸(PixcelSize),计算出实际需要显示在屏幕中图片区域,截取该区域,根据PixcelSize计算该区域显示的屏幕尺寸并进行缩放,计算绘制起点,最后重绘在控件上。

CvDisplayGraphicsMat 类中包含了绘制Mat图片的操作

  1. /// <summary>
  2. /// 需要绘制的Mat对象
  3. /// </summary>
  4. public class CvDisplayGraphicsMat : CvDisplayGraphicsObject
  5. {
  6. protected Mat _Image = null;
  7. public Mat Image
  8. {
  9. get
  10. {
  11. return _Image;
  12. }
  13. set
  14. {
  15. if (_Image != null)
  16. {
  17. _Image.Dispose();
  18. }
  19. if (value != null)
  20. _Image = new Mat(value,new Rect(0,0,value.Width,value.Height));
  21. Reset();
  22. }
  23. }

  24. public Rect2d DispRect
  25. {
  26. get
  27. {
  28. return new Rect2d(DispOrigin, DispSize);
  29. }
  30. }

  31. public Size2d DispSize;




  32. public CvDisplayGraphicsMat()
  33. {
  34. DispSize = new Size2d(0,0);
  35. }



  36. #region override


  37. public override void Reset()
  38. {
  39. base.Reset();
  40. if(Image != null)
  41. {
  42. DispSize = new Size2d(Image.Width, Image.Height);
  43. }
  44. else
  45. DispSize = new Size2d(0, 0);

  46. }
  47. public override void Dispose()
  48. {
  49. if (_Image != null)
  50. {
  51. _Image.Dispose();
  52. }
  53. base.Dispose();

  54. }

  55. public override void OnPaint(PaintEventArgs e, Size2d pixelSize)
  56. {
  57. Rect showMatRect = new Rect(); //需要裁减的图片范围
  58. System.Drawing.PointF drawImageStartPos = new System.Drawing.PointF(); //绘制showMatRect的起始点
  59. if (DispRect.X < 0)
  60. {
  61. //显示区域的起始点X不在屏幕内
  62. showMatRect.X = (int)(Math.Abs(DispRect.X) / pixelSize.Width);
  63. drawImageStartPos.X = (float)(showMatRect.X * pixelSize.Width + DispRect.X);
  64. }
  65. else
  66. {
  67. showMatRect.X = 0;
  68. drawImageStartPos.X = (float)DispRect.X;
  69. }
  70. showMatRect.Width = (int)((e.ClipRectangle.Width - drawImageStartPos.X) / pixelSize.Width) + 1;

  71. if (DispRect.Y < 0)
  72. {
  73. //显示区域的起始点Y不在屏幕内
  74. showMatRect.Y = (int)(Math.Abs(DispRect.Y) / pixelSize.Height);
  75. drawImageStartPos.Y = (float)(showMatRect.Y * pixelSize.Height + DispRect.Y);
  76. }
  77. else
  78. {
  79. showMatRect.Y = 0;
  80. drawImageStartPos.Y = (float)DispRect.Y;
  81. }
  82. showMatRect.Height = (int)((e.ClipRectangle.Height - drawImageStartPos.Y) / pixelSize.Height) + 1;


  83. AdjustMatRect(Image, ref showMatRect);//调整需要显示Mat区域,以免截取的区域超出图片范围

  84. using (Mat displayMat = new Mat(Image, showMatRect))
  85. {
  86. //计算截取区域需要显示在屏幕中的大小
  87. CvSize drawSize = new CvSize((int)(displayMat.Width * pixelSize.Width),
  88. (int)(displayMat.Height * pixelSize.Height));

  89. if (drawSize.Width < 1) drawSize.Width = 1;
  90. if (drawSize.Height < 1) drawSize.Height = 1;
  91. Mat resizeMat = new Mat();

  92. //以Nearest的方式缩放图片尺寸
  93. Cv2.Resize(displayMat, resizeMat, drawSize, 0, 0, InterpolationFlags.Nearest);

  94. //缩放完的图片直接画在控件上
  95. System.Drawing.Image drawImage = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(resizeMat);
  96. e.Graphics.DrawImage(drawImage, drawImageStartPos);
  97. }
  98. }

  99. public override bool IsFocus(PointF pos)
  100. {
  101. return DispRect.Contains(pos.X, pos.Y);
  102. }



  103. #endregion

  104. #region public method

  105. /// <summary>
  106. /// 根据需要显示的像素大小,重新计算图像显示的尺寸
  107. /// </summary>
  108. /// <param name="pixelSize"></param>
  109. public void ResizeDispRectWithPixcelSize(Size2d pixelSize)
  110. {
  111. if (Image == null)
  112. DispSize = new Size2d(0, 0);
  113. else
  114. DispSize = new Size2d(
  115. Image.Width * pixelSize.Width, Image.Height * pixelSize.Height
  116. );
  117. }

  118. /// <summary>
  119. /// 转换屏幕坐标为图片中的像素坐标
  120. /// </summary>
  121. /// <param name="pos">屏幕坐标</param>
  122. /// <param name="pixclSize">单像元尺寸</param>
  123. /// <returns></returns>
  124. public CvPoint TransformPixelPostion(SdPoint pos,Size2d pixclSize)
  125. {
  126. CvPoint res = new CvPoint(-1, -1);
  127. if (IsFocus(pos))
  128. {
  129. res.X = (int)((pos.X - DispRect.X) / pixclSize.Width);
  130. res.Y = (int)((pos.Y - DispRect.Y) / pixclSize.Height);
  131. }
  132. return res;
  133. }
  134. #endregion

  135. #region protected method

  136. /// <summary>
  137. /// 调整显示的图片区域,以免截取的mat越界
  138. /// </summary>
  139. /// <param name="mt"></param>
  140. /// <param name="rect"></param>
  141. protected void AdjustMatRect(Mat mt, ref Rect rect)
  142. {
  143. //调整XY坐标
  144. if (rect.X < 0)
  145. rect.X = 0;
  146. if (rect.X >= mt.Width)
  147. rect.X = mt.Width - 1;
  148. if (rect.Y < 0)
  149. rect.Y = 0;
  150. if (rect.Y >= mt.Height)
  151. rect.Y = mt.Height - 1;

  152. //调整长宽
  153. if (rect.Width + rect.X > mt.Width)
  154. rect.Width = mt.Width - rect.X;
  155. if (rect.Height + rect.Y > mt.Height)
  156. rect.Height = mt.Height - rect.Y;
  157. }
  158. #endregion
  159. }

       

CvDisplay 类用于绘制所有需要绘图的元素,以及一些缩放、移动等操作

 

  1. class CvDisplay : PictureBox
  2. {
  3. #region 内部操作数据



  4. protected CvDisplayGraphicsMat _cdgMat; //Mat绘制类

  5. protected Size2d _pixcelSize; //一个图片像素需要在绘图中绘制的大小



  6. protected bool _isMouseMoving = false; //鼠标是否允许移动
  7. protected Point _mouseDownLocation; //鼠标点下的坐标

  8. protected System.Drawing.Point _mouseLocation; //鼠标实时位置


  9. protected Point _mousePixcelLocation; //鼠标放置位置的像素实际坐标



  10. #endregion



  11. #region 事件

  12. /// <summary>
  13. /// 当前像元位置变化
  14. /// </summary>
  15. public event EventHandler<PosChangedEventArgs> PositionChanged;


  16. #endregion

  17. #region 公开属性

  18. public enum AutoDisplayMode
  19. {
  20. Original,
  21. Fit,
  22. Full
  23. }

  24. /// <summary>
  25. /// 绘图元素集合
  26. /// </summary>
  27. [EditorBrowsable(EditorBrowsableState.Never)]
  28. public CvDisplayGraphicsObjectCollection GraphicsObjects
  29. {
  30. get;protected set;
  31. }

  32. [EditorBrowsable(EditorBrowsableState.Always)]
  33. [CategoryAttribute("CvDisplay"), DescriptionAttribute("自动显示图片模式")]
  34. public AutoDisplayMode AutoDisplay
  35. {
  36. get;
  37. set;
  38. }



  39. [EditorBrowsable(EditorBrowsableState.Always)]
  40. [CategoryAttribute("CvDisplay"), DescriptionAttribute("OpenCv2 Mat图片数据类")]
  41. public new Mat Image
  42. {
  43. get
  44. {
  45. return _cdgMat.Image;
  46. }
  47. set
  48. {
  49. _cdgMat.Image = value;
  50. ImageResize();
  51. }
  52. }


  53. [EditorBrowsable(EditorBrowsableState.Never)]
  54. public override Image BackgroundImage
  55. {
  56. get
  57. {
  58. return base.BackgroundImage;
  59. }
  60. set
  61. {
  62. base.BackgroundImage = null;
  63. }
  64. }


  65. #endregion

  66. public CvDisplay()
  67. {
  68. _cdgMat = new CvDisplayGraphicsMat();
  69. DoubleBuffered = true;

  70. AutoDisplay = AutoDisplayMode.Original;
  71. this.ContextMenuStrip = new ContextMenuStrip();

  72. ContextMenuStrip.Items.Add("Fit image", null, OnFitImageClick);
  73. ContextMenuStrip.Items.Add("Original image", null, OnOriginalImageClick);
  74. ContextMenuStrip.Items.Add("Full image", null, OnFullImageClick);

  75. ContextMenuStrip.Items.Add("Save as", null, OnSaveAsClick);


  76. GraphicsObjects = new CvDisplayGraphicsObjectCollection();

  77. }


  78. #region 事件处理


  79. protected virtual void OnFitImageClick(object sender, EventArgs e)
  80. {
  81. Fit();
  82. }

  83. protected virtual void OnOriginalImageClick(object sender, EventArgs e)
  84. {
  85. OriginalSize();
  86. }

  87. protected virtual void OnFullImageClick(object sender, EventArgs e)
  88. {
  89. Full();
  90. }

  91. protected virtual void OnSaveAsClick(object sender, EventArgs e)
  92. {
  93. if (Image == null) return;
  94. using (SaveFileDialog ofd = new SaveFileDialog())
  95. {
  96. ofd.Filter = "Bitmap|*.bmp";
  97. if (ofd.ShowDialog() == DialogResult.OK)
  98. {
  99. SaveAs(ofd.FileName);
  100. }
  101. }
  102. }

  103. #endregion



  104. #region 父类重载
  105. protected override void OnMouseDown(MouseEventArgs e)
  106. {

  107. if (e.Button == MouseButtons.Left)
  108. {
  109. this.Cursor = Cursors.SizeAll;
  110. _isMouseMoving = true;
  111. _mouseDownLocation = new Point(e.Location.X, e.Location.Y);
  112. }

  113. base.OnMouseDown(e);
  114. }

  115. protected virtual void ImageResize()
  116. {
  117. switch (AutoDisplay)
  118. {
  119. case AutoDisplayMode.Original:
  120. OriginalSize();
  121. break;
  122. case AutoDisplayMode.Fit:
  123. Fit();
  124. break;
  125. case AutoDisplayMode.Full:
  126. Full();
  127. break;
  128. }
  129. }
  130. protected override void OnResize(EventArgs e)
  131. {
  132. if (this.Width != 0 && this.Height != 0)
  133. {
  134. ImageResize();
  135. }

  136. base.OnResize(e);
  137. }
  138. protected override void OnMouseUp(MouseEventArgs e)
  139. {
  140. this.Cursor = Cursors.Default;
  141. _isMouseMoving = false;
  142. base.OnMouseUp(e);
  143. }

  144. protected override void OnMouseWheel(MouseEventArgs e)
  145. {
  146. if (e.Delta > 0)
  147. {
  148. Zoom(2, 2, new PointF(e.X, e.Y));
  149. }
  150. else
  151. {
  152. Zoom(0.5, 0.5, new PointF(e.X, e.Y));
  153. }
  154. base.OnMouseWheel(e);
  155. }

  156. protected override void OnMouseMove(MouseEventArgs e)
  157. {
  158. _mouseLocation = e.Location;
  159. if (_isMouseMoving && Image != null)
  160. {
  161. //移动图片
  162. Point nowLocation = new Point(e.X, e.Y);
  163. Point move = (nowLocation - _mouseDownLocation);

  164. SyncUpdateOrigin( _cdgMat.DispOrigin + move);

  165. Refresh();
  166. _mouseDownLocation = nowLocation;

  167. }
  168. else if (_cdgMat.IsFocus(e.Location))
  169. {
  170. //坐标在绘图区域内
  171. //记录实际像素点和颜色 ,提示在tooltip上
  172. this.Cursor = Cursors.Cross;
  173. Point p = _cdgMat.TransformPixelPostion(e.Location,_pixcelSize);
  174. if (!p.Equals(_mouseLocation) && !p.Equals(_mousePixcelLocation))
  175. {
  176. string tip = string.Format("({0},{1})", p.X, p.Y);
  177. object[] res = null;
  178. MatHelper.GetMatChannelValues(Image, p.X, p.Y, out res);
  179. tip += " [";
  180. foreach (object obj in res)
  181. {
  182. tip += obj + ",";
  183. }
  184. tip = tip.Substring(0, tip.Length - 1) + ']';

  185. Console.WriteLine(tip);

  186. if (PositionChanged != null)
  187. PositionChanged(this, new PosChangedEventArgs(p, res));
  188. }

  189. _mousePixcelLocation = p;
  190. }
  191. else
  192. {
  193. //坐标不在绘图区域内
  194. _mousePixcelLocation = new Point(-1, -1);
  195. }
  196. base.OnMouseMove(e);
  197. }


  198. protected override void OnPaint(PaintEventArgs e)
  199. {
  200. base.OnPaint(e);
  201. Graphics gh = e.Graphics;
  202. gh.Clear(this.BackColor);
  203. if (Image != null)
  204. {
  205. _cdgMat.OnPaint(e, _pixcelSize);

  206. }

  207. foreach(CvDisplayGraphicsObject obj in GraphicsObjects)
  208. {
  209. obj.OnPaint(e, _pixcelSize);
  210. }
  211. }

  212. #endregion

  213. #region 内部使用函数

  214. /// <summary>
  215. /// 同步更新所有绘图的原点
  216. /// </summary>
  217. /// <param name="p"></param>
  218. protected void SyncUpdateOrigin(Point2d p)
  219. {
  220. _cdgMat.DispOrigin = p;
  221. foreach(CvDisplayGraphicsObject obj in this.GraphicsObjects)
  222. {
  223. obj.DispOrigin = p;
  224. }
  225. }



  226. static System.Drawing.Point ConvertCvPoint2DrawingPoint(Point p)
  227. {
  228. return new System.Drawing.Point(p.X, p.Y);
  229. }

  230. static Point ConvertDrawingPoint2CvPoint(System.Drawing.Point p)
  231. {
  232. return new Point(p.X, p.Y);
  233. }


  234. #endregion
  235. #region 对外接口

  236. /// <summary>
  237. /// 图片缩放
  238. /// </summary>
  239. /// <param name="scale">x,y等比例缩放参数</param>
  240. public void Zoom(double scale)
  241. {
  242. Zoom(scale, scale);
  243. }

  244. /// <summary>
  245. /// 另存为
  246. /// </summary>
  247. /// <param name="filepath"></param>
  248. public void SaveAs(string filepath)
  249. {
  250. if (Image == null) return;
  251. Cv2.ImWrite(filepath, Image);
  252. }

  253. /// <summary>
  254. /// 图片缩放
  255. /// </summary>
  256. /// <param name="xScale">x缩放参数</param>
  257. /// <param name="yScale">y缩放参数</param>
  258. public void Zoom(double xScale, double yScale)
  259. {
  260. Zoom(xScale, yScale, new PointF(0, 0));
  261. }

  262. /// <summary>
  263. /// 根据某个原点进行缩放
  264. /// </summary>
  265. /// <param name="xScale">x缩放参数</param>
  266. /// <param name="yScale">y缩放参数</param>
  267. /// <param name="zoomOrign">缩放参考点</param>
  268. public void Zoom(double xScale, double yScale, PointF zoomOrign)
  269. {
  270. if (Image == null) return;
  271. double newXPixelSize = Math.Abs(xScale) * _pixcelSize.Width;
  272. double newYPixelSize = Math.Abs(yScale) * _pixcelSize.Height;
  273. if (newXPixelSize > 0 && newYPixelSize > 0)
  274. {
  275. int dispPixelX = (int)(this.Width / newXPixelSize),
  276. dispPixelY = (int)(this.Height / newYPixelSize);
  277. if (dispPixelX < 1 || dispPixelY < 1) //最少显示一个像素点
  278. return;

  279. _pixcelSize = new Size2d(newXPixelSize, newYPixelSize);
  280. if (_cdgMat.IsFocus(zoomOrign)) //如果在聚焦在图片某点放大
  281. {
  282. //变换前 图片绘制坐标原点距离 当前鼠标鼠标的距离
  283. double disX = zoomOrign.X - _cdgMat.DispOrigin.X,
  284. disY = zoomOrign.Y - _cdgMat.DispOrigin.Y;

  285. //缩放后的距离
  286. disX *= xScale;
  287. disY *= yScale;

  288. //同步更新所有需要绘图的元素的原点
  289. SyncUpdateOrigin( new Point2d(zoomOrign.X - disX, zoomOrign.Y - disY));
  290. }
  291. _cdgMat.ResizeDispRectWithPixcelSize(_pixcelSize);
  292. Refresh();
  293. }
  294. }

  295. /// <summary>
  296. /// 整个图片充满控件
  297. /// </summary>
  298. public virtual void Full()
  299. {
  300. if (Image == null) return;
  301. //换算单个像素尺寸
  302. _pixcelSize.Width = this.Width / (double)Image.Width;
  303. _pixcelSize.Height = this.Height / (double)Image.Height;

  304. _cdgMat.DispOrigin = new Point2d(0, 0);
  305. _cdgMat.ResizeDispRectWithPixcelSize(_pixcelSize);

  306. Refresh();
  307. }

  308. /// <summary>
  309. /// 自适应图片的横纵比最大化
  310. /// </summary>
  311. public virtual void Fit()
  312. {
  313. if (Image == null) return;
  314. Size2d newsize = new Size2d();
  315. double hvScale1 = this.Width / (double)this.Height,//控件横纵比
  316. hvScale2 = Image.Width / (double)Image.Height;//图片横纵比


  317. //根据横纵比算出实际上画图的大小
  318. if (hvScale1 > hvScale2)
  319. {
  320. newsize.Height = this.Height;
  321. newsize.Width = (Image.Width * ((double)newsize.Height / Image.Height));
  322. }
  323. else
  324. {
  325. newsize.Width = this.Width;
  326. newsize.Height = (Image.Height * ((double)newsize.Width / Image.Width));
  327. }


  328. //计算单像素尺寸
  329. _pixcelSize.Width = newsize.Width / (double)Image.Width;
  330. _pixcelSize.Height = newsize.Height / (double)Image.Height;
  331. _cdgMat.ResizeDispRectWithPixcelSize(_pixcelSize);

  332. SyncUpdateOrigin(new Point2d((this.Width - _cdgMat.DispRect.Width) / 2,
  333. (this.Height - _cdgMat.DispRect.Height) / 2));

  334. Refresh();
  335. }

  336. /// <summary>
  337. /// 恢复图片原始比例
  338. /// </summary>
  339. public virtual void OriginalSize()
  340. {
  341. if (Image == null) return;
  342. _pixcelSize.Width = 1.0;
  343. _pixcelSize.Height = 1.0;
  344. SyncUpdateOrigin( new Point2d(0, 0));
  345. _cdgMat.ResizeDispRectWithPixcelSize(_pixcelSize);

  346. Refresh();
  347. }

  348. #endregion

  349. }

 

结尾:目前完成图片的查看,代码比较糙(后续代码可能会重构过),后续会添加 画点、线、圆、旋转矩形等操作,最后会结合人机交互绘制以上几何形状

 

几何图形绘制及调整操作已完成,最近工作较忙,这方面的学习暂停了,源代码分享地址,需要的可自行下载:

https://download.csdn.net/download/hhf15980873586/12437898

(0)

相关推荐