11
votes

Façons efficaces de déterminer l'inclinaison d'une image

J'essaie d'écrire un programme pour déterminer de manière programmable l'inclinaison ou l'angle de rotation dans une image arbitraire.

Les images ont les propriétés suivantes: p>

  • consiste en un texte sombre sur un fond clair li>
  • contient occasionnellement des lignes horizontales ou verticales qui ne se croisent que à des angles de 90 degrés. li>
  • asymétrique entre -45 et 45 degrés. Li>
  • Voir Cette image comme référence (sa cérébrale 2,8 degrés). li> ul>

    Jusqu'à présent, j'ai proposé cette stratégie: dessinez un itinéraire de gauche à droite, sélectionnez toujours le pixel blanc le plus proche. Vraisemblablement, la route de gauche à droite préférera suivre le chemin entre les lignes de texte le long de l'inclinaison de l'image. P>

    Voici mon code: p>

    private bool IsWhite(Color c) { return c.GetBrightness() >= 0.5 || c == Color.Transparent; }
    
    private bool IsBlack(Color c) { return !IsWhite(c); }
    
    private double ToDegrees(decimal slope) { return (180.0 / Math.PI) * Math.Atan(Convert.ToDouble(slope)); }
    
    private void GetSkew(Bitmap image, out double minSkew, out double maxSkew)
    {
        decimal minSlope = 0.0M;
        decimal maxSlope = 0.0M;
        for (int start_y = 0; start_y < image.Height; start_y++)
        {
            int end_y = start_y;
            for (int x = 1; x < image.Width; x++)
            {
                int above_y = Math.Max(end_y - 1, 0);
                int below_y = Math.Min(end_y + 1, image.Height - 1);
    
                Color center = image.GetPixel(x, end_y);
                Color above = image.GetPixel(x, above_y);
                Color below = image.GetPixel(x, below_y);
    
                if (IsWhite(center)) { /* no change to end_y */ }
                else if (IsWhite(above) && IsBlack(below)) { end_y = above_y; }
                else if (IsBlack(above) && IsWhite(below)) { end_y = below_y; }
            }
    
            decimal slope = (Convert.ToDecimal(start_y) - Convert.ToDecimal(end_y)) / Convert.ToDecimal(image.Width);
            minSlope = Math.Min(minSlope, slope);
            maxSlope = Math.Max(maxSlope, slope);
        }
    
        minSkew = ToDegrees(minSlope);
        maxSkew = ToDegrees(maxSlope);
    }
    


5 commentaires

J'aime comment le code mélange Snake_Case, Camelcase et Pascalcase tout en un seul bloc de code. Clairement, j'écris trop de f #.


Pourquoi utilisez-vous décimal ? Il n'ajoute pas beaucoup de précision pour calculer la pente et vous devez le jeter à double lorsque vous le transmettez à la méthode ATAN .


@Cecil: Ajout et diviser un tas de doubles ont causé des problèmes de précision. Travailler avec des décimales à l'avance, puis la conversion au double à la fin semblait fonctionner avec un minimum de fausses.


Google et Google Scholar invitent les myriades hits pour «Document Skew Angle» (merci pour les mots-clés, le socle). Avez-vous eu un look là-bas pour des idées d'algorithme?


Super fil ici. Merci. Au lieu de simplement choisir le prochain pixel blanc, qu'en est-il de choisir le prochain pixel blanc qui est le plus éloigné de ses pixels noirs voisins régionaux? Une région la taille de Font_Height devrait bien fonctionner. La convolution avec un noyau peut le faire assez rapidement.


9 Réponses :


3
votes

Si le texte est laissé (à droite) aligné, vous pouvez déterminer la pente en mesurant la distance entre le bord gauche (à droite) de l'image et le premier pixel noir dans deux endroits aléatoires et calculez la pente à partir de celle-ci. Des mesures supplémentaires réduiraient l'erreur lors de la prise de temps supplémentaire.


2 commentaires

