本篇博客应该是属于开荒,因为很难找到C#版Opencv的文章。
本文会详细讲解如何一步步配置OPENCVSHARP(C#中的OPENCV),并给出三个demo,分别是追踪算法CamShift以及Tracker在.NET C#中的实现,以及OPENCV 图像类OpenCvSharp.Mat与C# 图像类System.Drawing.Bitmap的互相转换。
任意新建一个控制台程序,然后打开Nuget包管理器,搜索OpenCvSharp,选择那个头像为猿猴脸的那个库。为什么在众多OpenCvSharp库中选择这个库,是因为其他库我都经过了测试,不能跑。
如下图:
首先声明下,OpenCvSharp是对Opencv C++的封装,以便其能够在.Net中使用。
引入该库 OpenCvSharp;
using OpenCvSharp;
我在这里直接上源代码了,我对一些OPENCV C++ 的程序在C# OpenCvSharp 中进行了重写,下面分别是两个例子:
追踪_OpenCVSharp_Tracker;
Camshift交互式追踪CSharp版;
关于如何使用,两个程序都需要你在OPENCV的窗体上画一个矩形ROI框,移动物体,该方框就会跟随物体而移动,并实时输出该方框左上角坐标。
关于该opencv的图像处理讲解,请大家自己查一下。
//追踪_OpenCVSharp_Tracker;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using OpenCvSharp;
using OpenCvSharp.Tracking;
namespace 追踪_OpenCVSharp_Tracker
{
class Program
{
private static Mat image = new Mat();
private static OpenCvSharp.Point originPoint = new OpenCvSharp.Point();
private static Rect2d selectedRect = new Rect2d();
private static bool selectRegion = false;
private static int trackingFlag = 0;
private static void OnMouse(MouseEvent Event, int x, int y, MouseEvent Flags, IntPtr ptr)
{
if (selectRegion)
{
selectedRect.X = Math.Min(x, originPoint.X);
selectedRect.Y = Math.Min(y, originPoint.Y);
selectedRect.Width = Math.Abs(x - originPoint.X);
selectedRect.Height = Math.Abs(y - originPoint.Y);
selectedRect = selectedRect & new Rect2d(0, 0, image.Cols, image.Rows);
}
switch (Event)
{
case MouseEvent.LButtonDown:
originPoint = new OpenCvSharp.Point(x, y);
selectedRect = new Rect2d(x, y, 0, 0);
selectRegion = true;
break;
case MouseEvent.LButtonUp:
selectRegion = false;
if (selectedRect.Width > 0 && selectedRect.Height > 0)
{
trackingFlag = -1;
}
break;
}
}
static void Main(string[] args)
{
TrackerKCF tracker_KCF= TrackerKCF.Create();
TrackerMIL trackerMIL = TrackerMIL.Create();
//cv::Ptr<cv::Tracker> tracker = TrackerCSRT.Create();
TrackerMedianFlow trackerMedianFlow = TrackerMedianFlow.Create();
TrackerMOSSE trackerMOSSE = TrackerMOSSE.Create();
TrackerTLD trackerTLD = TrackerTLD.Create();
VideoCapture cap = new VideoCapture();
cap.Open(0);
if (cap.IsOpened())
{
string windowName = "KCF Tracker";
string windowName2 = "OriginFrame";
Mat frame = new Mat();
Mat outputMat = new Mat();
Cv2.NamedWindow(windowName, 0);
Cv2.NamedWindow(windowName2, 0);
Cv2.SetMouseCallback(windowName, OnMouse, new IntPtr());
while (true)
{
cap.Read(frame);
// Check if 'frame' is empty
if (frame.Empty())
{
break;
}
frame.CopyTo(image);
if (trackingFlag != 0)
{
tracker_KCF.Init(frame, selectedRect);
tracker_KCF.Update(frame, ref selectedRect);
frame.CopyTo(outputMat);
Rect rect = new Rect((int)selectedRect.X, (int)selectedRect.Y, (int)selectedRect.Width, (int)selectedRect.Height);
Console.WriteLine(rect.X+" "+rect.Y);
Cv2.Rectangle(outputMat, rect, new Scalar(255, 255, 0), 2);
Cv2.ImShow(windowName2, outputMat);
}
if (selectRegion && selectedRect.Width > 0 && selectedRect.Height > 0)
{
Mat roi = new Mat(image, new Rect((int)selectedRect.X, (int)selectedRect.Y, (int)selectedRect.Width, (int)selectedRect.Height));
Cv2.BitwiseNot(roi, roi);
}
Cv2.ImShow(windowName, image);
int ch = Cv2.WaitKey(25);
if (ch == 27)
{
break;
}
}
}
}
}
}
//Camshift交互式追踪CSharp版;
using OpenCvSharp;
using System;
namespace 交互式追踪CSharp版
{
class Program
{
private static Mat image = new Mat();
private static Point originPoint = new Point();
private static Rect selectedRect = new Rect();
private static bool selectRegion = false;
private static int trackingFlag = 0;
//private static CvMouseCallback callBackFunc = new CvMouseCallback(OnMouse);
private static void OnMouse(MouseEvent Event, int x, int y, MouseEvent Flags, IntPtr ptr)
{
if (selectRegion)
{
selectedRect.X = Math.Min(x, originPoint.X);
selectedRect.Y = Math.Min(y, originPoint.Y);
selectedRect.Width = Math.Abs(x - originPoint.X);
selectedRect.Height = Math.Abs(y - originPoint.Y);
selectedRect = selectedRect & new Rect(0, 0, image.Cols, image.Rows);
}
switch (Event)
{
case MouseEvent.LButtonDown:
originPoint = new Point(x, y);
selectedRect = new Rect(x, y, 0, 0);
selectRegion = true;
break;
case MouseEvent.LButtonUp:
selectRegion = false;
if (selectedRect.Width > 0 && selectedRect.Height > 0)
{
trackingFlag = -1;
}
break;
}
}
static void Main(string[] args)
{
VideoCapture cap = new VideoCapture();
cap.Open(0);
if (cap.IsOpened())
{
int ch;
Rect trackingRect = new Rect();
// range of values for the 'H' channel in HSV ('H' stands for Hue)
Rangef hist_range = new Rangef(0.0f, 180.0f);
Rangef[] histRanges = { hist_range };
//const float* histRanges = hueRanges;
// min value for the 'S' channel in HSV ('S' stands for Saturation)
int minSaturation = 40;
// min and max values for the 'V' channel in HSV ('V' stands for Value)
int minValue = 20, maxValue = 245;
// size of the histogram bin
int[] histSize = { 8 };
string windowName = "CAMShift Tracker";
//string windowNameTest = "Test";
Cv2.NamedWindow(windowName, 0);
//Cv2.NamedWindow(windowNameTest, 0);
Cv2.SetMouseCallback(windowName, OnMouse, new IntPtr());
Mat frame = new Mat();
Mat hsvImage = new Mat();
Mat hueImage = new Mat();
Mat mask = new Mat();
Mat hist = new Mat();
Mat backproj = new Mat();
// Image size scaling factor for the input frames from the webcam
double scalingFactor = 1;
// Iterate until the user presses the Esc key
while (true)
{
// Capture the current frame
cap.Read(frame);
// Check if 'frame' is empty
if (frame.Empty())
break;
// Resize the frame
Cv2.Resize(frame, frame, new Size(), scalingFactor, scalingFactor, InterpolationFlags.Area);
frame.CopyTo(image);
// Convert to HSV colorspace
Cv2.CvtColor(image, hsvImage, ColorConversionCodes.BGR2HSV);
if (trackingFlag != 0)
{
// Check for all the values in 'hsvimage' that are within the specified range
// and put the result in 'mask'
Cv2.InRange(hsvImage, new Scalar(0, minSaturation, minValue), new Scalar(180, 256, maxValue), mask);
/* # 通俗的来讲,这个函数就是判断hsv中每一个像素是否在[lowerb,upperb]之间,注意集合的开闭。
# 结果是,那么在mask相应像素位置填上255,反之则是0。即重点突出该颜色
# 即检查数组元素是否在另外两个数组元素值之间。这里的数组通常也就是矩阵Mat或向量。
# 要特别注意的是:该函数输出的mask是一幅二值化之后的图像。*/
//imshow(windowNameTest, mask);
//waitKey(0);
// Mix the specified channels
int[] channels = { 0, 0 };
//cout << hsvImage.depth() << endl;
hueImage.Create(hsvImage.Size(), hsvImage.Depth());
//cout << hueImage.channels() << endl; ;
hueImage = hsvImage.ExtractChannel(0);
//Cv2.MixChannels(hsvImage, hueImage, channels);
/*mixChannels mixChannels()函数用于将输入数组的指定通道复制到输出数组的指定通道。
void mixChannels(
const Mat* src, //输入数组或向量矩阵,所有矩阵的大小和深度必须相同。
size_t nsrcs, //矩阵的数量
Mat* dst, //输出数组或矩阵向量,大小和深度必须与src[0]相同
size_t ndsts,//矩阵的数量
const int* fromTo,//指定被复制通道与要复制到的位置组成的索引对
size_t npairs //fromTo中索引对的数目*/
if (trackingFlag < 0)
{
// Create images based on selected regions of interest
Mat roi = new Mat(hueImage, selectedRect);
Mat maskroi = new Mat(mask, selectedRect);
Mat[] roi_source = { roi };
int[] channels_ = { 0 };
// Compute the histogram and normalize it
Cv2.CalcHist(roi_source, channels_, maskroi, hist, 1, histSize, histRanges);
Cv2.Normalize(hist, hist, 0, 255, NormTypes.MinMax);
trackingRect = selectedRect;
trackingFlag = 1;
}
Mat[] hueImgs = { hueImage };
int[] channels_back = { 0 };
// Compute the histogram back projection
Cv2.CalcBackProject(hueImgs, channels_back, hist, backproj, histRanges);
backproj &= mask;
//TermCriteria criteria = new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.MaxIter, 10, 1);
RotatedRect rotatedTrackingRect = Cv2.CamShift(backproj, ref trackingRect, new TermCriteria(CriteriaType.Eps | CriteriaType.MaxIter, 10, 1));
// Check if the area of trackingRect is too small
if ((trackingRect.Width * trackingRect.Height) <= 1)
{
// Use an offset value to make sure the trackingRect has a minimum size
int cols = backproj.Cols, rows = backproj.Rows;
int offset = Math.Min(rows, cols) + 1;
trackingRect = new Rect(trackingRect.X - offset, trackingRect.Y - offset, trackingRect.X + offset, trackingRect.Y + offset) & new Rect(0, 0, cols, rows);
}
// Draw the ellipse on top of the image
Cv2.Ellipse(image, rotatedTrackingRect, new Scalar(0, 255, 0), 3, LineTypes.Link8);
}
// Apply the 'negative' effect on the selected region of interest
if (selectRegion && selectedRect.Width > 0 && selectedRect.Height > 0)
{
Mat roi = new Mat(image, selectedRect);
Cv2.BitwiseNot(roi, roi);
}
// Display the output image
Cv2.ImShow(windowName, image);
// Get the keyboard input and check if it's 'Esc'
// 27 -> ASCII value of 'Esc' key
ch = Cv2.WaitKey(25);
if (ch == 27)
{
break;
}
}
}
}
}
}
另外i,还有一个.Net FrameWork Winform 窗体实时演示摄像机的画面,并包括OpenCVSharp.Mat 类与System.Drawing.Bitmap类的互相转换。
该Demo的窗体界面:
该demo代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenCvSharp;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace OpenCVSharp_Mat与Bitmap的转换
{
public partial class Form1 : Form
{
VideoCapture cap;
Mat frame = new Mat();
Mat dstMat = new Mat();
Bitmap bmp;
public Form1()
{
InitializeComponent();
}
public static Bitmap MatToBitmap(Mat dst)
{
return new Bitmap(dst.Cols, dst.Rows, (int)dst.Step(), PixelFormat.Format24bppRgb, dst.Data);
}
public static Mat BitmapToMat(Bitmap srcbit)
{
int iwidth = srcbit.Width;
int iheight = srcbit.Height;
int iByte = iwidth * iheight * 3;
byte[] result = new byte[iByte];
int step;
Rectangle rect = new Rectangle(0, 0, iwidth, iheight);
BitmapData bmpData = srcbit.LockBits(rect, ImageLockMode.ReadWrite, srcbit.PixelFormat);
IntPtr iPtr = bmpData.Scan0;
Marshal.Copy(iPtr, result, 0, iByte);
step = bmpData.Stride;
srcbit.UnlockBits(bmpData);
return new Mat(srcbit.Height, srcbit.Width, new MatType(MatType.CV_8UC3), result, step);
}
private void btnRun_Click(object sender, EventArgs e)
{
timer1.Enabled = true;
}
private void timer1_Tick(object sender, EventArgs e)
{
if (cap.IsOpened())
{
cap.Read(frame);
bmp = MatToBitmap(frame);
pictureBox1.Image = bmp;
dstMat = BitmapToMat(bmp);
Cv2.ImShow("dstMat", dstMat);
//Cv2.WaitKey();
}
}
private void Form1_Load(object sender, EventArgs e)
{
try
{
cap = new VideoCapture();
cap.Open(0);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
timer1.Enabled = false;
if (cap.IsOpened())
{
cap.Dispose();
}
}
}
}
里面涉及到一些数字图像处理的函数,大家不懂得可以自己去翻书或者上网查阅资料。多多单步运行,你就会发现他为什么要这样写。具体理论知识篇幅较长,一晚上都讲不完,请大家自行查资料。
如果大家能看到这里,相比是非常喜欢这篇博客了,也对UP主很认可。
那就请关注点赞加收藏吧。
本文图像类的转换部分参考以下博客:
https://blog.csdn.net/qq_34455723/article/details/90053593