OpenCV是计算机视觉算法,图像处理和具有开放源代码的通用数值算法的库,许多C ++开发人员都很熟悉。 除C ++外,还为Python,Java,Ruby,Matlab,Lua和其他语言开发了OpenCV。 由于在这些语言中没有我的主要语言C#,因此我决定关注C#下的OpenCvSharp-包装器库,并检查该项目。 可以在本文中找到这些内容。
引言
在加入PVS-Studio之前,我在展览中从事机器人技术的研究。 我的任务包括最基本的维修(如果发生重大故障,则将机器人交给另一个人),以及开发各种软件和实用程序。
累了,最近刚到一个新城市,我和一个刚拆开的KIKI机器人一起。说到发展。 真是有趣。 每当团队中的某个人想到一个主意时,还有什么会令展览的客人感到惊讶,我们将这个问题提出来进行一般性讨论;如果这个主意不错,我们便开始实施。 有一次,我们想到了制作一个能以热情洋溢的讲话回应人脸的生物。
在互联网上搜索了我需要的库之后,我遇到了OpenCV网站,即计算机视觉算法的库。 我很快就感到失望-OpenCV是用C ++实现的。 我对我从大学获得的收益的了解显然还不够。 因此,通过短暂的谷歌搜索,我遇到了OpenCvSharp-这个库的包装,在我的主要语言C#下。 从那时起已经过去了六个月,该程序已经编写并使用了很长时间,我决定进入OpenCvSharp的行列,并使用PVS-Studio静态分析器检查其源代码。
审核项目
OpenCvSharp是OpenCV的包装,用于在C#项目中使用该库。 顺便说一下,
我们还检查了 OpenCV库。 OpenCvSharp的优点包括大量的代码示例,跨平台(可以在Mono支持的任何平台上运行)和易于安装。
包装器是一个小项目,包含约112,200行C#代码。 在这些评论中,有1.2%是评论,顺便说一句,该评论很小。 但是对于这么小的项目,会有很多错误。 在这篇文章中,我写出了20多个错误,但是还有其他错误却不那么明显。
PVS-Studio代码分析器
PVS-Studio是用于检测用C,C ++,C#和Java编写的程序的源代码中的错误和潜在漏洞的工具。 在Windows,Linux和macOS上运行。 如上所述,除了无法实现的代码,错误和错别字之外,PVS-Studio还可检测潜在的漏洞。 因此,可以将其视为用于静态应用程序安全测试(静态应用程序安全测试,SAST)的工具。
检查分析器报告时引起注意的代码段
WriteableBitmapConverter方法通过四个相同类型的PVS-Studio警告立即引起注意:
- V3005将'optimumChannels [PixelFormats.Indexed1]'变量分配给它自己。 WriteableBitmapConverter.cs 22
- V3005将'optimumChannels [PixelFormats.Indexed8]'变量分配给它自己。 WriteableBitmapConverter.cs 23
- V3005将'optimumTypes [PixelFormats.Indexed1]'变量分配给它自己。 WriteableBitmapConverter.cs 50
- V3005将'optimumTypes [PixelFormats.Indexed8]'变量分配给它自己。 WriteableBitmapConverter.cs 51
static WriteableBitmapConverter() { optimumChannels = new Dictionary <PixelFormat, int>(); optimumChannels[PixelFormats.Indexed1] =
PixelFormats类在
System.Windows.Media命名空间中定义,并且是各种像素格式的集合。 分析器引起注意的事实是
WriteableBitmapConverter方法将值重新分配给
optimumChannels [PixelFormats.Indexed1]和
optimumChannels [PixelFormats.Indexed8]元素 ,这没有实际意义。 目前尚不清楚这是简单的错字还是其他含义。 顺便说一下,这段代码清楚地展示了静态分析器的好处。 当您在眼前看到一堆相同类型的行时,您的眼睛开始“模糊”,并且您的注意力消散了-即使在经过代码审查后,错字也会潜入程序中,这也不足为奇。 而且,静态分析仪的注意力没有问题,也不需要休息,因此更容易发现此类错误。
感受静态分析的力量。PVS-Studio警告 :
V3021有两个带有相同条件表达式的'if'语句。 第一个'if'语句包含方法return。 这意味着第二个'if'语句是没有意义的InputArray.cs 394
private static MatType EstimateType(Type t) { .... if (t == typeof(Vec2b)) return MatType.CV_8UC2; if (t == typeof(Vec3b)) return MatType.CV_8UC3; if (t == typeof(Vec4b)) return MatType.CV_8UC4; if (t == typeof(Vec6b)) return MatType.CV_8UC(6); if (t == typeof(Vec2s))
该错误有点像上一个错误。 程序员提出了对相同条件进行两次检查的情况。 在这种情况下,这没有意义-然后将不会执行“重复”
if语句的分支,因为:
- 如果第一个条件表达式为true,则该方法将退出;否则,方法将退出。
- 如果第一个条件为假,则第二个也将为假,因为要测试的变量-t-在条件表达式之间不改变。
开发人员应仔细检查这段代码。 代替第二变量
Vec2s可能应该使用其他变量。
PVS-Studio警告 :
V3010必须使用函数“ ToString”的返回值。 ImgProcTest.cs 80
public static RectanglesIntersectTypes RotatedRectangleIntersection(RotatedRect rect1, RotatedRect rect2, out Point2f[] intersectingRegion) { using (var intersectingRegionVec = new VectorOfPoint2f()) { int ret = NativeMethods .imgproc_rotatedRectangleIntersection_vector( rect1, rect2, intersectingRegionVec.CvPtr); intersectingRegion = intersectingRegionVec.ToArray(); return (RectanglesIntersectTypes) ret; } } public void RotatedRectangleIntersectionVector() { var rr1 = new RotatedRect(new Point2f(100, 100), new Size2f(100, 100), 45); var rr2 = new RotatedRect(new Point2f(130, 100), new Size2f(100, 100), 0); Cv2.RotatedRectangleIntersection(rr1, rr2, out var intersectingRegion); .... intersectingRegion.ToString(); }
RotatedRectangleIntersection方法通过
intersectingRegion参数返回
Point2f元素的数组。 程序
用值填充
intersectingRegion之后,在此array上调用
ToString()方法
。 对于数组的元素,此更改不会发生任何变化,最后一行也不会做任何有用的工作,因此有理由怀疑开发人员他只是忘记删除了它。
PVS-Studio警告 :
- V3021有两个带有相同条件表达式的'if'语句。 第一个'if'语句包含方法return。 这意味着第二个'if'语句是毫无意义的Cv2_calib3d.cs 1370
- V3022表达式'objectPoints == null'始终为false。 Cv2_calib3d.cs 1372
public static double CalibrateCamera(....) { if (objectPoints == null) throw new ArgumentNullException(nameof(objectPoints)); if (objectPoints == null) throw new ArgumentNullException(nameof(objectPoints)); .... }
在这种情况下,一个代码片段被复制了,因此出现了两个警告。 第一个警告指示两个
if语句具有相同的条件表达式。 如果此表达式为true,则该方法将从第一个
if语句的
then-分支退出。 因此,第二个条件将始终为假,如以下警告所示。 显然,文本已被复制,但忘记更正了。
可爱的复制粘贴。其他类似的分析仪警告:
- V3021有两个带有相同条件表达式的'if'语句。 第一个'if'语句包含方法return。 这意味着第二个'if'语句是毫无意义的Cv2_calib3d.cs 1444
- V3022表达式'objectPoints == null'始终为false。 Cv2_calib3d.cs 1446
PVS-Studio 警告 :
V3022表达式'label == MarkerValue'始终为false。 Labeller.cs 135
internal static class Labeller { .... private const int MarkerValue = -1; public static int Perform(Mat img, CvBlobs blobs) { .... int label = 0; int lastLabel = 0; CvBlob lastBlob = null; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { if (imgIn[x + y * step] == 0) continue; bool labeled = labels[y, x] != 0; if (....) { labeled = true;
在这段代码中,创建的
标签变量为零。 当满足特定条件时,可以将一个变量添加到该变量中。 在这种情况下,在代码中,变量
标签的值不会向下变化。 在带有箭头的线中,将此变量与等于-1的常数进行比较,这没有实际意义。
PVS-Studio警告 :
V3038参数多次传递给方法。 可能应改为传递其他参数。 Cv2_photo.cs 124
public static void FastNlMeansDenoisingMulti(....) { .... NativeMethods.photo_fastNlMeansDenoisingMulti( srcImgPtrs, srcImgPtrs.Length, dst.CvPtr, imgToDenoiseIndex, templateWindowSize, h, templateWindowSize, searchWindowSize); .... }
要了解分析器的含义,让我们看一下
photo_fastNlMeansDenoisingMulti方法的参数:
public static extern void photo_fastNlMeansDenoisingMulti( IntPtr[] srcImgs, int srcImgsLength, IntPtr dst, int imgToDenoiseIndex, int temporalWindowSize, float h, int templateWindowSize, int searchWindowSize)
进一步简化以使其变得显而易见。 比较这些行:
NativeMethods.photo_fastNlMeansDenoisingMulti( .... templateWindowSize, .... templateWindowSize, ....); public static extern void photo_fastNlMeansDenoisingMulti( .... int temporalWindowSize, .... int templateWindowSize, ....)
分析人员提请注意以下事实:开发人员两次使用
templateWindowSize变量,尽管最有可能使用
temporalWindowSize代替该变量的第一次提及。 还怀疑
根本没有使用
photo_fastNlMeansDenoisingMulti方法中的
temporalWindowSize的值。 也许这是故意完成的,但是值得代替开发人员仔细研究此代码,其中是否存在错误?
类似的分析仪警告:
- V3038参数多次传递给方法。 可能应改为传递其他参数。 Cv2_photo.cs 149
- V3038参数多次传递给方法。 可能应改为传递其他参数。 Cv2_photo.cs 180
- V3038参数多次传递给方法。 可能应改为传递其他参数。 Cv2_photo.cs 205
以下错误将与上一个错误类似。
警告PVS-Studio :
V3066传递给“ calib3d_Rodrigues_MatToVec”方法的参数的可能错误顺序:“ matrixM.CvPtr”和“ vectorM.CvPtr”。 Cv2_calib3d.cs 86
public static void Rodrigues(double[,] matrix, out double[] vector, out double[,] jacobian) { .... using (var jacobianM = new Mat<double>()) { NativeMethods.calib3d_Rodrigues_MatToVec (matrixM.CvPtr, vectorM.CvPtr, jacobianM.CvPtr); .... } }
让我们看一下参数
calib3d_Rodrigues_MatToVec public static extern void calib3d_Rodrigues_MatToVec( IntPtr vector, IntPtr matrix, IntPtr jacobian)
也许当调用
calib3d_Rodrigues_MatToVec方法时,参数
matrixM.CvPtr和
vectorM.CvPtr混合了。 开发人员应仔细阅读此代码。 可能会出现错误,从而干扰正确的计算。
PVS-Studio警告 :
V3063如果条件表达式的一部分被求值,则始终为false:data == null。 Mat.cs 3539
private void CheckArgumentsForConvert(....) { .... if (data == null) throw new ArgumentNullException(nameof(data)); MatType t = Type(); if (data == null || (data.Length * dataDimension)
分析器指示第二个检查
数据== null永远不会为
true ,因为 如果在第一个条件下
data为
null ,则将引发异常,并且程序执行将无法进行第二次检查。
我知道您已经累了,但所剩无几。PVS-Studio警告 :
V3127找到两个相似的代码片段。 也许,这是一个错字,应该使用“窗口”变量而不是“ src2” Cv2_imgproc.cs 1547
public static Point2d PhaseCorrelateRes(....) { if (src1 == null) throw new ArgumentNullException(nameof(src1)); if (src2 == null) throw new ArgumentNullException(nameof(src2)); if (window == null) throw new ArgumentNullException(nameof(src2));
然后分析仪发现了一个错字。 在代码的此部分,将检查变量的值是否为
null ,如果满足此条件,则会为每个变量引发异常。 但是,
window变量并不是那么简单。 如果此变量为
null ,则也会为其生成一个异常,但是此异常的文本拼写错误。 变量
窗口本身不会出现在此异常的文本中;而是在其中指示
src2 。 显然,最后一个条件应该是这样的:
if (window == null) throw new ArgumentNullException(nameof(window));
PVS-Studio警告 :
V3142检测到无法访问的代码。 可能存在错误。 MatOfT.cs 873
现在,进行更改,让我们看一下在报告无法访问的代码时分析器实际上是正确的情况,但是没有错误。 可以说分析仪同时生成正确和错误的警告时就是这种情况。
public new Mat<TElem> SubMat(params Range[] ranges) { Mat result = base.SubMat(ranges); return Wrap(result); }
分析器声称在这里无法
返回return语句 。 若要进行检查,请查看
SubMat方法的主体。
public Mat SubMat(params Range[] ranges) { throw new NotImplementedException(); }
如您所见,该函数尚未添加,并且始终会引发异常。 报告无法访问的代码时,分析仪是正确的。 但这不能称为真正的错误。
分析仪发现的以下三个错误属于同一类型,但是它们是如此酷,以至于我都无法将它们全部写出来。
PVS-Studio 警告 :
V3022表达式'String.IsNullOrEmpty(“ winName”)'始终为false。 Cv2_highgui.cs 46
public static void DestroyWindow(string winName) { if (String.IsNullOrEmpty("winName")) .... }
PVS-Studio 警告 :
V3022表达式'string.IsNullOrEmpty(“ fileName”)'始终为false。 FrameSource.cs 37
public static FrameSource CreateFrameSource_Video(string fileName) { if (string.IsNullOrEmpty("fileName")) .... }
PVS-Studio 警告 :
V3022表达式'string.IsNullOrEmpty(“ fileName”)'始终为false。 FrameSource.cs 53
public static FrameSource CreateFrameSource_Video_CUDA(string fileName) { if (string.IsNullOrEmpty("fileName")) .... }
有时,在
V3022分析仪的警告之后(表达式始终为true / false),确实有一些奇怪或有趣的事情。 在所有三种情况下,都观察到相同的情况。 方法代码具有类型为
string的参数,必须检查其值。 但是,检查的不是变量的值,而是带有其名称的字符串文字,即 该名称被引用了。
显然,开发人员密封了一次,并使用复制粘贴通过代码传播了此错误。
结论
OpenCvSharp开发人员已完成了重要且出色的工作。 作为这个库的用户,我非常感谢他们。 谢谢啦
但是,在PVS-Studio团队中研究代码时,我不得不承认其质量问题尚未得到充分解决。 在此项目中,很可能不定期使用静态代码分析器。 许多错误可以通过更昂贵的方法来纠正(例如,根据用户评论进行测试)。 而且有些通常可以在代码中保留很长时间,而我们只是找到了它们。 在有关使用静态分析方法论的哲学主题的简短
注释中,将更详细地介绍此想法。
由于该项目是开放的,并且位于GitHub上,因此其开发人员有机会利用
免费许可选项 PVS-Studio并开始定期应用分析。
谢谢您的关注。 使用试用版的PVS-Studio
下载并测试您的项目。

如果您想与讲英语的读者分享这篇文章,请使用以下链接:Ekaterina Nikiforova。
使用PVS-Studio检查OpenCv的OpenCvSharp包装 。