اهداف

در پردازش تصویر چون با عملیات های زیادی در ثانیه کار داریم باید کد ما درست باشد و بهترین راه حل باشد در این قسمت یاد میگیریم :

  • چطوری عملکرد کد رو اندازه بگیریم
  • چند تا نکته برای بهبود عملکرد

با توابع cv.getTickCount، cv.getTickFrequency آشنا میشیم

و ماژول profile کمک میکنه گزارش دقیقی از کد بدست بیاریم

اندازه گیری عملکرد

تابع cv.getTickCount تعداد سیکل‌های کلاک بعد از یک رویداد مرجع (مثل زمانی که کامپیوتر روشن می‌شود) تا لحظه‌ای که این تابع فراخوانی شده را برمی‌گردونه. برای همین اگه قبل و بعد از یک اجرای تابع صداش بزنیم میتونیم تعداد سیکل های کلاک مصرف شده رو به دست بیاریم

نمونه استفاده :

e1 = cv.getTickCount()
# اجرای کد شما
e2 = cv.getTickCount()
time = (e2 - e1) / cv.getTickFrequency()

ما این را با مثال زیر نشان می‌دهیم. در این مثال، فیلتر میانگین‌گیری را با هسته‌هایی از اندازه‌های فرد از ۵ تا ۴۹ اعمال می‌کنیم. (نگران اینکه نتیجه چگونه به نظر می‌رسه نباشید هدف چیز دیگه ای هست)

img1 = cv.imread('messi5.jpg')
assert img1 is not None, "فایل خوانده نشد، با os.path.exists() بررسی کنید"

e1 = cv.getTickCount()
for i in range(5, 49, 2):
    img1 = cv.medianBlur(img1, i)
e2 = cv.getTickCount()
t = (e2 - e1) / cv.getTickFrequency()
print(t)
# نتیجه‌ای که من گرفتم: 0.521107655 ثانیه

توجه کنید که شما میتونید از تابع time.time() استفاده کنید و درآخر دو مقدار رو از هم کم کنید

بهینه‌سازی پیش‌فرض در OpenCV

بسیاری از توابع OpenCV با استفاده از SSE2، AVX و غیر بهینه‌ هستن این کتابخونه کد های غیر بهینه هم داره و به طور پیشفرض کتابخونه از کد های بهینه استفاده میکنه اما شما میتونید با دستور cv.setUseOptimized() این موضوع رو برسی کنید

# بررسی فعال بودن بهینه‌سازی
In [5]: cv.useOptimized()
Out[5]: True

In [6]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 34.9 ms per loop

# غیرفعال کردن بهینه‌سازی
In [7]: cv.setUseOptimized(False)

In [8]: cv.useOptimized()
Out[8]: False

In [9]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 64.1 ms per loop

درم ثال بالا می بینید که در حالت بهینه شده عملکرد کد دوبرابر بهتر شده اگر سورس کد رو نگاه کنید متوجه میشوید که فیلتر SIMD بهینه شده

اندازه‌گیری عملکرد در IPython

بعضی مواقع شاید بخویاد عملکرد دو کد مشابه رو برسی کنید , در این موارد در IPython میتونید از دستور timeit استفاده کنید این دستور رو چند بار اجرا کنید تا نتیجه دقیقی بدست بیارید

به عنوان مثال کدوم عملیات بهتره؟ x = 5; y = x**2 یا x = 5; y = x*x یا x = np.uint8([5]); y = x*x یا y = np.square(x) با استفاده از timeit در محیط IPython پاسخ آن را پیدا میکنیم

In [10]: x = 5

In [11]: %timeit y = x**2
10000000 loops, best of 3: 73 ns per loop

In [12]: %timeit y = x*x
10000000 loops, best of 3: 58.3 ns per loop

In [15]: z = np.uint8([5])

In [17]: %timeit y = z*z
1000000 loops, best of 3: 1.25 us per loop

In [19]: %timeit y = np.square(z)
1000000 loops, best of 3: 1.16 us per loop

میبینیم که دستور x = 5 ; y = x*x سریعترین است و تقریبا 20 برابر سریع تر از Numpy است اگه دستور ایجاد ارایه رو در نظر بگیریم ممکنه تا 100 برابر سریع تر باشهNumpy (توسعه‌دهندگان Numpy روی این مشکل کار می‌کنند)

  • توجه عملیات‌های اسکالر در پایتون سریع‌تر از عملیات‌های اسکالر در Numpy هستند. بنابراین برای عملیات‌هایی که شامل یک یا دو عنصر هستند، اسکالر پایتون بهتر است. Numpy در زمانی که اندازه آرایه کمی بزرگ‌تر باشد مزیت دارد.

یه مثال دیگه عملکرد دو کد cv.countNonZero() و np.count_nonzero() رو برسی میکنیم

In [35]: %timeit z = cv.countNonZero(img)
100000 loops, best of 3: 15.8 us per loop

In [36]: %timeit z = np.count_nonzero(img)
1000 loops, best of 3: 370 us per loop

همان‌طور که می‌بینید، تابع OpenCV تقریباً ۲۵ برابر سریع‌تر از تابع Numpy است.

  • توجه توابع opencv معمولا از Numpy سریع تر هستن برای همین معمولا از توابع خود کتابخونه استفاده کنیم بهتره مگر در موارد خاص

تکنیک‌های بهینه‌سازی عملکرد

  • الگوریتم کد رو تا حد ممکن برداری کنید چون Numpy و OpenCV برای عملیات‌های برداری بهینه شده‌اند
  • از حافظه آشکار استفاده کنید
  • تا زمانی که لازم نیست، از کپی کردن آرایه‌ها خودداری کنید. به جای آن سعی کنید از view‌ها استفاده کنید. کپی کردن آرایه یک عملیات پرهزینه اند
  • اگر پس از انجام همه این کارها، کد شما هنوز کند است یا استفاده نکردن از حلقه ها ممکن نیست , از cpython استفاده کنید