OpenCV es una biblioteca de algoritmos de visión por computadora, procesamiento de imágenes y algoritmos numéricos de propósito general con código abierto, familiares para muchos desarrolladores de C ++. Además de C ++, OpenCV también se está desarrollando para Python, Java, Ruby, Matlab, Lua y otros lenguajes. Como entre estos lenguajes no existe mi principal, C #, decidí prestar atención a la biblioteca de contenedor OpenCvSharp en C # y verificar este proyecto. Lo que resultó de esto se puede encontrar en este artículo.
Introduccion
Anteriormente, antes de unirme a PVS-Studio, me dedicaba a la robótica en exposiciones. Mis tareas incluyeron la solución más básica (si ocurriera una falla importante, entonces el robot fue entregado a otra persona), así como el desarrollo de una amplia variedad de software y utilidades.
Cansado y recientemente llegado a una nueva ciudad, junto con un robot KIKI recién desempaquetado.Hablando de desarrollo. Eso fue muy gracioso. Cada vez que se le ocurrió una idea a alguien del equipo, qué más sorprendería a los invitados de las exposiciones, sacamos este tema para discusión general y, si la idea era buena, la tomamos para su implementación. Una vez, se nos ocurrió la idea de hacer una criatura que responda a un rostro humano con un discurso de bienvenida.
Después de buscar en Internet una biblioteca para mis necesidades, me encontré con el sitio web de OpenCV, bibliotecas de algoritmos de visión por computadora. Pronto me decepcionó: OpenCV se implementó en C ++. Mi conocimiento sobre los beneficios que obtuve de la universidad claramente no era suficiente. Por lo tanto, al buscar brevemente en Google, me encontré con OpenCvSharp - contenedor de esta biblioteca en C #, mi idioma principal. Han pasado seis meses desde entonces, el programa ya se ha escrito y usado durante mucho tiempo, y decidí ponerme bajo el capó de OpenCvSharp y verificar su código fuente usando el analizador estático PVS-Studio.
Proyecto auditado
OpenCvSharp es un contenedor sobre OpenCV para usar la biblioteca en proyectos de C #. La biblioteca de OpenCV, por cierto,
también revisamos . Las ventajas de OpenCvSharp incluyen una gran colección de ejemplos de código, multiplataforma (puede funcionar en cualquier plataforma compatible con Mono) y facilidad de instalación.
El contenedor es un proyecto pequeño y contiene alrededor de 112.200 líneas de código C #. De estos, el 1.2% son comentarios que, por cierto, son sospechosamente pequeños. Pero para un proyecto tan pequeño hay muchos errores. En el artículo, escribí más de 20 errores, pero había otros que no eran tan interesantes u obvios.
Analizador de código PVS-Studio
PVS-Studio es una herramienta para detectar errores y vulnerabilidades potenciales en el código fuente de programas escritos en C, C ++, C # y Java. Se ejecuta en Windows, Linux y macOS. Además de código inalcanzable, errores y errores tipográficos, PVS-Studio puede detectar vulnerabilidades potenciales, como se señaló anteriormente. Por lo tanto, se puede considerar como una herramienta para pruebas de seguridad de aplicaciones estáticas (Pruebas de seguridad de aplicaciones estáticas, SAST).
Piezas de código que llamaron la atención al examinar un informe del analizador
El método
WriteableBitmapConverter atrae la atención inmediatamente con cuatro del mismo tipo de advertencias PVS-Studio:
- V3005 La variable 'opticalChannels [PixelFormats.Indexed1]' se asigna a sí misma. WriteableBitmapConverter.cs 22
- V3005 La variable 'opticalChannels [PixelFormats.Indexed8]' se asigna a sí misma. WriteableBitmapConverter.cs 23
- V3005 La variable 'opticalTypes [PixelFormats.Indexed1]' se asigna a sí misma. WriteableBitmapConverter.cs 50
- V3005 La variable 'opticalTypes [PixelFormats.Indexed8]' se asigna a sí misma. WriteableBitmapConverter.cs 51
static WriteableBitmapConverter() { optimumChannels = new Dictionary <PixelFormat, int>(); optimumChannels[PixelFormats.Indexed1] =
La clase
PixelFormats se define en el
espacio de nombres System.Windows.Media y es una colección de varios formatos de píxeles. El analizador llama la atención sobre el hecho de que el método
WriteableBitmapConverter reasigna los valores a los elementos
opticalChannels [PixelFormats.Indexed1] y
opticalChannels [PixelFormats.Indexed8] , lo que no tiene ningún sentido práctico. No está claro si se trata de un error tipográfico simple o de otra cosa. Por cierto, esta sección de código demuestra claramente los beneficios de los analizadores estáticos. Cuando ves un montón de líneas del mismo tipo ante tus ojos, tus ojos comienzan a "desdibujarse", y tu atención se disipa; no es sorprendente que incluso después de la revisión del código un error tipográfico ingrese al programa. Y el analizador estático no tiene problemas de atención y no necesita descansar, por lo tanto, es más fácil encontrar tales errores.
Siente el poder y el poder del análisis estático.Advertencia de PVS-Studio :
V3021 Hay dos declaraciones 'if' con expresiones condicionales idénticas. La primera instrucción 'if' contiene el método return. Esto significa que la segunda declaración 'if' no tiene sentido 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))
Este error es un poco como el anterior. El programador hizo una situación en la que la misma condición se verifica dos veces. En este caso, esto no tiene sentido, entonces la rama de la
instrucción if "duplicada" no se ejecutará, ya que:
- si la primera expresión condicional es verdadera, el método saldrá;
- si la primera condición es falsa, la segunda también será falsa, ya que la variable que se está probando, t , no cambia entre expresiones condicionales.
El desarrollador debe verificar esta pieza de código. Es probable que en lugar de la segunda variable,
Vec2s deba ser alguna otra.
Advertencia de PVS-Studio :
V3010 Se
requiere el valor de retorno de la función '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(); }
El método
RotatedRectangleIntersection devuelve una matriz de elementos
Point2f a través del parámetro
intersectingRegion . Después de que el programa
complete la
intersección de la región con valores, se llama al método
ToString () en esta matriz
. Con los elementos de la matriz, no se producen cambios a partir de esto y no se realiza ningún trabajo útil en la última línea, por lo tanto, hay razones para sospechar que el desarrollador simplemente olvidó eliminarlo.
Advertencias de PVS-Studio :
- V3021 Hay dos declaraciones 'if' con expresiones condicionales idénticas. La primera instrucción 'if' contiene el método return. Esto significa que la segunda instrucción 'if' no tiene sentido Cv2_calib3d.cs 1370
- V3022 La expresión 'objectPoints == null' siempre es falsa. Cv2_calib3d.cs 1372
public static double CalibrateCamera(....) { if (objectPoints == null) throw new ArgumentNullException(nameof(objectPoints)); if (objectPoints == null) throw new ArgumentNullException(nameof(objectPoints)); .... }
En este caso, se duplicó un fragmento de código, debido a lo cual aparecieron dos advertencias. La primera advertencia indica que ambas
declaraciones if tienen la misma expresión condicional. Si esta expresión es verdadera, el método saldrá de la rama
entonces de la primera
instrucción if . Por esta razón, la segunda condición siempre será falsa, como lo indica la siguiente advertencia. Aparentemente, el texto fue copiado, pero olvidó corregirlo.
Lindo copiar y pegar.Otras advertencias similares del analizador:
- V3021 Hay dos declaraciones 'if' con expresiones condicionales idénticas. La primera instrucción 'if' contiene el método return. Esto significa que la segunda declaración 'if' no tiene sentido Cv2_calib3d.cs 1444
- V3022 La expresión 'objectPoints == null' siempre es falsa. Cv2_calib3d.cs 1446
Advertencia de PVS-Studio :
V3022 La expresión 'label == MarkerValue' siempre es falsa. 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;
En esta sección de código, se crea una variable de
etiqueta de cero. Cuando se cumple una determinada condición, se puede agregar una a esta variable. En este caso, en el código, el valor de la
etiqueta variable no cambia hacia abajo. En la línea marcada con una flecha, esta variable se compara con una constante igual a -1, lo que no tiene ningún sentido práctico.
Advertencia de PVS-Studio :
V3038 El argumento se pasó al método varias veces. Es posible que se pase otro argumento en su lugar. Cv2_photo.cs 124
public static void FastNlMeansDenoisingMulti(....) { .... NativeMethods.photo_fastNlMeansDenoisingMulti( srcImgPtrs, srcImgPtrs.Length, dst.CvPtr, imgToDenoiseIndex, templateWindowSize, h, templateWindowSize, searchWindowSize); .... }
Para entender lo que significa el analizador, echemos un vistazo a los parámetros del método
photo_fastNlMeansDenoisingMulti :
public static extern void photo_fastNlMeansDenoisingMulti( IntPtr[] srcImgs, int srcImgsLength, IntPtr dst, int imgToDenoiseIndex, int temporalWindowSize, float h, int templateWindowSize, int searchWindowSize)
Simplifique aún más para hacerlo muy obvio. Compara estas líneas:
NativeMethods.photo_fastNlMeansDenoisingMulti( .... templateWindowSize, .... templateWindowSize, ....); public static extern void photo_fastNlMeansDenoisingMulti( .... int temporalWindowSize, .... int templateWindowSize, ....)
El analizador llama la atención sobre el hecho de que el desarrollador usó la variable
templateWindowSize dos veces, aunque lo más probable es que
temporalWindowSize esté en el lugar de la primera mención de esta variable. También es sospechoso que el valor de
temporalWindowSize en el método
photo_fastNlMeansDenoisingMulti no se utilice en absoluto. Tal vez esto se hizo deliberadamente, pero en lugar de los desarrolladores, vale la pena echar un vistazo más de cerca a este código, ¿hay algún error?
Advertencias de analizador similares:
- V3038 El argumento se pasó al método varias veces. Es posible que se pase otro argumento en su lugar. Cv2_photo.cs 149
- V3038 El argumento se pasó al método varias veces. Es posible que se pase otro argumento en su lugar. Cv2_photo.cs 180
- V3038 El argumento se pasó al método varias veces. Es posible que se pase otro argumento en su lugar. Cv2_photo.cs 205
El siguiente error será un poco similar al anterior.
Advertencia PVS-Studio :
V3066 Posible orden incorrecto de argumentos pasados al método 'calib3d_Rodrigues_MatToVec': 'matrixM.CvPtr' y '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); .... } }
Veamos los parámetros
calib3d_Rodrigues_MatToVec public static extern void calib3d_Rodrigues_MatToVec( IntPtr vector, IntPtr matrix, IntPtr jacobian)
Quizás al llamar al método
calib3d_Rodrigues_MatToVec , los argumentos
matrixM.CvPtr y
vectorM.CvPtr se mezclaron. Los desarrolladores deberían echar un vistazo más de cerca a este código. Existe la posibilidad de que se produzca un error que interfiera con los cálculos correctos.
Advertencia de PVS-Studio :
V3063 Una parte de la expresión condicional siempre es falsa si se evalúa: datos == nulo. Mat.cs 3539
private void CheckArgumentsForConvert(....) { .... if (data == null) throw new ArgumentNullException(nameof(data)); MatType t = Type(); if (data == null || (data.Length * dataDimension)
El analizador indica que el segundo
dato de verificación
== nulo nunca será
verdadero , porque si en la primera condición los
datos son
nulos , se lanzará una excepción y la ejecución del programa no alcanzará la segunda verificación.
Entiendo que ya estás cansado, pero queda muy poco.Advertencia de PVS-Studio :
V3127 Se
encontraron dos fragmentos de código similares. Quizás, este es un error tipográfico y la variable 'ventana' debería usarse en lugar de '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));
Y luego el analizador encontró un error tipográfico. En esta sección del código, el valor de las variables se verifica como
nulo , y si se cumple esta condición, se lanza una excepción para cada una de las variables. Sin embargo, la variable de
ventana no es tan simple. Si esta variable es
nula , también se genera una excepción para ella, pero el texto de esta excepción se escribe incorrectamente. La
ventana variable en sí no aparece en el texto de esta excepción; en cambio,
src2 se indica
allí . Aparentemente, la última condición debería ser así:
if (window == null) throw new ArgumentNullException(nameof(window));
Advertencia de PVS-Studio :
V3142 Código inalcanzable detectado. Es posible que haya un error presente. MatOfT.cs 873
Ahora, para variar, veamos el caso en que el analizador tiene toda la razón al informar un código inalcanzable, pero no hay ningún error. Este es el caso cuando se puede decir que el analizador genera una advertencia que es correcta y falsa al mismo tiempo.
public new Mat<TElem> SubMat(params Range[] ranges) { Mat result = base.SubMat(ranges); return Wrap(result); }
El analizador afirma que la
declaración de devolución es inalcanzable aquí. Para verificar esto, mire el cuerpo del método
SubMat .
public Mat SubMat(params Range[] ranges) { throw new NotImplementedException(); }
Como puede ver, la función aún no se ha agregado y siempre produce una excepción. Y el analizador tiene razón cuando informa un código inalcanzable. Pero esto no se puede llamar un verdadero error.
Los siguientes tres errores encontrados por el analizador son del mismo tipo, pero son tan geniales que no pude evitar escribirlos todos.
Advertencia de PVS-Studio :
V3022 La expresión 'String.IsNullOrEmpty ("winName")' siempre es falsa. Cv2_highgui.cs 46
public static void DestroyWindow(string winName) { if (String.IsNullOrEmpty("winName")) .... }
Advertencia de PVS-Studio :
V3022 La expresión 'string.IsNullOrEmpty ("fileName")' siempre es falsa. FrameSource.cs 37
public static FrameSource CreateFrameSource_Video(string fileName) { if (string.IsNullOrEmpty("fileName")) .... }
Advertencia de PVS-Studio :
V3022 La expresión 'string.IsNullOrEmpty ("fileName")' siempre es falsa. FrameSource.cs 53
public static FrameSource CreateFrameSource_Video_CUDA(string fileName) { if (string.IsNullOrEmpty("fileName")) .... }
A veces, detrás de la advertencia del analizador
V3022 (la expresión siempre es verdadera / falsa) hay cosas realmente extrañas o divertidas. En los tres casos, se observa la misma situación. El código del método tiene un parámetro de tipo
cadena , cuyo valor debe verificarse. Sin embargo, no se verifica el valor de la variable, sino el literal de cadena con su nombre, es decir, el nombre es en vano citado.
Aparentemente, el desarrollador selló una vez y, usando copiar y pegar, propagó este error por código.
Conclusión
Los desarrolladores de OpenCvSharp han hecho un trabajo importante y excelente. Y yo, como usuario de esta biblioteca, les estoy muy agradecido. Gracias
Sin embargo, estando en el equipo de PVS-Studio y mirando el código, tengo que admitir que el problema de su calidad no se ha resuelto lo suficiente. Lo más probable es que los analizadores de código estático no se usen regularmente en este proyecto. Y muchos errores se corrigen con métodos más caros (pruebas, de acuerdo con las opiniones de los usuarios, por ejemplo). Y algunos generalmente permanecen para vivir durante mucho tiempo en el código, y solo los encontramos. Esta idea se presenta con más detalle en una breve
nota sobre el tema de la filosofía del uso de la metodología del análisis estático.
Dado que el proyecto está abierto y ubicado en GitHub, sus desarrolladores tienen la oportunidad de aprovechar la
opción de licencia gratuita PVS-Studio y comenzar a aplicar el análisis de forma regular.
Gracias por su atencion
Descargue y pruebe sus proyectos con la versión de prueba de PVS-Studio.

Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Ekaterina Nikiforova.
Comprobación del OpenCvSharp Wrapper para OpenCV con PVS-Studio .