源码下载:下载资源包
(bookln.cn)
常用函数库:
** 英文:**OpenCV: OpenCV modules
** 中文:**Welcome to opencv documentation! — OpenCV 2.3.2
documentation
jetson nano上的OpenCV使用:图像识别小车(jetson
nano部分)——第五章
学习OpenCV的推荐网站和文章:
Getting Started with OpenCV | LearnOpenCV
spmallick/learnopencv: Learn OpenCV : C++ and Python Examples
(github.com)
目录
<4>linux上编译运行:参考:Linux下编译、链接、加载运行C++
OpenCV的两种方式及常见问题的解决_linux下安装配置好opencv后怎么加载_Adenialzz的博客-
CSDN博客
<1>(基础图像容器Mat类;点Point类;颜色Scalar类;尺寸Size类;矩形Rect类;旋转矩形RotatedRect类;固定向量Vec类;复数类complexf)
4.创建一定尺寸3通道RGB图像,并逐个访问其像素值,并绘制一绿色平面
<1>(边缘检测:算子:一阶导数【Sobel/Prewitt/Roberts】、二阶【拉普拉斯/LOG/Canny】)
(阈值分割【二值化】:全局阈值【OTSU、三角法】、自适应阈值)
3.linux(ubuntu18)上实现霍夫圆检测(可调各个参数)
[<1>(特征:HOG/LBP/HAAR-
LIKE)(分类:SVM/级联分类器)](about:blank#%3C1%3E%EF%BC%88%E7%89%B9%E5%BE%81%EF%BC%9AHOG%2FLBP%2FHAAR-
LIKE%EF%BC%89%EF%BC%88%E5%88%86%E7%B1%BB%EF%BC%9ASVM%2F%E7%BA%A7%E8%81%94%E5%88%86%E7%B1%BB%E5%99%A8%EF%BC%89)
[2.获取LBP/MB-LBP特征函数](about:blank#2.%E8%8E%B7%E5%8F%96LBP%2FMB-
LBP%E7%89%B9%E5%BE%81%E5%87%BD%E6%95%B0)
一.图像相关知识
二.opencv简介
<1>(主体模块、环境变量配置(VS2019)、源码手动编译(CMake)、调用动/静态库、cv命名空间、OpenCV
API特点、数据内部接口InputArray/OutputArray、错误处理、OpenCV头文件、HighGui模块)
<2>示例代码:
1.展示图片:
int main()
{
namedWindow(“lena”, WINDOW_NORMAL);
//setWindowProperty(“lena”, WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN);
setWindowTitle(“lena”, “Lena经典图像窗口”);
//resizeWindow(“lena”, 400, 300);
//moveWindow(“lena”, 0, 0);
createTrackbar(“t1”, “lena”, NULL, 10, NULL, NULL);
Mat img = imread(“lena512color.tiff”); //读取图像
if (img.empty()) {
return -1; //如果读取图像失败,则返回
}
imshow(“lena”, img); //显示图像
waitKey(0); //等待用户输入
std::cout << “Hello World!\n”;
}
2.播放视频
void PlayVideo()
{
cv::VideoCapture capture(“D:/files/picture and video/C0056.MP4”);
int nWidth = capture.get(CAP_PROP_FRAME_WIDTH); //视频图像宽度
int nHeight = capture.get(CAP_PROP_FRAME_HEIGHT); //视频图像高度
double dblFrameRate = capture.get(CAP_PROP_FPS); //视频帧率
double dblFrameCnt = capture.get(CAP_PROP_FRAME_COUNT); //视频总帧数
double dblStartFrames = dblFrameCnt / 2; //播放起始帧数
capture.set(CAP_PROP_POS_FRAMES, dblStartFrames); //从视频中间开始播放
cv::namedWindow(“video”,0 );
resizeWindow(“video”, 800, 600);
while (capture.isOpened()) {
Mat frame;
capture >> frame;
if (frame.empty()) {
break;
}
imshow(“video”, frame);
waitKey(10);
}
}
3.滑动条使用:
滑动条的使用实例
const int g_nMaxAlphaValue = 100; //Alpha值的最大值
int g_nCurAlphaValue; //当前滑动条对应的值
Mat g_srcImg1; //第1张图像
Mat g_srcImg2; //第2张图像
Mat g_mixImg; //混合图像
const char cszWindowName[] = “mix”;
//拖动滑动条的响应函数
void on_Trackbar(int, void *)
{
//求出当前alpha值相对于最大值的比例
double dblAlphaValue = double(g_nCurAlphaValue)/double(g_nMaxAlphaValue);
//则beta值为1减去alpha值
double dblBetaValue = 1.0 - dblAlphaValue;
//根据alpha和beta的值,对两张图像进行线性混合
addWeighted(g_srcImg1, dblAlphaValue, g_srcImg2, dblBetaValue, 0.0, g_mixImg);
//显示混合图像的效果
imshow(cszWindowName, g_mixImg);
if (0 == g_nCurAlphaValue) {
imwrite(“mix0.jpg”, g_mixImg);
}
if (50 == g_nCurAlphaValue) {
imwrite(“mix50.jpg”, g_mixImg);
}
if (100 == g_nCurAlphaValue) {
imwrite(“mix100.jpg”, g_mixImg);
}
}
int main(int argc, char ** argv)
{
//加载图像 (两图像的尺寸需相同)
g_srcImg1 = imread(“lenna.bmp”, IMREAD_COLOR);
if (g_srcImg1.empty()) {
std::cout << “读取第1张图像失败” << std::endl;
return -1;
}
g_srcImg2 = imread(“tiffany.bmp”, IMREAD_COLOR);
if (g_srcImg2.empty()){
std::cout << “读取第2张图像失败” << std::endl;
return -1;
}
// 设置滑动条的初值为70
g_nCurAlphaValue = 0;
//创建窗口,自动调整大小
namedWindow(“mix”, WINDOW_AUTOSIZE);
//在创建的窗体中创建一个滑动条控件
char TrackbarName[50];
sprintf_s(TrackbarName, “透明度 %d”, g_nMaxAlphaValue);
createTrackbar(TrackbarName, cszWindowName, &g_nCurAlphaValue, g_nMaxAlphaValue, on_Trackbar);
//调用一次回调函数,以显示图像
on_Trackbar(g_nCurAlphaValue, 0);
waitKey(0);
return 0;
}
4.鼠标事件响应:
Rect g_rectangle; //记录要绘制的矩形位置
bool g_bDrawingBox = false;//是否进行绘制
RNG g_rng(12345); //随机数对象
const String strWndName = “MouseWnd”;
void DrawRactangle(Mat & img, Rect rect)
{
//每次绘制矩形的颜色都是随机产生的
rectangle(img, rect, Scalar(g_rng.uniform(0, 255),
g_rng.uniform(0, 255), g_rng.uniform(0, 255)), 4);
}
void onMouseCallback(int event, int x, int y, int flags, void * param)
{
//将画矩形的图像作为参数传入回调函数
Mat &image = (Mat)param;
switch (event)
{
//鼠标移动时改变窗口的大小
case EVENT_MOUSEMOVE:
//如果g_bDrawingBox为真,则记录矩形信息到g_rectangle中
if (g_bDrawingBox) {
g_rectangle.width = x - g_rectangle.x;
g_rectangle.height = y - g_rectangle.y;
}
break;
//左键按下时记录窗口的起始位置
case EVENT_LBUTTONDOWN:
g_bDrawingBox = true;
//记录g_rectangle的起点
g_rectangle = Rect(x, y, 0, 0);
break;
//左键抬起时将当前绘制的矩形信息写入到图像中
case EVENT_LBUTTONUP:
// 标识符为false
g_bDrawingBox = false;
//向起点左边绘制
if (g_rectangle.width < 0) {
g_rectangle.x += g_rectangle.width;
g_rectangle.width *= -1;
}
//向起点上边绘制
if (g_rectangle.height < 0) {
g_rectangle.y += g_rectangle.height;
g_rectangle.height *= -1;
}
//调用函数进行绘制
DrawRactangle(image, g_rectangle);
break;
}
}
int main(int argc, char ** argv)
{
//准备参数
g_rectangle = Rect(-1, -1, 0, 0);
Mat srcImage(600, 800, CV_8UC3, Scalar(255,255,255)), tempImage;
srcImage.copyTo(tempImage);
g_rectangle = Rect(-1, -1, 0, 0);
// 设置鼠标操作回调函数
namedWindow(strWndName);
setMouseCallback(strWndName, onMouseCallback, (void *)&srcImage);
// 程序主循环,当进行绘制的标识符为真的时候进行绘制
while (true)
{
//复制原图到临时变量,这样可以清除上一次的鼠标拖动结果
srcImage.copyTo(tempImage);
if (g_bDrawingBox){
//在鼠标拖动时,每次都对图像进行临时绘制
Rect rectCur = g_rectangle;
//鼠标向上或向左移动时,需要对坐标进行处理
if (rectCur.width < 0) {
rectCur.x += rectCur.width;
rectCur.width *= -1;
}
if (rectCur.height < 0) {
rectCur.y += rectCur.height;
rectCur.height *= -1;
}
DrawRactangle(tempImage, rectCur);
}
imshow(strWndName, tempImage);
if (waitKey(10) == 27) // 按下ESC键,程序退出
break;
}
return 0;
}
<3>练手:
1.打开摄像头
#include “pch.h” //viscalC++预编译头文件
#include
#include <opencv.hpp> //OpenCV头文件
#include <highgui.hpp> //GUI界面头文件
using namespace cv; //打开cv的名词空间
#pragma comment(lib, “opencv_world480d.lib”)//打开动态库
int main()
{
cv::namedWindow(“camera”, 0); //新建名为“camera”的窗口
VideoCapture capture(0); //打开ID为0的摄像头
Mat frame; //新建Mat变量(矩阵)
while (capture.isOpened())
{
capture >> frame; //用重载运算符方式获取视频帧
if (frame.empty())break;
imshow(“camera”, frame); //在名为“camera”的窗口显示捕获帧
waitKey(10); //刷新图像,否则无法正常显示
}
}
2.视频播放器,滑动条控制进度,双击暂停/播放
#include “pch.h”
#include
#include <opencv.hpp>
#include <highgui.hpp>
using namespace cv;
#pragma comment(lib,”opencv_world480d.lib”)
const char* trackname = “progress”;
const char* windowname = “videoplayer”;
const char* filepath = “D:/files/picture and video/C0056.MP4”;
cv::VideoCapture capture(filepath, CAP_ANY);
int nWidth = capture.get(CAP_PROP_FRAME_WIDTH); //视频图像宽度
int nHeight = capture.get(CAP_PROP_FRAME_HEIGHT); //视频图像高度
double dblFrameRate = capture.get(CAP_PROP_FPS); //视频帧率
double dblFrameCnt = capture.get(CAP_PROP_FRAME_COUNT); //视频总帧数
const int g_nMaxProgressValue = 100; //Alpha值的最大值
int g_nCurProgressValue; //当前滑动条对应的值
int Cur_Frame; //记录暂停时的帧数
Mat frame;
int sign = 0;
int main()
{
void on_Trackbar(int, void*);
void onMouseCallback(int event, int x, int y, int flags, void* param);
cv::namedWindow(windowname,0);
setWindowProperty(windowname, WND_PROP_ASPECT_RATIO , WINDOW_FREERATIO);
resizeWindow(windowname, 400, 300);
moveWindow(windowname, 0, 0);
createTrackbar(trackname, windowname, &g_nCurProgressValue, g_nMaxProgressValue, on_Trackbar);
setMouseCallback(windowname, onMouseCallback, (void*)NULL);
while(capture.isOpened()){
Cur_Frame = capture.get(CAP_PROP_POS_FRAMES); //获取当前播放帧数
//判断是否双击,双击则暂停播放
if (sign)
{
capture.set(CAP_PROP_POS_FRAMES, Cur_Frame-1);
}
capture >> frame;
//如果播放完毕,等待按键,直接退出
if (Cur_Frame == dblFrameCnt)
{
waitKey(0);
break;
}
imshow(windowname, frame);
//中途按 ESC可以直接退出
if (waitKey(1) == 27)break;
}
}
//滑动条回调函数
void on_Trackbar(int, void*)
{
capture.set(CAP_PROP_POS_FRAMES, g_nCurProgressValue * dblFrameCnt / g_nMaxProgressValue); //从视频中间开始播放
capture >> frame;
}
//鼠标回调函数
void onMouseCallback(int event, int x, int y, int flags, void* param)
{
if(event==EVENT_LBUTTONDBLCLK)sign = (sign + 1) % 2;
}
注意控制输出暂停的方法:
1.waitKey等待:键盘控制,可参考其他博主
2.一直输出上一帧:即本人使用方法
capture.set(CAP_PROP_POS_FRAMES, Cur_Frame-1);
3.直接用system(“pause”)
**< 4>linux上编译运行:参考:[Linux下编译、链接、加载运行C++
OpenCV的两种方式及常见问题的解决_linux下安装配置好opencv后怎么加载_Adenialzz的博客-
CSDN博客](https://blog.csdn.net/weixin_44966641/article/details/120659285?spm=1001.2014.3001.5506
“Linux下编译、链接、加载运行C++ OpenCV的两种方式及常见问题的解决_linux下安装配置好opencv后怎么加载_Adenialzz的博客-
CSDN博客”)**
CMakeLists.txt编辑:[CMAKE] 详解CMakeLists.txt文件 - VictoKu - 博客园
(cnblogs.com)
或参考:jetson nano上的OpenCV使用:图像识别小车(jetson
nano部分)——第五章
三.OpenCV基本数据结构和基本组件
<1>(基础图像容器Mat类;点Point类;颜色Scalar类;尺寸Size类;矩形Rect类;旋转矩形RotatedRect类;固定向量Vec类;复数类complexf)
(辅助对象:迭代参数TermCriteria类;范围range类;指针Ptr类)
(工具和系统函数:数学、内存管理、性能优化、异常处理函数)
(图像绘制图形函数:线、矩形、圆、折线)
(图像保存函数;图像几何操作函数:均匀调整(尺寸)、仿射变换、对数极坐标变换)
<2>练手代码
1.Mat类操作
int main()
{
namedWindow(“image”, WINDOW_NORMAL);
setWindowTitle(“image”, “image:”);
Mat img(Size(200, 100), CV_8UC3, Scalar(80, 160, 240));
imshow(“image”, img);
waitKey(0);
return -1;
}
2.摄像头图像流极坐标变换
const int g_nMaxValue = 100; //滑条值的最大值
int g_nCurValue = 0; //当前滑动条对应的值
int main()
{
void on_Trackbar(int, void*);
namedWindow(“Polor”, WINDOW_NORMAL);
Mat frame;
VideoCapture capture(0);
int nWidth = capture.get(CAP_PROP_FRAME_WIDTH); //视频图像宽度
int nHeight = capture.get(CAP_PROP_FRAME_HEIGHT); //视频图像高度
createTrackbar(“zoom factor”, “Polor”, &g_nCurValue, g_nMaxValue, on_Trackbar);
while (capture.isOpened())
{
capture >> frame;
if (frame.empty())break;
logPolar(frame, frame, Point2i(nWidth / 2, nHeight / 2), g_nCurValue, WARP_FILL_OUTLIERS);
imshow(“Polor”, frame);
waitKey(1);
}
}
void on_Trackbar(int, void*)
{
;
}
3.读取图像像素RGB值(左键显示,右键清空)
#include “opencv.hpp”
#include “highgui.hpp”
#include
using namespace cv;
#pragma comment(lib,”opencv_world480d.lib”)
const char* filepath = “/test2.png”;
const char* windowname = “window”;
const char* windowtitle = “image”;
const char* trackname = “fontscale”;
int fontscale = 1;· //字体大小及线条粗细
const int max_fontscale = 100;
Mat img = imread(filepath, IMREAD_COLOR);
Mat draw_board = img.clone(); //图片拷贝以实现清除
char string[5];
int main()
{
void on_Trackbar(int, void*);
void onMouseCallback(int event, int x, int y, int flags, void* param);
namedWindow(windowname, WINDOW_NORMAL);
createTrackbar(trackname, windowname, &fontscale, max_fontscale, on_Trackbar);
setMouseCallback(windowname, onMouseCallback, (void*)NULL);
if (draw_board.empty())return -1;
while (!draw_board.empty())
{
imshow(windowname, draw_board);
if (waitKey(1) == 27)break;
}
return 0;
}
void on_Trackbar(int, void*)
{
;
}
void onMouseCallback(int event, int x, int y, int flags, void* param)
{
if (event == EVENT_LBUTTONDOWN)
{
//读取鼠标所指像素的值
int rgb[3] = { img.at<cv::Vec3b>(x, y)[2],img.at<cv::Vec3b>(x, y)[1], img.at<cv::Vec3b>(x, y)[0] };
//putText不支持\n换行,只能手动计算间隔:y + fontscale * 10 * i
for (int i = 0; i < 3; i++) {
sprintf_s(string, "%d", rgb[i]);
//文字写入图像
putText(draw_board, string, Point(x, y + fontscale * 10 * i), FONT_HERSHEY_PLAIN, fontscale, Scalar(rgb[2], rgb[1], rgb[0]), fontscale, 8, false);
}
}
if (event == EVENT_RBUTTONDOWN)
{
//使显示图像为原始图像,即清零
draw_board = img.clone();
}
}
注:
①.at()函数访问多通道Mat数据元素时为只能用at()函数,且注意at<>内为 Vec3b
at<cv::Vec3b>(x, y)[i]
②.putText()函数无法实现换行,需手动计算
4.创建一定尺寸3通道RGB图像,并逐个访问其像素值,并绘制一绿色平面
#include “opencv.hpp”
#include “highgui.hpp”
#include
using namespace cv;
#pragma comment(lib,”opencv_world480d.lib”)
const char* filepath = “test2.png”;
const char* windowname = “window”;
const char* windowtitle = “image”;
const char* trackname = “fontscale”;
#define WIDTH 800
#define HEIGHT 600
int main()
{
namedWindow(windowname, WINDOW_NORMAL);
Mat img(WIDTH, HEIGHT, CV_8UC3, Scalar(0, 0, 0));
for (int i = 0; i < WIDTH; i++) {
for (int j = 0; j < HEIGHT; j++) {
for (int k = 0; k < 3; k++) {
img.at
}
}
}
Point p1(200, 50), p2(400, 200);
rectangle(img,p1,p2,Scalar(0,255,0),8,8,0);
imshow(windowname, img);
//imwrite(“C:/Users/user/Desktop/1.png”, img);
waitKey(0);
}
四.数字图像灰度变换与空间滤波
<1>(灰度变换:线性、非线性;直方图:概率、累计)
<2>示例代码
1.灰度变换函数
①对数变换
//对数变换
void LogTransform(Mat srcImg, Mat &dstImg, const float c=1.0f)
{
if (srcImg.empty()){
cout << “No data” << endl;
return;
}
//Mat dstMat = Mat::zeros(srcImg.size(), srcImg.type());
add(srcImg, Scalar(1.0), srcImg); //计算 s+1
srcImg.convertTo(srcImg, CV_32F); //转化为32位浮点型
cv::log(srcImg, dstImg); //计算log(1+s)
dstImg = c*dstImg;
//归一化处理
normalize(dstImg, dstImg, 0, 255, NORM_MINMAX);
//cout << dstImg << endl;
cout << dstImg.elemSize() << endl;
//将dstImg转换到CV_8U类型
convertScaleAbs(dstImg, dstImg);
return;
}
②*伽马变换
//伽马校正
void MyGammaCorrection(const Mat& src, Mat& dst, float fGamma)
{
//CV_Assert(src.data);
if (src.empty()){
return;
}
//只处理位深度为8位的图像
CV_Assert(src.depth() != sizeof(uchar));
//创建查找表
unsigned char lut[256];
for (int i = 0; i < 256; i++){
lut[i] = saturate_cast
fGamma) * 255.0f);
}
dst = src.clone();
const int channels = dst.channels();
switch (channels){
case 1:
{
//MatIterator_
//for (it = dst.begin
// *it = lut[(*it)];
for (int j = 0; j < dst.rows; j++){
for (int i = 0; i < dst.cols; i++){
unsigned char val = dst.at
dst.at
}
}
break;
}
case 3:
{
MatIterator_
for (it = dst.begin
(*it)[0] = lut[((*it)[0])];
(*it)[1] = lut[((*it)[1])];
(*it)[2] = lut[((*it)[2])];
}
break;
}
}
}
注:saturate_cast<>的使用(防止颜色溢出)参考:【OpenCV】中saturate_cast
CSDN博客
2.直方图(概率直方图)绘制函数
①灰度直方图
//灰度直方图
void DrawGrayImgHist(const Mat &srcImg)
{
if (1 != srcImg.channels()){
return;
}
int channels = 0;
Mat dstHist;
int histSize[] = { 256 };
float midRanges[] = { 0, 256 };
const float *ranges[] = { midRanges };
calcHist(&srcImg, 1, &channels, Mat(), dstHist,
1, histSize, ranges, true, true);
//最终绘制的直方图图像,大小是256×256
Mat histImage = Mat::zeros(Size(256, 256), CV_8UC1);
double dblHistMaxValue;
//求得直方图的最大值
minMaxLoc(dstHist, 0, &dblHistMaxValue, 0, 0);
//将像素的个数整合到图像的最大范围内
for (int i = 0; i <= 255; i++){
int value = cvRound(dstHist.at
* 255 / dblHistMaxValue);
line(histImage, Point(i, histImage.rows - 1),
Point(i, histImage.rows - 1 - value), Scalar(255));
}
imshow(“直方图”, histImage);
imwrite(“desert_hist_规定化之后.bmp”, histImage);
}
②RGB彩色直方图
//RGB彩色直方图
void DrawRGBImgHist(const Mat &srcImg)
{
if (srcImg.empty() || srcImg.channels() != 3){
return;
}
//分割成3个单通道图像 ( R, G 和 B )
vector
split(srcImg, rgb_planes);
// 设定bin数目
int histSize = 256;
// 设定取值范围 ( R,G,B) )
float range[] = { 0, 256 };
const float* histRange = { range };
bool uniform = true;
bool accumulate = true;
Mat r_hist, g_hist, b_hist;
//计算直方图:
calcHist(&rgb_planes[0], 1, 0, Mat(), r_hist, 1,
&histSize, &histRange, uniform, accumulate);
calcHist(&rgb_planes[1], 1, 0, Mat(), g_hist, 1,
&histSize, &histRange, uniform, accumulate);
calcHist(&rgb_planes[2], 1, 0, Mat(), b_hist, 1,
&histSize, &histRange, uniform, accumulate);
// 创建直方图画布
int hist_w = 256; int hist_h = 200;
int bin_w = cvRound((double)hist_w / histSize);
Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));
// 将直方图归一化到范围 [ 0, histImage.rows ]
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX);
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX);
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX);
// 在直方图画布上画出直方图,3个直方图叠加在一起,用不同的颜色表示
for (int i = 1; i < histSize; i++)
{
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))),
Scalar(0, 0, 255), 2, 8, 0);
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))),
Scalar(0, 255, 0), 2, 8, 0);
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))),
Scalar(255, 0, 0), 2, 8, 0);
}
// 显示直方图
imshow("RGB彩色图像直方图", histImage);
imwrite("./colorhist_for_beatuty_after_qualization.bmp", histImage);
waitKey(0);
}
③累计直方图
//画累积直方图
void DrawAccumulateImgHist(const Mat &srcImg)
{
if (1 != srcImg.channels()){
return;
}
int channels = 0;
Mat dstHist;
int histSize[] = { 256 };
float midRanges[] = { 0, 256 };
const float *ranges[] = { midRanges };
calcHist(&srcImg, 1, &channels, Mat(), dstHist,
1, histSize, ranges, true, true);
//对直方图进行累积
for (int i = 1; i < dstHist.rows; i++){
dstHist.at
}
//最终绘制的直方图图像,大小是256×256
Mat histImage = Mat::zeros(Size(256, 256), CV_8UC1);
double dblHistMaxValue;
//求得直方图的最大值
minMaxLoc(dstHist, 0, &dblHistMaxValue, 0, 0);
//将像素的个数整合到图像的最大范围内
for (int i = 0; i < 256; i++){
int value = cvRound(dstHist.at
* 255 / dblHistMaxValue);
line(histImage, Point(i, histImage.rows - 1),
Point(i, histImage.rows - 1 - value), Scalar(255));
}
imshow(“累积直方图”, histImage);
imwrite(“./accumlate.bmp”, histImage);
}
七.图像分割
<1>(边缘检测:算子:一阶导数【Sobel/Prewitt/Roberts】、二阶【拉普拉斯/LOG/Canny】)
(几何形状检测【霍夫变换】:直线、圆)
(阈值分割【二值化】:全局阈值【OTSU、三角法】、自适应阈值)
<2>示例代码
1.霍夫直线变换和霍夫圆检测
void DetectLines()
{
Mat matSrc = imread(“Hough_src_clr.png”, IMREAD_GRAYSCALE);
Mat matEdge;
//Canny算子计算图像边缘
Canny(matSrc, matEdge, 250, 200, 3, false);
imshow(“原图像”, matSrc);
imshow(“Canny边缘”, matEdge);
imwrite(“hough_src_gray.bmp”, matSrc);
imwrite(“hough_src_canny.bmp”, matEdge);
std::vector
//标准霍夫变换检测直线,距离精度为1像素,角度精度为1度,阈值为300
HoughLines(matEdge, linesSHT, 1, CV_PI / 180, 280);
Mat matSHT = matSrc.clone();
for (size_t i = 0; i < linesSHT.size(); i++) {
//直线的rho和theta值
float rho = linesSHT[i][0], theta = linesSHT[i][1];
//pt1和pt2是直线的两个端点
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
pt1.x = cvRound(x0 + 2000 * (-b)); //把浮点数转化成整数
pt1.y = cvRound(y0 + 2000 * (a));
pt2.x = cvRound(x0 - 2000 * (-b));
pt2.y = cvRound(y0 - 2000 * (a));
line(matSHT, pt1, pt2, Scalar(255), 4);
}
imshow(“SHT直线检测结果”, matSHT);
imwrite(“hough_Lines_SHT.bmp”, matSHT);
Mat matPPHT = matSrc.clone();
std::vector
//累计概率霍夫变换检测直线,得到的是直线的起止端点
HoughLinesP(matEdge, linesPPHT, 1, CV_PI / 180, 280, 100, 50);
for (size_t i = 0; i < linesPPHT.size(); i++) {
//直接绘制直线
line(matPPHT, Point(linesPPHT[i][0], linesPPHT[i][1]),
Point(linesPPHT[i][2], linesPPHT[i][3]), Scalar(255), 4, 8);
}
imshow(“PPHT直线检测结果”, matPPHT);
imwrite(“Hough_lines_PPHT.bmp”, matPPHT);
waitKey(0);
}
void DetectCircles()
{
Mat src;
src = imread(“HoughCircles_src_clr.jpg”, IMREAD_GRAYSCALE);
//imwrite(“HoughCircles_src_gray.bmp”, src);
vector
HoughCircles(src, circles, HOUGH_GRADIENT, 1, 10, 60, 40, 20, 40);
//在原图中画出圆心和圆
for (size_t i = 0; i < circles.size(); i++){
//提取出圆心坐标
Point center(round(circles[i][0]), round(circles[i][1]));
//提取出圆半径
int radius = round(circles[i][2]);
//圆心
circle(src, center, 3, Scalar(255), -1, 4, 0);
//圆
circle(src, center, radius, Scalar(255), 3, 4, 0);
}
//imwrite(“HoughCircles_circles.bmp”, src);
imshow(“Circle”, src);
waitKey(0);
}
2.OTSU方法和三角法求全局阈值
//OTSU方法求阈值
int OtsuBinary(Mat src)
{
long lPixCnt = src.rows * src.cols;
long histogram[256] = { 0 }; //histogram是灰度直方图
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
unsigned char nCurVal = src.at
histogram[nCurVal]++;
}
}
int nThreshold = 0;
long sum0 = 0, sum1 = 0; //存储前景的灰度总和和背景灰度总和
long cnt0 = 0, cnt1 = 0; //前景像素总个数和背景像素总个数
double w0 = 0, w1 = 0; //前景和背景所占整幅图像的比例
double u0 = 0, u1 = 0; //前景和背景的平均灰度
double variance = 0; //类间方差
double maxVariance = 0; //用来存储最大类间方差
for (int i = 1; i < 256; i++) //遍历所有灰度级别
{
sum0 = 0; cnt0 = 0; w0 = 0;
sum1 = 0; cnt1 = 0; w1 = 0;
for (int j = 0; j < i; j++) {
cnt0 += histogram[j]; //前景像素总和
sum0 += j * histogram[j]; //前景灰度值总和
}
//前景部分灰度均值
u0 = cnt0 > 0 ? double(sum0) / cnt0 : 0;
w0 = (double)cnt0 / lPixCnt; //前景部分所占的比例
for (int j = i; j <= 255; j++) {
cnt1 += histogram[j]; //背景像素个数
sum1 += j * histogram[j]; //背景部分灰度值总和
}
//背景部分灰度均值
u1 = cnt1 > 0 ? double(sum1) / cnt1 : 0;
w1 = 1 - w0; //背景部分所占的比例
//分割阈值为i时的类间方差
variance = w0 * w1 * (u0 - u1) * (u0 - u1);
if (variance > maxVariance) {
maxVariance = variance;
nThreshold = i;
}
}
return nThreshold;
}
//三角法求阈值
int TriangleBinary(Mat src)
{
long lPixCnt = src.rows * src.cols;
long histogram[256] = { 0 }; //histogram是灰度直方图
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
unsigned char nCurVal = src.at
histogram[nCurVal]++;
}
}
//左右边界
int left_bound = 0, right_bound = 0;
//直方图最高峰和相应的灰度值
int max_ind = 0, maxPeak = 0;
int temp;
bool isflipped = false;
// 找到最左边零的位置
for (int i = 0; i < 256; i++) {
if (histogram[i] > 0) {
left_bound = i;
break;
}
}
//位置再移动一个步长,即为最左侧零位置
if (left_bound > 0)
left_bound–;
// 找到最右边零点位置
for (int i = 255; i > 0; i–) {
if (histogram[i] > 0) {
right_bound = i;
break;
}
}
// 位置再移动一个步长,即为最右侧零位置
if (right_bound < 255)
right_bound++;
// 在直方图上寻找最亮的点Hmax
for (int i = 0; i < 256; i++) {
if (histogram[i] > maxPeak) {
maxPeak = histogram[i];
max_ind = i;
}
}
// 如果最大值落在靠左侧这样就无法满足三角法求阈值,
//所以要检测是否最大值是否靠近左侧
// 如果靠近左侧则通过翻转到右侧位置。
if (max_ind - left_bound < right_bound - max_ind) {
isflipped = true;
int i = 0;
int j = 255;
// 左右交换
while (i < j) {
temp = histogram[i]; histogram[i] = histogram[j]; histogram[j] = temp;
i++; j--;
}
left_bound = 255 - right_bound;
max_ind = 255 - max_ind;
}
// 计算求得阈值
double thresh = left_bound;
double maxDist = 0, tempDist;
double peakIdxBound = left_bound - max_ind;
for (int i = left_bound + 1; i <= max_ind; i++)
{
// 计算距离
tempDist = maxPeak * i + peakIdxBound * histogram[i];
if (tempDist > maxDist) {
maxDist = tempDist;
thresh = i;
}
}
thresh--;
if (isflipped) {
thresh = 255 - thresh;
}
return thresh;
}
//手动二值化处理
Mat Binbary(Mat src, int nThreshold)
{
//遍历每个像素,对图像进行二值化
Mat dst = Mat::zeros(src.rows, src.cols, CV_8UC1);
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
if (src.at<uchar>(i, j) > nThreshold)
dst.at<uchar>(i, j) = 255;
}
}
return dst;
}
*注:
①二值化的原图都是灰度图,产生灰度图方法见后
②对于固定场景摄像头读取图像可以先调用以上函数求出全局阈值,之后使用cv::threshold()函数时直接调用该阈值,减少每帧计算阈值时间
<3>练手
1.边缘检测二阶导数算子使用
#include “opencv.hpp”
#include “highgui.hpp”
#include
using namespace cv;
#pragma comment(lib,”opencv_world480d.lib”)
#define IMAGE_TEST
#ifdef IMAGE_TEST
const char* filepath = “./Test.jpg”;
Mat img = imread(filepath, IMREAD_COLOR);
Mat draw_board = img.clone(); //图片拷贝以实现清除
#endif
#ifdef CAMERA_TEST
VideoCapture capture(0);
Mat image;
#endif // CAMERA_TEST
int main()
{
void changing(void);
changing();
}
//拉普拉斯高通滤波
void changing(void)
{
Mat LoG_Image(const Mat & image, int kervalue = 3, double sigma = 1.0f);
#ifdef IMAGE_TEST
Mat image = imread(filepath, IMREAD_COLOR);
if (image.empty()) {
std::cout << “打开图片失败,请检查” << std::endl;
return;
}
imshow(“原图像”, image);
Mat matDst;
// Laplacian(image, matDst, image.depth(), 5); //拉普拉斯算子
// matDst = LoG_Image(image, 3, 1.0f); //LOG算子
Canny(image, matDst, 80, 150, 3, false); //canny算子
imwrite(“changing.bmp”, matDst);
imshow(“变换效果”, matDst);
waitKey(0);
#endif
#ifdef CAMERA_TEST
while (capture.isOpened())
{
capture >> image;
if (image.empty())break;
Mat matDst;
// Laplacian(image, matDst, image.depth(), 5); //拉普拉斯算子
// matDst = LoG_Image(image, 3, 1.0f); //LOG算子
Canny(image, matDst, 100, 500, 3, false); //canny算子
imshow(“变换效果”, matDst);
if (waitKey(1) == 27)break;
}
#endif
}
//图像LoG算子运算
Mat LoG_Image(const Mat& image, int kervalue = 3, double sigma = 1.0f)
{
//首先对图像做高斯平滑
Mat matTemp;
GaussianBlur(image, matTemp, Size(kervalue, kervalue), sigma, sigma, BORDER_DEFAULT);
//通过拉普拉斯算子做边缘检测
Mat laplacian = Mat::zeros(image.rows, image.cols, CV_32FC1);
Laplacian(matTemp, laplacian, CV_32FC1, 3);
//求得最大边缘值
double dblMaxVal = 0;
minMaxLoc(laplacian, NULL, &dblMaxVal);
Mat dstImg;
convertScaleAbs(laplacian, dstImg);
imwrite(“edge.bmp”, dstImg);
Mat result = Mat::zeros(image.rows, image.cols, CV_8UC1);
//过零点交叉,寻找边缘像素
for (int i = 1; i < result.rows - 1; i++) {
for (int j = 1; j < result.cols - 1; j++) {
if (laplacian.at
continue;
}
//水平、垂直、45度方向,135度4个方向过零点判定
if (laplacian.at
* laplacian.at
result.at
if (laplacian.at
* laplacian.at
result.at
if (laplacian.at
* laplacian.at
result.at
if (laplacian.at
* laplacian.at
result.at
}
}
return result;
}
//Canny算子计算图像的梯度和方向
void CannyEdgeAndDirection(const Mat& src)
{
Mat magX = Mat(src.rows, src.cols, CV_32FC1);
Mat magY = Mat(src.rows, src.cols, CV_32FC1);
Mat slopes = Mat(src.rows, src.cols, CV_32FC1);
Sobel(src, magX, CV_32FC1, 1, 0, 3);//水平梯度
Sobel(src, magY, CV_32FC1, 1, 0, 3);//垂直梯度
//梯度方向
divide(magY, magX, slopes);
//梯度幅值
Mat magnitude;
sqrt(magX * magX + magY * magY, magnitude);
}
2.霍夫直线变换及霍夫圆的检测
#include “opencv.hpp”
#include “highgui.hpp”
#include
using namespace cv;
#pragma comment(lib,”opencv_world480d.lib”)
VideoCapture capture(0);
Mat image;
//#define SHT //SHT检测直线
//#define PPHT //PPHT检测直线
#define HCD //霍夫圆检测
int main()
{
while (capture.isOpened())
{
capture >> image;
if (image.empty())break;
Mat matCanny;
Mat matDst;
Canny(image, matCanny, 100, 300, 3, false); //canny算子
#ifdef SHT
std::vector
//标准霍夫变换检测直线,距离精度为1像素,角度精度为1度,阈值为300
HoughLines(matCanny, linesSHT, 1, CV_PI / 180, 280);
//直线在原图上绘制
matDst = image.clone();
for (size_t i = 0; i < linesSHT.size(); i++) {
//直线的rho和theta值
float rho = linesSHT[i][0], theta = linesSHT[i][1];
//pt1和pt2是直线的两个端点,2000是经验值(满足覆盖所有前景点像素)
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
pt1.x = cvRound(x0 + 2000 * (-b)); //把浮点数转化成整数
pt1.y = cvRound(y0 + 2000 * (a));
pt2.x = cvRound(x0 - 2000 * (-b));
pt2.y = cvRound(y0 - 2000 * (a));
line(matDst, pt1, pt2, Scalar(255), 4);
}
#endif // SHT标准霍夫变换
#ifdef PPHT
matDst = image.clone();
std::vector
//累计概率霍夫变换检测直线,得到的是直线的起止端点
HoughLinesP(matCanny, linesPPHT, 1, CV_PI / 180, 220, 100, 50);
for (size_t i = 0; i < linesPPHT.size(); i++) {
//直接绘制直线
line(matDst, Point(linesPPHT[i][0], linesPPHT[i][1]),
Point(linesPPHT[i][2], linesPPHT[i][3]), Scalar(255), 4, 8);
}
#endif // PPHT累计概率霍夫变换
#ifdef HCD
cvtColor(image, matDst, COLOR_BGR2GRAY);
std::vector
HoughCircles(matDst, circles, HOUGH_GRADIENT, 1, 10, 60, 40, 20, 40);
//在原图中画出圆心和圆
for (size_t i = 0; i < circles.size(); i++) {
//提取出圆心坐标
Point center(round(circles[i][0]), round(circles[i][1]));
//提取出圆半径
int radius = round(circles[i][2]);
//圆心
circle(matDst, center, 3, Scalar(255), -1, 4, 0);
//圆
circle(matDst, center, radius, Scalar(255), 3, 4, 0);
}
#endif // HCD霍夫圆检测
imshow(“检测结果”, matDst);
if (waitKey(1) == 27)break;
}
}
3.linux(ubuntu18)上实现霍夫圆检测(可调各个参数)
参数见:opencv —— HoughCircles
霍夫圆变换原理及圆检测
二值化函数threshold参数:OpenCV基础——threshold函数的使用
#include “opencv2/opencv.hpp”
#include “opencv2/highgui.hpp”
#include
using namespace cv;
VideoCapture capture(0);
Mat image;
const char* windowname=”win”;
int max_r=100;
int min_r=60;
int min_d=80;
int t_hold=45;
int param1=100;
int param2=10;
const int t_max=255;
const int r_max=1000;
const int d_max=100;
const int p1_max=200;
const int p2_max=200;
int main()
{
void on_Trackbar_1(int, void*);
void on_Trackbar_2(int, void*);
void on_Trackbar_3(int, void*);
void on_Trackbar_4(int, void*);
void on_Trackbar_5(int, void*);
void on_Trackbar_6(int, void*);
namedWindow(windowname,0);
setWindowProperty(windowname, WND_PROP_ASPECT_RATIO , WINDOW_FREERATIO);
resizeWindow(windowname, 400, 300);
moveWindow(windowname, 0, 0);
createTrackbar(“t_hold”,windowname, &t_hold, t_max, on_Trackbar_3);
createTrackbar(“max_r”,windowname, &max_r, r_max, on_Trackbar_1);
createTrackbar(“min_r”,windowname, &min_r, r_max, on_Trackbar_2);
createTrackbar(“min_d”,windowname, &min_d, d_max, on_Trackbar_6);
createTrackbar(“p_1”,windowname, ¶m1, p1_max, on_Trackbar_4);
createTrackbar(“p_2”,windowname, ¶m2, p2_max, on_Trackbar_5);
while (capture.isOpened())
{
capture >> image;
if (image.empty())break;
Mat matCanny;
Mat BinImg;
Mat matDst;
cvtColor(image, matDst, COLOR_BGR2GRAY);
threshold(matDst, BinImg, t_hold, 255, THRESH_BINARY_INV);
Canny(BinImg, matCanny, 100, 300, 3, false); //canny算子
std::vector
HoughCircles(matCanny, circles, HOUGH_GRADIENT, 1, min_d, param1, param2, min_r, max_r);
//在原图中画出圆心和圆
for (size_t i = 0; i < circles.size(); i++) {
//提取出圆心坐标
Point center(round(circles[i][0]), round(circles[i][1]));
//提取出圆半径
int radius = round(circles[i][2]);
//圆心
circle(image, center, 3, Scalar(255,0,0), -1, 4, 0);
//圆
circle(image, center, radius, Scalar(255,0,0), 3, 4, 0);
}
imshow("matCanny", matCanny);
imshow("BinImg", BinImg);
imshow(windowname, image);
if (waitKey(1) == 27)break;
}
}
void on_Trackbar_1(int, void*)
{
;
}
void on_Trackbar_2(int, void*)
{
;
}
void on_Trackbar_3(int, void*)
{
;
}
void on_Trackbar_4(int, void*)
{
;
}
void on_Trackbar_5(int, void*)
{
;
}
void on_Trackbar_6(int, void*)
{
;
}
注:OpenCV提供的SHT输出极坐标下直线的rho和theta值,需根据经验值推算该直线上的两点以绘图;而PPHT直接返回线段两端两点坐标
*注:图像灰度化的方法:可参考OpenCV图像灰度化的六种方法_opencv灰度化_郑德帅的博客-CSDN博客
4.阈值分割(OTSU/三角法/自适应阈值)
#include
using namespace cv;
#pragma comment(lib,”opencv_world480d.lib”)
VideoCapture capture(0);
Mat image;
int main()
{
int OtsuBinary(Mat src);
int TriangleBinary(Mat src);
Mat gray, dst;
capture >> image;
cvtColor(image, gray, COLOR_BGR2GRAY);
int nThreshold = OtsuBinary(gray);
// int nThreshold = TriangleBinary(gray);
while(capture.isOpened())
{
capture >> image;
if (image.empty())break;
cvtColor(image, gray, COLOR_BGR2GRAY);
//用OTSU方法
// threshold(gray, dst, nThreshold, 255, THRESH_BINARY);
// threshold(gray, dst, 0, 255, THRESH_BINARY | THRESH_OTSU);
//用三角法
// threshold(gray, dst, nThreshold, 255, THRESH_BINARY);
// threshold(gray, dst, 0, 255, THRESH_BINARY|THRESH_TRIANGLE);
//自适应阈值
adaptiveThreshold(gray, dst, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 7, 5);
imshow(“BinbaryImage”, dst);
if (waitKey(1) == 27)break;
}
}
*注:自适应阈值保留信息更多,注意使用场合
5.识别车道(二值化、边缘检测、霍夫直线变换)
#include “opencv.hpp”
#include “highgui.hpp”
#include
using namespace cv;
#pragma comment(lib,”opencv_world480d.lib”)
int main() {
Mat image = imread(“car_track.jpg”, IMREAD_GRAYSCALE);
Mat BinImg;
threshold(image, BinImg, 150, 255, THRESH_BINARY);
imwrite(“BinImg.jpg”, BinImg);
Mat matCanny;
Canny(BinImg, matCanny, 100, 300, 3, false); //canny算子
imwrite(“CannyImg.jpg”, matCanny);
Mat matDst = image.clone();
std::vector
//标准霍夫变换检测直线,距离精度为1像素,角度精度为1度,阈值为100
HoughLines(matCanny, linesSHT, 1, CV_PI / 180, 100);
//直线在原图上绘制
matDst = image.clone();
for (size_t i = 0; i < linesSHT.size(); i++) {
//直线的rho和theta值
float rho = linesSHT[i][0], theta = linesSHT[i][1];
//pt1和pt2是直线的两个端点,2000是经验值(满足覆盖所有前景点像素)
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
pt1.x = cvRound(x0 + 2000 * (-b)); //把浮点数转化成整数
pt1.y = cvRound(y0 + 2000 * (a));
pt2.x = cvRound(x0 - 2000 * (-b));
pt2.y = cvRound(y0 - 2000 * (a));
line(matDst, pt1, pt2, Scalar(255), 8);
}
imshow(“car_track”, matDst);
waitKey(0);
imwrite(“car_track_show.jpg”, matDst);
}
处理结果:
九.特征提取和目标检测
<1>(特征:HOG/LBP/HAAR-LIKE)(分类:SVM/级联分类器)
<2>示例代码
1.HOG特征提取+SVM
//SVM参考代码
#include “pch.h”
#include
#include
#include <windows.h>
//#include <afxwin.h>
#include
#include “opencv.hpp”
using namespace cv;
using namespace std;
using namespace cv::ml;
#ifdef _DEBUG
#pragma comment(lib, “opencv_world480d.lib”)
#else
#pragma comment(lib, “opencv_world480.lib”)
#endif
//************************************
const char* file_path = “E:/测试视频数据/Video_2016_8_26__10_10_48.mp4”;
//************************************
vector< float > get_svm_detector(const Ptr< SVM >& svm)
{
//得到支持向量
Mat sv = svm->getSupportVectors();
const int sv_total = sv.rows;
//得到支持向量对应的系数值
Mat alpha, svidx;
double rho = svm->getDecisionFunction(0, alpha, svidx);
CV_Assert(alpha.total() == 1 && svidx.total() == 1 && sv_total == 1);
CV_Assert((alpha.type() == CV_64F && alpha.at
(alpha.type() == CV_32F && alpha.at
CV_Assert(sv.type() == CV_32F);
//将支持向量的值写入一个vector返回
vector< float > hog_detector(sv.cols + 1);
memcpy(&hog_detector[0], sv.ptr(), sv.cols * sizeof(hog_detector[0]));
hog_detector[sv.cols] = (float)-rho;
return hog_detector;
}
//sampleMat是采样矩阵,labelMat是类别矩阵,nCurRows当前是矩阵的行数
//提取正样本HOG特征
void PosData(HOGDescriptor& hog, Mat& sampleMat, vector<int>& Labels, int& nRowIdx)
{
vector<String> files; //文件名列表
//************************************
glob("pos_src/*.*", files); //搜索positive目录下所有文件
//************************************
for (size_t i = 0; i < files.size(); ++i) {
Mat imgSrc = imread(files[i], IMREAD_GRAYSCALE); //加载图像
if (imgSrc.empty()) {
cout << files[i] << " is invalid!" << endl;
continue;
}
Mat imgDst;
resize(imgSrc, imgDst, hog.winSize); //将正例缩放到检测窗口大小
vector<float> featureVec;
hog.compute(imgDst, featureVec, Size(8, 8), Size(0, 0));
//将特征向量加入采样矩阵
for (int i = 0; i < featureVec.size(); i++) {
sampleMat.at<float>(nRowIdx, i) = featureVec[i];
}
nRowIdx++;
Labels.push_back(+1); //正样本类别为+1
cout << nRowIdx << files[i] << endl;
}
}
//提取负样本HOG特征
void NegData(HOGDescriptor& hog, Mat& sampleMat, vector<int>& Labels, int& nRowIdx)
{
vector<String> files; //文件名列表
//************************************
glob("neg_src/*.*", files); //搜索positive目录下所有文件
//************************************
Rect box;
box.width = hog.winSize.width;
box.height = hog.winSize.height;
for (size_t i = 0; i < files.size(); ++i) {
Mat img = imread(files[i], IMREAD_GRAYSCALE);
if (img.empty()) {
continue;
}
Mat matDst;
if (img.cols <= hog.winSize.width + 1 || img.rows <= hog.winSize.height + 1) {
//cout << "image too small" << endl;
resize(img, matDst, hog.winSize);
}
else {
//随机选择窗口位置
box.x = rand() % (img.cols - box.width);
box.y = rand() % (img.rows - box.height);
matDst = img(box);
}
vector<float> featureVec;
hog.compute(matDst, featureVec, Size(8, 8), Size(0, 0));
//将特征向量加入采样矩阵
for (int i = 0; i < featureVec.size(); i++) {
sampleMat.at<float>(nRowIdx, i) = featureVec[i];
}
nRowIdx++;
Labels.push_back(-1);//负样本类别为-1
cout << nRowIdx << files[i] << endl;
}
}
void TrainSVMModel()
{
//车牌检测window大小为128X48,block大小为16X16,cell大小为8X8,滑动窗口大小为8X8
HOGDescriptor hog(cv::Size(128, 48), cv::Size(16, 16), cv::Size(8, 8), cv::Size(8, 8), 9);
int nVecLen = hog.getDescriptorSize();
//样本的特征向量,行数等于正负样本个数,列数等于HOG特征向量长度
Mat sampleFeatureMat = Mat::zeros(9689, nVecLen, CV_32FC1);
//样本的类别向量,行数等于所有样本的个数,列数等于1;1表示正样本,-1表示负样本
vector<int> Labels;
int nRowIdx = 0;
PosData(hog, sampleFeatureMat, Labels, nRowIdx);
NegData(hog, sampleFeatureMat, Labels, nRowIdx);
Ptr
svm->setCoef0(0.0);
svm->setDegree(3);
svm->setGamma(0);
svm->setKernel(SVM::LINEAR);
svm->setNu(0.5);
svm->setP(0.1);
svm->setC(0.01);
svm->setType(SVM::EPS_SVR); //分类器类型为EPS_SVR
//************************************
//训练结束条件:要么达到1000次,要么两次误差小于1e-3
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 1e-3));
//************************************
svm->train(sampleFeatureMat, ROW_SAMPLE, Labels);
//svm->trainAuto();
//直接将支持向量系数写入文本文件,以方便在检测器的头文件中导入
vector
//************************************
//支持向量结果保存
ofstream file(“dector.txt”);
//************************************
for (int i = 0; i < vecHogCof.size(); i++) {
file << vecHogCof[i] << “,”;
}
file.close();
}
void TestSVMModel()
{
//************************************
//SVM检测器系数向量都放在hogCof数组中(detect.txt中内容,模型建立好后可直接调用)
float hogCof[] = {……};
//************************************
//创建HOG检测器,参数与训练时的参数相同
//在这里特别注意将nLevels参数从默认64修改为4,可以加快检测速度
HOGDescriptor hog(cv::Size(128, 48), cv::Size(16, 16), cv::Size(8, 8),
cv::Size(8, 8), 9, 1, -1.0, HOGDescriptor::L2Hys, 0.2, false, 4);
const int vecLen = sizeof(hogCof) / sizeof(float);
vector
hog.setSVMDetector(vecHogCof); //设置HOG检测器的系数
//打开一个视频文件
VideoCapture cap;
cap.open(file_path);
if (!cap.isOpened()) {
return;
}
Mat frame;
int nFrmIdx = 0;
while (true) {
cap >> frame;
if (frame.empty()) {
break;
}
vector
vector
hog.detectMultiScale(frame, detections, foundWeights, 0.5, Size(8, 8), Size(0, 0), 1.1, 3.0, false);
for (int i = 0; i < detections.size(); i++) {
if (frame.rows - detections[i].y < 200)
continue;
rectangle(frame, detections[i], Scalar(0, 0, 255), 4);
}
imshow(“LP HOG Detection”, frame);
if (detections.size() > 0) {
char szFileName[100] = { 0 };
sprintf_s(szFileName, “%03d.jpg”, nFrmIdx++);
imwrite(szFileName, frame);
}
waitKey(40);
}
}
int mian()
{
TestSVMModel();
}
注:
①.//*…*之间的是copy代码时要修改的地方
②.void TrainSVMModel()求出支持向量系数,保存在文件中,之后可以直接复制其中数值以调用
2.获取LBP/MB-LBP特征函数
//使用函数模板,保证函数对所有类型图像都适用
//_tp参数可以是uchar,float等
template
//原始LBP特征
void getOriginLBPFeature(InputArray _src, OutputArray _dst)
{
Mat src = _src.getMat();
Mat srcExtented;
//对图像边界进行扩充,边界像素采用复制的形式
copyMakeBorder(src, srcExtented, 1, 1, 1, 1, BORDER_REPLICATE);
//输出图像与原图像大小相同
_dst.create(src.rows, src.cols, CV_8UC1);
Mat dst = _dst.getMat();
dst.setTo(0);
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
//中心像素的值
_tp center = srcExtented.at<_tp>(i + 1, j + 1);
unsigned char lbpCode = 0; //LBP编码值
lbpCode |= (srcExtented.at<_tp>(i, j) > center) << 7; //左上角
lbpCode |= (srcExtented.at<_tp>(i, j + 1) > center) << 6; //上边
lbpCode |= (srcExtented.at<_tp>(i, j + 2) > center) << 5; //右上角
lbpCode |= (srcExtented.at<_tp>(i + 1, j + 2) > center) << 4; //右边
lbpCode |= (srcExtented.at<_tp>(i + 2, j + 2) > center) << 3; //右下角
lbpCode |= (srcExtented.at<_tp>(i + 2, j + 1) > center) << 2; //下边
lbpCode |= (srcExtented.at<_tp>(i + 2, j) > center) << 1; //左下角
lbpCode |= (srcExtented.at<_tp>(i + 1, j) > center) << 0; //左边
dst.at
}
}
}
//MB-LBP特征
void getMultiScaleBlockLBPFeature(InputArray _src, OutputArray _dst, int scale)
{
Mat src = _src.getMat();
int cellSize = scale / 3;
int offset = cellSize / 2;
Mat srcExtented;
//图像扩大一圈
copyMakeBorder(src, srcExtented, offset, offset, offset, offset, BORDER_REFLECT);
//以当前点为中心,计算每个cell的像素均值
Mat cellImage(src.rows, src.cols, CV_8UC1);
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
int temp = 0;
for (int m = -offset; m < offset + 1; m++) {
for (int n = -offset; n < offset + 1; n++) {
temp += srcExtented.at
}
}
temp /= (cellSize * cellSize);
cellImage.at
}
}
getOriginLBPFeature
}
3.cascade级联分类器使用
//级联分类器实现人脸检测
void DetectFaces()
{
//创建一个级联分类器对象,并加载分类器文件
//CascadeClassifier faceDetector(“haarcascade_frontalface_alt2.xml”);
CascadeClassifier faceDetector(“cascade.xml”);
if (faceDetector.empty()) {
return;
}
VideoCapture cap(0); //打开USB摄像头
if (!cap.isOpened()) {
return;
}
Mat frame;
while (true) {
cap >> frame; //从摄像头获取一帧图像
if (frame.empty())
break;
std::vector<cv::Rect> objects;
//使用级联分类器检测人脸
faceDetector.detectMultiScale(frame, objects);
//对人脸图像进行标记
for (int i = 0; i < objects.size(); i++) {
static int nIdx = 0;
char szFileName[100] = { 0 };
sprintf_s(szFileName, “detectedHeadShoulder/%03d.jpg”, nIdx++);
//sprintf_s(szFileName, “DetetecdFaces/%03d.jpg”, nIdx++);
cv::rectangle(frame, objects[i], Scalar(0, 0, 255), 4);
imwrite(szFileName, frame);
}
imshow(“人脸检测结果”, frame); //显示人脸检测结果
if (waitKey(25) == 27) //暂停25ms,如果按ESC键则退出
break;
}
cap.release(); //释放摄像头对象
return;
}
注:分类器文件生成使用opencv_traincacade.exe;创建正样本.vec文件使用opencv_creatsamples.exe;可视化过程使用opencv_visualisation.exe
本文转自 https://blog.csdn.net/qq_32971095/article/details/131609797,如有侵权,请联系删除。