27

画像と画像上の特定の点に付けられた一連のラベルが与えられた場合、特定の制約 (両側にほぼ同じ数のラベル、ほぼ等距離のラベル) で画像の側面にラベルを配置するアルゴリズムを探しています、交差する線なしでラベルをそれぞれのポイントに接続する線)。

さて、おおよその解は通常、この例のように(ラベルが参照する点の) Y 座標でラベルを並べることによって非常に素朴に見つけることができます(概念の証明のみであり、実際のデータの精度やその他の点は無視してください!)。

交差しないという条件を満たすために、いくつかのアイデアが思い浮かびました。

  • 遺伝的アルゴリズムを使用して、クロスオーバーのないラベルの順序を見つけます。
  • そのような順序付けを検索するには、別の方法 (動的計画法アルゴリズムなど) を使用します。
  • 上記のアルゴリズムのいずれかを使用して、間隔と順序の変動を考慮して、交差の数と均等な間隔からの変動を最小限に抑えるソリューションを見つけます。
  • おそらく、特定の基準内でラベルの可能なすべての順序付けをブルート検索するために使用できる基準があります (距離が X より大きい場合は、2 つのラベルを並べ替えないでください)。
  • 他のすべてが失敗した場合は、何百万ものランダムな順序付け/間隔のオフセットを試して、交差/間隔の変動が最小になるものを選択してください。(利点: プログラミングが簡単で、おそらく十分な解決策が見つかるでしょう。わずかな欠点ですが、ショーストッパーではありません: アプリケーション中にその場で実行して、ユーザーが画像のレイアウト/サイズを変更できるようにすることはできません。 )

これらのいずれかに着手する前に、他の人の意見を歓迎します。他の誰かが同様の問題を経験していて、上記の方法のいずれかの成功/失敗について報告する情報を持っていますか、またはそれらがより良い/私に起こっていないより簡単な解決策はありますか?ご意見ありがとうございます。

4

7 に答える 7

14

Lucas Bradsheet の優等論文Labeling Maps using Multi-Objective Evolutionary Algorithmsには、これに関する非常に良い議論があります。

まず、このホワイト ペーパーでは、ラベル付けの品質に関するさまざまな指標に使用できる指標を作成します。

たとえば、明快さ (サイトとラベルの間のマッピングがどれだけ明白か): 明快さ(s)=r s +r s 1/r t
ここで、r sはサイトとそのラベルの間の距離、r tはサイトとラベルの間の距離です。サイトとそこに最も近い他のラベル)。

また、ラベル、サイト、ボーダー間の競合、およびラベルの密度と対称性の測定に役立つメトリックも備えています。次に、Bradsheet は多目的遺伝的アルゴリズムを使用して、実行可能なソリューションの「パレート フロンティア」を生成します。また、彼が個人をどのように変異させたかについての情報と、アルゴリズムの速度を改善するためのメモも含まれています。

そこには多くの詳細があり、考えるための良い材料を提供するはずです.

于 2012-11-08T15:52:07.183 に答える
9

情報設計のことは少し忘れましょう。このタスクは、PCB ルーティング アルゴリズムに関連するいくつかの記憶を思い起こさせます。実際には、次のような多くの一般的な要件があります。

  1. 交差点の最適化
  2. サイズの最適化
  3. ギャップの最適化

したがって、最初のタスクを PCB ルーティングに似たものに変えることができる可能性があります。

利用可能な情報はたくさんありますが、Tan Yan による PCB ルーティングに関するアルゴリズムの研究に目を通すことをお勧めします。

多くの詳細と数十のヒントを提供します。

現在のタスクへの適応

アイデアは、画像とラベルのマーカーを 2 組のピンとして扱い、エスケープ ルーティングを使用してタスクを解決することです。通常、PCB 領域はピンの配列として表されます。可能な最適化を使用して、画像に対して同じことを行うことができます。

  1. コントラストの低い領域を避ける
  2. テキストボックスがある場合は避ける

したがって、タスクは「未使用ピンの場合のルーティング」に削減できます。

ここに画像の説明を入力

最終結果は、要求されたスタイルに非常に近いものになる可能性があります。

ここに画像の説明を入力

Tan Yan による PCB 配線に関するアルゴリズム研究は、継続するのに適した場所です。

その他の注意事項

類似性を強調するために、描画のスタイルを少し変更します。

ここに画像の説明を入力

見栄えと読みやすさを維持しながら、逆変換を行うことは大きな問題にはなりません。

ここに画像の説明を入力

