O OpenCV é uma biblioteca de algoritmos de visão computacional, processamento de imagem e algoritmos numéricos de uso geral com código aberto, familiar a muitos desenvolvedores de C ++. Além do C ++, o OpenCV também está sendo desenvolvido para Python, Java, Ruby, Matlab, Lua e outras linguagens. Como entre esses idiomas não existe o meu principal, o C #, decidi prestar atenção à biblioteca OpenCvSharp - wrapper em C # e verificar este projeto. O que veio disso pode ser encontrado neste artigo.
1. Introdução
Antes, antes de ingressar no PVS-Studio, participei de robótica em exposições. Minhas tarefas incluíam a correção mais básica (se ocorresse um grande colapso, o robô era entregue a outra pessoa), bem como o desenvolvimento de uma ampla variedade de software e utilitários.
Cansado e recém-chegado a uma nova cidade, eu juntamente com um robô KIKI recém-desembalado.Falando em desenvolvimento. Isso foi bem engraçado. Toda vez que uma idéia surgia de alguém da equipe, o que mais surpreenderia os convidados das exposições, levávamos essa questão para discussão geral e, se a idéia era boa, retomavamos a implementação. Certa vez, surgiu a ideia de criar uma criatura que responde a um rosto humano com um discurso de boas-vindas.
Depois de pesquisar na Internet por uma biblioteca para minhas necessidades, deparei-me com o site OpenCV, bibliotecas de algoritmos de visão computacional. Logo fiquei decepcionado - o OpenCV foi implementado em C ++. Meu conhecimento sobre os benefícios que recebi da faculdade claramente não era suficiente. Portanto, pesquisando brevemente, me deparei com o OpenCvSharp - wrapper desta biblioteca em C #, meu idioma principal. Seis meses se passaram desde então, o programa já foi escrito e usado por um longo tempo, e eu decidi ficar sob o capô do OpenCvSharp e verificar seu código-fonte usando o analisador estático PVS-Studio.
Projeto Auditado
O OpenCvSharp é um wrapper sobre o OpenCV para usar a biblioteca em projetos C #. Biblioteca OpenCV, a propósito,
também verificamos . As vantagens do OpenCvSharp incluem uma grande coleção de amostras de código, plataforma cruzada (pode funcionar em qualquer plataforma compatível com o Mono) e facilidade de instalação.
O wrapper é um projeto pequeno e contém cerca de 112.200 linhas de código C #. Destes, 1,2% são comentários, que, aliás, são de alguma forma suspeitosamente pequenos. Mas para um projeto tão pequeno, existem muitos erros. No artigo, escrevi mais de 20 erros, mas havia outros que não eram tão interessantes ou óbvios.
Analisador de código PVS-Studio
O PVS-Studio é uma ferramenta para detectar erros e possíveis vulnerabilidades no código fonte dos programas escritos em C, C ++, C # e Java. É executado no Windows, Linux e macOS. Além de códigos, erros e erros de digitação inatingíveis, o PVS-Studio pode detectar possíveis vulnerabilidades, conforme observado acima. Portanto, pode ser considerado como um meio de teste de segurança de aplicativo estático (Static Application Security Testing, SAST).
Partes do código que chamaram a atenção ao examinar um relatório do analisador
O método
WriteableBitmapConverter atrai a atenção imediatamente com quatro do mesmo tipo de avisos do PVS-Studio:
- V3005 A variável 'optimumChannels [PixelFormats.Indexed1]' é atribuída a si mesma. WriteableBitmapConverter.cs 22
- V3005 A variável 'optimumChannels [PixelFormats.Indexed8]' é atribuída a si mesma. WriteableBitmapConverter.cs 23
- V3005 A variável 'optimumTypes [PixelFormats.Indexed1]' é atribuída a si mesma. WriteableBitmapConverter.cs 50
- V3005 A variável 'optimumTypes [PixelFormats.Indexed8]' é atribuída a si mesma. WriteableBitmapConverter.cs 51
static WriteableBitmapConverter() { optimumChannels = new Dictionary <PixelFormat, int>(); optimumChannels[PixelFormats.Indexed1] =
A classe
PixelFormats é definida no
espaço para nome System.Windows.Media e é uma coleção de vários formatos de pixel. O analisador chama a atenção para o fato de que o método
WriteableBitmapConverter reatribui os valores aos elementos
optimumChannels [PixelFormats.Indexed1] e
optimumChannels [PixelFormats.Indexed8] , o que não faz sentido prático. Não está claro se se trata de um erro de digitação simples ou de alguma outra coisa. A propósito, esta seção do código demonstra claramente os benefícios dos analisadores estáticos. Quando você vê um monte de linhas do mesmo tipo diante de seus olhos, seus olhos começam a "embaçar" e sua atenção se dissipa - não é de surpreender que, mesmo após a revisão do código, um erro de digitação entre no programa. E o analisador estático não tem problemas com atenção e não precisa descansar; portanto, é mais fácil encontrar esses erros.
Sinta o poder e o poder da análise estática.PVS-Studio Warning :
V3021 Existem duas instruções 'if' com expressões condicionais idênticas. A primeira instrução 'if' contém retorno de método. Isso significa que a segunda instrução 'if' não faz 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 erro é um pouco como o anterior. O programador criou uma situação em que a mesma condição é verificada duas vezes. Nesse caso, isso não faz sentido - então o ramo da
instrução if "duplicated"
if não será executado, pois:
- se a primeira expressão condicional for verdadeira, o método será encerrado;
- se a primeira condição for falsa, a segunda também será falsa, pois a variável que está sendo testada - t - não muda entre expressões condicionais.
O desenvolvedor deve verificar duas vezes esse trecho de código. É provável que, no lugar da segunda variável,
Vec2s deva ser outra.
PVS-Studio Warning :
V3010 O valor de retorno da função 'ToString' deve ser utilizado. 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(); }
O método
RotatedRectangleIntersection retorna uma matriz de elementos
Point2f através do parâmetro
intersectingRegion . Depois que o programa preenche a
intersectingRegion com valores, o método
ToString () é chamado nessa matriz
. Com os elementos da matriz, nenhuma alteração ocorre e nenhum trabalho útil é feito na última linha; portanto, há motivos para suspeitar que o desenvolvedor simplesmente esqueceu de removê-la.
Avisos do PVS-Studio :
- V3021 Existem duas instruções 'if' com expressões condicionais idênticas. A primeira instrução 'if' contém retorno de método. Isso significa que a segunda declaração 'if' não faz sentido Cv2_calib3d.cs 1370
- A expressão V3022 'objectPoints == null' é sempre 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)); .... }
Nesse caso, um fragmento de código foi duplicado, devido ao qual dois avisos apareceram. O primeiro aviso indica que ambas as
instruções if têm a mesma expressão condicional. Se essa expressão for verdadeira, o método sairá do ramo
então da primeira
instrução if . Por esse motivo, a segunda condição sempre será falsa, conforme indicado no aviso a seguir. Aparentemente, o texto foi copiado, mas esqueceu de corrigir.
Copiar e colar bonito.Outros avisos semelhantes do analisador:
- V3021 Existem duas instruções 'if' com expressões condicionais idênticas. A primeira instrução 'if' contém retorno de método. Isso significa que a segunda declaração 'if' não faz sentido Cv2_calib3d.cs 1444
- A expressão V3022 'objectPoints == null' é sempre falsa. Cv2_calib3d.cs 1446
Aviso do PVS-Studio :
A expressão
V3022 'label == MarkerValue' é sempre 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;
Nesta seção do código, uma variável de
rótulo zero é criada. Quando uma determinada condição é atendida, uma pode ser adicionada a essa variável. Nesse caso, no código, o valor do
rótulo da variável não muda para baixo. Na linha marcada com uma seta, essa variável é comparada com uma constante igual a -1, o que não faz sentido prático.
PVS-Studio Warning :
V3038 O argumento foi passado para o método várias vezes. É possível que outro argumento seja passado. Cv2_photo.cs 124
public static void FastNlMeansDenoisingMulti(....) { .... NativeMethods.photo_fastNlMeansDenoisingMulti( srcImgPtrs, srcImgPtrs.Length, dst.CvPtr, imgToDenoiseIndex, templateWindowSize, h, templateWindowSize, searchWindowSize); .... }
Para entender o que o analisador significa, vejamos os parâmetros do 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 ainda mais para torná-lo muito óbvio. Compare estas linhas:
NativeMethods.photo_fastNlMeansDenoisingMulti( .... templateWindowSize, .... templateWindowSize, ....); public static extern void photo_fastNlMeansDenoisingMulti( .... int temporalWindowSize, .... int templateWindowSize, ....)
O analisador chama a atenção para o fato de o desenvolvedor ter usado a variável
templateWindowSize duas vezes, embora provavelmente o
temporalWindowSize deva estar no lugar da primeira menção dessa variável. Também é suspeito que o valor de
temporalWindowSize no método
photo_fastNlMeansDenoisingMulti não
seja usado. Talvez isso tenha sido feito deliberadamente, mas no lugar dos desenvolvedores, vale a pena examinar mais de perto esse código. Existe um erro aí?
Avisos semelhantes do analisador:
- V3038 O argumento foi passado para o método várias vezes. É possível que outro argumento seja passado. Cv2_photo.cs 149
- V3038 O argumento foi passado para o método várias vezes. É possível que outro argumento seja passado. Cv2_photo.cs 180
- V3038 O argumento foi passado para o método várias vezes. É possível que outro argumento seja passado. Cv2_photo.cs 205
O erro a seguir será um pouco semelhante ao anterior.
Aviso PVS-Studio :
V3066 Possível ordem incorreta de argumentos passada para o método 'calib3d_Rodrigues_MatToVec': 'matrixM.CvPtr' e '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); .... } }
Vejamos os parâmetros
calib3d_Rodrigues_MatToVec public static extern void calib3d_Rodrigues_MatToVec( IntPtr vector, IntPtr matrix, IntPtr jacobian)
Talvez ao chamar o método
calib3d_Rodrigues_MatToVec , os argumentos
matrixM.CvPtr e
vectorM.CvPtr tenham sido misturados. Os desenvolvedores devem examinar mais de perto esse código. Existe a possibilidade de que um erro ocorra, o que interfere nos cálculos corretos.
PVS-Studio Warning :
V3063 Uma parte da expressão condicional é sempre falsa se for avaliada: 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)
O analisador indica que os segundos
dados de verificação
== null nunca serão
verdadeiros , porque se na primeira condição os
dados forem
nulos , uma exceção será lançada e a execução do programa não atingirá a segunda verificação.
Entendo que você já está cansado, mas resta muito pouco.PVS-Studio Warning :
V3127 Dois fragmentos de código semelhantes foram encontrados. Talvez seja um erro de digitação e a variável 'window' deva ser usada em vez 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));
E então o analisador encontrou um erro de digitação. Nesta seção do código, o valor das variáveis é verificado como
nulo e, se essa condição for atendida, uma exceção será lançada para cada uma das variáveis. No entanto, a variável
window não é tão simples. Se essa variável for
nula , também será gerada uma exceção para ela, mas o texto dessa exceção será digitado incorretamente. A
janela da variável em si não aparece no texto desta exceção; em vez disso,
src2 é indicado
lá . Aparentemente, a última condição deve ser assim:
if (window == null) throw new ArgumentNullException(nameof(window));
Aviso do PVS-Studio :
V3142 Código inacessível detectado. É possível que haja um erro. MatOfT.cs 873
Agora, para variar, vejamos o caso em que o analisador está realmente certo ao relatar um código inacessível, mas não há erro. É o caso quando se pode dizer que o analisador gera um aviso que é correto e falso ao mesmo tempo.
public new Mat<TElem> SubMat(params Range[] ranges) { Mat result = base.SubMat(ranges); return Wrap(result); }
O analisador alega que a
declaração de retorno está inacessível aqui. Para verificar isso, observe o corpo do método
SubMat .
public Mat SubMat(params Range[] ranges) { throw new NotImplementedException(); }
Como você pode ver, a função ainda não foi adicionada e sempre lança uma exceção. E o analisador está certo ao relatar um código inacessível. Mas isso não pode ser chamado de um erro real.
Os três erros a seguir encontrados pelo analisador são do mesmo tipo, mas são tão legais que não pude deixar de escrevê-los.
Aviso do PVS-Studio :
A expressão
V3022 'String.IsNullOrEmpty ("winName")' é sempre falsa. Cv2_highgui.cs 46
public static void DestroyWindow(string winName) { if (String.IsNullOrEmpty("winName")) .... }
Aviso do PVS-Studio :
A expressão
V3022 'string.IsNullOrEmpty ("fileName")' é sempre falsa. FrameSource.cs 37
public static FrameSource CreateFrameSource_Video(string fileName) { if (string.IsNullOrEmpty("fileName")) .... }
Aviso do PVS-Studio :
A expressão
V3022 'string.IsNullOrEmpty ("fileName")' é sempre falsa. FrameSource.cs 53
public static FrameSource CreateFrameSource_Video_CUDA(string fileName) { if (string.IsNullOrEmpty("fileName")) .... }
Às vezes, por trás do aviso do analisador
V3022 (a expressão é sempre verdadeira / falsa), existem coisas realmente estranhas ou engraçadas. Nos três casos, a mesma situação é observada. O código do método possui um parâmetro do tipo
string , cujo valor deve ser verificado. No entanto, não é o valor da variável que está marcada, mas a string literal com seu nome, ou seja, o nome é em vão citado.
Aparentemente, o desenvolvedor selou uma vez e, usando copiar e colar, propagou esse erro por código.
Conclusão
Os desenvolvedores do OpenCvSharp fizeram um trabalho importante e excelente. E eu, como usuário desta biblioteca, sou muito grato a eles. Obrigada
No entanto, estando na equipe PVS-Studio e analisando o código, devo admitir que o problema de sua qualidade não foi resolvido o suficiente. Provavelmente, analisadores de código estático não são usados regularmente neste projeto. E muitos erros são corrigidos por métodos mais caros (teste, de acordo com avaliações de usuários, por exemplo). E alguns geralmente permanecem por muito tempo no código, e apenas os encontramos. Essa idéia é apresentada com mais detalhes em uma breve
nota sobre o tópico da filosofia de uso da metodologia de análise estática.
Como o projeto é aberto e localizado no GitHub, seus desenvolvedores têm a oportunidade de aproveitar a
opção de licenciamento gratuito PVS-Studio e começar a aplicar a análise regularmente.
Obrigado pela atenção.
Faça o download e teste seus projetos usando a versão de avaliação do PVS-Studio.

Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Ekaterina Nikiforova.
Verificando o OpenCvSharp Wrapper para OpenCV com PVS-Studio .