لقد تعاملت لأول مرة مع معالجة الصور الرقمية في المدرسة الثانوية في ذلك الوقت، وكان برنامج Photoshop لا يزال 4.0. ربما بسبب أفكاري المسبقة، لا أزال غير مهتم بتعلم برنامج 3DMAX وما شابه، وربما لا يكون الانتقال من 2D إلى 3D أمرًا ضروريًا افعل معي، ولا أستطيع تحمل ترك هذا المربع إلى راتب مكعب مرتفع.... هاها.
عندما كنت في الكلية، كتبت بعض برامج معالجة الصور مع زملائي في الفصل، وكانت البرمجة لا تزال عادية جدًا، وكل ما فكرت فيه هو كيفية تنفيذها. والآن يبدو أن التكنولوجيا الحقيقية هي القدرة على فهم كل شيء الوضع، بدلا من سحر ومضة الإلهام. لقد تواصلت مع بعض برامج معالجة الصور الأجنبية منذ بضعة أيام، وفيما يلي ملخص لذلك، وأقدر أنني لن أدرس معالجة الصور على وجه التحديد في المستقبل.
أخبرني أحد زملائي السابقين ذات مرة أن .net لا يحتوي على مؤشرات. ويبدو أن العديد من الدورات التدريبية تقول نفس الشيء في الواقع. كل ما في الأمر هو أن إطار العمل لا يوصي باستخدام المؤشرات، خاصة في العمليات المشتركة مثل خدمة الويب والاتصال عن بعد، فالمؤشرات غير آمنة. ولكن يجب على كل من استخدم TC أن يعجب بشدة بكفاءة تنفيذ المؤشرات في ظل الطلب على الحسابات المجمعة للبيانات واسعة النطاق، فإن المؤشرات هي الخيار الوحيد. لذلك، يحتفظ .net بالمؤشر بشكل ذكي ويدرجه في مجموعة الأساليب غير الآمنة. سيؤدي الاستخدام المعقول للمؤشرات إلى تحسين كفاءة التنفيذ بشكل كبير. لقد أجريت تجارب لإجراء عمليات نقطة بنقطة على صورة مقاس 640*480 تستغرق عدة دقائق، بينما تكتمل عمليات المؤشر على الفور تقريبًا. لذلك لا تخف من استخدام المؤشرات.
والثاني هو الرياضيات، وأنصح الجميع بفهمه قبل كتابة البرنامج. حصة الرياضيات ليست مزحة... إذا كنت لا تفهم، عليك أن تستلقي في السرير وتفكر في الأمر مرارًا وتكرارًا أن الرياضيات يمكن أن تمنع مرض الزهايمر.
أقرب إلى المنزل، دعونا نتحدث عن هيكل البرنامج:
مشاريع التصوير (المرشحات، والقوام، وأوضاع الصورة)
مشروع الرياضيات (الخوارزمية والحدود والتخصيص وطرق الحساب الشائعة)
مشروع البرنامج الرئيسي
دعني أعطيك مثالاً وسأقوم أيضًا ببعض البرمجة الموجهة للواجهة
العامة IFilter
{
تطبيق الصورة النقطية (صورة نقطية img) ؛
}
للتوضيح، سأقوم أيضًا بتنفيذ البرمجة الموجهة للواجهة. يجب أن يقوم كل مرشح بتنفيذ هذه الواجهة. يتضمن تعريف الواجهة أيضًا تعريفًا للعذر لا يُنشئ صورًا فعلية، ولكنه يُنشئ فقط كائنات ثنائية، والتي لن يتم أخذها في الاعتبار هنا في الوقت الحالي. كون. خذ مرشح اللون المقلوب كمثال
تطبيق الصورة النقطية العامة (Bitmap srcImg)
{
// الحصول على حجم الصورة المصدر
int width = srcImg.Width;
int height = 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 )
;
}
إنه المدخل إلى طريقة التصفية ويكمل العمل التحضيري قبل المعالجة. يستدعي ProcessFilter أيضًا طريقة ProcessFilter الشائعة في كل فئة مرشح، وهذا ProcessFilter هو المفتاح لتحقيق الوظيفة أو التشغيل نقطة بنقطة أو تشغيل القالب.
// معالجة عامل التصفية
ProcessFilter الخاص غير الآمن (بيانات BitmapData، PixelFormat fmt)
{
عرض كثافة العمليات = data.Width;
int height = data.Height;
int lineSize = width * ( ( fmt == PixelFormat.Format8bppIndexed ) ? 1 : 3 );
int Offset = data.Stride - lineSize
؛
byte * ptr = (byte *) data.Scan0.ToPointer( )
;
لـ (int y = 0; y < height; y++ )
{
لـ ( int x = 0; x < lineSize; x++, ptr ++ )
{
// عكس كل بكسل
*ptr = (بايت)( 255 - *ptr );
}
ptr += إزاحة؛
}
}
من بينها، Format8bppIndexed لا داعي للقلق كثيرًا، وأنا شخصيًا أعتقد أنه ليس من الضروري النظر في مسألة التوافق معه في المراحل الأولى من التصميم.
الآن دعونا نتحدث عن الأنسجة. لم أفكر كثيرًا في هذا من قبل، لكنني وجدت أن الأجانب يحبون اللعب بها لأن الأنسجة لديها مساحة أكبر للعب في الرياضيات يكون صحيحًا بناءً على الخيال، إنه أمر صعب. ربما اكتشف أحدهم هذه الطريقة عند تشغيل برامج النمذجة الرياضية، لذلك رفض مدرس الرياضيات عالي المستوى قبول أي شخص ولعب بالخوارزمية بشدة. على أية حال، أعتقد أن هذا ما حدث. . .
الواجهة العامة ITextureGenerator
{
/**//// <الملخص>
/// إنشاء نسيج
/// </الملخص>
float[,] إنشاء( int width, int height );
/**//// <summary>
/// إعادة التعيين - إعادة إنشاء أرقام عشوائية داخلية
/// </الملخص>
إعادة تعيين باطلة ()؛
}
هذه هي واجهة التنفيذ الخاصة بمولد النسيج للتأكد من اختلاف النسيج في كل مرة، يجب تحديث الأرقام العشوائية كمعلمات حسابية.
الضوضاء الخاصة Math.PerlinNoise = new Math.PerlinNoise( 1.0 / 32, 0.05, 0.5, 8 );
يتطلب تحقيق تفاصيل الملمس أيضًا ضوضاء، لذلك يجب تنفيذ العديد من أنواع الضوضاء.
//المنشئون
WoodTexture () العام: هذا (12.0) { }
نسيج الخشب العام (حلقات مزدوجة)
{
this.rings = Rings;
إعادة ضبط( )؛
}
يوفر المنشئ إعداد القيمة الافتراضية، وهو الحد الأقصى لحجم نسيج الوحدة.
// إنشاء نسيج
تعويم عام[،] إنشاء (عرض int، ارتفاع int)
{
float[,] الملمس = تعويم جديد[الارتفاع, العرض];
كثافة العمليات w2 = العرض / 2؛
int h2 = الارتفاع / 2;
لـ ( int y = 0; y < height; y++ )
{
لـ (int x = 0; x < width; x++)
{
double xv = (double) ( x - w2 ) / width;
مزدوج yv = (مزدوج) ( y - h2 ) / الارتفاع؛
الملمس[y, x] =
Math.Max( 0.0f، Math.Min( 1.0f، (تعويم)
الرياضيات. عبس (الرياضيات. الخطيئة (
( Math.Sqrt( xv * xv + yv * yv ) + الضوضاء.Function2D( x + r, y + r ) )
* Math.PI * 2 * حلقات
))
));
}
}
عودة الملمس.
}
هذا كل شيء. . . نتيجة حساباتي السيئة. لا أعرف حتى ما الذي تتحدث عنه، فأنا أختار القيمة القصوى من القيمة الدنيا. ليس من الصعب العثور على الخوارزميات، والمفتاح هو معرفة كيفية دمج البنية لها.
إعادة تعيين الفراغ العام ()
{
ص = راند.التالي(5000);
}لا تنس هذا الرقم العشوائي، فصورة الرقم تحتاج أيضًا إلى جمال طبيعي.
ليس من السهل تنفيذ المفهوم الموجه للكائنات في هندسة الرياضيات. ألق نظرة على PerlinNoise لإلهام الآخرين.
PerlinNoise العامة (initFrequency المزدوج، initAmplitude المزدوج، الثبات المزدوج، int octaves)
{
this.initFrequency = initFrequency;
this.initAmplitude = initAmplitude;
this.persistance = Perstance;
this.octaves = octaves;
}
أولاً، نحتاج إلى جمع البيانات، لأن معالجة الصور تتضمن مواقف أحادية البعد وثنائية الأبعاد، لذا تحتاج الأساليب الأساسية مثل الضوضاء إلى توفير طرق مقابلة للحالتين.
/**//// <الملخص>
/// وظيفة الضوضاء بيرلين 1-D
/// </الملخص>
وظيفة مزدوجة عامة (مزدوج x)
{
تردد مزدوج = initFrequency؛
السعة المزدوجة = initAmplitude؛
مجموع مزدوج = 0؛
// الأوكتافات
لـ (int i = 0; i < الأوكتافات; i++)
{
مجموع += الضوضاء الناعمة (x * التردد) * تردد
السعة * = 2؛
السعة *= الثبات؛
}
مبلغ الإرجاع؛
}
/**//// <summary>
/// وظيفة ضوضاء بيرلين ثنائية الأبعاد
/// </الملخص>
الوظيفة المزدوجة العامة 2D (مزدوج x، مزدوج y)
{
تردد مزدوج = initFrequency؛
السعة المزدوجة = initAmplitude؛
مجموع مزدوج = 0؛
// الأوكتافات
لـ (int i = 0; i < الأوكتافات; i++)
{
مجموع += الضوضاء الناعمة (x * التردد، y * التردد) *
تردد السعة * = 2؛
السعة *= الثبات؛
}
مبلغ الإرجاع؛
}
ما هو الفرق بين أحادي البعد وثنائي الأبعاد عندما كنت في المدرسة المتوسطة، تعلمت أن حركة الخطوط تولد الأسطح عندما كنت في الكلية، تعلمت أن الخطوط الدورية والمتغيرة يمكن أن تمثل الأسطح التعرف على الحواف وشحذها، وجدت أيضًا أنني قللت من تقدير الخط من قبل، لكنه في الواقع أقل بمعلمة واحدة فقط من السطح.
/**//// <الملخص>
/// وظيفة الضوضاء العادية
/// </الملخص>
الضوضاء المزدوجة المحمية (int x)
{
int n = ( x << 13 ) ^ x;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7ffffff ) / 1073741824.0);
}
الضوضاء المزدوجة المحمية (int x، int y)
{
كثافة العمليات ن = س + ص * 57؛
n
= ( n << 13 ) ^ n ;
} مرة أخرى تثبت الفقرة السابقة، شخصيًا، أشعر أن x+y*57 له معنى إسقاطي قليلًا. احصل على قيمة الضوضاء المقابلة. لكن لا يمكن استخدام الضوضاء بشكل مباشر.
/**//// <الملخص>
/// الضوضاء الناعمة
/// </الملخص>
ضوضاء ناعمة مزدوجة محمية (مزدوج x)
{
int xInt = (int) x;
double xFrac = x - xInt;
return CosineInterpolate( Noise( xInt ) , Noise( xInt + 1 ), xFrac );
}
ضوضاء ناعمة مزدوجة محمية (مزدوج x، مزدوج y)
{
int xInt = (int) x;
int yInt = (int) y;
مزدوج xFrac = x - xInt;
double yFrac = y - yInt
;
مزدوج x0y0 = Noise( xInt , yInt );
double x1y0 = Noise( xInt + 1, yInt );
مزدوج x0y1 = الضوضاء( xInt , yInt + 1 );
double x1y1 = Noise( xInt + 1, yInt + 1)
;
double v1 = CosineInterpolate( x0y0, x1y0, xFrac );
double v2 = CosineInterpolate( x0y1, x1y1, xFrac );
//yinterpolation
إرجاع CosineInterpolate( v1, v2, yFrac );
} تعمل الضوضاء الناعمة، التي يبدو من غير المناسب تسميتها، عن طريق استيفاء جيب التمام بدلاً من جيب التمام المنفصل. ما هو الاستيفاء جيب التمام؟ /**//// <الملخص>
/// استيفاء جيب التمام
/// </الملخص>
جيب التمام المزدوج المحمي (مزدوج x1، مزدوج x2، مزدوج أ)
{
double f = ( 1 - Math.Cos( a * Math.PI ) ) * 0.5;
return x1 * ( 1 - f ) + x2 * f;
}هذا كل شيء، هناك بعض الأشياء التي يكفي أن يعرفها المعلم، ويمكنك القيام بها وفقًا لذلك. لماذا؟ لأنك قد لا تفهمها في حياتك، ولكن من الطبيعي أن يكتشفها شخص ما، ولا تزال المعرفة تنتقل. مثلما لا يتعين عليك معرفة نسبة حمض المعدة لديك للاستمتاع بالأطعمة اللذيذة والحارة، فلا داعي للقلق بشأن عسر الهضم للأجيال القادمة. ليست هناك حاجة لفرض بعض الأشياء، فهي سلبية بعض الشيء، هاها.
الصورة ليست صعبة، طالما أنك تفهم علاقة الاتصال، والشكل العائم مثل الفوتوشوب هو الخيار الأفضل، على ما أعتقد، // عكس الصورة
invertColorFiltersItem_Click باطل خاص (مرسل الكائن، System.EventArgs e)
{
ApplyFilter(new Invert());
}
// تطبيق عامل التصفية على الصورة
تطبيق ApplyFilter باطل خاص (مرشح IFilter)
{
يحاول
{
// اضبط مؤشر الانتظار
this.Cursor = Cursors.WaitCursor;
// تطبيق الفلتر على الصورة
filter.Apply
(image);
{
// فتح صورة جديدة في مستند جديد
host.NewDocument(newImage);
}
آخر
{
إذا (المضيف.RememberOnChange)
{
// نسخة احتياطية للصورة الحالية
إذا (النسخ الاحتياطي! = فارغة)
النسخ الاحتياطي.التخلص من
النسخ الاحتياطي = الصورة؛
}
آخر
{
// الافراج عن الصورة الحالية
image.Dispose();
}
newImage
;
UpdateNewImage();
}
}
قبض على (استثناء الوسيطة)
{
messageBox.Show("لا يمكن تطبيق المرشح المحدد على الصورة"، "Error"،MessageBoxButtons.OK،MessageBoxIcon.Error)؛
}
أخيراً
{
// استعادة المؤشر
this.Cursor = Cursors.Default;
}
}إذا كانت المكالمة سلسة، فلن يشعر أي قدر من التعليمات البرمجية بالفوضى. بالنسبة للمبتدئين، استفد من المناطق بشكل جيد.
هناك أيضًا مفهوم DocumentsHost وهو مناسب جدًا لاستخدامه لاستضافة ملفات الصور وتوصيل الصور والنماذج /**//// <summary>
/// واجهة IDocumentsHost
/// يوفر الاتصال بين المستندات والشاشة الرئيسية
/// </الملخص>
الواجهة العامة IDocumentsHost
{
منطقي CreateNewDocumentOnChange{get;}
bool RememberOnChange{get;}
bool NewDocument(Bitmap image);
bool NewDocument(ComplexImage image);
Bitmap GetImage(مرسل الكائن، نص السلسلة، حجم الحجم، تنسيق PixelFormat);
}الجميع مرحب به للمناقشة معي