私がデジタル画像処理に出会ったのは高校生の頃で、まだ PHOTOSHOP が 4.0 だったので、2D から 3D への飛躍にはまだ興味がありませんでした。一緒にやってください。高給取りのキュービックにその広場を任せるのは耐えられません...はは。
大学時代、クラスメートと画像処理のプログラムを書いたことがありますが、当時はまだプログラミングがカジュアルで、それをどうやって実装するかということだけを考えていました。今では全体を把握する能力が本当の技術であると言えます。ひらめきの魔法ではなく、状況です。数日前にいくつかの外国の画像処理プログラムに触れましたが、今後は画像処理について詳しく学ぶことはないと思います。
かつて同級生が、.net にはポインタがないと言っていたのですが、実際、これは誤解です。ただ、フレームワークはポインターの使用を推奨していません。特に Web サービスやリモート処理などのクロスプロセス操作では、ポインターは安全ではありません。しかし、TC を使用したことがある人なら誰でも、大規模なデータのバッチ計算の要求の下ではポインタしか選択肢がないことに、ポインタの実行効率に深く感銘を受けるはずです。したがって、.net は巧妙にポインタを保持し、それを安全でないメソッド セットにリストします。ポインタを適切に使用すると、実行効率が大幅に向上します。640*480 の画像上でポイントごとの操作を実行する実験を行ったところ、ポインタを使用しない操作はほぼ瞬時に完了しました。したがって、ポインターを使用することを恐れないでください。
2つ目は数学です。プログラムを書く前にそれを理解するように勧めます。数学の授業は冗談ではありません。理解できない場合は、ベッドに横になって何度も考える必要があります。数学がアルツハイマー病を予防できるということです。
もう少し身近なところで、プログラムの構造について話しましょう。
イメージング プロジェクト (フィルター、テクスチャ、画像モード)
数学プロジェクト (アルゴリズム、境界、カスタマイズ、一般的な計算方法)
メインプログラムプロジェクト
例を示します。インターフェイス指向の
パブリック インターフェイス IFilter
も実行します。
{
ビットマップ適用( ビットマップ img );
例
として、インターフェイス指向のプログラミングも行います。各フィルターはこのインターフェイスを実装する必要があります。インターフェイス定義には、実際のイメージを生成せず、バイナリ オブジェクトのみを生成するエクスキューズ定義も含まれています。これについては、ここでは考慮しません。いる。反転カラーフィルターを例に挙げます
public Bitmap apply( ビットマップ srcImg )
{
//ソース画像のサイズを取得
int width = srcImg.Width;
int 高さ = srcImg.Height;
PixelFormat fmt = ( srcImg.PixelFormat == PixelFormat.Format8bppIndexed ) ?
PixelFormat.Format8bppIndexed : PixelFormat.Format24bppRgb;
// ソース ビットマップ データをロックします。
BitmapData srcData = srcImg.LockBits(
新しい長方形(0, 0, 幅, 高さ),
ImageLockMode.ReadOnly, fmt );
// 新しいイメージを作成します
ビットマップ dstImg = ( fmt == PixelFormat.Format8bppIndexed ) ?
AForge.Imaging.Image.CreateGrayscaleImage(幅, 高さ):
new Bitmap( width, height, fmt );
// 宛先ビットマップデータをロックします。
BitmapData dstData = dstImg.LockBits(
新しい長方形(0, 0, 幅, 高さ),
ImageLockMode.ReadWrite, fmt );
// 画像をコピー
srcData.Stride
* height );
ProcessFilter( dstData, fmt );
// 両方のイメージのロックを解除します
dstImg.UnlockBits(dstData);
srcImg.UnlockBits( srcData );
dstImg を返します。
}
ProcessFilter はフィルターメソッドの入り口であり、処理前の準備作業を完了します。ProcessFilter も各フィルタークラスに共通の ProcessFilter メソッドを呼び出します。この ProcessFilter が関数、ポイントバイポイント操作、またはテンプレート操作を実現するための鍵となります。
// フィルターを処理します
private unsafe void ProcessFilter( BitmapData data, PixelFormat fmt )
{
int width = データ.幅;
int height = data.Height;
int lineSize = width * ( ( fmt == PixelFormat.Format8bppIndexed ) ? 1 : 3 );
int offset = data.Stride - lineSize
// ジョブを実行します。
byte * ptr = (byte *) data.Scan0.ToPointer( );
// 反転
for ( int y = 0; y < height; y++ )
{
for ( int x = 0; x < lineSize; x++, ptr ++ )
{
// 各ピクセルを反転します
*ptr = (バイト)( 255 - *ptr );
}
ptr += オフセット;
}
この
うち、Format8bppIndexed については、設計の初期段階で互換性の問題を考慮する必要はないと個人的には考えています。
さて、テクスチャについて話しましょう。私はこれまであまり考えたことはありませんでしたが、テクスチャには数学で遊ぶ余地があるため、それをどうやって思いついたのかはわかりません。想像に基づいて真実であることは難しいでしょう。おそらく、彼らのうちの1人が数学モデリングソフトウェアをプレイしているときにこの方法を発見したため、高レベルの数学教師は誰も受け入れることを拒否し、アルゴリズムを一生懸命いじりました。とにかく、そういうことが起こったのだと思います。 。 。
パブリック インターフェイス ITextureGenerator
{
/**//// <概要>
/// テクスチャを生成する
/// </概要>
float[,] Generate( int width, int height );
/**//// <概要>
/// リセット - 内部乱数を再生成します
/// </概要>
void リセット( );
}
これはテクスチャ ジェネレータの実装インターフェイスです。テクスチャが毎回異なるようにするには、計算パラメータとして乱数を更新する必要があります。
private Math.PerlinNoise ノイズ = new Math.PerlinNoise( 1.0 / 32, 0.05, 0.5, 8 );
テクスチャの詳細を実現するにはノイズも必要となるため、多くの種類のノイズを実装する必要があります。
//コンストラクター
パブリック WoodTexture( ) : this( 12.0 ) { }
パブリック WoodTexture(二重リング)
{
this.rings = リング;
リセット( );
、
ユニット テクスチャ サイズの制限であるデフォルト値の設定を提供します。
//テクスチャを生成する
public float[,] Generate( int width, int height )
{
float[,] テクスチャ = 新しい float[高さ, 幅];
int w2 = 幅 / 2;
int h2 = 高さ / 2;
for ( int y = 0; y < 高さ; y++ )
{
for (int x = 0; x < width; x++)
{
double xv = (double) ( x - w2 ) / 幅;
double yv = (double) ( y - h2 ) / テクスチャ
[y, x] =
Math.Max( 0.0f, Math.Min( 1.0f, (float)
Math.Abs( Math.Sin(
( Math.Sqrt( xv * xv + yv * yv ) + noise.Function2D( x + r, y + r ) )
* Math.PI * 2 * リング
))
));
}
}
テクスチャを返します。
}
それでおしまい。 。 。私の下手な計算の結果です。彼女が何を言っているのかさえわかりません。最小値から最大値を選択します。アルゴリズムを見つけるのは難しくありません。重要なのは、アルゴリズムが構造にどのように統合されているかを確認することです。
public void Reset()
{
r = rand.Next( 5000 );
}この乱数を忘れないでください。数字の画像には自然な美しさも必要です。
数学工学におけるオブジェクト指向の概念を実装するのは簡単ではありません。他の人にインスピレーションを与えるために、PerlinNoise を見てください。
public PerlinNoise( double initFrequency, double initAmplitude, doublepersistance, int octaves )
{
this.initFrequency = initFrequency;
this.initAmplitude = initAmplitude;
this.persistance = 永続性;
this.octaves = オクターブ;
}
まず、データを収集する必要があります。画像処理には 1 次元と 2 次元の状況が含まれるため、ノイズなどの基礎となるメソッドは 2 つの状況に対応するメソッドを提供する必要があります。
/**//// <概要>
/// 1 次元パーリン ノイズ関数
/// </概要>
public double 関数(double x)
{
倍の周波数 = initFrequency;
倍振幅 = initAmplitude;
二重和 = 0;
// オクターブ
for (int i = 0; i < オクターブ; i++)
{
合計 += SmoothedNoise( x * 周波数 ) * 振幅
*= 2;
振幅 *= 持続性;
}
合計を返します。
}
/**//// <概要>
/// 2 次元パーリン ノイズ関数
/// </概要>
public double Function2D(double x, double y)
{
倍の周波数 = initFrequency;
倍振幅 = initAmplitude;
二重和 = 0;
// オクターブ
for (int i = 0; i < オクターブ; i++)
{
合計 += SmoothedNoise( x * 周波数, y * 周波数 ) * 振幅
周波数 *= 2;
振幅 *= 持続性;
}
合計を返します。
}
1次元と2次元の違いは何ですか? 中学生の頃、線の動きで面が現れると習いましたが、実際にやってみると、線の動きで面が表現できると習いました。エッジ認識とシャープ化を行うと、以前にラインを過小評価していたことがわかりましたが、実際には、サーフェスよりパラメータが 1 つ少ないだけです。
/**//// <概要>
/// 通常のノイズ関数
/// </概要>
protected double Noise(int x)
{
int n = ( x << 13 ) ^ x;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7fffffff ) / 1073741824.0 );
}
protected double Noise(int x, int y)
{
int n = x + y * 57;
n = ( n << 13 ) ^ n ;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7fffffff ) / 1073741824.0 );
前の段落をもう一度証明します。個人的には、この x+y*57 には少し射影の意味があると感じます。対応するノイズ値を取得します。ただし、ノイズをそのまま使用することはできません。
/**//// <概要>
/// 平滑化されたノイズ
/// </概要>
protected double SmoothedNoise( double x )
{
int xInt = (int) x;
double xFrac = x - xInt;
return CosineInterpolate( Noise( xInt ) , Noise( xInt + 1 ), xFrac );
}
protected double SmoothedNoise(double x, double y)
{
int xInt = (int) x;
int yInt = (int) y;
double xFrac = x - xInt;
double yFrac = y - yInt;
// 4 つのノイズ値を取得します。
double x0y0 = Noise( xInt , yInt );
double x1y0 = ノイズ( xInt + 1, yInt );
double x0y1 = ノイズ( xInt , yInt + 1 );
double x1y1 = Noise( xInt + 1, yInt + 1)
// x 補間
;
double v1 = CosineInterpolate( x0y0, x1y0, xFrac );
double v2 = CosineInterpolate( x0y1, x1y1, xFrac );
//y補間
return CosineInterpolate( v1, v2, yFrac );
スムーズ ノイズは、その呼び方には少し違和感があるように思えますが、離散コサインではなくコサイン補間によって動作します。コサイン補間とは何ですか? /**//// <概要>
/// コサイン補間
/// </概要>
protected double CosineInterpolate( double x1, double x2, double a )
{
double f = ( 1 - Math.Cos( a * Math.PI ) ) * 0.5;
return x1 * ( 1 - f ) + x2 * f;
}以上です、マスターが知っていれば十分なことがいくつかあります。なぜなら、あなたが生きている間には理解できないかもしれませんが、誰かが自然にそれを理解するでしょうし、知識は今でも受け継がれているからです。美味しくて辛い食べ物を楽しむために自分の胃酸比を知る必要がないのと同じように、将来の世代のために消化不良を心配する必要はありません。無理にやる必要はないんですけど、ちょっとネガティブです(笑)。
画像は呼び出し関係さえ把握できれば難しくないので、Photoshop のような浮遊形式が最適だと思います。 // 画像を反転
private void invertColorFiltersItem_Click(オブジェクト送信者、System.EventArgs e)
{
applyFilter(new Invert());
}
// 画像にフィルターを適用します
private void applyFilter(IFilter フィルター)
{
試す
{
// 待機カーソルを設定します
this.Cursor = Cursors.WaitCursor;
// 画像にフィルターを適用します。
ビットマップ newImage = filter.Apply(image)
if (host.CreateNewDocumentOnChange)
;
{
// 新しいドキュメントで新しい画像を開きます
host.NewDocument(newImage);
}
それ以外
{
if (host.RememberOnChange)
{
// 現在のイメージをバックアップします
if (バックアップ != null)
バックアップ.Dispose();
バックアップ = 画像;
}
それ以外
{
// 現在のイメージを解放します
image.Dispose();
}
画像 = newImage;
// 更新
UpdateNewImage();
}
}
catch(引数例外)
{
MessageBox.Show("選択したフィルターは画像に適用できません", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
ついに
{
// カーソルを復元する
this.Cursor = Cursors.Default;
}
呼び出しがスムーズであれば、初心者にとっては、コードがいくらあっても煩雑に感じることはありません。リージョンを上手に活用してください。
また、DocumentsHost という概念もあり、これを使用して画像ファイルをホストしたり、画像とフォームを接続したりするのに非常に便利です。
/// IDocumentsHost インターフェイス
/// ドキュメントとメイン ウィンドウ間の接続を提供します
/// </概要>
パブリック インターフェイス IDocumentsHost
{
bool CreateNewDocumentOnChange{get;}
bool RememberOnChange{get;}
bool NewDocument(ビットマップ画像);
bool NewDocument(ComplexImage 画像);
Bitmap GetImage(オブジェクト送信者、文字列テキスト、サイズ サイズ、PixelFormat 形式);
}どなたでも私と議論することを歓迎します