اهداف
در پردازش تصویر چون با عملیات های زیادی در ثانیه کار داریم باید کد ما درست باشد و بهترین راه حل باشد در این قسمت یاد میگیریم :
- چطوری عملکرد کد رو اندازه بگیریم
- چند تا نکته برای بهبود عملکرد
با توابع 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 استفاده کنید