-
1.图片读取CV_EXPORTS_W Mat imread( const String& filename, int flags = IMREAD_COLOR ); enum ImreadModes { IMREAD_UNCHANGED = -1, //!< If set, return the loaded image as is (with alpha channel, otherwise it gets cropped). Ignore EXIF orientation. IMREAD_GRAYSCALE = 0, //!< If set, always convert image to the single channel grayscale image (codec internal conversion). IMREAD_COLOR = 1, //!< If set, always convert image to the 3 channel BGR color image. IMREAD_ANYDEPTH = 2, //!< If set, return 16-bit/32-bit image when the input has the corresponding depth, otherwise convert it to 8-bit. IMREAD_ANYCOLOR = 4, //!< If set, the image is read in any possible color format. IMREAD_LOAD_GDAL = 8, //!< If set, use the gdal driver for loading the image. IMREAD_REDUCED_GRAYSCALE_2 = 16, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/2. IMREAD_REDUCED_COLOR_2 = 17, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/2. IMREAD_REDUCED_GRAYSCALE_4 = 32, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/4. IMREAD_REDUCED_COLOR_4 = 33, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/4. IMREAD_REDUCED_GRAYSCALE_8 = 64, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/8. IMREAD_REDUCED_COLOR_8 = 65, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/8. IMREAD_IGNORE_ORIENTATION = 128 //!< If set, do not rotate the image according to EXIF's orientation flag. };2.创建窗口CV_EXPORTS_W void namedWindow(const String& winname, int flags = WINDOW_AUTOSIZE);参数如下参数含义winname(window name)窗体名3.图片显示CV_EXPORTS_W void imshow(const String& winname, InputArray mat);参数如下参数含义winname(window name)窗体名mat输入的欲显示的图片若窗体未创建,会自动进行创建CV_EXPORTS_W int waitKey(int delay = 0);控制图片的展示时间,如设置delay=0,则表示一直展示,按SPACE停止展示如设置delay不为0,则表示停留delay毫秒4.图片保存CV_EXPORTS_W bool imwrite( const String& filename, InputArray img, const std::vector<int>& params = std::vector<int>());参数如下参数含义filename保存的文件名img(image)要保存的图片5.视频输入输出CV_WRAP explicit VideoCapture::VideoCapture(const String& filename, int apiPreference = CAP_ANY); CV_WRAP explicit VideoCapture::VideoCapture(const String& filename, int apiPreference, const std::vector<int>& params); CV_WRAP explicit VideoCapture::VideoCapture(int index, int apiPreference = CAP_ANY); CV_WRAP explicit VideoCapture::VideoCapture(int index, int apiPreference, const std::vector<int>& params); CV_WRAP VideoWriter::VideoWriter(const String& filename, int fourcc, double fps,Size frameSize, bool isColor = true); CV_WRAP VideoWriter::VideoWriter(const String& filename, int fourcc, double fps, const Size& frameSize,const std::vector<int>& params); CV_WRAP VideoWriter::VideoWriter(const String& filename, int apiPreference, int fourcc, double fps,const Size& frameSize, const std::vector<int>& params); //fps:帧率 //frameSize:输出视频中每一帧的尺寸5.1 filename影片档案名称(例如video.avi)图片序列(例如img_%02d.jpg,将读取像这样的样本img_00.jpg, img_01.jpg, img_02.jpg, …)视频流的网址(例如protocol://host:port/script_name?script_params|auth)。请注意,每个视频流或IP摄像机源均具有其自己的URL方案。请参考源流的文档以了解正确的URL。5.2 index要打开的视频捕获设备的ID。要使用默认后端打开默认摄像头,只需传递0。当apiPreference为CAP_ANY时,使用camera_id + domain_offset(CAP_ *)向后兼容有效。5.3 fourcc用于编码视频文件的编码器,通过VideoWriter::fourcc函数获得CV_WRAP static int fourcc(char c1, char c2, char c3, char c4);参数如下代码含义VideoWriter::fourcc('P','I','M','1')MPEG-1编码,输出文件拓展名aviVideoWriter::fourcc('X','V','I','D')MPEG-4编码,输出文件拓展名aviVideoWriter::fourcc('M','P','4','V')旧MPEG-4编码,输出文件拓展名aviVideoWriter::fourcc('I','4','2','0')YUV编码,输出文件拓展名aviVideoWriter::fourcc('X','2','6','4')MPEG-4编码,输出文件拓展名mp4VideoWriter::fourcc('T','H','E','O')ogg vorbis编码,输出文件拓展名ogvVideoWriter::fourcc('F',L','V','1')flash video编码,输出文件拓展名flv5.4 apiPreference(not important)首选使用的Capture API后端。如果有多个可用的读取器实现,则可以用于实施特定的读取器实现。设置读取的摄像头编号,默认CAP_ANY=0,自动检测摄像头。多个摄像头时,使用索引0,1,2,…进行编号调用摄像头。 apiPreference = -1时单独出现窗口,选取相应编号摄像头。5.5 演示VideoCapture video("demo.mp4"); Mat fps; video.read(fps); VideoWriter video_out("demo_out.avi",VideoWriter::fourcc('P','I','M','1'),30,fps.size()); while (1){ Mat fps; video>>fps; //video.read(fps); fps>>video_out; //video_out.write(fps); imshow("video",fps); waitKey(10);//控制帧率 }6.通道分离与合并6.1 分离API(一)CV_EXPORTS void split(const Mat& src, Mat* mvbegin);参数如下参数含义src(source)输入图像mvbegin(mat vector begin)分离后的Mat数组的地址API(二)CV_EXPORTS_W void split(InputArray m, OutputArrayOfArrays mv);参数如下参数含义m(mat)输入图像mv(mat vector)分离后的的Mat数组,可以使用STL容器vector。6.2 合并API(一)CV_EXPORTS void merge(const Mat* mv, size_t count, OutputArray dst);参数如下参数含义mv(mat vector)欲合并的图像数组的地址count欲合并的图像的个数dst(destination)输出图片API(二)CV_EXPORTS_W void merge(InputArrayOfArrays mv, OutputArray dst);参数如下参数含义mv(mat vector)欲合并的图像数组,可以使用STL容器vector。dst(destination)输出图片7.图片色彩模式转换7.1 APICV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );参数如下参数含义src(source)源图像dst(destination)输出图片code转换码7.2 转换类型和转换码RGB和BGR(opencv默认的彩色图像的颜色空间是BGR)颜色空间的转换cv::COLOR_BGR2RGBcv::COLOR_RGB2BGRcv::COLOR_RGBA2BGRAcv::COLOR_BGRA2RGBA向RGB和BGR图像中增添alpha通道cv::COLOR_RGB2RGBAcv::COLOR_BGR2BGRA从RGB和BGR图像中去除alpha通道cv::COLOR_RGBA2RGBcv::COLOR_BGRA2BGR从RBG和BGR颜色空间转换到灰度空间cv::COLOR_RGB2GRAYcv::COLOR_BGR2GRAYcv::COLOR_RGBA2GRAYcv::COLOR_BGRA2GRAY从灰度空间转换到RGB和BGR颜色空间cv::COLOR_GRAY2RGBcv::COLOR_GRAY2BGRcv::COLOR_GRAY2RGBAcv::COLOR_GRAY2BGRARGB和BGR颜色空间与BGR565颜色空间之间的转换cv::COLOR_RGB2BGR565cv::COLOR_BGR2BGR565cv::COLOR_BGR5652RGBcv::COLOR_BGR5652BGRcv::COLOR_RGBA2BGR565cv::COLOR_BGRA2BGR565cv::COLOR_BGR5652RGBAcv::COLOR_BGR5652BGRA灰度空间与BGR565之间的转换cv::COLOR_GRAY2BGR555cv::COLOR_BGR5552GRAYRGB和BGR颜色空间与CIE XYZ之间的转换cv::COLOR_RGB2XYZcv::COLOR_BGR2XYZcv::COLOR_XYZ2RGBcv::COLOR_XYZ2BGRRGB和BGR颜色空间与uma色度(YCrCb空间)之间的转换cv::COLOR_RGB2YCrCbcv::COLOR_BGR2YCrCbcv::COLOR_YCrCb2RGBcv::COLOR_YCrCb2BGRRGB和BGR颜色空间与HSV颜色空间之间的相互转换cv::COLOR_RGB2HSVcv::COLOR_BGR2HSVcv::COLOR_HSV2RGBcv::COLOR_HSV2BGRRGB和BGR颜色空间与HLS颜色空间之间的相互转换cv::COLOR_RGB2HLScv::COLOR_BGR2HLScv::COLOR_HLS2RGBcv::COLOR_HLS2BGRRGB和BGR颜色空间与CIE Lab颜色空间之间的相互转换cv::COLOR_RGB2Labcv::COLOR_BGR2Labcv::COLOR_Lab2RGBcv::COLOR_Lab2BGRRGB和BGR颜色空间与CIE Luv颜色空间之间的相互转换cv::COLOR_RGB2Luvcv::COLOR_BGR2Luvcv::COLOR_Luv2RGBcv::COLOR_Luv2BGRBayer格式(raw data)向RGB或BGR颜色空间的转换cv::COLOR_BayerBG2RGBcv::COLOR_BayerGB2RGBcv::COLOR_BayerRG2RGBcv::COLOR_BayerGR2RGBcv::COLOR_BayerBG2BGRcv::COLOR_BayerGB2BGRcv::COLOR_BayerRG2BGRcv::COLOR_BayerGR2BGR8.改变图片的对比度和亮度8.1 概述Mat.ptr(i,j)=Mat.ptr(i,j)*a+ba:控制对比度增益b:控制亮度增益8.2 手动(使用saturate_cast函数确保输出值不溢出范围)Mat xuenai = imread("xuenai.jpg"); imshow("xuenai", xuenai); for(int i=0;i<xuenai.rows;i++){ for(int j=0;j<xuenai.cols;j++){ for(int k=0;k<xuenai.channels();k++) { xuenai.at<Vec3b>(i, j)[k] = saturate_cast<uchar>(xuenai.at<Vec3b>(i, j)[k] * 1.2 + 30); } } } imshow("xuenai_convertTo",xuenai); waitKey();8.3 APIvoid Mat::convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;参数如下参数含义m(mat)输出图片rtype(result type)输出图片的深度,-1表示与原图一致alpha对应系数beta对应常数不能进行原地运算8.4 效果Mat xuenai = imread("xuenai.jpg"); imshow("xuenai", xuenai); xuenai.convertTo(xuenai,-1,1.2,30); imshow("xuenai_convertTo",xuenai); waitKey();可以看到效果是一样的9.图片混合CV_EXPORTS_W void addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype = -1);参数如下参数含义src(source1)输入图片1alphasrc1的权重src2(source2)输入图片2betasrc2的权重gamma额外的增量dst(destination)输出图片dtype(destination type)输出图片的数据类型,-1表示与输入图片一致10.图片尺寸调整CV_EXPORTS_W void resize( InputArray src, OutputArray dst, Size dsize, double fx = 0, double fy = 0, int interpolation = INTER_LINEAR );参数如下参数含义src(source)输入图片dsize(destination size)输出图片的尺寸fxx方向(width方向)的缩放比例,如果它是0,那么它就会按照(double)dsize.width/src.cols来计算fyy方向(height方向)的缩放比例,如果它是0,那么它就会按照(double)dsize.height/src.rows来计算interpolation插值算法的选择10.1 插值算法(not important)enum InterpolationFlags{ /** nearest neighbor interpolation */ INTER_NEAREST = 0, /** bilinear interpolation */ INTER_LINEAR = 1, /** bicubic interpolation */ INTER_CUBIC = 2, /** resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire'-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method. */ INTER_AREA = 3, /** Lanczos interpolation over 8x8 neighborhood */ INTER_LANCZOS4 = 4, /** Bit exact bilinear interpolation */ INTER_LINEAR_EXACT = 5, /** Bit exact nearest neighbor interpolation. This will produce same results as the nearest neighbor method in PIL, scikit-image or Matlab. */ INTER_NEAREST_EXACT = 6, /** mask for interpolation codes */ INTER_MAX = 7, /** flag, fills all of the destination image pixels. If some of them correspond to outliers in the source image, they are set to zero */ WARP_FILL_OUTLIERS = 8, /** flag, inverse transformation For example, #linearPolar or #logPolar transforms: - flag is __not__ set: \f$dst( \rho , \phi ) = src(x,y)\f$ - flag is set: \f$dst(x,y) = src( \rho , \phi )\f$ */ WARP_INVERSE_MAP = 16 };10.2 注意事项使用注意事项:dsize和fx/fy不能同时为0指定dsize的值,让fx和fy空置直接使用默认值。让dsize为0,指定好fx和fy的值,比如fx=fy=0.5,那么就相当于把原图两个方向缩小一倍。11.图像金字塔(常用于神经网络的池化层,对图像进行成倍的放大或缩小)//缩小一倍 CV_EXPORTS_W void pyrDown( InputArray src, OutputArray dst, const Size& dstsize = Size(), int borderType = BORDER_DEFAULT ); //放大一倍 CV_EXPORTS_W void pyrUp( InputArray src, OutputArray dst, const Size& dstsize = Size(), int borderType = BORDER_DEFAULT );参数如下参数含义src(source)输入图片dst(destination)输出图片dstsize(destination size)输出图片的尺寸,默认自动调整borderType边界填充方式,默认为黑边。如果没有设置dstsize,则不会出现黑边,因为已经进行了自动调整12.二值化(对灰度图)CV_EXPORTS_W double threshold( InputArray src, OutputArray dst, double thresh, double maxval, int type );参数如下参数含义src(source)输入图片dst(destination)输出图片thresh(threshold)阈值maxval(max value)最大值type阈值类型12.1 阈值类型enum ThresholdTypes { THRESH_BINARY = 0, //!< \f[\texttt{dst} (x,y) = \fork{\texttt{maxval}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\f] THRESH_BINARY_INV = 1, //!< \f[\texttt{dst} (x,y) = \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{maxval}}{otherwise}\f] THRESH_TRUNC = 2, //!< \f[\texttt{dst} (x,y) = \fork{\texttt{threshold}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\f] THRESH_TOZERO = 3, //!< \f[\texttt{dst} (x,y) = \fork{\texttt{src}(x,y)}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\f] THRESH_TOZERO_INV = 4, //!< \f[\texttt{dst} (x,y) = \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\f] THRESH_MASK = 7, THRESH_OTSU = 8, //!< flag, use Otsu algorithm to choose the optimal threshold value THRESH_TRIANGLE = 16 //!< flag, use Triangle algorithm to choose the optimal threshold value };13.图片裁剪13.1 方式一inline Mat Mat::operator()( const Rect& roi ) const { return Mat(*this, roi); }以下为实例Mat xuenai = imread("xuenai.jpg"); resize(xuenai,xuenai,Size(1000,1000)); imshow("xuenai", xuenai); Mat tuanzi(xuenai,(Rect(0,0,500,1000))); imshow("tuanzi",tuanzi); waitKey();13.2 方式二Mat::Mat(const Mat& m, const Rect& roi);以下为实例Mat xuenai = imread("xuenai.jpg"); resize(xuenai,xuenai,Size(1000,1000)); imshow("xuenai", xuenai); Mat tuanzi(xuenai(Rect(0,0,500,1000))); imshow("tuanzi",tuanzi); waitKey();13.3 Rect类构造template<typename _Tp> inline Rect_<_Tp>::Rect_(_Tp _x, _Tp _y, _Tp _width, _Tp _height) : x(_x), y(_y), width(_width), height(_height) {} template<typename _Tp> inline Rect_<_Tp>::Rect_(const Point_<_Tp>& org, const Size_<_Tp>& sz) : x(org.x), y(org.y), width(sz.width), height(sz.height) {} template<typename _Tp> inline Rect_<_Tp>::Rect_(const Point_<_Tp>& pt1, const Point_<_Tp>& pt2) { x = std::min(pt1.x, pt2.x); y = std::min(pt1.y, pt2.y); width = std::max(pt1.x, pt2.x) - x; height = std::max(pt1.y, pt2.y) - y; }14.基本变换14.1 翻转CV_EXPORTS_W void flip(InputArray src, OutputArray dst, int flipCode);参数如下参数含义src(source)输入图片dst(destination)输出图片flipCode翻转类型,参见下表flipCode 可选值如下flipCode 可选值含义flipcode==0上下翻转flipcod>0左右翻转flipcode<0上下加左右翻转,等价于旋转180°效果Mat xuenai = imread("xuenai.jpg"); imshow("xuenai", xuenai); Mat xuenai_flip(xuenai.size(), xuenai.type()); flip(xuenai, xuenai_flip, 0); imshow("xuenai_flip", xuenai_flip); waitKet();14.2 90°旋转CV_EXPORTS_W void rotate(InputArray src, OutputArray dst, int rotateCode); enum RotateFlags { ROTATE_90_CLOCKWISE = 0, //!<Rotate 90 degrees clockwise ROTATE_180 = 1, //!<Rotate 180 degrees clockwise ROTATE_90_COUNTERCLOCKWISE = 2, //!<Rotate 270 degrees clockwise };参数如下参数含义src(source)输入图片dst(destination)输出图片rotateCode旋转类型效果Mat xuenai = imread("xuenai.jpg"); imshow("xuenai", xuenai); Mat xuenai_rotate(xuenai.size(), xuenai.type()); rotate(xuenai, xuenai_rotate, ROTATE_180); imshow("xuenai_rotate", xuenai_rotate); waitKet();15.仿射变换15.1 APICV_EXPORTS_W void warpAffine( InputArray src, OutputArray dst, InputArray M, Size dsize, int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar());参数如下参数含义src(source)输入图片dst(destination)输出图片M变换矩阵dsize(destination size)输出图片的尺寸,若不对输出图片的尺寸进行调整,那么很可能会出现黑边flags插值算法borderMode边界外推法borderValue填充边界的值15.2 平移只需将变换矩阵M设置成如下形式:float delta_x=200,delta_y=200; float M_values[]={1,0,delta_x, 0,1,delta_y}; Mat M(Size(3,2),CV_32F,M_values);delta_x:x方向上的偏移量delta_y:y方向上的偏移量M_values:必须是浮点类型的数组对象M:必须是CV_32F,不能用逗号式分隔创建效果Mat xuenai = imread("xuenai.jpg"); imshow("xuenai",xuenai); double M_values[]={1,0,200, 0,1,200}; Mat M(Size(3,2), CV_64F,M_values); Mat xuenai_shift(xuenai.size(),xuenai.type()); warpAffine(xuenai,xuenai_shift,M,xuenai.size()); imshow("xuenai_shift",xuenai_shift); waitKet();15.3 任意角度旋转获得变换矩阵Minline Mat getRotationMatrix2D(Point2f center, double angle, double scale) { return Mat(getRotationMatrix2D_(center, angle, scale), true); }参数如下参数含义center旋转中心点的坐标angle逆时针偏角scale生成图与原图之比效果Mat xuenai = imread("xuenai.jpg"); imshow("xuenai", xuenai); Mat M= getRotationMatrix2D(Point2f(xuenai.cols/2,xuenai.rows/2),45,1); Mat xuenai_rotate(xuenai.size(),xuenai.type()); warpAffine(xuenai,xuenai_rotate,M,xuenai.size()); imshow("xuenai_flip",xuenai_rotate);15.4 仿射(不破坏几何关系)获得变换矩阵MCV_EXPORTS Mat getAffineTransform( const Point2f src[], const Point2f dst[] );参数如下参数含义src[](source[])输入图片的坐标点集,含三个坐标点dst[](destination[])三个坐标点变换的目标位置三个点要一一对应16.透射变换(破坏几何关系)16.1 API进行变换CV_EXPORTS_W void warpPerspective( InputArray src, OutputArray dst, InputArray M, Size dsize, int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar());参数如下参数含义src(source)输入图片dst(destination)输出图片M变换矩阵dsize(destination size)输出图片的尺寸,若不对输出图片的尺寸进行调整,那么很可能会出现黑边flags插值算法borderMode边界外推法borderValue填充边界的值已知变换后图片,逆推变换矩阵MCV_EXPORTS_W Mat getPerspectiveTransform(InputArray src, InputArray dst, int solveMethod = DECOMP_LU);参数如下参数含义src(source)输入图片dst(destination)输出图片获得变换矩阵MCV_EXPORTS Mat getPerspectiveTransform(const Point2f src[], const Point2f dst[], int solveMethod = DECOMP_LU);参数如下参数含义src[](source[])输入图片的坐标点集,含四个坐标点dst[](destination[])三个坐标点变换的目标位置四个点要一一对应16.2 效果Mat origin = imread("origin.jpg"); Point2f point2F_origin[4]={Point2f (405,105),Point2f(2469,217),Point2f(2573,3489),Point2f(349,3547)}; Point2f point2F_tansform[4]={Point2f (0,0),Point2f(2500,0),Point2f(2500,3500),Point2f(0,3500)}; Mat M=getPerspectiveTransform(point2F_origin,point2F_tansform); Mat transfrom(origin.size(),origin.type()); warpPerspective(origin,transfrom,M,Size(2500,3500)); resize(origin,origin,Size(500,700)); resize(transfrom,transfrom,Size(500,700)); imshow("origin",origin); imshow("transform",transfrom);
-
1.全零矩阵CV_NODISCARD_STD static MatExpr Mat::zeros(int rows, int cols, int type); CV_NODISCARD_STD static MatExpr Mat::zeros(Size size, int type); CV_NODISCARD_STD static MatExpr Mat::zeros(int ndims, const int* sz, int type); //not recommended参数如下参数含义rows行数cols列数type数据类型(CV_16F)sizeSize(宽(列数),高(行数))Size与Mat中的成员函数.size()的返回值,有相同的数据类型,是[宽*高]。Mat中的成员变量.size,与以上二者不同,是 rows*cols2.全一矩阵CV_NODISCARD_STD static MatExpr Mat::ones(int rows, int cols, int type); CV_NODISCARD_STD static MatExpr Mat::ones(Size size, int type); CV_NODISCARD_STD static MatExpr Mat::ones(int ndims, const int* sz, int type); //not recommended参数如下参数含义rows行数cols列数type数据类型(CV_16F)sizeSize(宽(列数),高(行数))3.单位矩阵CV_NODISCARD_STD static MatExpr Mat::eye(int rows, int cols, int type); CV_NODISCARD_STD static MatExpr Mat::eye(Size size, int type);参数如下参数含义rows行数cols列数type数据类型(CV_16F)sizeSize(宽(列数),高(行数))4.矩阵转置MatExpr Mat::t() const;5.求逆矩阵MatExpr Mat::inv(int method=DECOMP_LU) const;6.逗号式分隔创建矩阵常用于自定义卷积核template<typename _Tp> inline Mat_<_Tp>::Mat_(int _rows, int _cols) : Mat(_rows, _cols, traits::Type<_Tp>::value) { } template<typename _Tp> inline Mat_<_Tp>::Mat_(int _rows, int _cols, const _Tp& value) : Mat(_rows, _cols, traits::Type<_Tp>::value) { *this = value; } template<typename _Tp> inline Mat_<_Tp>::Mat_(Size _sz) : Mat(_sz.height, _sz.width, traits::Type<_Tp>::value) {} template<typename _Tp> inline Mat_<_Tp>::Mat_(Size _sz, const _Tp& value) : Mat(_sz.height, _sz.width, traits::Type<_Tp>::value) { *this = value; }以下为使用实例,注意括号的位置Mat a=(Mat_<int>(2,2)<<1,2,3,4); Mat b=(Mat_<double>(Size(2,2))<<1,2,3,4);注意 :给出的数据类型必须是基本数据类型,如int,double。不能是CV_16F等。7.矩阵定义(只列出常用的)Mat::Mat() CV_NOEXCEPT; Mat::Mat(int rows, int cols, int type); Mat::Mat(Size size, int type); Mat::Mat(int rows, int cols, int type, const Scalar& s); Mat::Mat(Size size, int type, const Scalar& s); Mat::Mat(const std::vector<int>& sizes, int type); Mat::Mat(const std::vector<int>& sizes, int type, const Scalar& s); Mat::Mat(const Mat& m); void Mat::create(int rows, int cols, int type); void Mat::create(Size size, int type); void Mat::create(const std::vector<int>& sizes, int type);参数如下参数含义rows行数cols列数type数据类型(CV_16F)sizeSize(宽(列数),高(行数))7.1 数据类型ScalarScalar(gray)Scalar(blue,green,red)8.通过ptr与at函数遍历矩阵8.1 Vec类型typedef Vec<uchar, 2> Vec2b; typedef Vec<uchar, 3> Vec3b; typedef Vec<uchar, 4> Vec4b; typedef Vec<short, 2> Vec2s; typedef Vec<short, 3> Vec3s; typedef Vec<short, 4> Vec4s; typedef Vec<ushort, 2> Vec2w; typedef Vec<ushort, 3> Vec3w; typedef Vec<ushort, 4> Vec4w; typedef Vec<int, 2> Vec2i; typedef Vec<int, 3> Vec3i; typedef Vec<int, 4> Vec4i; typedef Vec<int, 6> Vec6i; typedef Vec<int, 8> Vec8i; typedef Vec<float, 2> Vec2f; typedef Vec<float, 3> Vec3f; typedef Vec<float, 4> Vec4f; typedef Vec<float, 6> Vec6f; typedef Vec<double, 2> Vec2d; typedef Vec<double, 3> Vec3d; typedef Vec<double, 4> Vec4d; typedef Vec<double, 6> Vec6d;以下为实例Mat a(Size(2560,1440),CV_8UC3); for(int i=0;i<a.rows;i++){ for(int j=0;j<a.cols;j++){ a.ptr(i,j)[0]=0; a.ptr(i,j)[1]=0; a.ptr(i,j)[2]=255; } } for(int i=0;i<a.rows;i++){ for(int j=0;j<a.cols;j++){ a.ptr<Vec3b>(i,j)[0]=0; a.ptr<Vec3b>(i,j)[1]=0; a.ptr<Vec3b>(i,j)[2]=255; } } for(int i=0;i<a.rows;i++){ for(int j=0;j<a.cols;j++){ a.at<Vec3b>(i,j)[0]=0; a.at<Vec3b>(i,j)[1]=0; a.at<Vec3b>(i,j)[2]=255; } }用ptr访问可以不加Vec类型,ptr访问是最快的用at访问必须加Vec类型,at访问比ptr略微慢一些9.通过迭代器遍历矩阵(easy but very very slow)Mat a(Size(2560,1440),CV_8UC3); for(auto iter=a.begin<Vec3b>();iter!=a.end<Vec3b>();iter++){ iter[0]=255; iter[1]=0; iter[2]=0; }
-
业务里需要对视频里出现的人的面部打马赛克,但是用了几个模型效果都不太理想(OpenCV自带的、CNN模型,开源的面部检测模式都测试了)。 正脸,一般的侧脸可以完美识别。 但是低头、半侧脸这种就识别不到,华为云有没有人脸的模型SDK或者在线API,我去试试? 或者有没有推荐的开源面部模型和算法推荐推荐。
-
1、GOTURN TrackerGoturn是一种基于深度学习的对象跟踪算法。最初的实现是在Caffe,目前已经移植到OpenCV跟踪API。Goturn是一种基于深度学习的跟踪算法,是回归网络的一般对象跟踪的缩写。大多数跟踪算法都是在线训练的。换句话说,跟踪算法学习运行时跟踪的对象的外观。因此,许多实时追踪器依赖于在线学习算法,这通常比基于深度学习的解决方案快得多。Goturn改变了我们将深度学习应用于跟踪问题的方式,通过离线方式学习对象的运动。Goturn模型接受了数千个视频序列的训练,不需要在运行时执行任何学习。与其他基于深度学习的追踪器相比,Goturn速度更快。它在caffe的gpu上以100fps的速度运行,在opencv cpu上以20fps的速度运行。尽管跟踪器是通用的,但理论上,通过将传输集与特定类型的对象进行偏移,可以在特定对象(例如行人)上获得更好的结果。局限性:神经网络体现的优势,往往就是它的劣势。神经网络依赖于训练集中样本所能代表的场景种类,对于不存在的场景,就会存在问题。如在实际使用中,希望跟踪手掌,把手掌移到脸上时,跟踪器锁定在脸上,并不会在手掌上。而跟踪脸,并用手堵遮住脸,但追踪器能够跟踪通过遮挡的脸,这说明训练集存在大量的手掌遮脸的场景。2、MIL Tracker 密尔跟踪器这个跟踪器的概念与上面描述的BOOSTING Tracker相似。最大的区别是,它不只是将对象的当前位置视为一个正示例,还会在当前位置周围的一个小邻域中查找,以生成几个潜在的正示例。你可能认为这是一个坏主意,因为在这些“积极”的例子中,大多数的对象都不是中心。这就是多实例学习(mil)来拯救的地方。在mil中,您不指定正负示例,而是指定正负“bags”。正面的图像收集并非都是正面的例子。一个正面的bag包含了以对象当前位置为中心的区域,以及它周围的一个小邻域中的区域。即使被跟踪对象的当前位置不准确,当来自当前位置附近的样本放入正袋中时,很有可能该袋至少包含一个图像,并且该对象很好地居中。优点:性能不错。它不会像助推跟踪器那样漂移,并且在部分遮挡下也能正常工作。缺点:失败率较高。3、BOOSTING Tracker助推跟踪器该跟踪器基于ADaboost的在线版本,ADaboost是基于HAAR级联的人脸检测器内部使用的算法。这个分类器需要在运行时用对象的正负示例进行培训。以用户(或其他对象检测算法)提供的初始边界框为对象的正例,边界框外的许多图像部位作为背景。给定一个新的帧,分类器在前一个位置附近的每个像素上运行,并记录分类器的得分。对象的新位置是得分最大的位置。缺点:速度较慢,并且表现不好,跟踪失败后,不能及时呈现错误报告。4、KCF跟踪器 KCF代表kernelized correlation filters。这个追踪器建立在前两个追踪器中提出的想法之上。该跟踪器利用了这样一个事实:在MIL跟踪器中使用的多个正样本具有较大的重叠区域。这些重叠的数据导致了一些很好的数学特性,这些特性被跟踪器利用,从而使跟踪速度更快、更准确。优点:准确度和速度都比MIL跟踪器好,它报告跟踪故障比BOOSTING和MIL这两个追踪算法好。缺点:无法从完全遮挡中恢复。5、TLD跟踪器 TLD代表跟踪、学习和检测。顾名思义,这个跟踪器将长期跟踪任务分解为三个组件(短期)跟踪、学习和检测。在作者的论文中,“跟踪器跟踪对象从一帧到另一帧。探测器定位到目前为止观察到的所有外观,并在必要时纠正跟踪器。学习估计检测器的错误并更新它以避免将来出现这些错误。”这个跟踪器的输出有点跳跃。例如,如果您正在跟踪一个行人,并且场景中还有其他行人,则此跟踪器有时可以临时跟踪一个与您要跟踪的行人不同的行人。在积极的一面,这条轨迹似乎是在更大的比例、运动和遮挡上跟踪一个对象。如果你有一个隐藏在另一个物体后面的视频序列,这个跟踪器可能是个不错的选择。 优点:在多帧遮挡下效果最好。此外,跟踪最佳的超比例变化。 缺点:很多误报使它几乎不可用。6、MEDIANFLOW跟踪器在内部,这个跟踪器可以实时地跟踪物体的前后方向,并测量这两个轨迹之间的差异。最大限度地减少这种向前向后的误差,使他们能够可靠地检测跟踪故障,并在视频序列中选择可靠的轨迹。在测试中发现这个跟踪器在运动可预测和对象小的情况下工作得最好。与其他跟踪者不同的是,即使跟踪明显失败,跟踪者也知道跟踪何时失败。 优点:出色的跟踪故障报告。当运动是可预测的并且没有遮挡时,效果非常好。 缺点:大幅度运动跟踪,模型会失效。7、MOSSE 莫斯跟踪器最小平方误差输出和(mosse)使用自适应相关进行对象跟踪,当使用单帧进行初始化时,可产生稳定的相关滤波器。Mosse跟踪器对光照、比例、姿势和非刚性变形的变化具有鲁棒性。对于遮挡,跟踪器能够在对象重新出现时暂停并恢复到停止的位置。优点:速度快。缺点:准确率不如CSRT和KCF高。8、CSRT跟踪器在具有信道和空间可靠性的鉴别相关滤波器(DCF-CSR)中,我们使用空间可靠性图从帧中调整滤波器支持到所选区域的一部分进行跟踪。这样可以确保选定区域的放大和定位,并改进对非矩形区域或对象的跟踪。它只使用两个标准功能(HoGs and Colornames)。它也在相对较低的fps(25 fps)下工作,但提供了更高的目标跟踪精度。优点:比KCR精度高。缺点:速度较慢。
-
0. 前言cv::Mat 类是用于保存图像(以及其他矩阵数据)的数据结构,该数据结构是所有 OpenCV 类和函数的核心,这是 OpenCV 库的一个关键元素,用于处理图像和矩阵(从计算和数学的角度来看,图像本质上是一个矩阵),同时 cv::Mat 数据结构结合了优雅的内存管理机制,因此,使用起来十分高效。此数据结构在应用程序开发中广泛使用,因此再进一步学习前我们必须熟悉 cv::Mat 数据结构。1. cv::Mat 数据结构1.1 cv::Mat 简介cv::Mat 数据结构基本上由两部分组成:标头 (header) 和数据块 (data block)。标头包含与矩阵相关的所有信息(尺寸大小、通道数、数据类型等)。我们已经介绍了如何访问包含在 cv::Mat 标头中的一些属性,例如,通过使用 cols 、rows 或 channels 访问矩阵的列数、行数或通道数;数据块包含图像的所有像素值,标头中包含一个指向这个数据块的指针变量,即 data 属性。cv::Mat 数据结构的一个重要特性是内存块仅在明确请求时才被复制,大多数操作只是简单地复制 cv::Mat 标头,这样多个对象本质上同时指向同一个数据块,这种内存管理模型使应用程序更高效,同时可以避免内存泄漏。1.2 cv::Mat 属性接下来,我们通过编写测试程序 (mat.cpp) 来测试 cv::Mat 数据结构的不同属性。1. 首先,声明 opencv 头文件和 c++ i/o 流文件:#include#include#include2. 创建函数来生成新的灰度图像,其所有像素具有相同的默认值:cv::Mat function() {// 创建新图像并返回cv::Mat ima(500, 500, CV_8U, 50);return ima;}默认情况下, cv::Mat 对象在创建时的大小为零,但也可以指定初始大小:cv::Mat image1(240,320,CV_8U,100);1如果指定图像初始大小,就需要指定每个矩阵元素的类型,以上代码使用 CV_8U 类型,对应创建的图像像素为 1 字节 (8 位),字母 U 表示它是无符号的;还可以使用字母 S 声明有符号整数类型。对于彩色图像,需要指定三个通道 (CV_8UC3),也可以声明大小为 16 或 32 的整数(同样可以为有符号或无符号)图像,例如,CV_16SC3 表示 16 位有符号整数类型。除了整数类型,还可以使用 32 位或 64 位浮点数,例如,CV_32F 表示 32 位浮点数。3. 在主函数中,创建六个窗口来显示图像处理结果:cv::namedWindow("Image 1");cv::namedWindow("Image 2");cv::namedWindow("Image 3");cv::namedWindow("Image 4");cv::namedWindow("Image 5");cv::namedWindow("Image");4. 创建多个不同的图像矩阵(具有不同的尺寸、通道和默认值),并等待按键被按下:// 创建一个尺寸为 240x320 的图像,创建时直接定义像素值cv::Mat image1(240,320,CV_8U,100);cv::imshow("Image", image1); // 显示图像cv::waitKey(0);// 当尺寸或数据类型不同时,需要重新分配内存image1.create(200,200,CV_8U);image1 = 200;cv::imshow("Image", image1); // 显示图像cv::waitKey(0);// 创建一个由红色填充的图像,OpenCV 中色彩通道顺序为 BGRcv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));// 也可以使用以下方法定义// cv::Mat image2(cv::Size(320,240),CV_8UC3);// image2= cv::Scalar(0,0,255);cv::imshow("Image", image2); // 显示图像cv::waitKey(0);图像(或矩阵)的每个元素可以由多个值组成(例如,彩色图像具有三个通道,因此每个坐标点都有三个像素值),因此,OpenCV 引入了一种简单的数据结构,用于将像素值传递给函数,即 cv::Scalar 结构体,一般用来保存一个值或者三个值。例如,要创建一个用红色像素初始化的彩色图像:cv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));1同样,灰度图像的初始化也可以通过使用此结构完成 (cv::Scalar(100))。图像尺寸大小通常也需要作为参数传递给函数,我们已经知道 cols 和 rows 属性可用于获取 cv::Mat 实例的维度。尺寸大小信息也可以通过 cv::Size 结构提供,它只包含矩阵的高度和宽度,size() 方法可以获取当前矩阵大小。cv::Size 结构是许多必须指定矩阵大小的方法中常用的格式。例如,使用以下方式创建图像:cv::Mat image2(cv::Size(320,240),CV_8UC3);15. 使用 imread 函数读取图像并将其复制到另一个图像矩阵:// 读取图像cv::Mat image3 = cv::imread("1.png");// 令 image4 和 image1 指向同一个数据块 image3cv::Mat image4(image3);image1 = image3;// image2 和 image5 是 image3 的副本image3.copyTo(image2);cv::Mat image5 = image3.clone();6. 对复制后的图像应用图像转换函数 (cv::flip()),显示创建的所有图像,然后等待按键:// 水平翻转图像cv::flip(image3,image3,1);// 检查图像cv::imshow("Image 3", image3);cv::imshow("Image 1", image1);cv::imshow("Image 2", image2);cv::imshow("Image 4", image4);cv::imshow("Image 5", image5);cv::waitKey(0);7. 使用创建的函数来生成一个新的灰色图像:// 使用 function 函数创建灰度图像cv::Mat gray = function();cv::imshow("Image", gray); // 显示图像cv::waitKey(0);8. 加载一个彩色图像,在加载过程中将其转换为灰度图像,然后,将其值转换为浮点矩阵:// 将图像读取为灰度图像image1= cv::imread("1.png", cv::IMREAD_GRAYSCALE);// 将图像转换为值在 [0, 1] 区间内的浮点图像image1.convertTo(image2,CV_32F,1/255.0,0.0);cv::imshow("Image", image2); // 显示图像1.3 完整代码示例完整代码 (mat.cpp) 如下所示:#include#include#includecv::Mat function() {// 创建新图像并返回cv::Mat ima(500,500,CV_8U,50);return ima;}int main() {// 创建一个尺寸为 240x320 的图像,创建时直接定义像素值cv::Mat image1(240,320,CV_8U,100);cv::imshow("Image", image1); // 显示图像cv::waitKey(0);// 当尺寸或数据类型不同时,需要重新分配内存image1.create(200,200,CV_8U);image1 = 200;cv::imshow("Image", image1); // 显示图像cv::waitKey(0);// 创建一个由红色填充的图像,OpenCV 中色彩通道顺序为 BGRcv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));// 也可以使用以下方法定义// cv::Mat image2(cv::Size(320,240),CV_8UC3);// image2= cv::Scalar(0,0,255);cv::imshow("Image", image2); // 显示图像cv::waitKey(0);// 读取图像cv::Mat image3 = cv::imread("1.png");// 令 image4 和 image1 指向同一个数据块 image3cv::Mat image4(image3);image1 = image3;// image2 和 image5 是 image3 的副本image3.copyTo(image2);cv::Mat image5 = image3.clone();// 水平翻转图像cv::flip(image3,image3,1);// 检查图像cv::imshow("Image 3", image3);cv::imshow("Image 1", image1);cv::imshow("Image 2", image2);cv::imshow("Image 4", image4);cv::imshow("Image 5", image5);cv::waitKey(0);// 使用 function 函数创建灰度图像cv::Mat gray = function();cv::imshow("Image", gray); // 显示图像cv::waitKey(0);// 将图像读取为灰度图像image1= cv::imread("1.png", cv::IMREAD_GRAYSCALE);// 将图像转换为值在 [0, 1] 区间内的浮点图像image1.convertTo(image2,CV_32F,1/255.0,0.0);cv::imshow("Image", image2); // 显示图像// 使用 cv::Matx 创建一个 3x3 的双精度 (double-precision) 矩阵cv::Matx33d matrix(3.0, 2.0, 1.0,2.0, 1.0, 3.0,1.0, 2.0, 3.0);// 使用 cv::Matx 创建一个 3x1 的双精度矩阵 (向量)cv::Matx31d vector(5.0, 1.0, 3.0);// 矩阵相乘cv::Matx31d result = matrix*vector;std::cout << result;cv::waitKey(0);return 0;}编译后,运行此程序查看生成图像(生成的纯色图像未列出):$ g++ mat.cpp -o mat `pkg-config --cflags --libs opencv4`$ ./mat122. 探索 cv::Mat 数据结构2.1 cv::Mat 对象的创建图像的数据块可以使用 create() 方法分配或重新分配。当图像先前已被分配时,它的旧内容首先被释放,出于效率考虑,如果新分配的尺寸大小和类型与现有的尺寸大小和类型匹配,则不会执行新的内存分配:image1.create(200,200,CV_8U);1当没有引用指向给定的 cv::Mat 对象时,分配的内存会自动释放,这样可以避免在 C++ 中经常与动态内存分配相关的常见内存泄漏问题,这是 OpenCV 中的一个关键机制,通过让 cv::Mat 类实现引用计数和浅复制来实现。因此,当一个图像分配给另一个图像时,图像数据(即像素)不会被复制,两个图像都将指向同一个内存块,按值传递或按值返回的图像同样也是如此。保留引用计数,以便仅当对图像的所有引用都被销毁或分配给另一个图像时才会释放内存:cv::Mat image4(image3);image1= image3;应用于以上图像 (image3、image4 和 image1) 之一的任何变换也将影响其他图像;如果希望创建图像内容的深层副本,需要使用 copyTo() 方法,在这种情况下,将在目标图像上调用 create() 方法;另一种生成图像副本的方法是 clone() 方法,它创建一个相同的新图像:image3.copyTo(image2);cv::Mat image5= image3.clone();如果需要将一个图像复制到另一个不一定具有相同数据类型的图像中,则必须使用 convertTo() 方法:image1.convertTo(image2,CV_32F,1/255.0,0.0);1以上代码中,源图像 image3 被复制到浮点图像 image2 中。convertTo() 方法包括两个可选参数——比例因子和偏移量。需要注意的是,两个图像的通道数必须相同。cv::Mat 对象的分配模型还允许我们安全地编写返回图像的函数(或类方法):cv::Mat function() {// 创建新图像并返回cv::Mat ima(500,500,CV_8U,50);return ima;}可以从主函数 main() 中调用这个函数 function():cv::Mat gray= function();1如果我们调用函数 function(),那么变量 gray 将保存由函数 function() 创建的图像,而无需分配额外的内存。事实上,从 funtion() 返回的 cv::Mat 实例只是 ima 图像的浅拷贝。当 ima 局部变量超出范围时,该变量被释放,但由于关联的引用计数器指示其内部图像数据正在被另一个实例(即变量 gray )引用,因此不会释放其内存块。需要注意的是,在创建类实例时,不要返回图像的类属性。接下来,我们介绍一个容易出错的实现示例:class ErrorExample {// 图像属性cv::Mat ima;public:ErrorExample(): ima(240, 320, CV_8U, cv::Scalar(100)){}cv::Mat method() {return ima;}}在以上代码中,如果一个函数调用这个类的方法,它会获得一个图像属性的浅拷贝。如果以后这个副本被修改,类属性也会被修改,这会影响类的后续行为(反之亦然),为了避免这类错误,我们应该返回属性的副本。2.2 OpenCV 输入和输出数组查看 OpenCV 文档,可以看到许多方法和函数都接受 cv::InputArray 类型的参数作为输入。该类型是一个简单的代理类,引入此类型用于概括 OpenCV 中数组的概念,从而避免重复实现具有不同输入参数类型的相同方法或函数的多个版本,这意味着可以通过提供 cv::Mat 对象或其他兼容类型作为参数,该类只是一个接口,所以不应该在代码中显式地声明它。cv::InputArray 也可以使用 std::vector 类构造,这意味着这类对象可以用作 OpenCV 方法和函数的输入。其他兼容类型包括 cv::Scalar 和 cv::Vec;相应的,还存在一个 cv::OutputArray 代理类,用于指定某些方法或函数返回的数组。小结cv::Mat 数据结构是 OpenCV 中最基础也是最核心的数据结构,通过此数据结构构成了 OpenCV 图像处理所用的复杂类和函数。本节中,我们介绍了 cv::Mat 结构的基本用法,包括如何创建空白/非空白图像、修改图像数据类型以及复制图像内容等操作,并且深入探索了创建 cv::Mat 数据结构时的内存分配方式,最后介绍了 OpenCV 输入和输出数组的两个代理类,包括 cv::InputArray 和 cv::OutputArray。————————————————版权声明:本文为CSDN博主「盼小辉丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/LOVEmy134611/article/details/126692582
-
目录: 1.开篇之言 2.简单介绍 3.主要代码 4.结篇之语 ------------------------------------------------------------------------------------------- 1.开篇之言 (不用看这,都是废话) ……由于新型冠状病毒的影响,不能开学,也不能回学校准备比赛。又学校推迟一周在网上开课,无聊中开启我人生中第一篇博客,由于第一次写博客,错误之处多多指教。也以此为第一篇博客来记录自己的学习和加深印象,也当做笔记啦!!!!!! ……半年前的大一暑假协会组织我们留校培训,这是目前最充实的一个暑假,两个月的暑假在家待了4天 5夜,在路上待了两天,其余都是在学校度过余假啦!!!!!! ……在培训二十天的时候,学长给布置任务练手,让做个 捡羽毛球机器人 当时刚学完51,下面准备学stm32,就决定用stm32作为主控来完成任务。 ……当天晚上买了是他们32学习板,我买的是洋桃一号开发板,当时买回来大概十天学了是stm32大部分,然后我们放了一星期假,我提前回来一天开始着手做任务,从OpenMV开始开启下半暑假旅程。 ……当然十天学stm32比较水,后来开学又看了正点原子的视频,正点原子和杜洋老师的stm32区别是有的,个人感觉正点原子比杜洋老师讲的细,不过杜洋老师的学着比正点原子好学点,好上手一些,但是推荐用正点原子比较好,毕竟是32界的大佬,讲的也挺好,资料也多。 2.简单介绍 这里我主要写一点关于OpenMV与stm32通信的,识别羽毛球需要OpenMV,然后把数据转给stm32 1.OpenMV使用Python编程,用的语法较少,简单了解一下即可,OpenMV底层是用C语言写好的函数,用户只要用Python调用就可以啦,我这是通过形状识别的圆形识别来检测羽毛球的位置(只传输在画面中的x,y坐标)。 2.stm32使用串口中断来接受数据。 3.主控是洋桃一号开发板,由于是半年前做的,现在在家没有任何实物,所以里面接线图,实物图几乎都没有,请见谅。 4.另外说句题外话,想学Python的小伙伴们可以考虑风变科技的Python小课,我是前几天看卓大大推荐的8.9元的体验课,于是体验了一下,教学方式采用文字交互式学习,挺有趣,我是今天刚闯完体验课的4关,由于后面因为比赛也没时间,所以打算以后再学吧!!!! 3.主要代码 OpenMV的所有代码,这里识别摄像头画面中最大的圆的中心坐标(x,y) import sensor, image, time, math from pyb import UART import json import ustruct sensor.reset() sensor.set_pixformat(sensor.RGB565) # grayscale is faster sensor.set_framesize(sensor.QQVGA) sensor.skip_frames(time = 2000) clock = time.clock() uart = UART(3,115200) #定义串口3变量 uart.init(115200, bits=8, parity=None, stop=1) # init with given parameters def find_max(circles): #定义寻找球面积最大的函数 max_size=0 for circle in circles: if circle.magnitude() > max_size: max_circle=circle max_size = circle.magnitude() return max_circle def sending_data(cx,cy): #发送函数(我这里没用的,还是留着吧) global uart; #frame=[0x2C,18,cx%0xff,int(cx/0xff),cy%0xff,int(cy/0xff),0x5B]; #data = bytearray(frame) data = ustruct.pack("<bbhhb", 0x2C, 0x12, int(cx), int(cy), 0x5B) uart.write(data); def recive_data(): #接收函数 global uart if uart.any(): tmp_data = uart.readline(); print(tmp_data) while(True): clock.tick() img = sensor.snapshot() circles = img.find_circles(threshold = 2500, x_margin = 10, y_margin = 10, r_margin = 10, r_min = 2, r_max = 100, r_step = 2); cx=0;cy=0; if circles: #如果找到了目标球 max_c = find_max(circles); #在圆周围画一个圆 #img.draw_circle(circle.x(), circle.y(), circle.r(), color = (255, 0, 0)) #用圆标记出目标颜色区域 img.draw_cross(max_c[0], max_c[1]) # cx, cy img.draw_cross(160, 120) # 在中心点画标记 #在目标颜色区域的中心画十字形标记 cx=max_c[0]; cy=max_c[1]; img.draw_line((80,60,cx,cy), color=(127)); #img.draw_string(160,120, "(%d, %d)"%(160,120), color=(127)); img.draw_string(cx, cy, "(%d, %d)"%(cx,cy), color=(127)); sending_data(cx,cy); #发送点位坐标 recive_data(); stm32里部分代码,只是串口部分的,用的PA.9和PA.10 usart.c void USART1_Init(void){ //串口1初始化并启动 //GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; //串口端口配置结构体变量 USART_InitTypeDef USART_InitStructure; //串口参数配置结构体变量 NVIC_InitTypeDef NVIC_InitStructure;//串口中断配置结构体变量 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //打开串口复用时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //打开PC端口时钟 //USART1_TX PA.9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//设定IO口的输出速度为50MHz GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.10 //USART1_RX PA.10 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PA.10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.10 //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级2 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 //USART 初始化设置 USART_InitStructure.USART_BaudRate = 115200;//串口波特率为115200 USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式 USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART1, &USART_InitStructure); //初始化串口1 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启ENABLE USART_Cmd(USART1, ENABLE); //使能串口1 //如下语句解决第1个字节无法正确发送出去的问题 USART_ClearFlag(USART1, USART_FLAG_TC); //清串口1发送标志 } //串口1中断处理函数 void USART1_IRQHandler(void) //串口1全局中断服务函数 { u8 temp; if( USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET ) { USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清除中断标志 temp = USART_ReceiveData(USART1); Openmv_Receive_Data(temp);//openmv数据处理函数 } } u8 RxCounter1 = 0;//接受OpenMV数据里用到的 u8 RxFlag1 = 0;//主函数中用的,可不用在意 u8 RxBuffer1[18];//接受OpenMV数据里用到的数组 u16 posX = 0,posY = 0;//x,y坐标 //接收OpenMV传过来的数据 void Openmv_Receive_Data(int16_t data) { static u8 state = 0; if(state==0&&data==0x2C) { state=1; RxBuffer1[RxCounter1++]=data; } else if(state==1&&data==18) { state=2; RxBuffer1[RxCounter1++]=data; } else if(state==2) { RxBuffer1[RxCounter1++]=data; if(RxCounter1>19||data == 0x5B) state=3; //最后字符是openmv[19] } else if(state==3) //state == 3 检测是否接受到结束标志 { if(RxBuffer1[RxCounter1-1] == 0x5B) { state = 0; // RxFlag1 = 1; USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);//关闭DTSABLE中断 posX = RxBuffer1[3]<<8 | RxBuffer1[2]; //这时x,y坐标已经赋值给posX和posY posY = RxBuffer1[5]<<8 | RxBuffer1[4]; OLED_DISPLAY_8x16(2,6*8,posX/100+0x30); //x,y坐标显示OLED上方便观看 OLED_DISPLAY_8x16(2,7*8,posX0/10+0x30); OLED_DISPLAY_8x16(2,8*8,posX+0x30); OLED_DISPLAY_8x16(4,6*8,posY/100+0x30); OLED_DISPLAY_8x16(4,7*8,posY0/10+0x30); OLED_DISPLAY_8x16(4,8*8,posY+0x30); if(posX > 105 && posX < 125) //下面几行是我用到的,可不用看 { RxFlag1 = 1; // posX = 0; // posY = 0; } // RxFlag1 = 0; RxCounter1 = 0; // posX = 0; posY = 0; //上面几行是我用到的,可不用看 USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);开启ENABLE中断 } else //wrong thing { state = 0; RxCounter1=0; } } else //wrong thing { state = 0; RxCounter1=0; } usart.h extern u16 USART1_RX_STA; //接受状态标记 //Openmv_Receive_Data()函数用到的全局变量 extern u8 RxCounter1; //接受OpenMV数据里用到的 extern u8 RxFlag1; //主函数中用的,可不用在意 extern u8 RxBuffer1[18]; //接受OpenMV数据里用到的数组 extern u16 posX; //OpenMV图像中羽毛球中心x坐标 可不用在意 extern u16 posY; //OpenMV图像中羽毛球中心y坐标 //函数声明 void USART1_Init(void);//串口1初始化并启动 void Openmv_Receive_Data(int16_t data);//接收OpenMV传过来的数据 4.结篇之语 ……好了,到此第一篇博客就结束,里面可能有有些小错误,不过在我这是可以捡羽毛球的,只不过因为一些原因,成功率不高,不过OpenMV与stm32通信还是可以的,希望能帮助到你一点点就很开心了。 ……第一篇博客(2020.2.18.22:00----2020.2.19.1:30)。 ……里面废话多点,可以忽略。 ……最后希望疫情早日过去,我想回学校啦,啊哈哈哈哈哈。 ……中国加油,武汉加油。 ———————————————— 版权声明:本文为CSDN博主「苦逼的虚拟」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_45417468/article/details/104384547
-
引言这篇文章,让你用短短的16行代码基于opencv检测一张图片上的人脸,很有意思,快快学起来吧!一、代码先将代码直接粘在下面:import cv2 as cvdef face_detect_demo(img):img = cv.resize(img, dsize=(800, 800))gary = cv.cvtColor(img, cv.COLOR_BGR2GRAY)face_detect = cv.CascadeClassifier("D:/opencv/sources/data/haarcascades/haarcascade_frontalface_default.xml")face = face_detect.detectMultiScale(gary, 1.004, 28, 0, (40, 40), (50, 50))for x, y, w, h in face:cv.rectangle(img, (x, y), (x + w, y + h), color=(0, 0, 225), thickness=4)cv.imshow("result", img)# img.save("result.jpg") # 保存图片cv.imwrite(r"final_result.jpg", img)img = cv.imread("photo.jpg")face_detect_demo(img) # 检测单个图片while True:if ord("q") == cv.waitKey(1):breakcv.destroyAllWindows()二、代码解读首先导入我们所需要的必要包opencv,对应指令为import cv2 as cv,这里的as就相当于给这个模块起了个别名。如果读者opencv还没有安装的话,可以在终端里面pip install opencv,还不行的话可以看看这个教程:Opencv安装小白教程首先,我们定义一个函数face_detect_demo,这个函数有一个参数img,即我们要检测的图片,这是一个形参。调用cv里面的函数resize,改变图片的大小。resize第一个参数是要处理的图片,第二个参数是你想要改成图片的大小,这里的(800,800)就是改成800*800的这样一张图片。处理的结果再返回给img,当然,如果你喜欢的话也可以另外设一个变量,传进去。接下来更改图片颜色,直接调用cvtColor函数,同样,第一个参数是要处理的图片,第二个参数是要更改的颜色类型,这里的颜色类型有很多种,我这里设置的是COLOR_BGR2GRAY类型。前两步,你可以理解为将图片标准化,即传入不同大小,不同颜色的图片可以转化成同一大小,同一颜色类型的图片。这样的操作好处在于后面调参数更容易。重头戏来了!,这是opencv官方已经训练好的一个人脸检测模型,直接调用就可以,非常方便!文章最后有文件和代码提取链接,也有一些其他训练好的模型,如果感兴趣的话你也可以试一试其他模型。接着,我们检测图片中的人脸,face_detect.detectMultiScale,就是调用模型里面的detectMultiScale。第一个参数gray是要处理的图片,第二个参数是,第三个参数28意思是检测的时候,30次都在同一个位置检测到人脸;这个值高,模型就需要反复验证。第四个参数是。第五个参数是最小矩形框大小,第六个参数是最大矩形框大小。这六个参数需要自己去调,以求最佳匹配。detectMultiScale方法会直接用该模型检测图片,并返回多个x,y,w,h。(x,y)是左顶点的坐标,w是矩形框的宽度,h是矩形框的高度。for循环的功能就是在原来的图片上面绘制矩形框啦,对应的函数是cv.rectangle,这里面的参数很简单就不一一介绍了。cv.imshow进行展示,cv.imwrite进行保存,注意imwrite保存图片时,命名要加上.jpg函数到此结束,接下来是调用函数:首先读取图片,由于我把程序和图片放在一个文件夹,可以直接用名字去读,如果你没有放在一个文件家里面,要用绝对路径去读取。调用上面写好的函数去检验人脸最后做一个键盘的关联事件退出,,按下键盘q键会退出循环,程序结束。别忘了用cv.destroyAllWindows销毁所以窗口哦。四、一些思考我感觉这是一个很有意思的demo,虽然没有什么技术含量,没有数据获取,数据处理,数据训练,模型验证…但是,他能让我们看到cv的强大,如果计算机视觉没有opencv,很多简单的功能将变得复杂,当opencv+openmv,我们能做很多我们感兴趣的事。回到这个人脸检测里面去,为什么叫人脸检测不叫人脸识别呢?因为这个demo只能够知道图片里有几个人,至于他们到底分别是谁就没有办法知道了。如果加上神经网络又会怎么样呢?比如现在我有A这个人的大量图片,用神经网络进行训练,让计算机能够认出他。那么在一张合照里面,计算机能够很快认出哪个是A,并把他框选出来。那么到底有什么用呢?畅想未来:现在有很多重要犯罪嫌疑人逍遥法外,如果能将他的人脸数据训练出来的模型传到全国各地的摄像头中,无论在哪,只要摄像头检测到他的人脸就把地址信息发回警局,这样是不是就能即使抓捕他。儿童拐卖同样是一个社会热点,现在也有很多网站在帮助找被拐卖的儿童。父母主要是通过网站上上传的照片去猜测这到底是不是他的孩子。但是很多照片都是孩子被拐卖两三年甚至五年十年之后的照片了,就连是亲生父母都很难辨认。如果能用深度学习,强化学习,根据小时候的照片训练出来的模型去匹配网站上的照片,将匹配结果高的反馈给父母,这样父母和子女是不是更容易重逢呢?当然这只是我的猜想。人脸作为我们的生物密码给我们带来了不少便利,比如说手机一键解锁,支付宝一键支付,各种门禁刷脸进入。“刷脸”逐渐渗透到我们生活,我们的人脸数据不断传入互联网中,互联网的数据库越来越丰富,对应背后的产业链也越来越发达,面部识别并不是一项全新的技术,但人工智能和机器学习不断使面部识别变得更强大。文件链接:https://pan.baidu.com/s/1FV3NWT3Vayq3eKq2QsB3mg?pwd=tsvd提取码:tsvd————————————————版权声明:本文为CSDN博主「爱睡觉的咋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/weixin_63866037/article/details/127341085
-
0. 前言 OpenCV 是一个的跨平台计算机视觉库,包含了 500 多个用于图像和视频分析的高效算法。本节将介绍 OpenCV 的基础知识,以及如何编译运行 OpenCV 程序,并将学习如何完成最基本的图像处理任务——读取、显示和保存图像。除此之外,鼠标事件和图形绘制也是 OpenCV 计算机视觉项目中常用的基本功能(例如图像标注场景,利用鼠标事件在图像中绘制目标对象的边界框),本节介绍了如何使用这两个重要的 OpenCV 功能。 1. OpenCV 基础 1.1 安装 OpenCV OpenCV 是一个开源库,可以用于开发在 Windows、Linux、Android 和 macOS 等平台上运行的计算机视觉应用程序。自 1999 年推出以来,它已成为计算机视觉领域广泛采用的主要开发工具。在 OpenCV 网站,根据所用计算机的不同平台( Unix/Windows 或 Android) 下载相对应的 OpenCV 包,关于不同平台 OpenCV 的安装方式可以参考官方指南或相关博客。 1.2 OpenCV 主要模块 从 OpenCV 2.2 版开始,OpenCV 库被分成了多个模块,这些模块是位于 lib 目录中的内置库文件,一些常用的模块如下: core 模块包含 OpenCV 库的核心函数,主要包括基本数据结构和算术函数 imgproc 模块包含主要的图像处理函数 highgui 模块包含图像和视频读写函数以及一些用户界面函数 features2d 模块包含特征点检测器和描述符以及特征点匹配框架 calib3d 模块包含相机校准、视图几何估计和立体函数 video 模块包含运动估计、特征跟踪和前景提取函数和类 objdetect 模块包含人脸检测和人物检测等目标检测函数 OpenCV 还包括许多其他实用模块,例如机器学习函数 (ml)、计算几何算法 (flann)等;除此之外,还包括其他实现更高级函数的专用库,例如,用于计算摄影的 photo 和用于图像拼接算法的 stitching。 所有这些模块都有一个与之关联的头文件,因此,典型的 OpenCV C++ 代码首先应当声明引入所需的模块,例如,建议的声明样式类似于以下代码: #include #include #include 有时,我们也可能会看到以以下命令开头的 OpenCV 代码,这是由于为了与旧定义兼容而使用了旧样式: #include "cv.h" 1 1.3 使用 Qt 进行 OpenCV 开发 Qt 是作为开源项目开发的 C++ 应用程序的跨平台 IDE。它由两个独立的元素组成——一个名为 Qt Creator 的跨平台 IDE,以及一组 Qt 类和开发工具。使用 Qt 开发 C++ 应用程序有以下优点: 它是一个由 Qt 社区开发的开源计划,可以访问不同 Qt 组件的源代码 它是一个跨平台的 IDE,可以开发能够运行在不同操作系统上的应用程序,例如 Windows、Linux、macOS 等 它包含一个完整的跨平台 GUI 库,遵循面向对象和事件驱动模型 Qt 还包括多个跨平台库,可用于开发多媒体、数据库、多线程、Web 应用程序以及其他高级应用程序 使用 Qt 可以很方便的编译 OpenCV 库,因为它可以读取 CMake 文件。一旦安装了 OpenCV 和 CMake,只需从 Qt 的 File 菜单中选择 Open File or Project...,然后打开 OpenCV 的源目录下的 CMakeLists.txt 文件,然后在弹出窗口中单击 configure project: 创建一个 OpenCV 项目后,可以通过单击 Qt 菜单中的 Build Project 来构建该项目: 2. OpenCV 图像处理基础 一切准备就绪,接下来是运行第一个 OpenCV 应用程序的时候了。由于 OpenCV 的核心就是处理图像,因此我们首先学习如何执行图像应用程序所需的最基本操作,即从文件系统中加载输入图像、在窗口中显示图像、应用处理函数以及将输出图像存储在磁盘上。 2.1 加载、显示和保存图像 创建新文件 hello_opencv.cpp,包含头文件,声明将使用的类和函数。由于,本节我们只需要显示一个图像,所以需要声明图像数据结构的 core 头文件和包含所有图形界面功能的 highgui 头文件: #include #include 1 2 2.1.1 加载图像 在 mian 函数中首先声明一个保存图像的变量,定义一个 cv::Mat 类对象: cv::Mat image; // 创建一个空图像 1 根据此定义将创建一个大小为 0 x 0 的图像,可以通过访问 cv::Mat size 属性来确认图像尺寸: std::cout << "This image is " << image.rows << " x " << image.cols << std::endl; 1 接下来,调用 imread 函数将从文件中读取图像,对其进行解码并分配内存: image = cv::imread("1.png"); // 读取图像 1 当使用 imread 打开图像而不指定完整路径时,将使用默认目录。当直接运行应用程序时,可执行文件显然位于该目录下;但是,如果直接从 IDE 运行应用程序,默认目录通常是包含项目文件的目录,因此,需要确保输入图像文件位于正确的目录中。 读取图像后就可以使用此图像了。但是,为了保证图像被正确读取(如果找不到文件、文件是否已损坏或不是可识别的格式,则会出现错误),应该首先使用 empty() 函数检查图像是否正确读取。如果没有分配图像数据,empty() 方法返回 true: if (image.empty()) { // 异常处理 return 0; } 2.1.2 显示图像 可以通过使用 highgui 模块中的函数来显示图像。首先需要声明用于显示图像的窗口,然后指定要在此窗口上显示的图像: cv::namedWindow("Orginal Image"); cv::imshow("Orginal Image", image); 1 2 窗口由名称标识,我们也可以重复使用此窗口来显示另一个图像,或者可以创建多个具有不同名称的窗口。运行此应用程序时,可以看到一个图像窗口,如下所示: 加载图像后,通常需要对图像进行一些处理。OpenCV 提供了广泛的图像处理函数,例如,我们可以使用一个非常简单的 flip() 函数水平翻转图像。OpenCV 中的许多图像转换操作可以原地 (in-place) 执行,这意味着转换可以直接应用于输入图像,而不必创建新图像。翻转操作就属于原地操作: cv::flip(image,image,1); // 原地操作 1 但是,我们也可以创建另一个矩阵来保存输出结果: cv::Mat result; cv::flip(image, result, 1); // flip 函数中,正数表示水平翻转;0表示垂直翻转;负数表示同时进行水平和垂直翻转 1 2 在另一个窗口中显示图像翻转后的结果: cv::namedWindow("Output Image"); cv::imshow("Output Image", result); 1 2 由于控制台窗口在 main 函数结束时就会终止,因此我们添加一个额外的 highgui 库函数以在结束程序之前等待用户按键操作: cv::waitKey(0); 1 可以看到输出图像显示在一个不同的窗口中,如下图所示: 2.1.3 保存图像 最后,将处理后的图像保存在磁盘上,可以使用 highgui 库函数完成图像保存操作: cv::imwrite("output.png", result); // 保存处理结果 1 文件扩展名决定了将使用哪个编解码器来保存图像,常见的图像格式包括 BMP、JPG、TIFF 和 PNG 等。 2.1.4 完整代码 完整代码如下所示: #include #include #include int main(){ cv::Mat image; // 创建一个空图像 std::cout << "This image is " << image.rows << " x " << image.cols << std::endl; image = cv::imread("1.png"); // 读取图像 if (image.empty()) { // 异常处理 std::cout << "Error reading image..." << std::endl; return 0; } cv::namedWindow("Orginal Image"); cv::imshow("Orginal Image", image); cv::waitKey(0); cv::Mat result; cv::flip(image, result, 1); // 正数表示水平翻转;0表示垂直翻转;负数表示同时进行水平和垂直翻转 cv::namedWindow("Output Image"); cv::imshow("Output Image", result); cv::imwrite("output.png", result); // 保存处理结果 cv::waitKey(0); return 0; } 2.2 OpenCV 命名空间 OpenCV 的 C++ API 中的所有类和函数都定义在 cv 命名空间中。可以通过两种方式访问这些类和函数。第一种方法是在 main 函数的定义之前添加以下声明: using namespace cv; 1 第二种方法是,按照命名空间规范(即 cv:: )作为所有 OpenCV 类和函数名称的前缀,前缀的使用使 OpenCV 类和函数更容易识别。 2.3 cv::imread() 函数详解 highgui 模块包含一组用于可视化图像并与之交互的函数,使用 imread 函数加载图像时,还可以选择将其作为灰度图像读取,这对于某些需要灰度图像的计算机视觉算法而言是十分有用的。在读取图像时即时转换输入的彩色图像可以节省时间并最大限度地减少内存使用: // 将输入图片读取为灰度图像 image= cv::imread("1.png", cv::IMREAD_GRAYSCALE); 1 2 使用以上代码可以得到一个由无符号字节 (C++ 中的 unsigned char) 组成的图像,OpenCV 用 CV_8U 定义的常量表示这种数据类型。 有时即使输入图像为灰度图像,也需要将图像读取为三通道彩色图像。这可以通过设定第二个参数为正数调用 imread 函数来实现: // 将输入图像读取为三通道彩色图像 image= cv::imread("1.png", cv::IMREAD_COLOR); 1 2 此时,可以得到一个每个像素由三个字节组成的图像,在 OpenCV 中指定为 CV_8UC3,如果输入图像为灰度图像,则所有三个通道都将包含相同的值。最后,如果希望以保存时的格式读取图像,只需输入一个负值作为 imread 的第二个参数。可以使用 channels 方法检查图像中的通道数: std::cout << "This image has " << image.channels() << " channel(s)"; 1 当使用 imshow 显示由整数组成的图像 (16 位无符号整数指定为 CV_16U,32 位有符号整数指定为 CV_32S) 时,该图像的像素值将首先除以 256,以尝试使其可显示为 256 种灰度值。同样,由浮点数组成的图像通过使用 0.0 (显示为黑色)和 1.0 (显示为白色)之间的可能值来显示。超出此定义范围的值以白色(对于高于 1.0 的值)或黑色(对于低于 0.0 的值)显示。 2.4 OpenCV 应用程序的编译执行 2.4.1 编译 OpenCV 应用程序 程序编写完成后,需要进行编译后才能执行,在大多数 IDE 中编写程序时,可以很方便的编译并执行,除此之外,我们还可以使用命令行编译并执行,我们主要介绍以下两种编译方法。 1. 方法一,通过g++命令进行编译得到可执行文件: $ g++ hello_opencv.cpp -o hello_opencv `pkg-config --cflags --libs opencv` 1 在以上编译命令中,hello_opencv.cpp 是源文件,-o 选项用于指定编译后生成的输出文件 hello_opencv,pkg-config 具有以下用途: 检查库的版本号,避免链接错误版本的库文件 获得编译预处理参数,例如头文件位置等 获得链接参数,例如库及其依赖库的位置、文件名和其他链接参数 自动加入所依赖的其他库的位置 在安装 OpenCV 的安装链接库文件目录 lib 中包含一个 pkgconfig 目录,其中包含一个 opencv.pc 文件,该文件即为 pkg-config 下的 OpenCV 配置文件,使用 pkg-config 时,选项 –cflags 用来指定程序在编译时所需要头文件所在的目录,选项 –libs 则指定程序在链接时所需要的动态链接库的目录。 2. 方法二,通过 cmake 进行编译,编辑 CMakeLists.txt 文件,添加以下代码: #指定需要的cmake的最低版本 cmake_minimum_required(VERSION 2.8) #创建工程 project(hello_opencv) #指定C++语言为C++ 11 set(CMAKE_CXX_FLAGS "-std=c++11") #查找OpenCV 安装路径 find_package(OpenCV REQUIRED) #引入OpenCV头文件路径 include_directories(${OpenCV_INCLUDE_DIRS}) #指定编译 hello_opencv.cpp 程序编译后生成可执行文件 hello_opencv add_executable(hello_opencv hello_opencv.cpp) #指定可执行文件 hello_opencv 链接OpenCV lib target_link_libraries(hello_opencv ${OpenCV_LIBS}) 文件中各行代码作用使用注释进行说明,编写完成后,执行以下代码进行编译: $ mkdir build $ cd build $ cmake .. $ make 2.4.1 执行 OpenCV 应用程序 编译完成后,执行可执行程序: $ ./hello_opencv 3. OpenCV 鼠标事件 highgui 模块包含一组丰富的函数,可用于与图像进行交互。使用这些函数,应用程序可以对鼠标或按键事件做出响应。 当鼠标位于所创建的图像窗口上时,通过定义回调函数,可以对鼠标进行编程以执行特定操作。回调函数是没有显式调用的函数,但它会被应用程序调用以响应特定事件(例如鼠标与图像窗口交互的事件) 。为了被应用程序识别,回调函数需要有一个特定的签名并且必须被注册。在鼠标事件处理程序的情况下,回调函数必须具有以下签名: void onMouse(int event, int x, int y, int flags, void* param); 1 第一个参数 event 是一个整数,用于指定哪种类型的鼠标事件触发了对回调函数的调用;紧接着的两个参数 x 和 y 是事件发生时鼠标位置的像素坐标;最后一个参数用于以指向对象的指针的形式向函数发送一个额外的参数。可以通过以下方式在应用程序中注册此回调函数: cv::setMouseCallback("Original Image", onMouse, reinterpret_cast(&image)); 其中,onMouse 函数与名为 Original Image 的图像窗口相关联,并且需要图像的地址作为额外参数传递给该函数。如果我们定义如下代码所示的 onMouse 回调函数,那么每次点击鼠标时,都会在控制台上显示相应像素的值(假设图像是灰度图像) : void onMouse(int event, int x, int y, int flags, void* param){ cv::Mat *im = reinterpret_cast(param); switch (event){ case cv::EVENT_LBUTTONDOWN: // 打印坐标为 (x, y) 处的像素坐标 std::cout << "at (" << x << "," << y << ") value is: " << static_cast(im->at(cv::Point(x, y))) << std::endl; break; } } 为了获得 (x,y) 处的像素值,我们使用了 cv::Mat 对象的 at() 方法,在之后的学习中会进行详细介绍,此处的重点在于介绍鼠标事件。鼠标事件回调函数可以接收的其他事件包括 cv::EVENT_MOUSE_MOVE、cv::EVENT_LBUTTONUP、cv::EVENT_RBUTTONDOWN 和 cv::EVENT_RBUTTONUP 等。 完整代码 ( mouse_event.cpp`) 如下所示: #include #include #include #include void onMouse( int event, int x, int y, int flags, void* param) { cv::Mat *im= reinterpret_cast(param); switch (event) { // dispatch the event case cv::EVENT_LBUTTONDOWN: // mouse button down event // display pixel value at (x,y) std::cout << "at (" << x << "," << y << ") value is: " << static_cast(im->at(cv::Point(x,y))) << std::endl; break; } } int main() { cv::Mat image; // 异常处理 std::cout << "This image is " << image.rows << " x " << image.cols << std::endl; // 将输入图像读取为灰度图像 image= cv::imread("1.png", cv::IMREAD_GRAYSCALE); if (image.empty()) { // error handling std::cout << "Error reading image..." << std::endl; return 0; } std::cout << "This image is " << image.rows << " x " << image.cols << std::endl; std::cout << "This image has " << image.channels() << " channel(s)" << std::endl; // 创建窗口,显示图像 cv::namedWindow("Original Image"); cv::imshow("Original Image", image); // 为图像设置鼠标回调函数 cv::setMouseCallback("Original Image", onMouse, reinterpret_cast(&image)); cv::waitKey(0); // 等待键盘事件 return 0; } 编译并执行程序,结果如下所示: $ g++ mouse_event.cpp -o mouse_event `pkg-config --cflags --libs opencv` $ ./mouse_event 4. OpenCV 绘制图形 OpenCV highgui 模块还提供了一些在图像上绘制图形和书写文本的函数。基本形状绘制函数包括circle()、ellipse()、line() 和 rectangle() 等,以 circle() 函数为例,其他绘图函数的基本用法类似: cv::circle(image, // 绘制的目标图像 cv::Point(155, 110), // 圆心坐标 65, // 半径 0, // 颜色 3, // 线条粗细 ); cv::Point 结构常用于 OpenCV 方法和函数中来指定像素坐标,我们假设绘制是在灰度图像上完成的;因此我们使用单个整数 0 指定绘制颜色。在之后的学习中,我们会学习如何在使用 cv::Scalar 结构的彩色图像的情况下指定颜色值。我们也可以在图像上写入文本: cv::putText(image, // 绘制的目标图像 "This is a dog.", // 文本 cv::Point(40, 200), // 文本位置 cv::FONT_HERSHEY_PLAIN, // 字体类型 2.0, // 缩放比例 255, // 文字颜色 2, // 文字粗细 ) 完整代码 (draw_on_image.cpp) 如下所示: #include #include #include #include int main() { cv::Mat image; // create an empty image std::cout << "This image is " << image.rows << " x " << image.cols << std::endl; // 读取图像 image= cv::imread("1.png", cv::IMREAD_GRAYSCALE); // 创建图像窗口 cv::namedWindow("Drawing on an Image"); // 绘制圆形 cv::circle(image, // 目标图像 cv::Point(430,160), // 圆心坐标 150, // 半径 0, // 颜色 3); // 线条粗细 // 绘制文本 cv::putText(image, // 目标图像 "This is a person.", // 文本 cv::Point(280,350), // 文本位置 cv::FONT_HERSHEY_PLAIN, // 字体类型 2.0, // 缩放 255, // 字体颜色 2); // 字体粗细 cv::imshow("Drawing on an Image", image); // 显示图像 cv::waitKey(0); // 等待键盘响应 return 0; } 编译并执行代码,在测试图像上调用这两个函数可以得到以下结果: $ g++ draw_on_image.cpp -o draw_on_image `pkg-config --cflags --libs opencv` $ ./draw_on_image 5. 使用 Qt 运行 OpenCV 应用程序 如果想要使用 Qt 来运行 OpenCV 应用程序,需要创建项目文件。例如,对于上一小节的示例,项目文件 (draw_on_image.pro) 如下所示: # 库模板 TEMPLATE = app # 目标文件 TARGET = draw_on_image # 项目目录 INCLUDEPATH += . # 向qmake声明应用程序依赖于 widgets 模块 greaterThan(QT_MAJOR_VERSION,4): QT += widgets # OpenCV 安装路径 unix:!mac { INCLUDEPATH += /home/brainiac/Program/opencv4/include/opencv4 LIBS += -L/home/brainiac/Program/opencv4/lib -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs } WIN32 { INCLUDEPATH += C:/OpenCV4.6.0/build/include/opencv4 LIBS += -lc:/OpenCV4.6.0/build/lib/ -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs } # 源文件 SOURCES += draw_on_image.cpp 该文件声明了文件和库文件的位置,还列出了代码使用的库模块。确保使用与 Qt 正在使用的编译器兼容的库二进制文件。 使用 qmake 编译并执行应用程序,结果如下所示: $ qmake -makefile $ make $ ./draw_on_image 小结 OpenCV 是一个跨平台计算机视觉和机器学习库,实现了图像处理和计算机视觉方面的很多通用算法。本文,首先介绍了 OpenCV 的基础知识,并介绍了如何在不同平台安装 OpenCV 库,同时演示了如何使用不同方式编写、编译和执行 OpenCV 应用程序。然后,我们还总结了 OpenCV 处理图像的基础函数和功能,包括读取、显示和保存图像,以及鼠标事件和图像绘制。 ———————————————— 版权声明:本文为CSDN博主「盼小辉丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/LOVEmy134611/article/details/127478248
-
在使用C++进行推理时用到了函数mindspore::dataset::GetAffineTransform,但是输入相同的数据后,与Python的cv2中的同名函数cv2.getAffineTransform所输出的结果不同。C++ Ascend310端测试核心代码#include #include #include "include/dataset/lite_cv/lite_mat.h" #include "include/dataset/lite_cv/image_process.h" using mindspore::MSTensor; using mindspore::DataType; using mindspore::dataset::LiteMat; using mindspore::dataset::Point; using mindspore::dataset::Affine; using mindspore::dataset::GetAffineTransform; int main(int argc, char **argv) { float src[3][2] = {154.61, 90.4373, 154.61, 38.1699, 102.342, 38.1699,}; float dst[3][2] = {144, 192, 144, 48, 0, 48,}; mindspore::dataset::LiteMat trans; std::vector src_point = {Point(src[0][0], src[0][1]), Point(src[1][0], src[1][1]), Point(src[2][0], src[2][1])}; std::vector dst_point = {Point(dst[0][0], dst[0][1]), Point(dst[1][0], dst[1][1]), Point(dst[2][0], dst[2][1])}; int inv = 0; if (inv) mindspore::dataset::GetAffineTransform(dst_point, src_point, trans); else mindspore::dataset::GetAffineTransform(src_point, dst_point, trans); std::cout << "[D]trans: " << trans.width_ << ' ' << trans.height_ << std::endl; std::cout << *(float*)(trans.data_ptr_) << ' ' << *((float*)(trans.data_ptr_) + 1) << ' ' << *((float*)(trans.data_ptr_) + 2) << std::endl; std::cout << *((float*)(trans.data_ptr_ )+ 2 * 1 + 0) << ' ' << *((float*)(trans.data_ptr_ )+ 2 * 1 + 1) << ' ' << *((float*)(trans.data_ptr_) + 2 * 1 + 2) << std::endl; return 0; }程序运行环境为:Ascend310,Ubuntu18.04,g++8.1.0,mindspore1.8.0,mindspore-lite1.8.1 程序输出结果为:[D]trans: 3 2 2.80742e-15 2.09438 0 0 -0 -3.64823e-16Python cv2.getAffineTransform()import numpy as np import cv2 src = np.array([[154.6097, 90.43735], [154.6097, 38.16989], [102.34224, 38.16989]]) dst = np.array([[144., 192.], [144., 48.], [0., 48.]]) inv = 0 if inv: trans = cv2.getAffineTransform(np.float32(dst), np.float32(src)) else: trans = cv2.getAffineTransform(np.float32(src), np.float32(dst)) print(trans)程序运行环境为:CPU,Windows10,Python3.8.8,opencv4.6.0 程序输出结果为:[[ 2.75506043e+00 -0.00000000e+00 -2.81959054e+02] [ 9.19143827e-17 2.75506043e+00 -5.71603573e+01]]问题可以看到两者的输出完全不同。我想知道,这两个函数是否是对应关系,如果是的话,我的代码在哪里出了问题,如果不是的话,那么Mindspore在C++中是否有对应的API可以使用。
-
人脸任务在计算机视觉领域中十分重要,本项目主要使用了两类技术:人脸检测+人脸识别。代码分为两部分内容:人脸注册 和 人脸识别人脸注册:将人脸特征存储进数据库,这里用feature.csv代替人脸识别:将人脸特征与CSV文件中人脸特征进行比较,如果成功匹配则写入考勤文件attendance.csv文章前半部分为一步步实现流程介绍,最后会有整理过后的完整项目代码。一、项目实现A. 注册:导入相关包import cv2import numpy as npimport dlibimport timeimport csv# from argparse import ArgumentParserfrom PIL import Image, ImageDraw, ImageFont设计注册功能注册过程我们需要完成的事:打开摄像头获取画面图片在图片中检测并获取人脸位置根据人脸位置获取68个关键点根据68个关键点生成特征描述符保存(优化)展示界面,加入注册时成功提示等1、基本步骤我们首先进行前三步:# 检测人脸,获取68个关键点,获取特征描述符def faceRegister(faceId=1, userName='default', interval=3, faceCount=3, resize_w=700, resize_h=400):'''faceId:人脸IDuserName: 人脸姓名faceCount: 采集该人脸图片的数量interval: 采集间隔'''cap = cv2.VideoCapture(0)# 人脸检测模型hog_face_detector = dlib.get_frontal_face_detector()# 关键点 检测模型shape_detector = dlib.shape_predictor('./weights/shape_predictor_68_face_landmarks.dat')# resnet模型face_descriptor_extractor = dlib.face_recognition_model_v1('./weights/dlib_face_recognition_resnet_model_v1.dat')while True:ret, frame = cap.read()# 镜像frame = cv2.flip(frame,1)# 转为灰度图frame_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)# 检测人脸detections = hog_face_detector(frame,1)for face in detections:# 人脸框坐标 左上和右下l, t, r, b = face.left(), face.top(), face.right(), face.bottom()# 获取68个关键点points = shape_detector(frame,face)# 绘制关键点for point in points.parts():cv2.circle(frame,(point.x,point.y),2,(0,255,0),1)# 绘制矩形框cv2.rectangle(frame,(l,t),(r,b),(0,255,0),2)cv2.imshow("face",frame)if cv2.waitKey(10) & 0xFF == ord('q'):breakcap.release()cv2.destroyAllWindowsfaceRegister()此时一张帅脸如下:2、描述符的采集之后,我们根据参数,即faceCount 和 Interval 进行描述符的生成和采集。(这里我默认是faceCount=3,Interval=3,即每3秒采集一次,共3次)def faceRegister(faceId=1, userName='default', interval=3, faceCount=3, resize_w=700, resize_h=400):'''faceId:人脸IDuserName: 人脸姓名faceCount: 采集该人脸图片的数量interval: 采集间隔'''cap = cv2.VideoCapture(0)# 人脸检测模型hog_face_detector = dlib.get_frontal_face_detector()# 关键点 检测模型shape_detector = dlib.shape_predictor('./weights/shape_predictor_68_face_landmarks.dat')# resnet模型face_descriptor_extractor = dlib.face_recognition_model_v1('./weights/dlib_face_recognition_resnet_model_v1.dat')# 开始时间start_time = time.time()# 执行次数collect_times = 0while True:ret, frame = cap.read()# 镜像frame = cv2.flip(frame,1)# 转为灰度图frame_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)# 检测人脸detections = hog_face_detector(frame,1)for face in detections:# 人脸框坐标 左上和右下l, t, r, b = face.left(), face.top(), face.right(), face.bottom()# 获取68个关键点points = shape_detector(frame,face)# 绘制人脸关键点for point in points.parts():cv2.circle(frame, (point.x, point.y), 2, (0, 255, 0), 1)# 绘制矩形框cv2.rectangle(frame, (l, t), (r, b), (0, 255, 0), 2)# 采集:if collect_times < faceCount:# 获取当前时间now = time.time()# 时间限制if now - start_time > interval:# 获取特征描述符face_descriptor = face_descriptor_extractor.compute_face_descriptor(frame,points)# dlib格式转为数组face_descriptor = [f for f in face_descriptor]collect_times += 1start_time = nowprint("成功采集{}次".format(collect_times))else:# 时间间隔不到intervalprint("等待进行下一次采集")passelse:# 已经成功采集完3次了print("采集完毕")cap.release()cv2.destroyAllWindows()returncv2.imshow("face",frame)if cv2.waitKey(10) & 0xFF == ord('q'):breakcap.release()cv2.destroyAllWindows()faceRegister()等待进行下一次采集...成功采集1次等待进行下一次采集...成功采集2次等待进行下一次采集...成功采集3次采集完毕3、完整的注册最后就是写入csv文件这里加入了注册成功等的提示,且把一些变量放到了全局,因为后面人脸识别打卡时也会用到。# 加载人脸检测器hog_face_detector = dlib.get_frontal_face_detector()cnn_detector = dlib.cnn_face_detection_model_v1('./weights/mmod_human_face_detector.dat')haar_face_detector = cv2.CascadeClassifier('./weights/haarcascade_frontalface_default.xml')# 加载关键点检测器points_detector = dlib.shape_predictor('./weights/shape_predictor_68_face_landmarks.dat')# 加载resnet模型face_descriptor_extractor = dlib.face_recognition_model_v1('./weights/dlib_face_recognition_resnet_model_v1.dat')# 绘制中文def cv2AddChineseText(img, text, position, textColor=(0, 255, 0), textSize=30):if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))# 创建一个可以在给定图像上绘图的对象draw = ImageDraw.Draw(img)# 字体的格式fontStyle = ImageFont.truetype("./fonts/songti.ttc", textSize, encoding="utf-8")# 绘制文本draw.text(position, text, textColor, font=fontStyle)# 转换回OpenCV格式return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)# 绘制左侧信息def drawLeftInfo(frame, fpsText, mode="Reg", detector='haar', person=1, count=1):# 帧率cv2.putText(frame, "FPS: " + str(round(fpsText, 2)), (30, 50), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)# 模式:注册、识别cv2.putText(frame, "Mode: " + str(mode), (30, 80), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)if mode == 'Recog':# 检测器cv2.putText(frame, "Detector: " + detector, (30, 110), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)# 人数cv2.putText(frame, "Person: " + str(person), (30, 140), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)# 总人数cv2.putText(frame, "Count: " + str(count), (30, 170), cv2.FONT_ITALIC, 0.8, (0, 255, 0), 2)# 注册人脸def faceRegiser(faceId=1, userName='default', interval=3, faceCount=3, resize_w=700, resize_h=400):# 计数count = 0# 开始注册时间startTime = time.time()# 视频时间frameTime = startTime# 控制显示打卡成功的时长show_time = (startTime - 10)# 打开文件f = open('./data/feature.csv', 'a', newline='')csv_writer = csv.writer(f)cap = cv2.VideoCapture(0)while True:ret, frame = cap.read()frame = cv2.resize(frame, (resize_w, resize_h))frame = cv2.flip(frame, 1)# 检测face_detetion = hog_face_detector(frame, 1)for face in face_detetion:# 识别68个关键点points = points_detector(frame, face)# 绘制人脸关键点for point in points.parts():cv2.circle(frame, (point.x, point.y), 2, (0, 255, 0), 1)# 绘制框框l, t, r, b = face.left(), face.top(), face.right(), face.bottom()cv2.rectangle(frame, (l, t), (r, b), (0, 255, 0), 2)now = time.time()if (now - show_time) < 0.5:frame = cv2AddChineseText(frame,"注册成功 {count}/{faceCount}".format(count=(count + 1), faceCount=faceCount),(l, b + 30), textColor=(255, 0, 255), textSize=30)# 检查次数if count < faceCount:# 检查时间if now - startTime > interval:# 特征描述符face_descriptor = face_descriptor_extractor.compute_face_descriptor(frame, points)face_descriptor = [f for f in face_descriptor]# 描述符增加进data文件line = [faceId, userName, face_descriptor]# 写入csv_writer.writerow(line)# 保存照片样本print('人脸注册成功 {count}/{faceCount},faceId:{faceId},userName:{userName}'.format(count=(count + 1),faceCount=faceCount,faceId=faceId,userName=userName))frame = cv2AddChineseText(frame,"注册成功 {count}/{faceCount}".format(count=(count + 1), faceCount=faceCount),(l, b + 30), textColor=(255, 0, 255), textSize=30)show_time = time.time()# 时间重置startTime = now# 次数加一count += 1else:print('人脸注册完毕')f.close()cap.release()cv2.destroyAllWindows()returnnow = time.time()fpsText = 1 / (now - frameTime)frameTime = now# 绘制drawLeftInfo(frame, fpsText, 'Register')cv2.imshow('Face Attendance Demo: Register', frame)if cv2.waitKey(10) & 0xFF == ord('q'):breakf.close()cap.release()cv2.destroyAllWindows()此时执行:faceRegiser(3,"用户B")人脸注册成功 1/3,faceId:3,userName:用户B人脸注册成功 2/3,faceId:3,userName:用户B人脸注册成功 3/3,faceId:3,userName:用户B人脸注册完毕其features文件:B. 识别、打卡识别步骤如下:打开摄像头获取画面根据画面中的图片获取里面的人脸特征描述符根据特征描述符将其与feature.csv文件里特征做距离判断获取ID、NAME考勤记录写入attendance.csv里这里与上面流程相似,不过是加了一个对比功能,距离小于阈值,则表示匹配成功。就加快速度不一步步来了,代码如下:# 刷新右侧考勤信息def updateRightInfo(frame, face_info_list, face_img_list):# 重新绘制逻辑:从列表中每隔3个取一批显示,新增人脸放在最前面# 如果有更新,重新绘制# 如果没有,定时往后移动left_x = 30left_y = 20resize_w = 80offset_y = 120index = 0frame_h = frame.shape[0]frame_w = frame.shape[1]for face in face_info_list[:3]:name = face[0]time = face[1]face_img = face_img_list[index]# print(face_img.shape)face_img = cv2.resize(face_img, (resize_w, resize_w))offset_y_value = offset_y * indexframe[(left_y + offset_y_value):(left_y + resize_w + offset_y_value), -(left_x + resize_w):-left_x] = face_imgcv2.putText(frame, name, ((frame_w - (left_x + resize_w)), (left_y + resize_w) + 15 + offset_y_value),cv2.FONT_ITALIC, 0.5, (0, 255, 0), 1)cv2.putText(frame, time, ((frame_w - (left_x + resize_w)), (left_y + resize_w) + 30 + offset_y_value),cv2.FONT_ITALIC, 0.5, (0, 255, 0), 1)index += 1return frame# 返回DLIB格式的facedef getDlibRect(detector='hog', face=None):l, t, r, b = None, None, None, Noneif detector == 'hog':l, t, r, b = face.left(), face.top(), face.right(), face.bottom()if detector == 'cnn':l = face.rect.left()t = face.rect.top()r = face.rect.right()b = face.rect.bottom()if detector == 'haar':l = face[0]t = face[1]r = face[0] + face[2]b = face[1] + face[3]nonnegative = lambda x: x if x >= 0 else 0return map(nonnegative, (l, t, r, b))# 获取CSV中信息def getFeatList():print('加载注册的人脸特征')feature_list = Nonelabel_list = []name_list = []# 加载保存的特征样本with open('./data/feature.csv', 'r') as f:csv_reader = csv.reader(f)for line in csv_reader:# 重新加载数据faceId = line[0]userName = line[1]face_descriptor = eval(line[2])label_list.append(faceId)name_list.append(userName)# 转为numpy格式face_descriptor = np.asarray(face_descriptor, dtype=np.float64)# 转为二维矩阵,拼接face_descriptor = np.reshape(face_descriptor, (1, -1))# 初始化if feature_list is None:feature_list = face_descriptorelse:# 拼接feature_list = np.concatenate((feature_list, face_descriptor), axis=0)print("特征加载完毕")return feature_list, label_list, name_list# 人脸识别def faceRecognize(detector='haar', threshold=0.5, write_video=False, resize_w=700, resize_h=400):# 视频时间frameTime = time.time()# 加载特征feature_list, label_list, name_list = getFeatList()face_time_dict = {}# 保存name,time人脸信息face_info_list = []# numpy格式人脸图像数据face_img_list = []# 侦测人数person_detect = 0# 统计人脸数face_count = 0# 控制显示打卡成功的时长show_time = (frameTime - 10)# 考勤记录f = open('./data/attendance.csv', 'a')csv_writer = csv.writer(f)cap = cv2.VideoCapture(0)# resize_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))//2# resize_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) //2videoWriter = cv2.VideoWriter('./record_video/out' + str(time.time()) + '.mp4', cv2.VideoWriter_fourcc(*'MP4V'), 15,(resize_w, resize_h))while True:ret, frame = cap.read()frame = cv2.resize(frame, (resize_w, resize_h))frame = cv2.flip(frame, 1)# 切换人脸检测器if detector == 'hog':face_detetion = hog_face_detector(frame, 1)if detector == 'cnn':face_detetion = cnn_detector(frame, 1)if detector == 'haar':frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)face_detetion = haar_face_detector.detectMultiScale(frame_gray, minNeighbors=7, minSize=(100, 100))person_detect = len(face_detetion)for face in face_detetion:l, t, r, b = getDlibRect(detector, face)face = dlib.rectangle(l, t, r, b)# 识别68个关键点points = points_detector(frame, face)cv2.rectangle(frame, (l, t), (r, b), (0, 255, 0), 2)# 人脸区域face_crop = frame[t:b, l:r]# 特征face_descriptor = face_descriptor_extractor.compute_face_descriptor(frame, points)face_descriptor = [f for f in face_descriptor]face_descriptor = np.asarray(face_descriptor, dtype=np.float64)# 计算距离distance = np.linalg.norm((face_descriptor - feature_list), axis=1)# 最小距离索引min_index = np.argmin(distance)# 最小距离min_distance = distance[min_index]predict_name = "Not recog"if min_distance < threshold:# 距离小于阈值,表示匹配predict_id = label_list[min_index]predict_name = name_list[min_index]# 判断是否新增记录:如果一个人距上次检测时间>3秒,或者换了一个人,将这条记录插入need_insert = Falsenow = time.time()if predict_name in face_time_dict:if (now - face_time_dict[predict_name]) > 3:# 刷新时间face_time_dict[predict_name] = nowneed_insert = Trueelse:# 还是上次人脸need_insert = Falseelse:# 新增数据记录face_time_dict[predict_name] = nowneed_insert = Trueif (now - show_time) < 1:frame = cv2AddChineseText(frame, "打卡成功", (l, b + 30), textColor=(0, 255, 0), textSize=40)if need_insert:# 连续显示打卡成功1sframe = cv2AddChineseText(frame, "打卡成功", (l, b + 30), textColor=(0, 255, 0), textSize=40)show_time = time.time()time_local = time.localtime(face_time_dict[predict_name])# 转换成新的时间格式(2016-05-05 20:28:54)face_time = time.strftime("%H:%M:%S", time_local)face_time_full = time.strftime("%Y-%m-%d %H:%M:%S", time_local)# 开始位置增加face_info_list.insert(0, [predict_name, face_time])face_img_list.insert(0, face_crop)# 写入考勤表line = [predict_id, predict_name, min_distance, face_time_full]csv_writer.writerow(line)face_count += 1# 绘制人脸点cv2.putText(frame, predict_name + " " + str(round(min_distance, 2)), (l, b + 30), cv2.FONT_ITALIC, 0.8,(0, 255, 0), 2)# 处理下一张脸now = time.time()fpsText = 1 / (now - frameTime)frameTime = now# 绘制drawLeftInfo(frame, fpsText, 'Recog', detector=detector, person=person_detect, count=face_count)# 舍弃face_img_list、face_info_list后部分,节约内存if len(face_info_list) > 10:face_info_list = face_info_list[:9]face_img_list = face_img_list[:9]frame = updateRightInfo(frame, face_info_list, face_img_list)if write_video:videoWriter.write(frame)cv2.imshow('Face Attendance Demo: Recognition', frame)if cv2.waitKey(10) & 0xFF == ord('q'):breakf.close()videoWriter.release()cap.release()cv2.destroyAllWindows()然后效果就和我们宿舍楼下差不多了~我年轻的时候,我大概比现在帅个几百倍吧,哎。二、总代码上文其实把登录和注册最后一部分代码放在一起就是了,这里就不再复制粘贴了,相关权重文件下载链接:opencv/data at master · opencv/opencv · GitHub懒得下载或者懒得找也可以私信我发你,看见或有时间回。当然本项目还有很多需要优化的地方,比如设置用户不能重复、考勤打卡每天只能一次、把csv改为链接成数据库等等,后续代码优化完成后就可以部署然后和室友**了。————————————————版权声明:本文为CSDN博主「老师我作业忘带了」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/suic009/article/details/127382811
-
系统信息:Ubuntu20.04 aarch64安装包下载:官网:cid:link_1选择source下载压缩包其他途径:cid:link_0将压缩包传至服务器并解压(此处以zip文件为例)unzip opencv-3.4.12.zip进入解压后的文件夹cd opencv-3.4.12创建build文件夹,并进入mkdir buildcd build进行文件编译及安装方法一:4.0以上版本使用方法二cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local ..方法二:cmake -D WITH_TBB=ON -D WITH_EIGEN=ON -D OPENCV_GENERATE_PKGCONFIG=ON -D BUILD_DOCS=ON -D BUILD_TESTS=OFF -D BUILD_PERF_TESTS=OFF -D BUILD_EXAMPLES=OFF -D WITH_OPENCL=OFF -D WITH_CUDA=OFF -D BUILD_opencv_gpu=OFF -D BUILD_opencv_gpuarithm=OFF -D BUILD_opencv_gpubgsegm=O -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local ..编译安装:makemake install执行以下命令验证是否安装成功:opencv_version环境配置打开/etc/ld.so.conf 并在最后面添加include /usr/local/libvim /etc/ld.so.conf/usr/local 是opencv安装路径,也就是makefile中的指定安装路径添加环境变量vim /etc/bash.bashrc在文末添加如下内容:PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfigexport PKG_CONFIG_PATH初始化环境变量:source /etc/bash.bashrc验证是否安装成功执行以下命令验证:pkg-config opencv --modversionFAQ1.如果验证过程中出现如下报错:Package opencv was not found in the pkg-config search path.Perhaps you should add the directory containing `opencv.pc'to the PKG_CONFIG_PATH environment variableNo package 'opencv' found解决办法:确认config文件中是否存在opencv.pc这个文件,按照上面的操作,pc 文件应该在“/usr/local/lib/pkgconfig/”路径下,进入路径验证即可:可以看到,路径下有一个文件是“opencv4.pc”,因为这边安装的是opencv4.2.0,那么,直接修改文件名来让系统可以读到“opencv.pc”:执行命令:mv /usr/local/lib/pkgconfig/opencv4.pc /usr/local/lib/pkgconfig/opencv.pc执行验证命令得到版本号即可
-
# Anaconda安排配置Python和Opencvhttps://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 全√conda config –-add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/main/conda config –-set show_channel_urls yes //设置搜索时显示通道conda config –-show channels创建名为opencv的新环境:conda create -n opencv python=3.7y# 安装opencv-python库:activate opencvpip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-pythonpip install -i https://pypi.tuna.tsinghua.edu.cn/simple matplotlib# pip install opencv-python (如果只用主模块,使用这个命令安装)pip install opencv-contrib-python (如果需要用主模块和contrib模块,使用这个命令安装)# 报错ModuleNotFoundError: No module named 'cv2'用conda安装:conda install opencv# 或者在导入opencv前,写上:import syssys.path.append('/*/*/*/python-*/site-packages')
-
转自公众号:小白学视觉图像处理之理解Homography matrix(单应性矩阵)单应性矩阵是投影几何中一个术语,本质上它是一个数学概念,但是在OpenCV中却是有几个函数与透视变换相关的函数,都用到了单应性矩阵的概念与知识。小编跟很多人一样,刚开始学习图像处理对单应性矩阵不是很了解,通过项目实践慢慢知道了一些这方面的知识和自己对它的理解,就跟大家分享一下。单应性矩阵概念这里说的单应性矩阵主要是指平面单应性矩阵,在三轴坐标中XYZ,Z=1这个有点类似于三维的齐次坐标。单应性矩阵主要用来解决两个问题,一是表述真实世界中一个平面与对应它图像的透视变换二是从通过透视变换实现图像从一种视图变换到另外一种视图首先看一下在三维空间中任意两个平面上图的中零点分别表示两个平面中任意两个点,a1、a2与b1、b2是这两点对应的两个方向上的线性向量。对于这两个平面直接的关系我们就可以通过这些点从而进一步确立两个平面直接的关系,而两个平面之间的关系用单应性矩阵来描述如下:这种关系被称为平面单应性。这个当中有一些数学知识推导,感兴趣的大家可以自己去看,我们最重要的是明白这个概念怎么来的。其次知道它的应用场景,下面我们就从应用层面和代码层面来说说单应性矩阵的应用。 - 用来解决拍照时候图像扭曲问题。这个在上一篇文章透视 变换中讲过,但是 当时没有说这个是单应性矩阵的应用。- 此外还两个计算机图形学的应用场景分布是纹理渲染与计算平面阴影。- 用来实现图像拼接时候解决对齐问题应用案例街拍的时候路两边有很多广告牌,如果在视频实时帧中获取到对应的广告牌位置,就可以获取广告牌的四个角坐标,然后通过准备好的内容,将广告牌内容替换,得到想要的虚拟广告牌效果,而这个过程中最重要的一步,可以通过计算单应性矩阵实现内容替换,演示效果如下: 时代广场的街拍看到左侧的广告牌不,我们准好了一张图像,准备替换它的内容,准备的图像如下:最终处理之后的效果如下:实现代码如下:#include #include #include using namespace cv;using namespace std;int main(int argc, char** argv) { // load images Mat src = imread("D:/vcprojects/images/times-square.jpg"); if (!src.data) { printf("could not load image..."); return -1; } // show images namedWindow("input image", CV_WINDOW_AUTOSIZE); imshow("input image", src); Mat replaceImg = imread("D:/vcprojects/images/kgirls.png"); imshow("adv content", replaceImg); // 定义两个平面上四个角坐标 vector src_corners(4); vector dst_corners(4); // 原图像平面四点坐标 src_corners[0] = Point(0, 0); src_corners[1] = Point(replaceImg.cols, 0); src_corners[2] = Point(0, replaceImg.rows); src_corners[3] = Point(replaceImg.cols, replaceImg.rows); // 目标平面四个角坐标 dst_corners[0] = Point(70, 131); dst_corners[1] = Point(168,216); dst_corners[2] = Point(21, 199); dst_corners[3] = Point(148, 267); // 计算单应性矩阵与透视变换 Mat h = findHomography(src_corners, dst_corners); Mat output_img; warpPerspective(replaceImg, output_img, h, src.size()); // create mask Mat m1 = Mat::zeros(replaceImg.size(), CV_8UC1); m1 = Scalar(255); Mat mask_output; warpPerspective(m1, mask_output, h, src.size()); imshow("Mask Result", mask_output); // use mask Mat result1; add(output_img, output_img, result1, mask_output); Mat result2; bitwise_not(mask_output, mask_output); add(src, result1, result2, mask_output); // put them together Mat result; add(result1, result2, result); imshow("Final Result", result); imwrite("D:/vcprojects/images/result.png", result); waitKey(0); return 0;}
-
【功能模块】 因为公司有需求要进行画框的,没有硬件支持的话,接几十路,然后都用 opencv 画框的话,那得降多少性能啊? 解码出来的 YUV 要转到 BGR,BGR 内存转到 CPU 上的内存?然后再用 opencv 画框?这不能接受吧。 哎,我一开始以为 ascend 跟 cuda 一样能编程呢。感觉就是个大号的海思啊。
-
【功能模块】windows 10 pc直连网线连接摄像机,使用python和opencv,从RTSP流采集、显示和编码图像帧,有明显的卡顿和跳帧现象;并做了如下尝试:1、抓RTSP子码流2、修改华为摄像头参数:如帧率(25->20)、是否动态帧率(如果勾选,则去勾选),帧间隔(50->80),I帧间隔(50->25)问题依然存在型号、版本和配置如下摄像头编号场景型号版本配置1下轴瓦D2150-10-SIUSDC 9.0.0见下2气缸盖D2150-10-SIUSDC 9.0.0 SPC307见下3活塞轴瓦D2150-10-SIUSDC 9.0.0见下4openlab实验室X2391-10-TLSDC 9.0.0.SPC602见下摄像头配置(1&4):摄像头配置(2):摄像头配置(3):【操作步骤&问题现象】使用python和opencv采集摄像头图像帧:1、collect线程循环VideoCapture.read(),将获取的解码后的图像帧放入队列,sleep(10),单位毫秒2、encode线程循环读取队列,将图像帧显示并编码成JPEG,发送边侧进行AI推理【截图信息】【日志信息】(可选,上传日志内容或者附件)通过日志分析,发现帧率不稳定,存在很多跳秒,见附件csv_path.rar(encode_time列是encode线程每秒执行时间,单位:毫秒;collect_time列是collect线程每秒执行时间,单位:毫秒)。
上滑加载中
推荐直播
-
0代码智能构建AI Agent——华为云AI原生应用引擎的架构与实践
2024/11/13 周三 16:30-18:00
苏秦 华为云aPaaS DTSE技术布道师
大模型及生成式AI对应用和软件产业带来了哪些影响?从企业场景及应用开发视角,面向AI原生应用需要什么样的工具及平台能力?企业要如何选好、用好、管好大模型,使能AI原生应用快速创新?本期直播,华为云aPaaS DTSE技术布道师苏秦将基于华为云自身实践出发,深入浅出地介绍华为云AI原生应用引擎,通过分钟级智能生成Agent应用的方式帮助企业完成从传统应用到智能应用的竞争力转型,使能千行万业智能应用创新。
去报名 -
TinyEngine低代码引擎系列第2讲——向下扎根,向上生长,TinyEngine灵活构建个性化低代码平台
2024/11/14 周四 16:00-18:00
王老师 华为云前端开发工程师,TinyEngine开源负责人
王老师将从TinyEngine 的灵活定制能力出发,带大家了解隐藏在低代码背后的潜在挑战及突破思路,通过实践及运用,帮助大家贴近面向未来低代码产品。
即将直播 -
华为云AI入门课:AI发展趋势与华为愿景
2024/11/18 周一 18:20-20:20
Alex 华为云学堂技术讲师
本期直播旨在帮助开发者熟悉理解AI技术概念,AI发展趋势,AI实用化前景,了解熟悉未来主要技术栈,当前发展瓶颈等行业化知识。帮助开发者在AI领域快速构建知识体系,构建职业竞争力。
即将直播
热门标签