c#中WinForm使用OpencvSharp4實現(xiàn)簡易抓邊
環(huán)境: VS2019 , OpencvSharp4 4.5.5.20211231 , .NET Framework 4.8
界面設(shè)計:

圖像顯示用的是picturebox 控件都是windows基本控件
效果展示:


圖像是自己畫圖畫的 所以抓的效果比較好 。其他圖片的話可能需要調(diào)整一下相關(guān)參數(shù),效果可能達不到這么好
實現(xiàn)原理: 在圖像中選擇ROI,從原圖上把對應ROI部分的圖像扣下來,然后對扣下來的圖像進行邊緣處理等操作,得到邊緣和擬合線,最后在原圖上將邊緣和擬合線畫出來即可。注意,得到的邊緣是相對于ROI區(qū)域的坐標,需要轉(zhuǎn)化成相對于原圖的坐標才行,只需加上ROI的坐標即可。
主要部分代碼:
定義的ROI類 注意一下四個點的相對位置
public class ROI
{
// 四個點的順序關(guān)系
// 1---2
// | |
// 3---4
public OpenCvSharp.Point FirstPoint { get; set; } = new OpenCvSharp.Point(0, 0);
public OpenCvSharp.Point SecondPoint { get; set; } = new OpenCvSharp.Point(0, 0);
public OpenCvSharp.Point ThirdPoint { get; set; } = new OpenCvSharp.Point(0, 0);
public OpenCvSharp.Point FourthPoint { get; set; } = new OpenCvSharp.Point(0, 0);
public OpenCvSharp.Point2f Center
{
get
{
OpenCvSharp.Point2f center = new OpenCvSharp.Point2f();
center.X = (float)((FirstPoint.X + SecondPoint.X + ThirdPoint.X + FourthPoint.X) / 4.0);
center.Y = (float)((FirstPoint.Y + SecondPoint.Y + ThirdPoint.Y + FourthPoint.Y) / 4.0);
return center;
}
}
public OpenCvSharp.Size2f Size
{
get
{
return new OpenCvSharp.Size2f(Width, Height);
}
}
public int XLeft
{
get { return FirstPoint.X; }
}
public int YTop
{
get { return FirstPoint.Y; }
}
public int XRight
{
get { return FourthPoint.X; }
}
public int YBottom
{
get { return FourthPoint.Y; }
}
public double Width
{
get { return FourthPoint.X - FirstPoint.X; }
}
public double Height
{
get { return FourthPoint.Y - FirstPoint.Y; }
}
public void Reset()
{
FirstPoint = new OpenCvSharp.Point(0, 0);
SecondPoint = new OpenCvSharp.Point(0, 0);
ThirdPoint = new OpenCvSharp.Point(0, 0);
FourthPoint = new OpenCvSharp.Point(0, 0);
}
// 四個點全為0 則判斷為空
public bool IsNull()
{
bool en = true;
en = en && FirstPoint == new OpenCvSharp.Point(0, 0);
en = en && SecondPoint == new OpenCvSharp.Point(0, 0);
en = en && ThirdPoint == new OpenCvSharp.Point(0, 0);
en = en && FourthPoint == new OpenCvSharp.Point(0, 0);
return en;
}
public OpenCvSharp.Point2f[] GetCoutonrs2f()
{
OpenCvSharp.Point2f[] coutonrs = new OpenCvSharp.Point2f[4];
coutonrs[0] = FirstPoint;
coutonrs[1] = SecondPoint;
coutonrs[2] = FourthPoint;
coutonrs[3] = ThirdPoint;
return coutonrs;
}
public OpenCvSharp.Point[] GetCoutonrs()
{
OpenCvSharp.Point[] coutonrs = new OpenCvSharp.Point[4];
coutonrs[0] = FirstPoint;
coutonrs[1] = SecondPoint;
coutonrs[2] = FourthPoint;
coutonrs[3] = ThirdPoint;
return coutonrs;
}
}相關(guān)變量:
public enum eDirections // ROI移動方向
{
NULL = 0,
上 = 1,
下 = 2,
左 = 3,
右 = 4
}
//ROI大小調(diào)整方式
public enum eResizeMode
{
All = 0, // 長寬一起調(diào)整
Width = 1, // 只變寬度 即 矩形的長
Height = 2, // 只變高度 即 矩形的寬
}
public class yVars
{
public static string OriImg; // 原圖
public static bool IsDrawEdgeOK = false;
public static bool pbxMouseDown = false;
public static bool IsMouseMove = false;
public static bool IsSelectROIOK = false;
public static bool IsMouseUp = false;
public static int step; //ROI區(qū)域移動步長
public static eDirections direct = eDirections.NULL;
public static int ROINum = 1; // 操作第一個ROI還是第二個ROI
public static bool IsSelectingROI = false;
//
public static bool IsSelectROI_1 = false;
public static bool IsSelectROI_1_OK = false;
public static bool IsSelectROI_2 = false;
public static bool IsSelectROI_2_OK = false;
public static ROI myROI_1 = new ROI();
public static ROI myROI_2 = new ROI();
}ROI的繪制:
矩形的ROI ,我們只需要兩個點就能確定一個矩形。
我們獲取到的位置是鼠標相對于picturebox的位置,需要轉(zhuǎn)化成相對于圖像的坐標,我的 picturebox 的 sizemode 是 stretchImage ,所以按比例轉(zhuǎn)化過去就行。
在 picturebox 的 mousedown 事件中 記錄鼠標按下的第一個位置 為ROI的第一個點。
我把繪制ROI的過程寫在 mousemove 事件里面,這樣就能實現(xiàn)在確定第一個點后鼠標移動時ROI區(qū)域一直顯示出來
private void pbxImgShow_MouseMove(object sender, MouseEventArgs e)
{
if (yVars.IsSelectROI_1 == false && yVars.IsSelectROI_2 == false)
return;
if (yVars.pbxMouseDown == false)
return;
if (yVars.IsMouseUp == true)
return;
int mx = 0, my = 0;
Mat mm = new Mat(yVars.OriImg);
// 鼠標相對于picturebox的位置
mx = Frm_Main.Instance.pbxImgShow.PointToClient(Control.MousePosition).X;
my = Frm_Main.Instance.pbxImgShow.PointToClient(Control.MousePosition).Y;
// 鼠標移動時 位置在 picturebox 中就畫出對應的ROI形狀
if (mx < pbxImgShow.Width && my < pbxImgShow.Height)
{
//轉(zhuǎn)成在圖片上的位置
double xx = mx * mm.Width * 1.0 / Frm_Main.Instance.pbxImgShow.Width;
double yy = my * mm.Height * 1.0 / Frm_Main.Instance.pbxImgShow.Height;
if (yVars.IsSelectROI_1 == true)
{
yVars.myROI_1.FourthPoint = new OpenCvSharp.Point(xx, yy);
yVars.myROI_1.SecondPoint = new OpenCvSharp.Point(xx, yVars.myROI_1.FirstPoint.Y);
yVars.myROI_1.ThirdPoint = new OpenCvSharp.Point(yVars.myROI_1.FirstPoint.X, yy);
mm = yActions.DrawROIMat(mm, yVars.myROI_1);
yVars.IsSelectROI_1_OK = true;
}
else if (yVars.IsSelectROI_2 == true)
{
yVars.myROI_2.FourthPoint = new OpenCvSharp.Point(xx, yy);
yVars.myROI_2.SecondPoint = new OpenCvSharp.Point(xx, yVars.myROI_2.FirstPoint.Y);
yVars.myROI_2.ThirdPoint = new OpenCvSharp.Point(yVars.myROI_2.FirstPoint.X, yy);
mm = yActions.DrawROIMat(mm, yVars.myROI_2);
yVars.IsSelectROI_2_OK = true;
}
yVars.IsMouseMove = true;
}
else // 釋放鼠標時的點位不在picturebox中 將相關(guān)變量值清空
{
if (yVars.IsSelectROI_1 == true)
{
yVars.myROI_1.Reset();
yVars.IsSelectROI_1_OK = false;
}
else if (yVars.IsSelectROI_2 == true)
{
yVars.myROI_2.Reset();
yVars.IsSelectROI_2_OK = false;
}
}
pbxImgShow.Image = yImgConvert.MatToBitmap(mm);
mm.Release();
}在線程或者循環(huán)等過程中定義的 mat 要及時 Release 掉。
在 mouseup 事件中就繪制完成了 注意選擇的第一點和第二點,分別是ROI的 FirstPoint 和 FourthPoint ,兩點的相對位置要確定好,要保證 FirstPoint 為左上角的點 FourthPoint 為右下角的點,不是的話 就對 FirstPoint 和 FourthPoint 重新賦值, FirstPoint 為兩點的 x , y 最小的點 ,F(xiàn)ourthPoint 為兩點的 x , y 最大的點。
繪制完ROI后可以對其位置和大小進行相應的調(diào)整。
public static Mat DrawROIMat(Mat src, ROI rOI, Scalar? scalar = null, int thickness = 1, LineTypes lineTypes = LineTypes.AntiAlias)
{
Scalar sc = scalar ?? Scalar.Red;
Cv2.Line(src, rOI.FirstPoint, rOI.SecondPoint, sc, thickness, lineTypes);
Cv2.Line(src, rOI.SecondPoint, rOI.FourthPoint, sc, thickness, lineTypes);
Cv2.Line(src, rOI.FourthPoint, rOI.ThirdPoint, sc, thickness, lineTypes);
Cv2.Line(src, rOI.ThirdPoint, rOI.FirstPoint, sc, thickness, lineTypes);
return src;
}對位置進行調(diào)整: 主要思想就是對ROI的四個點的坐標相應方向進行加減即可,主要超限問題即可。
public static void ImgROIMove(Mat src, out Mat dstImg, ref ROI rOI, eDirections eDirections, double step, int gap = 3)
{
dstImg = new Mat();
switch (eDirections)
{
case eDirections.NULL:
break;
case eDirections.上:
if (rOI.YTop - step <= gap)
{
rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X, rOI.ThirdPoint.Y - rOI.YTop + gap);
rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X, rOI.FourthPoint.Y - rOI.YTop + gap);
rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X, gap);
rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X, gap);
}
else
{
rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X, rOI.FirstPoint.Y - step);
rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X, rOI.SecondPoint.Y - step);
rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X, rOI.ThirdPoint.Y - step);
rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X, rOI.FourthPoint.Y - step);
}
break;
case eDirections.下:
if (rOI.YBottom + step >= src.Height - gap)
{
rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X, rOI.FirstPoint.Y + src.Height - rOI.YBottom - gap);
rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X, rOI.SecondPoint.Y + src.Height - rOI.YBottom - gap);
rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X, src.Height - gap);
rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X, src.Height - gap);
}
else
{
rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X, rOI.FirstPoint.Y + step);
rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X, rOI.SecondPoint.Y + step);
rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X, rOI.ThirdPoint.Y + step);
rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X, rOI.FourthPoint.Y + step);
}
break;
case eDirections.左:
if (rOI.XLeft - step <= gap)
{
rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X - rOI.XLeft + gap, rOI.SecondPoint.Y);
rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X - rOI.XLeft + gap, rOI.FourthPoint.Y);
rOI.ThirdPoint = new OpenCvSharp.Point(gap, rOI.ThirdPoint.Y);
rOI.FirstPoint = new OpenCvSharp.Point(gap, rOI.FirstPoint.Y);
}
else
{
rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X - step, rOI.FirstPoint.Y);
rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X - step, rOI.SecondPoint.Y);
rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X - step, rOI.ThirdPoint.Y);
rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X - step, rOI.FourthPoint.Y);
}
break;
case eDirections.右:
if (rOI.XRight + step >= src.Width - gap)
{
rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X + src.Width - rOI.XRight - gap, rOI.FirstPoint.Y);
rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X + src.Width - rOI.XRight - gap, rOI.ThirdPoint.Y);
rOI.FourthPoint = new OpenCvSharp.Point(src.Width - gap, rOI.FourthPoint.Y);
rOI.SecondPoint = new OpenCvSharp.Point(src.Width - gap, rOI.SecondPoint.Y);
}
else
{
rOI.FirstPoint = new OpenCvSharp.Point(rOI.FirstPoint.X + step, rOI.FirstPoint.Y);
rOI.SecondPoint = new OpenCvSharp.Point(rOI.SecondPoint.X + step, rOI.SecondPoint.Y);
rOI.ThirdPoint = new OpenCvSharp.Point(rOI.ThirdPoint.X + step, rOI.ThirdPoint.Y);
rOI.FourthPoint = new OpenCvSharp.Point(rOI.FourthPoint.X + step, rOI.FourthPoint.Y);
}
break;
default:
break;
}
dstImg = yActions.DrawROIMat(src, rOI);
}對大小進行調(diào)整: 主要思路是 ROI 大小調(diào)整前后,其中心點坐標不變,相應的長度和寬度變了。我們就可以采用 OpenCvSharp.RotatedRect 這個類,根據(jù) 中心點坐標,相應size,和傾斜角度(正矩形為0). 最后再把 RotatedRect 的四個頂點重新賦值給 ROI的四個頂點就好,注意一下點的相對位置關(guān)系。
public static void ImgROIResize(Mat src, out Mat dstImg, ref ROI rOI, bool IsAdd, double step, eResizeMode eResizeMode)
{
dstImg = new Mat();
double height = rOI.Height, width = rOI.Width;
if (IsAdd == true)
{
switch (eResizeMode)
{
case eResizeMode.All:
height = rOI.Height + step;
width = rOI.Width + step;
break;
case eResizeMode.Width:
width = rOI.Width + step;
break;
case eResizeMode.Height:
height = rOI.Height + step;
break;
}
}
else
{
switch (eResizeMode)
{
case eResizeMode.All:
height = rOI.Height - step;
width = rOI.Width - step;
break;
case eResizeMode.Width:
width = rOI.Width - step;
break;
case eResizeMode.Height:
height = rOI.Height - step;
break;
}
}
OpenCvSharp.Size2f size = new Size2f(width, height);
OpenCvSharp.RotatedRect rotateRect = new RotatedRect(rOI.Center, size, 0);
Point2f[] points = rotateRect.Points();// 獲得矩形四個頂點坐標
// 大小縮放后需要判斷坐標是否超限
for (int i = 0; i < points.Length; i++)
{
if (points[i].X <= 0 || points[i].Y <= 0 || points[i].X >= src.Width || points[i].Y >= src.Height)
{
return;
}
}
rOI.FirstPoint = new OpenCvSharp.Point(points[1].X, points[1].Y);
rOI.SecondPoint = new OpenCvSharp.Point(points[2].X, points[2].Y);
rOI.ThirdPoint = new OpenCvSharp.Point(points[0].X, points[0].Y);
rOI.FourthPoint = new OpenCvSharp.Point(points[3].X, points[3].Y);
dstImg = yActions.DrawROIMat(src, rOI);
}繪制并調(diào)整好ROI后,從原圖上將對應的ROI圖像扣下來,
public static Mat GetROIMat(Mat mm, ROI rOI)
{
Mat mask = Mat.Zeros(mm.Size(), MatType.CV_8UC1);
List<List<OpenCvSharp.Point>> pp = new List<List<OpenCvSharp.Point>>() {
rOI.GetCoutonrs().ToList()
};
Cv2.FillPoly(mask, pp, new Scalar(255, 255, 255));
OpenCvSharp.Rect rect = Cv2.BoundingRect(rOI.GetCoutonrs2f());
if (rect.X <= 0) rect.X = 1;
if (rect.Y <= 0) rect.Y = 0;
if (rect.X + rect.Width > mm.Width)
rect.Width = mm.Width - rect.X - 1;
if (rect.Y + rect.Height > mm.Height)
rect.Height = mm.Height - rect.Y - 1;
Mat src = new Mat(mm, rect);
Mat maskROI = new Mat(mask, rect);
Mat dstImg = new Mat();
Cv2.BitwiseAnd(src, src, dstImg, maskROI);
return dstImg;
}然后對每張扣下來的mat進行邊緣檢測 抓邊擬合等操作
部分代碼
coutonrs = yVars.myROI_1.GetCoutonrs2f();
srcROIImg = yActions.GetROIMat(src, yVars.myROI_1);
Cv2.CvtColor(srcROIImg, grayImg, ColorConversionCodes.RGB2GRAY);
Cv2.Blur(grayImg, grayImg, new OpenCvSharp.Size(3, 3));
Cv2.Canny(grayImg, cannyImg, param1, param2, param3, true);
//獲得輪廓
Cv2.FindContours(cannyImg, out contoursROI1, out hierarchly, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple, new OpenCvSharp.Point(0, 0));
if (contoursROI1.Length == 0)
{
YXH._01.yMessagebox.ShowDialogCN("ROI_1未抓到邊,請調(diào)整遲滯參數(shù),或重新選擇ROI區(qū)域");
return;
}
// 獲取輪廓后需要將點的坐標轉(zhuǎn)換到原圖上 此時的坐標是相對于ROI區(qū)域的坐標
// 即每個坐標需要加上ROI區(qū)域的左上角坐標 再將轉(zhuǎn)化后的坐標添加進擬合集合內(nèi)
for (int i = 0; i < contoursROI1.Length; i++)
{
for (int j = 0; j < contoursROI1[i].Length; j++)
{
contoursROI1[i][j] += yVars.myROI_1.FirstPoint;
ROI_1_Points.Add(contoursROI1[i][j]);
AllPoints.Add(contoursROI1[i][j]);
}
}操作完成后再根據(jù)想要在界面上顯示的進行相應的繪制即可。
到此這篇關(guān)于c#中WinForm使用OpencvSharp4實現(xiàn)簡易抓邊的文章就介紹到這了,更多相關(guān)c# OpencvSharp4 抓邊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