とにかく、シンプルさの達人 (たとえば私のような) は、数分を費やして、より良いもの (または別のもの) を発明することができます。

ここに画像の説明を入力

私にとっては、少なくともこの段階では、曲線は完全な解決策のようには見えません。とにかく、拡張の余地があることを示そうとしただけなので、PCB ルーティング アプローチはオプションと見なすことができます。

于 2012-11-17T14:13:03.413 に答える
8

1 つのオプションは、それを整数計画問題に変えることです。

ダイアグラムの外側に分布しているn pointsとしましょう。n corresponding labels

考えられる線の数は です。考えられる交点をすべて見ると、交点は (考えられるすべての線が表示された場合)n^2よりも少なくなります。n^4

整数計画問題では、次の制約を追加します。

(回線がオンになっている (つまり、画面に表示されている) かどうかを判断するため)

  1. ダイアグラム上の各ポイントに対して、それに接続する可能性のある n 本のラインのうちの 1 つだけをオンにする必要があります。

  2. ラベルごとに、接続可能な n 回線のうちの 1 つだけをオンにします。

  3. 交差するライン セグメント line1 と line2 の各ペアに対して、これらのラインの 0 または 1 つだけをオンにすることができます。

  4. 必要に応じて、スイッチがオンになっているすべての回線の合計距離を最小限に抑えることができます。これにより、審美性が向上します。

これらの制約がすべて同時に成立する場合、解決策があります。

ここに画像の説明を入力

以下のコードは、24 個のランダムな点について上記の図を作成しました。

15 かそこら以上のポイントを獲得し始めると、プログラムの実行時間が遅くなり始めます。

デフォルトのソルバーでPULPパッケージを使用しました。表示には PyGame を使用しました。

コードは次のとおりです。

__author__ = 'Robert'

import pygame
pygame.font.init()
import pulp
from random import randint

class Line():
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2
        self.length = (p1[0] - p2[0])**2 + (p1[1] - p2[1])**2

    def intersect(self, line2):
        #Copied some equations for wikipedia. Not sure if this is the best way to check intersection.
        x1, y1 = self.p1
        x2, y2 = self.p2
        x3, y3 = line2.p1
        x4, y4 = line2.p2
        xtop = (x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4)
        xbottom = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)
        ytop = (x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4)
        ybottom = xbottom
        if xbottom == 0:
            #lines are parallel. Can only intersect if they are the same line. I'm not checking that however,
            #which means there could be a rare bug that occurs if more than 3 points line up.
            if self.p1 in (line2.p1, line2.p2) or self.p2 in (line2.p1, line2.p2):
                return True
            return False
        x = float(xtop) / xbottom
        y = float(ytop) / ybottom
        if min(x1, x2) <= x <= max(x1, x2) and min(x3, x4) <= x <= max(x3, x4):
            if min(y1, y2) <= y <= max(y1, y2) and min(y3, y4) <= y <= max(y3, y4):
                return True
        return False

def solver(lines):
    #returns best line matching
    lines = list(lines)
    prob = pulp.LpProblem("diagram labelling finder", pulp.LpMinimize)

    label_points = {} #a point at each label
    points = {} #points on the image
    line_variables = {}
    variable_to_line = {}

    for line in lines:
        point, label_point = line.p1, line.p2
        if label_point not in label_points:
            label_points[label_point] = []
        if point not in points:
            points[point] = []
        line_on = pulp.LpVariable("point{0}-point{1}".format(point, label_point),
            lowBound=0, upBound=1, cat=pulp.LpInteger) #variable controls if line used or not
        label_points[label_point].append(line_on)
        points[point].append(line_on)
        line_variables[line] = line_on
        variable_to_line[line_on] = line

    for lines_to_point in points.itervalues():
        prob += sum(lines_to_point) == 1 #1 label to each point..

    for lines_to_label in label_points.itervalues():
        prob += sum(lines_to_label) == 1 #1 point for each label.

    for line1 in lines:
        for line2 in lines:
            if line1 > line2 and line1.intersect(line2):
                line1_on = line_variables[line1]
                line2_on = line_variables[line2]
                prob += line1_on + line2_on  <= 1 #only switch one on.

    #minimize length of switched on lines:
    prob += sum(i.length * line_variables[i] for i in lines)

    prob.solve()
    print prob.solutionTime
    print pulp.LpStatus[prob.status] #should say "Optimal"
    print len(prob.variables())

    for line_on, line in variable_to_line.iteritems():
        if line_on.varValue > 0:
            yield line #yield the lines that are switched on

