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

2 篇文章 0 订阅
1 篇文章 4 订阅

        之前实现过随意缩放的图片查看控件,利用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图片的操作

/// <summary>
    /// 需要绘制的Mat对象
    /// </summary> 
public class CvDisplayGraphicsMat : CvDisplayGraphicsObject
    {
        protected Mat _Image = null;
        public Mat Image
        {
            get
            {
                return _Image;
            }
            set
            {
                if (_Image != null)
                {
                    _Image.Dispose();
                }
                if (value != null)
                    _Image = new Mat(value,new Rect(0,0,value.Width,value.Height));
                Reset();
            }
        }

        public Rect2d DispRect
        {
            get
            {
                return new Rect2d(DispOrigin, DispSize);
            }
        }

        public Size2d DispSize;




        public CvDisplayGraphicsMat()
        {
            DispSize = new Size2d(0,0);
        }

     

        #region override


        public override void Reset()
        {
            base.Reset();
            if(Image != null)
            {
                DispSize = new Size2d(Image.Width, Image.Height);
            }
            else
                DispSize = new Size2d(0, 0);

        }
        public override void Dispose()
        {
            if (_Image != null)
            {
                _Image.Dispose();
            }
            base.Dispose();

        }

        public override void OnPaint(PaintEventArgs e, Size2d pixelSize)
        {
            Rect showMatRect = new Rect(); //需要裁减的图片范围
            System.Drawing.PointF drawImageStartPos = new System.Drawing.PointF(); //绘制showMatRect的起始点
            if (DispRect.X < 0)
            {
                //显示区域的起始点X不在屏幕内
                showMatRect.X = (int)(Math.Abs(DispRect.X) / pixelSize.Width);
                drawImageStartPos.X = (float)(showMatRect.X * pixelSize.Width + DispRect.X);
            }
            else
            {
                showMatRect.X = 0;
                drawImageStartPos.X = (float)DispRect.X;
            }
            showMatRect.Width = (int)((e.ClipRectangle.Width - drawImageStartPos.X) / pixelSize.Width) + 1;

            if (DispRect.Y < 0)
            {
                //显示区域的起始点Y不在屏幕内
                showMatRect.Y = (int)(Math.Abs(DispRect.Y) / pixelSize.Height);
                drawImageStartPos.Y = (float)(showMatRect.Y * pixelSize.Height + DispRect.Y);
            }
            else
            {
                showMatRect.Y = 0;
                drawImageStartPos.Y = (float)DispRect.Y;
            }
            showMatRect.Height = (int)((e.ClipRectangle.Height - drawImageStartPos.Y) / pixelSize.Height) + 1;


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

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

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

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

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

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



        #endregion

        #region public method

        /// <summary>
        /// 根据需要显示的像素大小,重新计算图像显示的尺寸
        /// </summary>
        /// <param name="pixelSize"></param>
        public void ResizeDispRectWithPixcelSize(Size2d pixelSize)
        {
            if (Image == null)
                DispSize = new Size2d(0, 0);
            else
                DispSize = new Size2d(
                    Image.Width * pixelSize.Width, Image.Height * pixelSize.Height
                    );
        }

/// <summary>
        /// 转换屏幕坐标为图片中的像素坐标
        /// </summary>
        /// <param name="pos">屏幕坐标</param>
        /// <param name="pixclSize">单像元尺寸</param>
        /// <returns></returns>
        public CvPoint TransformPixelPostion(SdPoint pos,Size2d pixclSize)
        {
            CvPoint res = new CvPoint(-1, -1);
            if (IsFocus(pos))
            {
                res.X = (int)((pos.X - DispRect.X) / pixclSize.Width);
                res.Y = (int)((pos.Y - DispRect.Y) / pixclSize.Height);
            }
            return res;
        }
        #endregion

        #region protected method

        /// <summary>
        /// 调整显示的图片区域,以免截取的mat越界
        /// </summary>
        /// <param name="mt"></param>
        /// <param name="rect"></param>
        protected void AdjustMatRect(Mat mt, ref Rect rect)
        {
            //调整XY坐标
            if (rect.X < 0)
                rect.X = 0;
            if (rect.X >= mt.Width)
                rect.X = mt.Width - 1;
            if (rect.Y < 0)
                rect.Y = 0;
            if (rect.Y >= mt.Height)
                rect.Y = mt.Height - 1;

            //调整长宽
            if (rect.Width + rect.X > mt.Width)
                rect.Width = mt.Width - rect.X;
            if (rect.Height + rect.Y > mt.Height)
                rect.Height = mt.Height - rect.Y;
        }
        #endregion
    }

       

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

 

class CvDisplay : PictureBox
    {
        #region 内部操作数据

     

        protected CvDisplayGraphicsMat _cdgMat; //Mat绘制类

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



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

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


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



        #endregion



        #region 事件

        /// <summary>
        /// 当前像元位置变化
        /// </summary>
        public event EventHandler<PosChangedEventArgs> PositionChanged; 


        #endregion

        #region 公开属性

        public enum AutoDisplayMode
        {
            Original,
            Fit,
            Full
        }

        /// <summary>
        /// 绘图元素集合
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public CvDisplayGraphicsObjectCollection GraphicsObjects
        {
            get;protected set;
        }

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



        [EditorBrowsable(EditorBrowsableState.Always)]
        [CategoryAttribute("CvDisplay"), DescriptionAttribute("OpenCv2 Mat图片数据类")]
        public new Mat Image
        {
            get
            {
                return _cdgMat.Image;
            }
            set
            {
                _cdgMat.Image = value;
                ImageResize();
            }
        }


        [EditorBrowsable(EditorBrowsableState.Never)]
        public override Image BackgroundImage
        {
            get
            {
                return base.BackgroundImage;
            }
            set
            {
                base.BackgroundImage = null;
            }
        }


        #endregion

        public CvDisplay()
        {
            _cdgMat = new CvDisplayGraphicsMat();
            DoubleBuffered = true;

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

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

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


            GraphicsObjects = new CvDisplayGraphicsObjectCollection();

        }


        #region 事件处理


        protected virtual void OnFitImageClick(object sender, EventArgs e)
        {
            Fit();
        }

        protected virtual void OnOriginalImageClick(object sender, EventArgs e)
        {
            OriginalSize();
        }

        protected virtual void OnFullImageClick(object sender, EventArgs e)
        {
            Full();
        }

        protected virtual void OnSaveAsClick(object sender, EventArgs e)
        {
            if (Image == null) return;
            using (SaveFileDialog ofd = new SaveFileDialog())
            {
                ofd.Filter = "Bitmap|*.bmp";
                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    SaveAs(ofd.FileName);
                }
            }
        }

        #endregion



        #region 父类重载
        protected override void OnMouseDown(MouseEventArgs e)
        {

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

            base.OnMouseDown(e);
        }

        protected virtual void ImageResize()
        {
            switch (AutoDisplay)
            {
                case AutoDisplayMode.Original:
                    OriginalSize();
                    break;
                case AutoDisplayMode.Fit:
                    Fit();
                    break;
                case AutoDisplayMode.Full:
                    Full();
                    break;
            }
        }
        protected override void OnResize(EventArgs e)
        {
            if (this.Width != 0 && this.Height != 0)
            {
                ImageResize();
            }

            base.OnResize(e);
        }
        protected override void OnMouseUp(MouseEventArgs e)
        {
            this.Cursor = Cursors.Default;
            _isMouseMoving = false;
            base.OnMouseUp(e);
        }

        protected override void OnMouseWheel(MouseEventArgs e)
        {
            if (e.Delta > 0)
            {
                Zoom(2, 2, new PointF(e.X, e.Y));
            }
            else
            {
                Zoom(0.5, 0.5, new PointF(e.X, e.Y));
            }
            base.OnMouseWheel(e);
        }

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

                SyncUpdateOrigin( _cdgMat.DispOrigin + move);

                Refresh();
                _mouseDownLocation = nowLocation;

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

                    Console.WriteLine(tip);

                    if (PositionChanged != null)
                        PositionChanged(this, new PosChangedEventArgs(p, res));
                }

                _mousePixcelLocation = p;
            }
            else
            {
                //坐标不在绘图区域内
                _mousePixcelLocation = new Point(-1, -1);
            }
            base.OnMouseMove(e);
        }


        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            Graphics gh = e.Graphics;
            gh.Clear(this.BackColor);
            if (Image != null)
            {
                _cdgMat.OnPaint(e, _pixcelSize);

            }

            foreach(CvDisplayGraphicsObject obj in GraphicsObjects)
            {
                obj.OnPaint(e, _pixcelSize);
            }
        }

        #endregion

        #region 内部使用函数

        /// <summary>
        /// 同步更新所有绘图的原点
        /// </summary>
        /// <param name="p"></param>
        protected void SyncUpdateOrigin(Point2d p)
        {
            _cdgMat.DispOrigin = p;
            foreach(CvDisplayGraphicsObject obj in this.GraphicsObjects)
            {
                obj.DispOrigin = p;
            }
        }



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

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

       
        #endregion
        #region 对外接口

        /// <summary>
        /// 图片缩放
        /// </summary>
        /// <param name="scale">x,y等比例缩放参数</param>
        public void Zoom(double scale)
        {
            Zoom(scale, scale);
        }

        /// <summary>
        /// 另存为
        /// </summary>
        /// <param name="filepath"></param>
        public void SaveAs(string filepath)
        {
            if (Image == null) return;
            Cv2.ImWrite(filepath, Image);
        }

        /// <summary>
        /// 图片缩放
        /// </summary>
        /// <param name="xScale">x缩放参数</param>
        /// <param name="yScale">y缩放参数</param>
        public void Zoom(double xScale, double yScale)
        {
            Zoom(xScale, yScale, new PointF(0, 0));
        }

        /// <summary>
        /// 根据某个原点进行缩放
        /// </summary>
        /// <param name="xScale">x缩放参数</param>
        /// <param name="yScale">y缩放参数</param>
        /// <param name="zoomOrign">缩放参考点</param>
        public void Zoom(double xScale, double yScale, PointF zoomOrign)
        {
            if (Image == null) return;
            double newXPixelSize = Math.Abs(xScale) * _pixcelSize.Width;
            double newYPixelSize = Math.Abs(yScale) * _pixcelSize.Height;
            if (newXPixelSize > 0 && newYPixelSize > 0)
            {
                int dispPixelX = (int)(this.Width / newXPixelSize),
                    dispPixelY = (int)(this.Height / newYPixelSize);
                if (dispPixelX < 1 || dispPixelY < 1) //最少显示一个像素点
                    return;

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

                    //缩放后的距离
                    disX *= xScale;
                    disY *= yScale;

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

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

            _cdgMat.DispOrigin = new Point2d(0, 0);
             _cdgMat.ResizeDispRectWithPixcelSize(_pixcelSize);
           
            Refresh();
        }

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


            //根据横纵比算出实际上画图的大小
            if (hvScale1 > hvScale2)
            {
                newsize.Height = this.Height;
                newsize.Width = (Image.Width * ((double)newsize.Height / Image.Height));
            }
            else
            {
                newsize.Width = this.Width;
                newsize.Height = (Image.Height * ((double)newsize.Width / Image.Width));
            }


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

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

            Refresh();
        }

        /// <summary>
        /// 恢复图片原始比例
        /// </summary>
        public virtual void OriginalSize()
        {
            if (Image == null) return;
            _pixcelSize.Width = 1.0;
            _pixcelSize.Height = 1.0;
           SyncUpdateOrigin( new Point2d(0, 0));
             _cdgMat.ResizeDispRectWithPixcelSize(_pixcelSize);

            Refresh();
        }

        #endregion

    }

 

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

 

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

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

  • 33
    点赞
  • 89
    收藏
    觉得还不错? 一键收藏
  • 28
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值