食品瓶のラベルの画像を平らにする方法は?


40

食品の瓶のラベルの写真を撮り、ラベルが平らになるようにラベルを変換できるようにしたいです。右側と左側が画像の中央に合わせてサイズ変更されます。

理想的には、エッジを見つけて補正を適用するために、ラベルと背景のコントラストを使用したいと思います。それ以外の場合は、ユーザーに画像の角と辺を何らかの方法で識別するように依頼できます。


私は、球状に(私の場合は円筒状に)歪んだ画像を撮影し、画像を平坦化できる一般的な手法とアルゴリズムを探しています。現在、瓶またはボトルに巻き付けられているラベルの画像には、画像の右または左に後退するにつれて縮小する機能とテキストがあります。また、ラベルの端を示す線は、画像の中央でのみ平行であり、ラベルの左右の端で互いに向かって傾斜します。

画像を操作した後、まるで瓶や瓶にないときにラベルの写真を撮ったように、テキストと機能が均一なサイズになっているほぼ完璧な長方形を残したいと思います。

また、適切な修正を適用するために、この手法がラベルの端を自動的に検出できれば、それが欲しいです。それ以外の場合、ラベルの境界を示すようにユーザーに要求する必要があります。

私はすでにグーグルで検索して、次のような記事を見つけました: カーブしたドキュメントを平坦化するが、シンプルなカーブのラベルが必要なため、もう少しシンプルなものを探しています。


ニキーには、包括的なソリューションと思われるものがあります。ただし、カメラが常にjarに対して「正方形」であり、混乱する背景がないことがわかっている場合は、はるかに簡単になります。次に、瓶の端を見つけて、余計な追加操作なしで、単純な三角法(アークサイン?)変換を適用します。画像が平坦化されると、ラベル自体を分離できます。
ダニエルRヒックス

@ダニエルそれは私がここでやったことです。理想的には、完全に平行ではない投影も考慮しますが、私は考慮しませんでした。
ザボルクス

仕事はとても良いです。しかし、私のシステムでエラーを示すコード。matlab 2017aを使用していますが、それと互換性があります。ありがとう、
サティシュクマール

回答:


60

同様の質問がMathematica.Stackexchangeに頼まれました。向こうの私の答えは進化し、最終的にはかなり長くなったので、ここでアルゴリズムを要約します。

抽象

基本的な考え方は次のとおりです。

  1. ラベルを見つけます。
  2. ラベルの境界線を見つける
  3. 画像座標を円柱座標にマッピングするマッピングを見つけて、ラベルの上部境界線に沿ったピクセルを([anything] / 0)に、右境界線に沿ったピクセルを(1 / [anything])などにマッピングします。
  4. このマッピングを使用して画像を変換します

アルゴリズムは、次の画像に対してのみ機能します。

  1. ラベルは背景よりも明るい(これはラベル検出に必要です)
  2. ラベルは長方形です(これはマッピングの品質を測定するために使用されます)
  3. jarは(ほぼ)垂直です(これは、マッピング機能を単純にするために使用されます)
  4. jarは円筒形です(これはマッピング機能をシンプルにするために使用されます)

ただし、アルゴリズムはモジュール式です。少なくとも原則として、暗い背景を必要としない独自のラベル検出を作成することも、楕円または八角形のラベルに対処できる独自の品質測定機能を作成することもできます。

結果

これらの画像は完全に自動的に処理されました。つまり、アルゴリズムはソース画像を取得し、数秒間動作し、マッピング(左)と歪みのない画像(右)を表示します。

ここに画像の説明を入力してください

ここに画像の説明を入力してください

ここに画像の説明を入力してください

ここに画像の説明を入力してください

ここに画像の説明を入力してください

ここに画像の説明を入力してください

ここに画像の説明を入力してください

次の画像は、アルゴリズムの修正版で処理されました。ラベルの曲率は正面ショットの画像から推定できないため、ユーザーが瓶の左右の境界線(ラベルではなく)を選択しました。完全に自動化されたアルゴリズムは、わずかに歪んだ画像を返します):

ここに画像の説明を入力してください

ここに画像の説明を入力してください

実装:

1.ラベルを見つける

ラベルは暗い背景の前で明るいため、2値化を使用して簡単に見つけることができます。

src = Import["http://i.stack.imgur.com/rfNu7.png"];
binary = FillingTransform[DeleteBorderComponents[Binarize[src]]]

二値化画像

最大の接続コンポーネントを選択し、それがラベルだと仮定します。

labelMask = Image[SortBy[ComponentMeasurements[binary, {"Area", "Mask"}][[All, 2]], First][[-1, 2]]]

最大のコンポーネント

2.ラベルの境界線を見つける

次のステップ:単純な微分畳み込みマスクを使用して、上/下/左/右の境界線を見つけます:

topBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1}, {-1}}]];
bottomBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1}, {1}}]];
leftBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1, -1}}]];
rightBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1, 1}}]];

ここに画像の説明を入力してください

これは、これら4つの画像のいずれかですべての白いピクセルを検出し、インデックスを座標に変換する小さなヘルパー関数です(インデックスをPosition返します。インデックスは1ベースの{y、x}タプルで、y = 1はしかし、すべての画像処理関数は、0ベースの{x、y}タプルである座標を期待します。ここで、y = 0は画像の下部です)。

