24bit の画像を 8bit (254 color) にする方法についてわからなくて困っております。
やりたいこととしては以下URLの内容の、固定パレット(24bit→8bit (245 color))+誤差拡散(Floyd-Steinberg)、もしくは__最適化パレット(24bit→8bit)+誤差拡散(Floyd-Steinberg)__で行いたいと考えています。
ただし、色数は255ではなく254です。
現状はとにかく減色させて8bitにと思って、OpenCvSharp3のCv2.Kmeansによる減色を試みたのですが、減色はできても8bitにはできずにてこずっております。
環境:WindowsForms
URL:[http://koujinz.cocolog-nifty.com/blog/2009/04/24bit-8bit-a879.html](http://koujinz.cocolog-nifty.com/blog/2009/04/24bit-8bit-a879.html
尚、単純に以下のCloneでPixelFormat.Format8bppIndexedを指定した場合、8bitにはできるものの、色数254の前に画質が荒すぎて酷かったので止めました。
C#
1Bitmap dstBmp = ImageToBmp.Clone(new Rectangle(0, 0, ImageToBmp.Width, ImageToBmp.Height), PixelFormat.Format8bppIndexed);
また、以下のようにGIFに変換してからbmpにする方法も試しましたが、最後ビットマップにしてもGif画像扱い(バイナリヘッダーを見て判断)だったので不可としました。
C#
1 public static Bitmap ConvertTo8bpp(Image img) 2 { 3 var ms = new System.IO.MemoryStream(); 4 img.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); 5 ms.Position = 0; 6 return new Bitmap(img); 7 }
<Kmeansで試している内容>
C#
1 /// <summary> 2 /// Kmeans法による減色 3 /// </summary> 4 /// <param name="input">入力画像</param> 5 /// <param name="result">減色結果画像</param> 6 /// <param name="ClusterCount">クラスター値</param> 7 public static void Kmeans(Mat input, Mat result, int ClusterCount) 8 { 9 using (Mat points = new Mat()) 10 { 11 using (Mat labels = new Mat()) 12 { 13 using (Mat centers = new Mat()) 14 { 15 int width = input.Cols; 16 int height = input.Rows; 17 18 points.Create(width * height, 1, MatType.CV_32FC3); 19 centers.Create(ClusterCount, 1, points.Type()); 20 result.Create(height, width, input.Type()); 21 22 int i = 0; 23 for (int y = 0; y < height; y++) 24 { 25 for (int x = 0; x < width; x++, i++) 26 { 27 Vec3f vec3f = new Vec3f 28 { 29 Item0 = input.At<Vec3b>(y, x).Item0, 30 Item1 = input.At<Vec3b>(y, x).Item1, 31 Item2 = input.At<Vec3b>(y, x).Item2 32 }; 33 34 points.Set<Vec3f>(i, vec3f); 35 } 36 } 37 38 Cv2.Kmeans(points, ClusterCount, labels, new TermCriteria(CriteriaType.Eps | CriteriaType.MaxIter, 10, 1.0), 3, KMeansFlags.PpCenters, centers); 39 40 i = 0; 41 for (int y = 0; y < height; y++) 42 { 43 for (int x = 0; x < width; x++, i++) 44 { 45 int idx = labels.Get<int>(i); 46 47 Vec3b vec3b = new Vec3b(); 48 49 50 int tmp = Convert.ToInt32(Math.Round(centers.At<Vec3f>(idx).Item0)); 51 tmp = tmp > 255 ? 255 : tmp < 0 ? 0 : tmp; 52 vec3b.Item0 = Convert.ToByte(tmp); 53 54 tmp = Convert.ToInt32(Math.Round(centers.At<Vec3f>(idx).Item1)); 55 tmp = tmp > 255 ? 255 : tmp < 0 ? 0 : tmp; 56 vec3b.Item1 = Convert.ToByte(tmp); 57 58 tmp = Convert.ToInt32(Math.Round(centers.At<Vec3f>(idx).Item2)); 59 tmp = tmp > 255 ? 255 : tmp < 0 ? 0 : tmp; 60 vec3b.Item2 = Convert.ToByte(tmp); 61 62 result.Set<Vec3b>(y, x, vec3b); 63 64 } 65 } 66 } 67 } 68 } 69 70 }
追記1>
要件:
・メディアンカットでの減色を行いたい(記載しているのはKmeans)
・24bit→8bit(254色)に変換したい
・上記に関するライブラリ、参考サイト、コードがあれば
現状:
メディアンカットでは進展なし。
24bit→8bit変換でサンプルを作成中。
追記2>
サンプルとして、以下のところまで作成。
NotionalKettleさん
つまり減色された画像データに含まれるRGBの組とそれに対応する値(8bit用)を定義して対応が付けられるように8bit側の各画素を上記の方法>で書き換えて、最後にパレットを対応させれば・・・
この部分でどうすればいいのかイメージがつかずに詰まってます。
下記コードではコメント①あたりです。
尚、byte*での方法については理解が追い付いていないので、とりあえずMarshal.Copyで試してます。
C#
1 2 unsafe static Bitmap ImageConvert24biTo8bit(Bitmap bmpSource) 3 { 4 int pixelSize = 4; 5 int color = 255; 6 7 //入力されたBitmapをロック 8 BitmapData bmpData = bmpSource.LockBits( 9 new Rectangle(0, 0, bmpSource.Width, bmpSource.Height), 10 ImageLockMode.WriteOnly, 11 bmpSource.PixelFormat 12 ); 13 14 //Bitmapの各画素情報のbyte配列データの先頭ポインタ 15 IntPtr pointer = bmpData.Scan0; 16 //byte* pImg = (byte*)bmpData.Scan0.ToPointer(); 17 18 byte[] pixels = new byte[bmpData.Stride * bmpSource.Height]; 19 20 // Bitmap を byte [ ] へコピー 21 Marshal.Copy(pointer, pixels, 0, pixels.Length); 22 23 // Bitmapオブジェクトを作成 24 Bitmap bmp8bit = new Bitmap(bmpSource.Width, bmpSource.Height, PixelFormat.Format8bppIndexed); 25 26 // カラーパレットを設定 27 ColorPalette pal = bmp8bit.Palette; 28 29 for (int y = 0; y < bmpData.Height; y++) 30 { 31 for (int x = 0; x < bmpData.Width; x++) 32 { 33 /*① この辺りが?です */ 34 35 //(x,y)のデータ位置 36 int pos = y * bmpData.Height + x * pixelSize; 37 38 // RGB 39 byte b = pixels[pos]; 40 byte g = pixels[pos + 1]; 41 byte r = pixels[pos + 2]; 42 43 // 色 44 for (int i = 0; i < color; ++i) 45 { 46 pal.Entries[i] = Color.FromArgb(255, r, g, b); 47 } 48 bmpSource.Palette = pal; 49 } 50 } 51 52 53 // 変更を反映 54 Marshal.Copy(pixels, 0, pointer, pixels.Length); 55 56 //ロックを解除する 57 bmpSource.UnlockBits(bmpData); 58 59 return bmpSource; 60 }
追記:
メディアンカットは本要件と違ったため、その部分を削除しました。
また追記部分には取り消し線で対応しました。

回答2件
あなたの回答
tips
プレビュー