class Diagram():
    def __init__(self, num_points=20, width=700, height=800, offset=150):
        assert(num_points % 2 == 0) #if even then labels align nicer (-:
        self.background_colour = (255,255,255)
        self.width, self.height = width, height
        self.screen = pygame.display.set_mode((width, height))
        pygame.display.set_caption('Diagram Labeling')
        self.screen.fill(self.background_colour)
        self.offset = offset
        self.points = list(self.get_points(num_points))
        self.num_points = num_points
        self.font_size = min((self.height - 2 * self.offset)//num_points, self.offset//4)

    def get_points(self, n):
        for i in range(n):
            x = randint(self.offset, self.width - self.offset)
            y = randint(self.offset, self.height - self.offset)
            yield (x, y)

    def display_outline(self):
        w, h = self.width, self.height
        o = self.offset
        outline1 = [(o, o), (w - o, o), (w - o, h - o), (o, h - o)]
        pygame.draw.lines(self.screen, (0, 100, 100), True, outline1, 1)
        o = self.offset - self.offset//4
        outline2 = [(o, o), (w - o, o), (w - o, h - o), (o, h - o)]
        pygame.draw.lines(self.screen, (0, 200, 0), True, outline2, 1)

    def display_points(self, color=(100, 100, 0), radius=3):
        for point in self.points:
            pygame.draw.circle(self.screen, color, point, radius, 2)

    def get_label_heights(self):
        for i in range((self.num_points + 1)//2):
            yield self.offset + 2 * i * self.font_size

    def get_label_endpoints(self):
        for y in self.get_label_heights():
            yield (self.offset, y)
            yield (self.width - self.offset, y)

    def get_all_lines(self):
        for point in self.points:
            for end_point in self.get_label_endpoints():
                yield Line(point, end_point)


    def display_label_lines(self, lines):
        for line in lines:
            pygame.draw.line(self.screen, (255, 0, 0), line.p1, line.p2, 1)

    def display_labels(self):
        myfont = pygame.font.SysFont("Comic Sans MS", self.font_size)
        label = myfont.render("label", True, (155, 155, 155))
        for y in self.get_label_heights():
            self.screen.blit(label, (self.offset//4 - 10, y - self.font_size//2))
            pygame.draw.line(self.screen, (255, 0, 0), (self.offset -  self.offset//4, y), (self.offset, y), 1)
        for y in self.get_label_heights():
            self.screen.blit(label, (self.width - 2*self.offset//3, y - self.font_size//2))
            pygame.draw.line(self.screen, (255, 0, 0), (self.width - self.offset + self.offset//4, y), (self.width - self.offset, y), 1)

    def display(self):
        self.display_points()
        self.display_outline()
        self.display_labels()
        #self.display_label_lines(self.get_all_lines())
        self.display_label_lines(solver(self.get_all_lines()))

diagram = Diagram()
diagram.display()

pygame.display.flip()
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
于 2012-11-14T08:05:13.567 に答える
8

この問題の実際の解決策は、少し異なるレイヤーにあると思います。情報設計を完全に無視してアルゴリズムの問​​題を解決し始めるのは正しい考えではないようです。ここに興味深い例があります

いくつかの重要な質問を特定しましょう。

  1. データはどのように表示するのが最適ですか?
  2. それは人々を混乱させるでしょうか?
  3. それは読めますか?
  4. それは実際に絵をよりよく理解するのに役立ちますか?

ところで、混沌は本当に紛らわしいです。私たちは秩序と予測可能性を好みます。初期画像に情報ノイズを追加する必要はありません。

ここに画像の説明を入力

グラフィックメッセージの読みやすさは、コンテンツとその表示によって決まります。メッセージの読みやすさには、テキストと画像のスタイルを理解する読者の能力が含まれます。追加の「ノイズの多い」アプローチにより、興味深いアルゴリズムタスクが発生します。混乱を取り除きます-より良い解決策を見つけてください:)

ここに画像の説明を入力

これは単なるPoCであることに注意してください。アイデアは、明確なマーカーを持つ水平線のみを使用することです。ラベルの配置は簡単で決定論的です。いくつかの同様のアイデアを提案することができます。

ここに画像の説明を入力

このようなアプローチを使用すると、左右のラベルのバランスを簡単に調整したり、行間の垂直方向の小さなギャップを回避したり、ラベルに最適な垂直密度を提供したりできます。

編集

では、最初のプロセスがどのように見えるか見てみましょう。

ユーザー ストーリー: ユーザーとして、理解を簡素化し、説明の価値を高めるために、重要な画像に注釈を付けたいと考えています。

重要な仮定:

  1. 初期画像はユーザーの主要なオブジェクトです
  2. 読みやすさは必須

したがって、可能な限り最善の解決策は、注釈を付けますが、付けないことです。(私は、発明的な問題解決の理論について読む時間を費やすことを本当にお勧めします).

基本的に、ユーザーが最初の画像を見るのに障害があってはなりませんが、注釈は必要なときにすぐそこにある必要があります。少しわかりにくいかもしれませんが、ご容赦ください。

次の画像の背後にあるのは交差点の問題だけだと思いますか?

ここに画像の説明を入力

開発されたアプローチの背後にある実際の目標は、2 つの情報フロー (画像と注釈) を提供し、ユーザーがすべてをできるだけ早く理解できるようにすることです。ところで、視覚記憶も非常に重要です。

人間の視覚の背後にあるもの:

  1. 選択的注意
  2. 親近感の検出
  3. パターン検出

これらのメカニズムの少なくとも 1 つを壊しますか? 私はあなたがしないことを願っています。実際の結果がユーザーフレンドリーでなくなるからです。

では、何が気を散らすのでしょうか?

  1. 画像上にランダムに分布する奇妙な線 (ランダムな幾何学的オブジェクトは非常に気を散らすものです)
  2. 注釈の配置とスタイルが統一されていない
  3. 画像と注釈レイヤーの最終的なマージの結果としての奇妙な複雑なパターン

なぜ私の提案を検討する必要があるのですか?

  1. シンプルなパターンであるため、パターン検出により、ユーザーは注釈に気付かずに画像を見ることができます。
  2. デザインが統一されているので、親密度検出も機能します
  3. 線の幅が最小限であるため、他のソリューションほど初期イメージには影響しません。
  4. 線は水平で、アンチエイリアスは使用されていないため、より多くの情報が保存され、きれいな結果が得られます
  5. 最後に、ルーティング アルゴリズムを大幅に簡素化します。

いくつかの追加コメント:

  1. ランダムなポイントを使用してアルゴリズムをテストしないでください。単純だが重要なケースを使用してください。自動化されたソリューションが劇的に失敗する場合があることがわかります。
  2. 私が提案したアプローチをそのまま使用することはお勧めしません。可能な拡張機能はたくさんあります。
  3. 私が実際に提案しているのは、1 レベル上に移動して、メタレベルで数回反復することです。

グループ化は、Robert King が言及した複雑なケースに対処するために使用できます。

ここに画像の説明を入力

または、デフォルトの位置の少し上にあるポイントがあると想像できます。ただし、メインの処理フローを中断して他のマーカーに影響を与えたくないため、ほんの一瞬だけです。

ここに画像の説明を入力

読んでくれてありがとう。

于 2012-11-16T13:09:39.197 に答える
5

ダイアグラムの中心を見つけて、中心から放射状に点から線を引くことができます。交差する唯一の方法は、2 つの点が同じ光線上にある場合です。この場合、次のように、線の 1 つを少しだけずらし、もう 1 つを少しずらすだけです。

中心線

実際の部品のみを表示:

すべて完了

中心と同一線上に 2 つ以上の点がある場合は、線を少し横にずらすことができます。

共線性の場合

これは非常に良い複数セグメントの線を生成しませんが、ダイアグラムに非常に明確にラベルを付けます。また、より魅力的なものにするために、点セットの中心ではなく、実際にオブジェクトの中心である中心点を選択することをお勧めします。

于 2012-11-16T16:36:15.010 に答える
3

プロトタイプにもう 1 つ追加します。この後、受け入れられる可能性があります。

すべての交差点を反復してラベルを交換し、交差点ができるまで繰り返します。

状態の数は有限であり、すべてのスワップはすべての行の長さの合計を減らすため、このプロセスは有限です。したがって、ループは不可能です。

于 2012-11-13T08:25:50.457 に答える
1

この問題は、グラフ レイアウトとしてキャストできます。

たとえば、 Graphviz ライブラリを参照することをお勧めします。実験はしていませんが、ラベリングする点とラベル自体をノード、引き出し線をエッジで表現すると良い結果が得られると思います。

ラベルが重なってはいけない領域を「ダミー」ノードとして表現する必要があります。

Graphvis には多くの言語のバインディングがあります

Graphviz には必要なことを正確に実行するのに十分な柔軟性がない場合でも、そのページの「理論」セクションには、問題に適用できるエネルギー最小化とスプリング アルゴリズムのリファレンスがあります。グラフ レイアウトに関する文献は膨大です。

于 2012-11-17T15:32:31.567 に答える