Si vous allez faire cet itinéraire, je choisirais quelque part environ 10 à 20 points d'échantillonnage aléatoires, puis jetez les anomalies statistiques (les échantillons entrant entre les lignes de texte). Ensuite, les échantillons restants doivent dessiner une ligne assez droite et vous pouvez les utiliser pour calculer la pente.


Basé sur une expérimentation limitée, je n'ai pas obtenu de meilleurs résultats en choisissant des points d'échantillonnage aléatoires vs échantillonnant tous les points. L'espace blanc dans l'image, tels que les paragraphes de séparation de l'espace, est échantillonné avec une pente près de zéro. Étant donné que l'échantillonnage aléatoire sélectionnera des chemins "plats" avec une fréquence proportionnelle au nombre total de chemins "plats" de toute l'image, je ne reçois pas une meilleure approximation, seulement un non déterministe. Cependant, j'ai trouvé que la moyenne de tous les chemins dans une écart type de la moyenne m'a donné une meilleure moyenne globale.



5
votes

GetPixel est lent. Vous pouvez obtenir une commande de magnitude accélère à l'aide de l'approche répertorie ici .


0 commentaires

3
votes

D'abord, je dois dire que j'aime l'idée. Mais je n'ai jamais eu à faire cela auparavant et je ne suis pas sûr de ce que tout suggère d'améliorer la fiabilité. La première chose à laquelle je peux penser est cette idée de jeter des anomalies statistiques. Si la pente change soudainement fortement, vous savez que vous avez trouvé une section blanche de l'image qui plonge dans le bord asymétrique (aucun jeu de mots destiné) de vos résultats. Donc, vous voudriez jeter cette affaire d'une manière ou d'une autre.

Mais d'un point de vue de la performance, un certain nombre d'optimisations que vous pourriez faire, qui peut s'additionner.

nommément, je changerais cet extrait. votre boucle interne de ceci: xxx

à ceci: xxx

C'est le même effet mais devrait réduire considérablement le nombre de appels à GetPixel.

envisagez également de mettre les valeurs qui ne changent pas en variables avant le début de la folie. Des choses comme l'image.Height and Image.Width Ayez une légère surcharge chaque fois que vous les appelez. Donc, stockez ces valeurs dans vos propres variables avant de commencer les boucles. La chose que je me dis toujours lorsque vous traitez avec des boucles imbriquées consiste à optimiser tout à l'intérieur de la boucle la plus intérieure au détriment de tout le reste.

aussi ... Comme Vinko Vrsalovic suggéra, vous pouvez regarder son alternative GetPixel pour encore un autre boost à la vitesse.


1 commentaires

Parce que isBlack ==! C'est white, la valeur de retour de ISBLACK peut être mise en cache de la même manière et utiliser pour les déclarations si si elles sont.



2
votes

À première vue, votre code a l'air trop naïf. Ce qui explique pourquoi cela ne fonctionne pas toujours.

J'aime l'approche Steve Battham suggéré, Mais cela pourrait rencontrer des problèmes si vous avez des images de fond.

Une autre approche qui aide souvent aux images est de les floue en premier. Si vous avez suffisamment d'image de votre exemple, chaque ligne de texte se retrouvera comme une ligne lisse floue. Vous appliquez ensuite une sorte d'algorithme à essentiellement faire une régression analisys. Il y a beaucoup de façons de faire cela, et beaucoup d'exemples sur le net.

La détection des bords pourrait être utile, ou cela pourrait causer plus de problèmes que sa valeur.

Au fait, un flou gaussien peut être mis en œuvre très efficacement si vous recherchez assez fort pour le code. Sinon, je suis sûr qu'il y a beaucoup de bibliothèques disponibles. N'avez pas beaucoup fait ces derniers temps, alors n'a pas de liens à la main. Mais une recherche de bibliothèque de traitement d'images vous obtiendra de bons résultats.

Je suppose que vous profitez du plaisir de résoudre ce problème, donc pas beaucoup dans la mise en œuvre réelle de Detalis ici.


0 commentaires

6
votes

J'ai apporté quelques modifications à mon code et cela fonctionne certainement beaucoup plus vite, mais ce n'est pas très précis.

