彩色目标追踪
彩色目标追踪,C++版本是采用毛星云的demo修改的,C#和Python版本则是在C++版本基础上延伸出来的,由于各种语言之间OpenCv的API有不少差异,尤其是在针对像素的操作上。这就导致了C++版本上的部分功能或者操作在C#和Python版本上没调通,毕竟官方demo很多操作都没有demo。
视频讲解如下:
当前系列所有demo下载地址:
https://github.com/GaoRenBao/OpenCv4-Demo
不同编程语言对应的OpenCv版本以及开发环境信息如下:
语言 | OpenCv版本 | IDE |
C# | OpenCvSharp4.4.8.0.20230708 | Visual Studio 2022 |
C++ | OpenCv-4.5.5-vc14_vc15 | Visual Studio 2022 |
Python | OpenCv-Python (4.6.0.66) | PyCharm Community Edition 2022.1.3 |
C#版本代码如下:
C#版本需要安装“OpenCvSharp4”、“OpenCvSharp4.runtime.win”两个库才行。不然会报错。
如果需要使用“ BitmapConverter.ToBitmap”操作,则需要追加安装“OpenCvSharp4.Extensions”库。
早期版本是opencv3的,目前已改成opencv4,但是3版本的代码,我也顺便保留,给大家做个对比参考。
下面我们先来看一下C#版本的操作,C#版本不是很稳定,偶尔会异常,不过基础功能倒是实现了。。。
效果演示如下:
C# OpenCv3版本代码如下:
using OpenCvSharp;
using System;
using System.Windows.Forms;
namespace WindowsFormsApp
{
public partial class Form1 : Form
{
// 声明全局变量
public Mat image = new Mat();
public bool selectObject = false;
public int trackObject = 0;
public Rect selection;
public Point origin;
/// <summary>
/// 视频操作
/// </summary>
public VideoCapture Cap = new VideoCapture();
public Form1()
{
InitializeComponent();
}
public void onMouse(MouseEvent @event, int x, int y, MouseEvent flags, IntPtr userdata)
{
//当左键按下,框选标志为真,执行如下程序得到矩形区域selection
if (selectObject)
{
selection.X = Math.Min(x, origin.X);
selection.Y = Math.Min(y, origin.Y);
selection.Width = Math.Abs(x - origin.X);
selection.Height = Math.Abs(y - origin.Y);
//矩形区域与image进行与运算,结果保存到矩形区域中
selection &= new Rect(0, 0, image.Cols, image.Rows);
}
if (@event == MouseEvent.LButtonDown)
{
origin = new Point(x, y);
selection = new Rect(x, y, 0, 0);
selectObject = true;
}
if (@event == MouseEvent.LButtonUp)
{
selectObject = false;
if (selection.Width > 0 && selection.Height > 0)
trackObject = -1;
}
}
// 打开摄像头
private void button1_Click(object sender, EventArgs e)
{
int hsize = 16;
float[] phranges = { 0, 180 };
Rect trackWindow = new Rect();
// 打开ID为0的摄像头
Cap.Open(0);
// 判断摄像头是否成功打开
if (!Cap.IsOpened())
{
MessageBox.Show("摄像头打开失败.");
return;
}
// 设置采集的图像尺寸为:640*480
Cap.Set(CaptureProperty.FrameHeight, 480);
Cap.Set(CaptureProperty.FrameWidth, 640);
Mat frame = new Mat();
Mat hsv = new Mat();
Mat hue = new Mat();
Mat mask = new Mat();
Mat hist = new Mat();
Mat backproj = new Mat();
Cv2.NamedWindow("Histogram");
Cv2.NamedWindow("CamShift Demo");
CvMouseCallback GetRGBCvMouseCallback = new CvMouseCallback(onMouse);
Cv2.SetMouseCallback("CamShift Demo", GetRGBCvMouseCallback);
while (true)
{
//读取当前帧
if (!Cap.Read(frame)) continue;
if (frame.Empty()) break;
frame.CopyTo(image); //将当前帧复制到image中
Cv2.CvtColor(image, hsv, ColorConversionCodes.BGR2HSV); //将image转为hsv色彩空间,保存到hsv中
if (trackObject != 0)//如果有操作,trackobject等于1或-1
{
// 亮度范围设置
int _vmin = 10, _vmax = 256;
// 色彩范围检测
// Scalar: 色调、饱和度、亮度,第一个Scalar是最小值,第二个Scalar是最大值
Cv2.InRange(hsv, new Scalar(0, 30, Math.Min(_vmin, _vmax)), new Scalar(180, 256, Math.Max(_vmin, _vmax)), mask);
hue.Create(hsv.Size(), hsv.Depth());//创建一个与hsv尺寸和深度一样的hue
// 从输入中拷贝某通道到输出中特定的通道。
// 官方代码参考位置:./Sample-4.1.0-20190417/SamplesCS/Samples/MergeSplitSample.cs:51:
int[] ch = { 0, 0 };
Mat[] input = { hsv };
Mat[] output = { hue, };
Cv2.MixChannels(input, output, ch);
Rangef[] range = new Rangef[3];//三个通道,范围
range[0].Start = phranges[0];//从0开始(含)
range[0].End = phranges[1]; //到180结束(不含)
range[1] = range[0];//通道2和通道1一样
range[2] = range[0];//通道3和通道1一样
if (trackObject < 0)//如果为-1,代表左键弹起,划定了区域
{
//hue是视频帧处理后的图像,selection是鼠标选定的矩形区域,同时创建一个感兴趣区域和一个标记感兴趣区域
Mat roi = new Mat(hue, selection);
Mat maskroi = new Mat(mask, selection);
Cv2.CalcHist(images: new[] { roi }, channels: new[] { 0 }, mask: maskroi, hist: hist, dims: 1,
histSize: new[] { 16 }, ranges: new[] { new Rangef(0, 180) });
Cv2.Normalize(hist, hist, 0, 255, NormTypes.MinMax);
trackWindow = selection;
trackObject = 1;
}
Mat[] arrs2 = { hue };
Cv2.CalcBackProject(arrs2, channels: new[] { 0 }, hist, backproj, range);
backproj &= mask;
RotatedRect trackBox = Cv2.CamShift(backproj, ref trackWindow, new TermCriteria(CriteriaType.Count | CriteriaType.Eps, 10, 1));
if ((trackWindow.Width * trackWindow.Height) <= 1)
{
int cols = backproj.Cols, rows = backproj.Rows, r = (Math.Min(cols, rows) + 5) / 6;
trackWindow = new Rect(trackWindow.X - r, trackWindow.Y - r,
trackWindow.X + r, trackWindow.Y + r) &
new Rect(0, 0, cols, rows);
}
// 投影视图
// Cv2.CvtColor(backproj, image, ColorConversionCodes.GRAY2BGR);
Cv2.Ellipse(image, trackBox, new Scalar(0, 0, 255), 3, LineTypes.AntiAlias);
}
if (selectObject && selection.Width > 0 && selection.Height > 0)
{
Mat roi = new Mat(image, selection);
Cv2.BitwiseNot(roi, roi);
}
Cv2.ImShow("CamShift Demo", image);
if ((char)Cv2.WaitKey(10) == 27) break;
}
}
}
}
C# OpenCv4版本代码如下:
using OpenCvSharp;
using System;
namespace ConsoleApp
{
internal class Program
{
// 声明全局变量
public static Mat image = new Mat();
public static bool selectObject = false;
public static int trackObject = 0;
public static Rect selection;
public static Point origin;
/// <summary>
/// 视频操作
/// </summary>
public static VideoCapture Cap = new VideoCapture();
private static void onMouse(MouseEventTypes @event, int x, int y, MouseEventFlags flags, IntPtr userData)
{
//当左键按下,框选标志为真,执行如下程序得到矩形区域selection
if (selectObject)
{
selection.X = Math.Min(x, origin.X);
selection.Y = Math.Min(y, origin.Y);
selection.Width = Math.Abs(x - origin.X);
selection.Height = Math.Abs(y - origin.Y);
//矩形区域与image进行与运算,结果保存到矩形区域中
selection &= new Rect(0, 0, image.Cols, image.Rows);
}
if (@event == MouseEventTypes.LButtonDown)
{
origin = new Point(x, y);
selection = new Rect(x, y, 0, 0);
selectObject = true;
}
if (@event == MouseEventTypes.LButtonUp)
{
selectObject = false;
if (selection.Width > 0 && selection.Height > 0)
trackObject = -1;
}
}
static void Main(string[] args)
{
int hsize = 16;
float[] phranges = { 0, 180 };
Rect trackWindow = new Rect();
// 打开ID为0的摄像头
Cap.Open(0);
// 判断摄像头是否成功打开
if (!Cap.IsOpened())
{
Console.WriteLine("摄像头打开失败.");
return;
}
// 设置采集的图像尺寸为:640*480
Cap.Set(VideoCaptureProperties.FrameHeight, 480);
Cap.Set(VideoCaptureProperties.FrameWidth, 640);
Mat frame = new Mat();
Mat hsv = new Mat();
Mat hue = new Mat();
Mat mask = new Mat();
Mat hist = new Mat();
Mat backproj = new Mat();
Cv2.NamedWindow("Histogram");
Cv2.NamedWindow("CamShift Demo");
MouseCallback GetRGBCvMouseCallback = new MouseCallback(onMouse);
Cv2.SetMouseCallback("CamShift Demo", GetRGBCvMouseCallback);
while (true)
{
//读取当前帧
if (!Cap.Read(frame)) continue;
if (frame.Empty()) break;
frame.CopyTo(image); //将当前帧复制到image中
Cv2.CvtColor(image, hsv, ColorConversionCodes.BGR2HSV); //将image转为hsv色彩空间,保存到hsv中
if (trackObject != 0)//如果有操作,trackobject等于1或-1
{
// 亮度范围设置
int _vmin = 10, _vmax = 256;
// 色彩范围检测
// Scalar: 色调、饱和度、亮度,第一个Scalar是最小值,第二个Scalar是最大值
Cv2.InRange(hsv, new Scalar(0, 30, Math.Min(_vmin, _vmax)), new Scalar(180, 256, Math.Max(_vmin, _vmax)), mask);
hue.Create(hsv.Size(), hsv.Depth());//创建一个与hsv尺寸和深度一样的hue
// 从输入中拷贝某通道到输出中特定的通道。
// 官方代码参考位置:./Sample-4.1.0-20190417/SamplesCS/Samples/MergeSplitSample.cs:51:
int[] ch = { 0, 0 };
Mat[] input = { hsv };
Mat[] output = { hue, };
Cv2.MixChannels(input, output, ch);
Rangef[] range = new Rangef[3];//三个通道,范围
range[0] = new Rangef(phranges[0], phranges[1]); //从0开始(含),到180结束(不含)
range[1] = range[0];//通道2和通道1一样
range[2] = range[0];//通道3和通道1一样
if (trackObject < 0)//如果为-1,代表左键弹起,划定了区域
{
//hue是视频帧处理后的图像,selection是鼠标选定的矩形区域,同时创建一个感兴趣区域和一个标记感兴趣区域
Mat roi = new Mat(hue, selection);
Mat maskroi = new Mat(mask, selection);
Cv2.CalcHist(images: new[] { roi }, channels: new[] { 0 }, mask: maskroi, hist: hist, dims: 1,
histSize: new[] { 16 }, ranges: new[] { new Rangef(0, 180) });
Cv2.Normalize(hist, hist, 0, 255, NormTypes.MinMax);
trackWindow = selection;
trackObject = 1;
}
Mat[] arrs2 = { hue };
Cv2.CalcBackProject(arrs2, channels: new[] { 0 }, hist, backproj, range);
backproj &= mask;
RotatedRect trackBox = Cv2.CamShift(backproj, ref trackWindow, new TermCriteria(CriteriaTypes.Count | CriteriaTypes.Eps, 10, 1));
if ((trackWindow.Width * trackWindow.Height) <= 1)
{
int cols = backproj.Cols, rows = backproj.Rows, r = (Math.Min(cols, rows) + 5) / 6;
trackWindow = new Rect(trackWindow.X - r, trackWindow.Y - r,
trackWindow.X + r, trackWindow.Y + r) &
new Rect(0, 0, cols, rows);
}
// 投影视图
// Cv2.CvtColor(backproj, image, ColorConversionCodes.GRAY2BGR);
Cv2.Ellipse(image, trackBox, new Scalar(0, 0, 255), 3, LineTypes.AntiAlias);
}
if (selectObject && selection.Width > 0 && selection.Height > 0)
{
Mat roi = new Mat(image, selection);
Cv2.BitwiseNot(roi, roi);
}
Cv2.ImShow("CamShift Demo", image);
if ((char)Cv2.WaitKey(10) == 27) break;
}
}
}
}
下面这是官方提供的关于CalcHist直方图的操作demo。
代码位置在:Sample-4.1.0-20190417/OpenCvSharp.Tests/imgproc/ImgProcTest.cs:600
效果呢,可以给大家看下,就是下面这个样子。
官方代码如下:
public void CalcHist()
{
using var src = new Mat(@"_data/image/mandrill.png", ImreadModes.Grayscale);
using var hist = new Mat();
Cv2.CalcHist(
images: new[] { src },
channels: new[] {0},
mask: null,
hist: hist,
dims: 1,
histSize: new[] {256},
ranges: new[] { new Rangef(0, 256) });
if (Debugger.IsAttached)
{
const int histW = 512;
const int histH = 400;
var binW = Math.Round((double)histW / 256);
using var histImage = new Mat(histH, histW, MatType.CV_8UC3, Scalar.All(0));
Cv2.Normalize(hist, hist, 0, histImage.Rows, NormTypes.MinMax, -1);
for (int i = 0; i < 256; i++)
{
var pt1 = new Point2d(binW * (i - 1), histH - Math.Round(hist.At<float>(i - 1)));
var pt2 = new Point2d(binW * (i), histH - Math.Round(hist.At<float>(i)));
Cv2.Line(
histImage, (Point)pt1, (Point)pt2,
Scalar.Red, 1, LineTypes.Link8);
}
Window.ShowImages(src, histImage);
}
}
C++版本
C++版本是毛星云的版本简化来的,去掉了很多没必要的操作界面,直接保留了核心功能。
效果演示如下:
代码如下:
#include "opencv2/video/tracking.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <ctype.h>
using namespace cv;
using namespace std;
// 声明全局变量
Mat image;
//bool backprojMode = false;
bool selectObject = false;
int trackObject = 0;
Rect selection;
Point origin;
// 鼠标操作回调:鼠标状态每改变一次,此函数就执行一次
void onMouse(int event, int x, int y, int, void*)
{
//当左键按下,框选标志为真,执行如下程序得到矩形区域selection
if (selectObject)
{
selection.x = MIN(x, origin.x);
selection.y = MIN(y, origin.y);
selection.width = std::abs(x - origin.x);
selection.height = std::abs(y - origin.y);
//矩形区域与image进行与运算,结果保存到矩形区域中
selection &= Rect(0, 0, image.cols, image.rows);
}
switch (event)
{
//此句代码的OpenCV2版为:
//case CV_EVENT_LBUTTONDOWN:
//此句代码的OpenCV3版为:
//当左键按下,记录原点,创建矩形区域,框选标志置为真
case EVENT_LBUTTONDOWN:
origin = Point(x, y);
selection = Rect(x, y, 0, 0);
selectObject = true;
break;
//此句代码的OpenCV2版为:
//case CV_EVENT_LBUTTONUP:
//此句代码的OpenCV3版为:
//当左键弹起,将框选标志置为假,如果矩形区域的长宽都大于零,令跟踪标志为-1
case EVENT_LBUTTONUP:
selectObject = false;
if (selection.width > 0 && selection.height > 0)
trackObject = -1;
break;
}
}
int main()
{
int hsize = 16;
float hranges[] = { 0,180 };
const float* phranges = hranges;
Rect trackWindow;
VideoCapture cap;
cap.open(0);
if (!cap.isOpened())
{
cout << "初始化摄像头失败\n";
while (true);
}
namedWindow("Histogram", 1);
namedWindow("CamShift Demo", 1);
// 鼠标事件检测
setMouseCallback("CamShift Demo", onMouse, 0);
Mat frame, hsv, hue, mask, hist, backproj;
while (true)
{
cap >> frame;//读取当前帧
if (frame.empty())
break;
frame.copyTo(image);//将当前帧复制到image中
cvtColor(image, hsv, COLOR_BGR2HSV);//将image转为hsv色彩空间,保存到hsv中
if (trackObject)//如果有操作,trackobject等于1或-1
{
// 亮度范围设置
int _vmin = 10, _vmax = 256;
// 色彩范围检测
// Scalar: 色调、饱和度、亮度,第一个Scalar是最小值,第二个Scalar是最大值
inRange(hsv, Scalar(0, 30, MIN(_vmin, _vmax)), Scalar(180, 256, MAX(_vmin, _vmax)), mask);
int ch[] = { 0, 0 };
hue.create(hsv.size(), hsv.depth());//创建一个与hsv尺寸和深度一样的hue
// 从输入中拷贝某通道到输出中特定的通道。
mixChannels(&hsv, 1, &hue, 1, ch, 1);
if (trackObject < 0)//如果为-1,代表左键弹起,划定了区域
{
//hue是视频帧处理后的图像,selection是鼠标选定的矩形区域,同时创建一个感兴趣区域和一个标记感兴趣区域
Mat roi(hue, selection), maskroi(mask, selection);
imshow("ROI", roi);
imshow("maskROI", maskroi);
calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);
normalize(hist, hist, 0, 255, NORM_MINMAX);
trackWindow = selection;
trackObject = 1;
}
calcBackProject(&hue, 1, 0, hist, backproj, &phranges);
backproj &= mask;
RotatedRect trackBox = CamShift(backproj, trackWindow, TermCriteria(TermCriteria::EPS | TermCriteria::COUNT, 10, 1));
if (trackWindow.area() <= 1)
{
int cols = backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5) / 6;
trackWindow = Rect(trackWindow.x - r, trackWindow.y - r,
trackWindow.x + r, trackWindow.y + r) &
Rect(0, 0, cols, rows);
}
// 投影视图
// cvtColor(backproj, image, COLOR_GRAY2BGR);
ellipse(image, trackBox, Scalar(0, 0, 255), 3, LINE_AA);
}
if (selectObject && selection.width > 0 && selection.height > 0)
{
Mat roi(image, selection);
bitwise_not(roi, roi);
}
cv::imshow("CamShift Demo", image);
char c = (char)waitKey(10);
if (c == 27) break;
}
}
Python版本
Python版本正好在网上看到有人改过这个版本,我这里分享一下链接,顺便对他的摄像头调用做了小调整,效果和前面的差不多,但是他这个包围图形是方形
链接:https://blog.csdn.net/zhangruijerry/article/details/79088945
演示效果如下:
代码如下:
import cv2
import numpy as np
xs,ys,ws,hs = 0,0,0,0 #selection.x selection.y
xo,yo=0,0 #origin.x origin.y
selectObject = False
trackObject = 0
#创建回调函数
def onMouse(event,x,y,flags,param):
global xs,ys,ws,hs,selectObject,xo,yo,trackObject
if selectObject == True:
xs = min(x, xo)
ys = min(y, yo)
ws = abs(x-xo)
hs = abs(y-yo)
if event == cv2.EVENT_LBUTTONDOWN:
xo,yo = x, y
xs,ys,ws,hs= x, y, 0, 0
selectObject = True
elif event == cv2.EVENT_LBUTTONUP:
selectObject = False
trackObject = -1
# 打开摄像头
Cap = cv2.VideoCapture(0)
# 判断视频是否打开
if (Cap.isOpened() == False):
print('Open Camera Error.')
else:
Cap.set(cv2.CAP_PROP_FRAME_WIDTH,640) # 设置图像宽
Cap.set(cv2.CAP_PROP_FRAME_HEIGHT,480) # 设置图像高
Cap.set(cv2.CAP_PROP_EXPOSURE, -3) # 设置曝光值
# 读取设置的参数
size = (int(Cap.get(cv2.CAP_PROP_FRAME_WIDTH)),int(Cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
baog = int(Cap.get(cv2.CAP_PROP_EXPOSURE))
# 输出参数
print('摄像头设置,尺寸:' + str(size))
print('摄像头设置,曝光:' + str(baog))
cv2.namedWindow("CamShift Demo", 1)
cv2.setMouseCallback("CamShift Demo", onMouse)
term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )
# 读取图像
while True:
grabbed, frame = Cap.read()
if frame is None:
continue
if trackObject != 0:
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, np.array((0., 30.,10.)), np.array((180.,256.,255.)))
if trackObject == -1:
track_window=(xs,ys,ws,hs)
maskroi = mask[ys:ys+hs, xs:xs+ws]
hsv_roi = hsv[ys:ys+hs, xs:xs+ws]
roi_hist = cv2.calcHist([hsv_roi],[0],maskroi,[180],[0,180])
cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)
trackObject = 1
dst = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)
dst &= mask
ret, track_window = cv2.CamShift(dst, track_window, term_crit)
pts = cv2.boxPoints(ret)
pts = np.int0(pts)
img2 = cv2.polylines(frame,[pts],True, 255,2)
if selectObject == True and ws>0 and hs>0:
cv2.imshow('imshow1',frame[ys:ys+hs,xs:xs+ws])
cv2.bitwise_not(frame[ys:ys+hs,xs:xs+ws],frame[ys:ys+hs,xs:xs+ws])
cv2.imshow('CamShift Demo',frame)
if cv2.waitKey(10)==27:
break
cv2.destroyAllWindows()