Histogram تصاویر در OpenCv
در سری مقالات آموزش OpenCv اندروید, به مبحث Histogram تصاویر میپردازیم.
در ابتدا باید به تعریف Histogram بپردازیم. نموداری که یک دید کلی از توزیع مقادیر تصویر به ما میدهد را Histogram گویند. نمودار در محور افقی مقادیری در بازه ۰ تا ۲۵۵ قرار دارد. محور عمودی مشخص کننده تعداد تکرار این مقادیر در تصویر است. برای مثال در نمودار زیر
میزان رنگ قرمز در یک تصویر را بررسی کردهایم. این نمودار مقادیر رنگ قرمز را از ۰ تا ۲۵۵ بر اساس شدت رنگ مرتب کرده است و میزان وقوع هر رنگ در تصویر را در نمودار عمودی مشخص کرده است. طبیعتا با تغییر در محور افقی شدت رنگ و با تغییر در محور عمودی میزان وجود رنگ تغییر میکند. وقتی این نمودار از تصویر را در اختیار داشته باشیم میتوان از شدت وقوع و عمق رنگ در تصویر اطلاع یافت. این نمودار اطلاعاتی پیرامون توزیع پارامتر مورد نظر ارائه میدهد.
پیش از شروع به محاسبه Histogram, نیاز است تا با یکسری از مفاهیم آشنا شویم:
- Bins: همانطور که گفته شد محور افقی شدت را مشخص میکند. به این ترتیب وقتی شدت در بازه ۰ تا ۲۵۵ قرار دارد, به ۲۵۵ bin برای مشخص کردن تعداد وقوع هریک از شدت ها نیاز داریم. اما خوانایی ۲۵۶ bins در یک فضای فشرده شده ممکن است بسیار دشوار باشد. برای حل این مشکل هر bin میتواند یک بازه ۱۰ تایی از شدت رنگ ها باشد و نمودار به ۲۵ bin تقسیم میشود. در واقع برای افزایش خوانایی نمودار میتوان تعداد bin ها را به منظور گسترش و یا کاهش بازه ای که شامل میشوند, تغییر داد.
- Dimensions: مشخص کننده تعداد رنگی است که شدت توزیع آن را در نمودار نمایش میدهیم. برای مثال در یک تصویر grayscale مقدار Dimensions برابر با یک است. و یا اگر در یک تصویر RGB توزیع رنگ آبی و قرمز را بخواهیم مشخص کنیم, Dimensions برابر با دو است.
- Range: بازه مقادیر را مشخص میکند. عموما این بازه بین ۰ تا ۲۵۵ است.
اکنون که تعاریف مورد نیاز بررسی شد, به کد نویسی و محاسبه Histogram میپردازیم. محاسبه را در راستای پروژه قبل ادامه میدهیم. به متد loadImage چنین دستوری را اضافه میکنم و دستور نمایش تصویر را حذف میکنیم:
private void loadImage(String imagePath) { .... Imgproc.resize(rgb, image, new Size(), ratio, ratio, Imgproc.INTER_AREA); Mat histogram = new Mat(); image.copyTo(histogram); calculate(histogram); // display }
خط اول از کد قبلی متد loadImage است و از خط بعد دستورات جدید آغاز میشوند. یک نمونه از mat را گرفته و برای محاسبه به متد calculate ارسال میکنیم. چون میخواهیم تصویر اصلی را داشته باشیم یک نمونه از تصویر را به calculate ارسال کردیم. متد calculate را اینگونه مینویسم:
private void calculate(Mat mat) { int bins = 25; MatOfInt matSaveBins = new MatOfInt(bins);
ابتدا تعداد bin ها را مشخص میکنیم. سپس یک object از کلاس MatOfInt تعریف میکنیم. این کلاس تنها مقادیر را به صورت integer ذخیره میکند. به این ترتیب در این کلاس مقادیر ۲۵ bin ذخیره میشوند.
Mat histogram = new Mat(); float[] floats = new float[bins]; MatOfFloat matSaveRange=new MatOfFloat(0f,255f);
سپس یک mat برای ذخیره و نمایش histogram ایجاد میکنیم. همانطور که میتوانستیم به تک تک مقادیر pixel ها دسترسی داشته باشیم, در اینجا نیز مقادیر را در یک آرایه نگهداری میکنیم. سپس با استفاده از کلاس MatOfFloat مقدار Range را مشخص میکنیم. کلاس MatOfFloat مقادیر را به صورت float ذخیره میکند.
Scalar rgbs[] = new Scalar[]{new Scalar(200, 0, 0, 255)};
در اینجا رنگ نمودار را مشخص میکنیم و ما در اینجا رنگ قرمز را انتخاب کرده ایم.
int thik = (int) (mat.width() / ((bins + 10) / 3)); thik = Math.min(thik, 3);
در کد بالا قطر هر خط را مشخص و تعیین میکنیم که بیش از ۳ نباشد.
MatOfInt[] channel = new MatOfInt[]{new MatOfInt(0), new MatOfInt(1), new MatOfInt(2)};
در اینجا channel ها را مشخص میکنیم و چون تصویر ما به صورت RGB است از سه Channel استفاده میکنیم.
Size size = mat.size(); int offset = (int) ((size.width - (3 * bins + 30) * thik));
در اینجا offset برای شروع نمودار را مشخص میکنیم.
Imgproc.calcHist(Arrays.asList(mat), channel[0], new Mat(), histogram, matSaveBins, matSaveRange);
در اینجا ابتدا یک آرایه از تصاویر را به عنوان اولین پارامتر ارسال میکنیم. در این مثال ما تنها از یک تصویر استفاده میکنیم و این آرایه شامل یک عضو است. سپس Channel مورد نظر را مشخص میکنیم. در حقیقت یک لیست از MatOfInt که در بالا برای Channel ها تعریف کردهایم را در اینجا استفاده میکنیم و چون Dimensions در این مثال برابر با ۱ است تنها از یک channel استفاده میکنیم که اولین عنصر این آرایه است. سومین پارامتر مشخص کننده یک ماسک است که محیط محاسبه را مشخص میکند. چون در این مثال هدف ما بررسی تمام تصویر است یک ماسک خالی که همان یک object جدید از Mat است را به آن ارجاع دادهایم. پارامتر چهارم مشخص کننده mat است که نمودار را در خود ذخیره میکند. پارامتر پنجم مقادیر bin ها و پارامتر آخر مقادیر Range را نگهداری میکند.
Core.normalize(histogram, histogram, size.height / 2, 0, Core.NORM_INF);
سپس نیاز است تا نمودار را نرمال سازی کنیم. پارامتر اول mat منبع و پارامتر دوم mat خروجی است که در اینجا هردو برابر با یک mat است. پارامتر سوم مشخص کننده alpha که ضریب نرمال سازی است. این مقدار میتواند برابر با کمترین مقدار range نیز باشد. پارامتر چهارم مشخص کننده beta که بیشترین مقدار بازه نرمال سازی را مشخص میکند. اگر این مقدار برابر با صفر باشد, OpenCv از این پارامتر صرف نظر میکند. پارامتر آخر مشخص میکند که از چه روشی برای نرمال سازی استفاده کند. Norm Inf مشخص میکند که بیشترین مقدار ورودی را برابر با alpha که ما در اینجا نصف ارتفاع تصویر قرار داده ایم قرار دهد. همچنین از روش های دیگری مانند Norm L1 و Norm L2 نیز میتوان استفاده کرد که از روش های نرمال سازی هستند. روش Norm MinMax نیز وجود دارد که نرمال سازی را در بازه alpha و beta انجام میدهد.
histogram.get(0, 0, floats);
در کد بالا مقادیر bin ها را در آرایه مختص به آنها نگهداری میکنیم.
int i = 0; org.opencv.core.Point p1 = new org.opencv.core.Point(); org.opencv.core.Point p2 = new org.opencv.core.Point(); while (i < bins) { p1.x = p2.x = offset + (i * thik); p1.y = size.height - 1; p2.y = p1.y - (int) floats[i]; Imgproc.line(mat, p1, p2, rgbs[0], thik); i++; }
سپس دو نقطه برای رسم خط ایجاد میکنیم. تعداد این خطوط برابر با تعداد bin ها است بنابراین به ازای هر bin باید نقاط تشکیل دهنده خط آن مجددا مقدار دهی شوند. در نهایت متد line یک خط از نقطه p1 به نقطه p2 با رنگ مشخص شده از آرایه rgbs و قطر thik بر روی تصویر mat ایجاد میکند. در نهایت mat مربوط به Histogram را در خروجی نمایش میدهیم و متد loadImage به این صورت تغییر میکند:
private void loadImage(String imagePath) { Mat image = Imgcodecs.imread(imagePath); Mat rgb = new Mat(); Imgproc.cvtColor(image, rgb, Imgproc.COLOR_BGR2RGB); Display disp = getWindowManager().getDefaultDisplay(); Point points = new Point(); disp.getSize(points); double ratio = getSize(rgb, points.x, points.y); Imgproc.resize(rgb, image, new Size(), ratio, ratio, Imgproc.INTER_AREA); Mat histogram = new Mat(); image.copyTo(histogram); calculate(histogram); // display Bitmap bitmap = Bitmap.createBitmap(histogram.cols(), histogram.rows(), Bitmap.Config.RGB_565); Utils.matToBitmap(histogram, bitmap); imageView.setImageBitmap(bitmap);
کد کامل متد calculate:
private void calculate(Mat mat) { int bins = 25; MatOfInt matSaveBins = new MatOfInt(bins); Mat histogram = new Mat(); float[] floats = new float[bins]; MatOfFloat matSaveRange=new MatOfFloat(0f,255f); int thik = (int) (mat.width() / ((bins + 10) / 3)); thik = Math.min(thik, 3); MatOfInt[] channel = new MatOfInt[]{new MatOfInt(0), new MatOfInt(1), new MatOfInt(2)}; Size size = mat.size(); int offset = (int) ((size.width - (3 * bins + 30) * thik)); Imgproc.calcHist(Arrays.asList(mat), channel[0], new Mat(), histogram, matSaveBins, matSaveRange); Core.normalize(histogram, histogram, size.height / 2, 0, Core.NORM_INF); histogram.get(0, 0, floats); int i = 0; org.opencv.core.Point p1 = new org.opencv.core.Point(); org.opencv.core.Point p2 = new org.opencv.core.Point(); Scalar rgbs[] = new Scalar[]{new Scalar(200, 0, 0, 255)}; while (i < bins) { p1.x = p2.x = offset + (i * thik); p1.y = size.height - 1; p2.y = p1.y - (int) floats[i]; Imgproc.line(mat, p1, p2, rgbs[0], thik); i++; } }
در این مقاله سعی بر این شد تا با مفهوم Histogram آشنا شویم و بتوانیم آن را برای تصاویر گوناگون حساب کنیم.
سری مقالات آموزش OpenCv اندروید همچنان ادامه دارد.
با ما همراه باشید.
مطالب زیر را حتما مطالعه کنید
آموزش Gradle – اهمیت Project Automation
درک مفهوم کدنویسی تمیز در اندروید
5 هک ساده برای کاهش سایز فایل APK
آشنایی با RecyclerView در اندروید
Open/Closed Principle در قوانین Solid
توابع در زبان برنامه نویسی Kotlin
2 Comments
Join the discussion and tell us your opinion.
دیدگاهتان را بنویسید لغو پاسخ
برای نوشتن دیدگاه باید وارد بشوید.
سلام
میشه درمورد روش Norm MinMax بیشتر توضیح بدید
سلام. در روش Norm MinMax از یک بازه برای نرمالسازی استفاده میکنه. به این صورت که مقدار alpha برابر کمترین مقدار مجاز و beta بیشترین مقدار مجاز هست. طبق تعریفی که داشتیم, هر pixel میتونه مقداری در بازه ۰ تا ۲۵۵ داشته باشه. حالا فرض کنید در روش minMax مقدار alpha=10 و beta=200 داشته باشیم. در این صورت مطمئن هستیم که مقدار pixel های ما از ۱۰ کمتر و از ۲۰۰ بیشتر نیستند.
ممنون از همراهیتون