J'ai fait les améliorations suivantes:


1 commentaires

Si vous vous demandez que vous vous demandez, je mélange des décimales et doublez de manière étrange pour des raisons de précision. Je continue à obtenir Nan lors du calcul de la régression linéaire en utilisant des doubles, mais cela fonctionne bien en utilisant décimal.



1
votes

Mesurer l'angle de chaque ligne semble être trop sucré, en particulier compte tenu des performances de GetPixel.

Je me demande si vous auriez une meilleure performance de la chance en recherchant un triangle blanc dans le coin supérieur gauche ou supérieur droit (en fonction de la direction inclinée) et de mesurer l'angle de l'hypoténuse. Tout le texte doit suivre le même angle de la page, et le coin supérieur gauche d'une page ne se trompera pas par les descendeurs ou les espaces de contenu au-dessus de celui-ci.

Un autre conseil à considérer: plutôt que flou, travailler dans une résolution considérablement réduite. Cela vous donnera les deux données plus fluides dont vous avez besoin et moins d'appels getpixel.

Par exemple, j'ai créé une routine de détection de page vierge une fois dans .NET pour des fichiers TIFF faxés qui ont simplement rééclamé la page entière à un seul pixel et testé la valeur d'une valeur de seuil de blanc.


0 commentaires

0
votes

Votre dernière sortie me confondre un peu. Lorsque vous superposez les lignes bleues sur l'image source, avez-vous eu le compensé un peu? On dirait que les lignes bleues sont d'environ 5 pixels au-dessus du centre du texte.

Je ne suis pas sûr de ce décalage, mais vous avez certainement un problème avec la ligne dérivée "dérivée" loin du mauvais angle. Il semble avoir un biais trop fort pour produire une ligne horizontale.

Je me demande si vous augmentez votre fenêtre de masque de 3 pixels (centre, une ci-dessus, une ci-dessous) à 5 pourrait améliorer cela (deux ci-dessus, deux ci-dessous). Vous obtiendrez également cet effet si vous suivez la suggestion de Richardtallent et rééchantillonner l'image plus petite.


0 commentaires

1
votes

Quelles sont vos contraintes en termes de temps?

La transformation de Hough est un mécanisme très efficace pour déterminer l'angle d'inclinaison d'une image. Cela peut être coûteux à temps, mais si vous allez utiliser Gaussien BLUR, vous brûlez déjà une pile de time de la CPU. Il existe également d'autres moyens d'accélérer la transformation de Hough qui impliquent un échantillonnage d'image créatif.


0 commentaires

0
votes

Application de recherche de chemin très cool. Je me demande si cette autre approche aiderait ou blesserait avec votre ensemble de données particulier.

supposons une image noire et blanche:

  • Projetez tous les pixels noirs à droite (est). Cela devrait donner un résultat d'un tableau unidimensionnel avec une taille d'image_height. Appelez la toile de tableau.
  • Lorsque vous projetez tous les pixels Est, gardez la piste numériquement du nombre de pixels dans chaque poubelle de toile.
  • Faites pivoter l'image un nombre arbitraire de degrés et de reconstitution.
  • Choisissez le résultat qui donne les plus hautes pics et les vallées les plus basses pour les valeurs de la toile.

    J'imagine que cela ne fonctionnera pas bien si vous devez vous rendre compte d'un réel -45 -> +45 degrés d'inclinaison. Si le nombre réel est plus petit (? +/- 10 degrés), cela pourrait être une très bonne stratégie. Une fois que vous avez un résultat initial, vous pourriez envisager de relancer avec une plus petite augmentation de degrés à régler la réponse. Je pourrais donc essayer d'écrire cela avec une fonction acceptée un degré de float_tick comme un parm afin que je puisse exécuter à la fois une passe grossière et fine (ou un spectre de grosseur ou de finesse) avec le même code.

    Cela pourrait être calculé en calcul. Pour optimiser, vous pouvez envisager de sélectionner une partie de l'image à la répétition-test-test-test-test-répéter.


0 commentaires