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>
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);
}
9 Réponses :
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. P>
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.
GetPixel est lent. Vous pouvez obtenir une commande de magnitude accélère à l'aide de l'approche répertorie ici . p>
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. P>
nommément, je changerais cet extrait. votre boucle interne de ceci: p> à ceci: p> C'est le même effet mais devrait réduire considérablement le nombre de appels à GetPixel. p> 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. P> aussi ... Comme Vinko Vrsalovic suggéra, vous pouvez regarder son alternative GetPixel pour encore un autre boost à la vitesse. P> p>
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.
À première vue, votre code a l'air trop naïf. Ce qui explique pourquoi cela ne fonctionne pas toujours. P>
J'aime l'approche Steve Battham suggéré, Mais cela pourrait rencontrer des problèmes si vous avez des images de fond. P>
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. p>
La détection des bords pourrait être utile, ou cela pourrait causer plus de problèmes que sa valeur. P>
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. P>
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. P>
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: p>
en utilisant suggestion de Vinko A >, J'évite GetPixel en faveur de travailler directement avec des octets directement, le code fonctionne maintenant à la vitesse dont j'avais besoin. P> li>
Mon code d'origine a simplement utilisé "ISBLACK" et "ISWHITE", mais ce n'est pas assez granulaire. Le code d'origine trace les chemins suivants via l'image: p>
http://img43.imageshack.us/img43/1545/tilted3degtextoriginalw.gif p>
Notez qu'un certain nombre de chemins passent dans le texte. En comparant mon centre, ci-dessus et ci-dessous des chemins de la valeur de luminosité réelle em> et en sélectionnant le pixel le plus brillant. Fondamentalement, je traite le bitmap comme une chaîne de hauteur et le chemin de gauche à droite suit les contours de l'image, entraînant un meilleur chemin: P>
http://img10.imageshack.us/img10/5807/tilted3degtextextfr p>
Comme suggéré par Toomalkster , Un flou gaussien lisse la carte de hauteur, je reçois encore de meilleurs résultats: p>
http://img197.imageshack.us/img197/742/tilted3degtextexblocurragedwi.gif p>
Étant donné que ce n'est que du code prototype, j'ai flou l'image à l'aide de Gimp, je n'ai pas écrit ma propre fonction de flou. P>
Le chemin sélectionné est plutôt bon pour un algorithme gourmand. P> li>
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.
Mesurer l'angle de chaque ligne semble être trop sucré, en particulier compte tenu des performances de GetPixel. P>
Je me demande si vous auriez une meilleure performance de la chance en recherchant un triangle blanc fort> 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. P>
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. P>
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. P>
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. P>
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. P>
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. P>
Quelles sont vos contraintes en termes de temps? p>
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. P>
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. P>
supposons une image noire et blanche: p>
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. P>
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. P>
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 code>? Il n'ajoute pas beaucoup de précision pour calculer la pente et vous devez le jeter àdouble code> lorsque vous le transmettez à la méthodeATAN code>.@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.