C#でのJPEGアーティファクトの削除
-
10-07-2019 - |
質問
私は、母体組織の一部であるクラブのウェブサイトを構築しています。私は自分のページに表示するために、母組織のプロフィールページに置かれた画像をダウンロードしています(リーチング;))。しかし、彼らのウェブサイトには白い背景があり、私のウェブサイトには背景にグレーのグラデーションがあります。これはうまく一致しません。したがって、私のアイデアは、サーバーに保存する前に画像を編集することでした。
画像を強化するためにGDI +を使用しており、BitmapのMakeTransparentメソッドを使用すると、機能し、想定どおりに動作しますが、まだこれらの白いjpegアーティファクトがいたるところにあります。アーティファクトにより画像が非常に悪くなります。画像を透明にせずに白のままにしておく方が良いですが、それは私自身のウェブサイトでは本当にいです。もちろん、白い背景の境界線はいつでもできますが、背景を透明に変更します。
それで、C#でいくつかの単純なJPEGアーティファクトを削除できるかどうか、またどのように削除できるのか疑問に思いました。誰もこれをやったことがありますか?
お時間をいただきありがとうございます。
サンプル画像:
変換された画像:
解決
まあ、私は完璧にはほど遠い何かを試みましたが、私はそれが他の誰かに役立つかもしれないと思います。
次のことができました:
問題が発生しました:影は「オフホワイト」から十分に離れているため、自動変換するのは困難です。上部からのまぶしさ...ハブの事柄は、アンチエイリアスビットよりもオフホワイトに近いです。画像には3〜7個の白の斑点があり、これらは主要な角のいずれにも接続されていません。最後に、エッジにまだ少し白い部分があります(コードを微調整することでおそらく取り除くことができますが、グレアトップの一部を外すことはできません。
C#の非効率的なコード:
static void Main()
{
Bitmap bmp=new Bitmap("test.jpg");
int width = bmp.Width;
int height = bmp.Height;
Dictionary<Point, int> currentLayer = new Dictionary<Point, int>();
currentLayer[new Point(0, 0)] = 0;
currentLayer[new Point(width - 1, height - 1)] = 0;
while (currentLayer.Count != 0)
{
foreach (Point p in currentLayer.Keys)
bmp.SetPixel(p.X, p.Y, Color.Black);
Dictionary<Point, int> newLayer = new Dictionary<Point, int>();
foreach (Point p in currentLayer.Keys)
foreach (Point p1 in Neighbors(p, width, height))
if (Distance(bmp.GetPixel(p1.X, p1.Y), Color.White) < 40)
newLayer[p1] = 0;
currentLayer = newLayer;
}
bmp.Save("test2.jpg");
}
static int Distance(Color c1, Color c2)
{
int dr = Math.Abs(c1.R - c2.R);
int dg = Math.Abs(c1.G - c2.G);
int db = Math.Abs(c1.B - c2.B);
return Math.Max(Math.Max(dr, dg), db);
}
static List<Point> Neighbors(Point p, int maxX, int maxY)
{
List<Point> points=new List<Point>();
if (p.X + 1 < maxX) points.Add(new Point(p.X + 1, p.Y));
if (p.X - 1 >= 0) points.Add(new Point(p.X - 1, p.Y));
if (p.Y + 1 < maxY) points.Add(new Point(p.X, p.Y + 1));
if (p.Y - 1 >= 0) points.Add(new Point(p.X, p.Y - 1));
return points;
}
コードは、2つのポイントから開始して機能します。それらを黒に設定してから、近くに白が近いかどうかを確認します。それらがリストに追加され、それに対して実行されます。最終的には、変更する白いピクセルが足りなくなります。
別の方法として、白い背景を使用するようにサイトを再設計することを検討できます。
他のヒント
R、G、Bが230よりも高い場合、画像の各ピクセルをループし、色を目的の色(または透明)に置き換えます。古い色が「真の」白からどれだけ離れているかに応じて、新しい色に重みを付けることもできます。
実際の画像も白の場合、問題が発生する可能性があります。そうでない場合は、グレーのストームトルーパーになります:)
100%の精度などでこれを自動的に行うことはできません。
この理由は、画像内の一部のピクセルがうまくブレンドしようとしていることを知っている色だけがあるということです。画像の一部のピクセルのみが、背景に陰影を付ける目的でこの値またはそれに近い色を実際に使用しますが、実際のオブジェクトは実際には白であるため(白の場合)他のピクセルが使用されますこれらの帝国ストームトルーパー)。
どれが興味深い問題領域であるかを検出する一種の洗練された機械学習であり、あなたにとって楽しいプロジェクトかもしれませんが、差し迫った問題の迅速な解決にはなりません。
他の問題は、背景に溶け込もうとしている画像の領域を高い信頼性で検出できたとしても、それらを「ブレンド解除」してから新しい背景色に再ブレンドする問題が発生することです色が合理的に互換性がない限り。この場合、グレーは白のような広いスペクトルの色なので、動作する可能性があります。
使用するテクニックは次のとおりです。
- 塗りつぶしアルゴリズムを使用して、既知の背景色のx%(1)内のすべてのピクセルを画像の端から内側に選択します。
- これらのピクセルでは、アルファチャネルを元の色との一致の割合として値に設定し、それに関連付けられている色かぶりを除去します。
- 背景がRGB値a、b、cでピクセルがa + 5、b、c-7の場合、結果は RGBA 5,0,0、((a + b + c-7)/(a + b + c)* 256)(1)
- このアルファブレンディングイメージを、新しいバックグラウンドカラーのペインスクエア上に合成します。
- アルファチャネルなしの結果を新しい画像としてレンダリングします。
これは、いずれかの背景色に近い色のオブジェクトにはまだ問題があります。 *オリジナルの場合、オブジェクトの存在を暗示するためにシャドウイングが使用されている可能性があります。そのため、塗りつぶしは画像の内側に「侵入」します。 *後者の場合、結果の画像はオブジェクトの定義を失い、オブジェクトの終点と背景の終点を示す微妙なシェーディング、ハイライト、または単なる線は表示されません。
これは非常に大まかな最初の近似ですが、ターゲットの妥当な割合をカバーする場合があります。透明な完全に囲まれた穴のある写真(例の外側のアーチの隙間など)は、アルゴリズムがホワイトホールと白いストームトルーパーを区別できないため、自動的にうまく機能することはほとんどありません。
再ブレンドを計画している写真の領域をアルゴリズムで強調表示し、含める/除外する領域の簡単な選択を許可することができます(必要に応じてこれを行う方法の例として、Pain.Netの魔法の杖選択ツールを使用します)洗練されたものにし、事前の手間をかけずにピクセルごとに簡単に選択できるようにします。
- xの値は調整したものになります-画像の一部の側面(背景色に近い画像の割合など)に基づいて、自動的に微調整できます。
- この式では、白に近い色を想定しているため、黒に近い場合は反転させたいことになります
コメントダイアログに基づくもう1つのアプローチ:
static void Main()
{
Bitmap mask = new Bitmap(@"mask.bmp");
Bitmap bmp=new Bitmap(@"test.jpg");
int width = bmp.Width;
int height = bmp.Height;
for(int x=0; x<width; x++)
for (int y = 0; y < height; y++)
if (mask.GetPixel(x, y).R < 250)
bmp.SetPixel(x,y,mask.GetPixel(x,y));
bmp.Save(@"test3.jpg");
}
指定されたマスク:
結果が得られます:
ペイントのアンチエイリアスを無効にして、Paint.NETでマスクの境界線をわずかにクリーンアップしました。繰り返しますが、どの境界線が使用されているかを見分けることができる場合にのみ適用されます...しかし、うまくいきました...緑を除いて...
オフホワイトの色合いも同様に処理する必要があります。おそらく最初に画像を作成して背景色を設定したときにアンチエイリアスが行われ、その後jpgとして保存すると、すべての色が完全に保持されるわけではありません。したがって、特定の色を透明にすると、その色のすべての色合いが得られず、多くのアーティファクトが残ります。色がキーカラーにどれだけ近いかに比例して透明になる何かが必要です。これは、Photoshopのようなバッチスクリプトとして行う方が簡単かもしれませんが、これがリアルタイムで行う必要があるかどうかはわかりません。
この問題をプログラムで処理する(リモートで簡単な)方法はありません。画像の端の周囲の白いアーチファクト領域は、ピクセルがほぼ白色であるが完全ではないため、透明効果を拾いません。マスク/コーヒーマグには、真っ白な点がいくつかあるため、透明になり、灰色になります。
最善の方法は、元のサイトのウェブマスターに連絡し、元の画像を送信できるかどうかを確認することです。できれば、Photoshopまたは元のレイヤーが個別に保存されるその他の形式で送信してください。その後、元の透明度を保持する形式(PNGまたはそのようなもの)で画像を再生成するか、背景にグラデーションを使用します(これを正しく行うのは非常に困難です。画像がレンダリングされるグラデーション)。
あなたが提案したように、画像の周りに何らかの境界線を付けます。
ご回答ありがとうございます。すべての良い提案..
これが昨日作ったものです:
public const int PIXEL_REGION = 60;
public const int TRANSPARENT_DISTANCE = 60;
public static readonly Color TRANSPARENT_COLOR = Color.White;
private System.Drawing.Image ProcessImage(System.Drawing.Image image)
{
Bitmap result = new Bitmap(image.Width, image.Height);
Bitmap workImage = new Bitmap(image);
for (int x = 0; x < image.Width; x++)
{
for (int y = 0; y < image.Height; y++)
{
Color color = workImage.GetPixel(x, y);
if (x < PIXEL_REGION || x > image.Width - PIXEL_REGION || y < PIXEL_REGION || y > image.Height - PIXEL_REGION)
{
double distance = CalculateColourDistance(TRANSPARENT_COLOR, color);
if(distance < TRANSPARENT_DISTANCE)
{
result.SetPixel(x, y, Color.Transparent);
continue;
}
}
result.SetPixel(x, y, color);
}
}
return result;
}
private double CalculateColourDistance(Color c1, Color c2)
{
int a = c2.A - c1.A;
int r = c2.R - c1.R;
int g = c2.G - c1.G;
int b = c2.B - c1.B;
return Math.Sqrt(Math.Pow(a, 2) + Math.Pow(r, 2) + Math.Pow(g, 2) + Math.Pow(b, 2));
}
これは本の中で最も美しいコードではありませんが、ピクセル領域内のすべてのピクセルについて、Color.Whiteと自分の色の間の距離を計算し、定義済みの距離より低い場合は透明に色を付けてください。
このコードは次の結果を生成します。
それは悪いことではありませんが..:)
成功するなら、私もあなたと一緒に分かち合う私の袖に1つのトリックがあります...
別の失敗した試みです;)
アイデアがありました。映画はグリーンスクリーンを使用しています。画像に緑のオーバーレイを使用しないでください。 オーバーレイは次のとおりです。
そして結果:
それで、PNG圧縮に関する別の貴重な教訓を学びました。また、bmpで試しましたが、どういうわけか、常に緑色の境界線が表示されます。背景、その周りにいくつかの愚かな境界線を配置し、それで完了です...
まあ..;)