{w, h} = ImageDimensions[topBorder];
maskToPoints = Function[mask, {#[[2]]-1, h - #[[1]]+1} & /@ Position[ImageData[mask], 1.]];

3.画像から円柱座標へのマッピングを見つける

これで、ラベルの上、下、左、右の境界の座標の4つの個別のリストができました。画像座標から円柱座標へのマッピングを定義します。

arcSinSeries = Normal[Series[ArcSin[\[Alpha]], {\[Alpha], 0, 10}]]
Clear[mapping];
mapping[{x_, y_}] := 
   {
    c1 + c2*(arcSinSeries /. \[Alpha] -> (x - cx)/r) + c3*y + c4*x*y, 
    top + y*height + tilt1*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]] + tilt2*y*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]]
   }

これは、ソース画像のX / Y座標を円筒座標にマッピングする円筒マッピングです。マッピングには、高さ/半径/中心/遠近/傾斜の自由度が10あります。ArcSinを直接使用して最適化を行うことができなかったため、テイラー級数を使用してアークサインを近似しました。のClip呼び出しは、最適化中に複素数を防ぐためのアドホックな試みです。ここにはトレードオフがあります:一方では、関数は可能な限り正確な円筒形のマッピングにできるだけ近く、最小限の歪みを与える必要があります。一方、複雑な場合、自由度の最適な値を自動的に見つけることははるかに難しくなります。(Mathematicaで画像処理を行うことの良い点は、このような数学モデルを簡単にいじり、さまざまな歪みの追加用語を導入し、同じ最適化関数を使用して最終結果を得ることができることです。 OpenCVやMatlabを使用するようなものです。しかし、Matlabのシンボリックツールボックスを試したことはありません。

次に、画像の品質を測定する「エラー関数」->シリンダー座標マッピングを定義します。これは、境界ピクセルの二乗誤差の合計です。

errorFunction =
  Flatten[{
    (mapping[#][[1]])^2 & /@ maskToPoints[leftBorder],
    (mapping[#][[1]] - 1)^2 & /@ maskToPoints[rightBorder],
    (mapping[#][[2]] - 1)^2 & /@ maskToPoints[topBorder],
    (mapping[#][[2]])^2 & /@ maskToPoints[bottomBorder]
    }];

このエラー関数は、マッピングの「品質」を測定します。左境界のポイントが(0 / [anything])にマッピングされ、上部境界のピクセルが([anything] / 0)にマッピングされる場合など、最低です。

これで、このエラー関数を最小化する係数を見つけるようにMathematicaに指示できます。いくつかの係数(たとえば、画像の瓶の半径と中心)について「教育的な推測」を行うことができます。これらを最適化の開始点として使用します。

leftMean = Mean[maskToPoints[leftBorder]][[1]];
rightMean = Mean[maskToPoints[rightBorder]][[1]];
topMean = Mean[maskToPoints[topBorder]][[2]];
bottomMean = Mean[maskToPoints[bottomBorder]][[2]];
solution = 
 FindMinimum[
   Total[errorFunction], 
    {{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0}, 
     {cx, (leftMean + rightMean)/2}, 
     {top, topMean}, 
     {r, rightMean - leftMean}, 
     {height, bottomMean - topMean}, 
     {tilt1, 0}, {tilt2, 0}}][[2]]

FindMinimumエラー関数を最小化するマッピング関数の10自由度の値を見つけます。汎用マッピングとこのソリューションを組み合わせると、ラベル領域に適合するX / Y画像座標からマッピングが得られます。MathematicaのContourPlot関数を使用して、このマッピングを視覚化できます。

Show[src,
 ContourPlot[mapping[{x, y}][[1]] /. solution, {x, 0, w}, {y, 0, h}, 
  ContourShading -> None, ContourStyle -> Red, 
  Contours -> Range[0, 1, 0.1], 
  RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[2]] /. solution) <= 1]],
 ContourPlot[mapping[{x, y}][[2]] /. solution, {x, 0, w}, {y, 0, h}, 
  ContourShading -> None, ContourStyle -> Red, 
  Contours -> Range[0, 1, 0.2],
  RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[1]] /. solution) <= 1]]]

ここに画像の説明を入力してください

4.画像を変換する

最後に、ImageForwardTransformこのマッピングに従ってMathematicaの関数を使用して画像を歪めます:

ImageForwardTransformation[src, mapping[#] /. solution &, {400, 300}, DataRange -> Full, PlotRange -> {{0, 1}, {0, 1}}]

上記の結果が得られます。

手動アシストバージョン

上記のアルゴリズムは全自動です。調整は必要ありません。写真が上または下から撮影される限り、それは合理的に機能します。しかし、正面ショットの場合、瓶の半径はラベルの形状から推定できません。これらの場合、ユーザーに手動でjarの左右の境界線を入力させ、マッピングで対応する自由度を明示的に設定すると、はるかに良い結果が得られます。

このコードにより、ユーザーは左/右の境界線を選択できます。

LocatorPane[Dynamic[{{xLeft, y1}, {xRight, y2}}], 
 Dynamic[Show[src, 
   Graphics[{Red, Line[{{xLeft, 0}, {xLeft, h}}], 
     Line[{{xRight, 0}, {xRight, h}}]}]]]]

LocatorPane

これは代替の最適化コードで、center&radiusが明示的に指定されています。

manualAdjustments = {cx -> (xLeft + xRight)/2, r -> (xRight - xLeft)/2};
solution = 
  FindMinimum[
   Total[minimize /. manualAdjustments], 
    {{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0}, 
     {top, topMean}, 
     {height, bottomMean - topMean}, 
     {tilt1, 0}, {tilt2, 0}}][[2]]
solution = Join[solution, manualAdjustments]

11
サングラスを外します ...神の母
...-スペイシー

円筒マッピングへの参照がありますか?そして、おそらく逆マッピングの方程式?@ niki-estner
イタ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.