دانشجویار
دانشجویار
خواندن ۱۳ دقیقه·۴ سال پیش

پردازش تصویر در پایتون

زبان برنامه نویسی پایتون یکی از زبان های برنامه نویسی است که در سال های اخیر بسیار پیشرفت کرده و به طور گسترده در بسیاری از پروژه ها از آن استفاده میشود. یکی از دلایل پیشرفت زبان برنامه نویسی پایتون و همچنین معروفیت این زبان را میتوان توانایی بالای این زبان در هوش مصنوعی دانست. در این مقاله میخواهیم به طور کامل راجع به پردازش تصویر در پایتون که یکی از عناصر بسیار مهم و تاثیر گذار در هوش مصنوعی است صحبت کنیم و بدانیم با استفاده از چه فریمورک ها و کتابخانه هایی در زبان برنامه نویسی پایتون، می توانیم پردازش تصویر را انجام دهیم. برای اینکار باید ابتدا توضیحاتی راجع به پردازش تصویر و معنی و مفهوم آن بدهیم و به طور کلی با مفهوم پردازش تصویر آشنا شویم.


پردازش تصویر چیست؟

واژه ها معانی خود را در طول تاریخ بار ها عوض می کنند و یک واژه ممکن است در طول زمان های مختلف معانی متفاوتی داشته باشد. در این بین پردازش تصویر واژه ای است که امروزه فقط به پردازش تصویر دیجیتالی گفته می شود و این واژه عموماً برای این کار استفاده می شود. پردازش تصویر که به زبان انگلیسی به آن Image processing میگویند. در حقیقت پردازش تصویر به معنی پردازش سیگنالهای دیجیتالی است که ممکن است از طریق اسکنر ها و یا دوربین های دیجیتالی دریافت شود. پردازش تصویر به طورکلی دارای مراحل زیر است.


چگونه تصویر خود را با پایتون پردازش کنیم؟

همانگونه که میدانید یکی از قدرتمند ترین قسمت های زبان برنامه نویسی پایتون کتابخانه ها و فریمورک های مختلفی است که برای این زبان ارائه شده و زبان برنامه نویسی پایتون برای انجام هرکاری فریمورک هایی را مخصوص به آن دارد. پردازش تصویر نیز از این قاعده مستثنا نیست و با استفاده از فریمورک های مختلفی میتوان پردازش تصویر را انجام داد. با استفاده از زبان برنامه نویسی پایتون و پردازش تصویر ما میتوانیم کار هایی به مانند Crop و یا تغییر سایز تصاویر را انجام دهیم. از طرفی با استفاده از این زبان میتوانیم دسته بندی عکس ها قطعه بندی و همچنین استخراج ویژگی ها را انجام دهیم. همه این ویژگی ها بخشی از کار هایی است که ما توانایی انجام آن با پایتون را داریم اما اگر هنوز هم برای استفاده از پایتون برای پردازش تصویر شک دارید باید به شما یادآوری کنیم که زبان برنامه نویسی پایتون یک زبان برنامه نویسی ساده است که توانایی انجام بسیاری از کار ها در حوزه پردازش تصویر دارد و این کار ها و ویژگی ها را در عین سادگی با نهایت قدرت در اختیار شما قرار میدهد.


پکیج Scikit-image

برای شروع پردازش تصویر با پایتون تصمیم گرفتیم تا شما را با پکیج Scikit-image آشنا کنیم که یک پکیج منبع باز بوده و با آرایه numpy کار میکند. این پکیج یکی از پکیج های بسیار قدرتمند پایتون است که از نتایج آن در صنایع و آموزش و همچنین در نرم افزار ها استفاده می شود. این پکیج به گونه ای است که علاوه بر سادگی و استفاده راحت قدرت بسیاری را به شما میدهد و این باعث می شود علاوه بر برنامه نویسان حرفه ای، افرادی که به تازگی زبان برنامه نویسی پایتون را یاد گرفتند نیز بتوانند از آن استفاده کنند. البته که یکی از مهمترین قسمت ها برای هر پکیجی وجود مرجع مناسب برای آموزش است اما درباره پکیج Scikit-image خیال شما تقریباً از این بابت راحت است. دلیل این امر این است که کدهای با کیفیت این این پکیج توسط یک اجتماع فعال از داوطلبین توسعه داده شده و مثال های بسیاری از نحوه انجام کار آن وجود دارد. بیشتر امکانات این پکیج در زیر ماژول ها است و پس از import این پکیح با نام Skimage با استفاده از زیرماژول ها میتوانید به بهترین بهره وری از آن برسید. کد زیر نحوه انجام Image filtering را با استفاده از Scikit-image نشان میدهد.

import matplotlib.pyplot as plt
%matplotlib inline
from skimage import data,filters
image = data.coins() # ... or any other NumPy array!
edges = filters.sobel(image)
plt.imshow(edges, cmap='gray')

نحوه انجام کار کد بالا به صورت شکل زیر است.


کتابخانه قدرتمند numpy

Numpy را میتوان یکی از کتابخانه های قدرتمند و اصلی پایتون نام برد که به واسطه آن به سادگی میتوان پردازش تصویر را انجام داد. در حقیقت همه تصاویر در پایتون به شکل یک آرایه numpy هستند و بر همین اساس میتوان با استفاده از این کتابخانه اطلاعات هر پیکسل را تغییر داد. از طرفی مراجع بسیار زیادی نیز برای یادگیری به وسیله numpy وجود دارد که این خود نیز به بیشتر شدن تعداد مخاطبین این کتابخانه اضافه میکند. شما میتوانید عکس های مورد نیاز خود را در این کتابخانه با استفاده از Scikit-image به سادگی Load کرده و با استفاده از Matplotlib نمایش دهید. برای اینکه بخواهیم با استفاده از Numpy ماسکی را روی عکس ایجاد کنیم از کد زیر استفاده میکنیم.

import numpy as np
from skimage import data
import matplotlib.pyplot as plt
%matplotlib inline
image = data.camera()
type(image)
numpy.ndarray #Image is a NumPy array:
mask = image < 87
image[mask]=255
plt.imshow(image, cmap='gray')

نتیجه استفاده از کد بالا تصویری به شکل زیر خواهد شد.


ماژول SciPy

این ماژول یکی از کتابخانه های اصلی زبان برنامه نویسی پایتون به مانند Numpy است اما تفاوت اصلی در اینجاست که SciPy در حقیقت میتواند برای پردازش ها و همچنین دستکاری های ساده تصویر استفاده می شود و در حقیقت یکی از زیر ماژول های SciPy به نام scipy.ndimage آرایه های چند بعدی Numpy را به SciPy میاورد که به سادگی میتوان از آن ها استفاده کرد. scipy.ndimage در حقیقت قابلیت هایی مثل فیلتر های خطی و غیر خطی و همچنین اندازه گیری اشیا و برخی دیگر از قابلیت ها را در اختیار ما قرار می دهد. در کد زیر فیلتر گوسی را با استقاده از Scipy مینویسیم.

from scipy import misc,ndimage
face = misc.face()
blurred_face = ndimage.gaussian_filter(face, sigma=3)
very_blurred = ndimage.gaussian_filter(face, sigma=5)
#Results
plt.imshow(<image to be displayed>)

نتیجه کد تصویری به شکل زیر می شود.


کتابخانه PIL/Pillow

کتابخانه PIL یکی از کتابخانه های منبع باز برای اندروید است که همین قابلیت باعث رایگان بودن آن می شود. این کتابخانه توانایی ذخیره دستکاری و همچنین پشتیبانی از فرمت های مختلف را به پایتون میدهد. این کتابخانه کتابخانه ای بسیار مناسب و قدرتمند بود که متاسفانه عمر آن به درازا نکشیده و در سال 2009 آخرین نسخه آن روانه بازار شد. اما صبر کنید. موضوع به همینجا ختم نمیشود و برنامه نویسان یک نسخه گسترش یافته از این کتابخانه را توسعه دادند که با داشتن پشتیبانی بسیار قدرتمند و همچنین داشتن قابلیت های پردازش تصویر اساسی مانند point operations ،color-space conversions و همچنین فیلتر کردن به وسیله تعدادی از هسته های پیچیده که در درون خود کتابخانه قرار دارند، میتواند به خوبی نیاز برنامه نویسان را تامین کرده و به کمک افراد مختلف در امر پردازش تصویر بیاید. با استفاده از این کتابخانه توسعه یافته که نام آن Pillow است میتوان به شکل زیر یک فیلتر Edge enhancement و یک increased edge enhancement نوشت.

# import image module
from PIL import Image
from PIL import ImageFilter
# Open an already existing image
imageObject = Image.open(&quot./sand.jpg&quot)
# Apply edge enhancement filter
edgeEnahnced = imageObject.filter(ImageFilter.EDGE_ENHANCE)
# Apply increased edge enhancement filter
moreEdgeEnahnced = imageObject.filter(ImageFilter.EDGE_ENHANCE_MORE)
# Show original image - before applying edge enhancement filters
imageObject.show()
# Show image - after applying edge enhancement filter
edgeEnahnced.show()
# Show image - after applying increased edge enhancement filter
moreEdgeEnahnced.show()

برای عکسی به شکل زیر نتیجه برای هر فیلتر را مشاهده میکنید.


بعد از اجرای فیلتر Edge enhancement تصویر به شکل زیر در می آید.


همچنین اگر فیلتر increased edge enhancement را اجرا کنیم تصویر به شکل زیر در می آید.


کتابخانه OpenCV-Python

مطمئناً اگر حتی یک بار هم به سراغ مطالبی راجع به پردازش تصویر با پایتون رفته باشید نام OpenCV-Python را شنیده اید. این کتابخانه یکی از کتابخانه هایی است که امروزه به طور بسیار گسترده و وسیعی در زبان برنامه نویسی پایتون استفاده می شود و همین امر نیز باعث می شود تا تعداد مراجع و آموزش هایی که برای این کتابخانه موجود است زیاد باشد. OpenCV-Python در حقیقت یک API برای OpenCV میباشد که بسیار سریع ساده و قدرتمند است. دلیل این اتفاق این است که کدهای پس زمینه که برای اجرا شدن OpenCV-Python استفاده می شود به شکل C و C++ هستند پس سرعت بالایی دارند. از طرفی چون OpenCV-Python یک API زبان برنامه نویسی پایتون است پس سادگی های این زبان را نیز به ارث میبرد. همین قابلیت ها این کتابخانه را به یکی از کتابخانه های بسیار قدرتمند پایتون برای نوشتن نرم افزار برای بینایی کامپیوتر و پردازش تصویر تبدیل میکند. برای نمونه ما برنامه زیر را با این کتابخانه مینویسیم تا نتیجه را به سادگی مشاهده کنیم.

import cv2
import numpy as np,sys
A = cv2.imread('apple.jpg')
B = cv2.imread('orange.jpg')
# generate Gaussian pyramid for A
G = A.copy()
gpA = [G]
for i in xrange(6):
G = cv2.pyrDown(G)
gpA.append(G)
# generate Gaussian pyramid for B
G = B.copy()
gpB = [G]
for i in xrange(6):
G = cv2.pyrDown(G)
gpB.append(G)
# generate Laplacian Pyramid for A
lpA = [gpA[5]]
for i in xrange(5,0,-1):
GE = cv2.pyrUp(gpA[i])
L = cv2.subtract(gpA[i-1],GE)
lpA.append(L)
# generate Laplacian Pyramid for B
lpB = [gpB[5]]
for i in xrange(5,0,-1):
GE = cv2.pyrUp(gpB[i])
L = cv2.subtract(gpB[i-1],GE)
lpB.append(L)
# Now add left and right halves of images in each level
LS = []
for la,lb in zip(lpA,lpB):
rows,cols,dpt = la.shape
ls = np.hstack((la[:,0:cols/2], lb[:,cols/2:]))
LS.append(ls)
# now reconstruct
ls_ = LS[0]
for i in xrange(1,6):
ls_ = cv2.pyrUp(ls_)
ls_ = cv2.add(ls_, LS[i])
# image with direct connecting each half
real = np.hstack((A[:,:cols/2],B[:,cols/2:]))
cv2.imwrite('Pyramid_blending2.jpg',ls_)
cv2.imwrite('Direct_blending.jpg',real)

اگر برنامه بالا را بنویسیم ترکیبی از سیب و پرتقال را به مانند شکل زیر خواهیم داشت.


فریمورک SimpleCV

فریمورک SimpleCV یکی از فریمورک های بسیار قدرتمند پایتون است که خود شامل کتابخانه های بسیار قدرتمندی همچون OpenCV نیز می شود. این فریمورک به دلیل سادگی خود بسیاری از افراد را جذب خود می کند. دلیل این امر این است که افراد میتوانند بدون داشتن دانش خاصی در زمینه های فرمت های فایلی و عمق بیت ها و ... به کار با این فریمورک بپردازند. افراد مبتدی که به تازگی می خواهند برنامه نویسی براساس بینایی ماشین و همچنین پردازش تصویر را شروع کنند به دلیل ساده بودن این فریمورک، SimpleCV یک انتخاب بسیار مناسب است. همچنین این کتابخانه توانایی کار با ویدیو های آنلاین و فایل های ویدیویی و همچنین دوربین ها را نیز دارد که این خود نیز یکی از مزیت های آن حساب میشود. برنامه زیر نحوه استفاده از این فریمورک را به سادگی برای شما به نمایش می گذارد.

# Make a function that does a half and half image.
def halfsies(left,right):
result = left
# crop the right image to be just the right side.
crop = right.crop(right.width/2.0,0,right.width/2.0,right.height)
# now paste the crop on the left image.
result = result.blit(crop,(left.width/2,0))
# return the results.
return result
# Load an image from imgur.
img = Image('http://i.imgur.com/lfAeZ4n.png')
# create an edge image using the Canny edge detector
# set the first threshold to 160
output = img.edges(t1=160)
# generate the side by side image.
result = halfsies(img,output)
# show the results.
result.show()
# save the output images.
result.save('juniperedges.png')

نتیجه این کد به شکل زیر است.


کتابخانه Mahotas

این کتابخانه نیز یکی دیگر از کتابخانه های زبان برنامه نویسی پایتون برای نوشتن برنامه راهجع به پردازش تصویر و بینایی ماشین است. نکته جالب توجه راجع به این کتابخانه این است که این کتابخانه ترکیبی از Function های سنتی و مدرن پردازش تصویر را در درون خود دارد از طرفی این کتابخانه با کد C++ توسعه داده شده است که این امر نیز به سرعت آن کمک فراوانی میکند. نمونه ای از برنامه نوشته شده با این کتابخانه به صورت زیر است.

# importing required libraries
import pylab as p
import numpy as np
import mahotas
# creating numpy array of type bool
f = np.ones((256, 256), bool)
# setting false values
f[200:, 240:] = False
f[128:144, 32:48] = False
# f is basically True with the exception of two islands:
# one in the lower-right
# corner, another, middle-left
# creating a distance using numpy array
dmap = mahotas.distance(f)
# showing iamge
p.imshow(dmap)
p.show()

نتیجه کد بالا، یک شکل تبدیل فاصله به صورت زیر است.


کتابخانه SimpleITK

ITK در حقیقت یک سیستم متن باز است که به دلیل کامل بودن ابزار های آن قدرت بسیار زیادی را در اختیار برنامه نویسان قرار داده و با استفاده از قابلیت Cross-platform بودن خود به توسعه دهندگان کمک میکند تا به سادگی بتوانند از آن استفاده کنند. SimpleITK در حقیقت یک لایه ساده برروی ITK است که یک ابزار بسیار مناسب برای نمونه سازی سریع و آموزش بوده که به دلیل پشتیبانی از فیلتر تصاویر و بخش بندی تصاویر به ابزاری قدرتمند جهت آنالیز تصاویر تبدیل شده است. نمونه کد استفاده شده برای این کتابخانه به شکل زیر است.

#
# Script for generating images illustrating the movement of images and change in
# similarity metric during registration.
#
import SimpleITK as sitk
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import numpy as np
# Paste the two given images together. On the left will be image1 and on the right image2.
# image2 is also centered vertically in the combined image.
def write_combined_image(image1, image2, horizontal_space, file_name):
combined_image = sitk.Image((image1.GetWidth() + image2.GetWidth() + horizontal_space,
max(image1.GetHeight(), image2.GetHeight())),
image1.GetPixelID(), image1.GetNumberOfComponentsPerPixel())
combined_image = sitk.Paste(combined_image, image1, image1.GetSize(), (0, 0), (0, 0))
combined_image = sitk.Paste(combined_image, image2, image2.GetSize(), (0, 0),
(image1.GetWidth()+horizontal_space,
round((combined_image.GetHeight()-image2.GetHeight())/2)))
sitk.WriteImage(combined_image, file_name)
# Callback invoked when the StartEvent happens, sets up our new data.
def start_plot():
global metric_values, multires_iterations
metric_values = []
multires_iterations = []
# Callback invoked when the EndEvent happens, do cleanup of data and figure.
def end_plot():
global metric_values, multires_iterations
del metric_values
del multires_iterations
# Close figure, we don't want to get a duplicate of the plot latter on.
plt.close()
# Callback invoked when the IterationEvent happens, update our data and
# save an image that includes a visualization of the registered images and
# the metric value plot.
def save_plot(registration_method, fixed, moving, transform, file_name_prefix):
#
# Plotting the similarity metric values, resolution changes are marked with
# a blue star.
#
global metric_values, multires_iterations
metric_values.append(registration_method.GetMetricValue())
# Plot the similarity metric values
plt.plot(metric_values, 'r')
plt.plot(multires_iterations, [metric_values[index] for index in multires_iterations], 'b*')
plt.xlabel('Iteration Number',fontsize=12)
plt.ylabel('Metric Value',fontsize=12)
# Convert the plot to a SimpleITK image (works with the agg matplotlib backend, doesn't work
# with the default - the relevant method is canvas_tostring_rgb())
plt.gcf().canvas.draw()
plot_data = np.fromstring(plt.gcf().canvas.tostring_rgb(), dtype=np.uint8, sep='')
plot_data = plot_data.reshape(plt.gcf().canvas.get_width_height()[::-1] + (3,))
plot_image = sitk.GetImageFromArray(plot_data, isVector=True)
#
# Extract the central axial slice from the two volumes, compose it using the transformation
# and alpha blend it.
#
alpha = 0.7
central_index = round((fixed.GetSize())[2]/2)
moving_transformed = sitk.Resample(moving, fixed, transform,
sitk.sitkLinear, 0.0,
moving_image.GetPixelIDValue())
# Extract the central slice in xy and alpha blend them
combined = (1.0 - alpha)*fixed[:,:,central_index] + \
alpha*moving_transformed[:,:,central_index]
# Assume the alpha blended images are isotropic and rescale intensity
# Values so that they are in [0,255], convert the grayscale image to
# color (r,g,b).
combined_slices_image = sitk.Cast(sitk.RescaleIntensity(combined), sitk.sitkUInt8)
combined_slices_image = sitk.Compose(combined_slices_image,
combined_slices_image,
combined_slices_image)
write_combined_image(combined_slices_image, plot_image, 0,
file_name_prefix + format(len(metric_values), '03d') + '.png')
# Callback invoked when the sitkMultiResolutionIterationEvent happens, update the index into the
# metric_values list.
def update_multires_iterations():
global metric_values, multires_iterations
multires_iterations.append(len(metric_values))
if __name__ == '__main__':
# Read the images
fixed_image = sitk.ReadImage(&quottraining_001_ct.mha&quot, sitk.sitkFloat32)
moving_image = sitk.ReadImage(&quottraining_001_mr_T1.mha&quot, sitk.sitkFloat32)
# Initial alignment of the two volumes
transform = sitk.CenteredTransformInitializer(fixed_image,
moving_image,
sitk.Euler3DTransform(),
sitk.CenteredTransformInitializerFilter.GEOMETRY)
# Multi-resolution rigid registration using Mutual Information
registration_method = sitk.ImageRegistrationMethod()
registration_method.SetMetricAsMattesMutualInformation(numberOfHistogramBins=50)
registration_method.SetMetricSamplingStrategy(registration_method.RANDOM)
registration_method.SetMetricSamplingPercentage(0.01)
registration_method.SetInterpolator(sitk.sitkLinear)
registration_method.SetOptimizerAsGradientDescent(learningRate=1.0,
numberOfIterations=100,
convergenceMinimumValue=1e-6,
convergenceWindowSize=10)
registration_method.SetOptimizerScalesFromPhysicalShift()
registration_method.SetShrinkFactorsPerLevel(shrinkFactors = [4,2,1])
registration_method.SetSmoothingSigmasPerLevel(smoothingSigmas=[2,1,0])
registration_method.SmoothingSigmasAreSpecifiedInPhysicalUnitsOn()
registration_method.SetInitialTransform(transform)
# Add all the callbacks responsible for ploting
registration_method.AddCommand(sitk.sitkStartEvent, start_plot)
registration_method.AddCommand(sitk.sitkEndEvent, end_plot)
registration_method.AddCommand(sitk.sitkMultiResolutionIterationEvent, update_multires_iterations)
registration_method.AddCommand(sitk.sitkIterationEvent, lambda: save_plot(registration_method, fixed_image, moving_image, transform, 'output/iteration_plot'))
registration_method.Execute(fixed_image, moving_image)

کد بالا به شکل زیر عمل میکند.


استفاده از pgmagick

Pgmagick در حقیقت یک پوشش براساس پایتون برای سیستم GraphicsMagick است. این سیستم پردازش تصویر در حقیقت به چاقوی ارتشی سوئیسی در میان برنامه نویسان شهرت دارد. دلیل این شهرت چیزی نیست جز توانایی بالای این سیستم در پشتیبانی از فرمت های مختلف مانند GIF, PNG, PDF, TIFF و 84 فرمت دیگر که نشان از توانایی این سیستم دارد. همچنین این سیستم از کتابخانه هایی برای نوشتن و خواندن و ایجاد تغییر در عکس ها پشتیبانی میکند. کد زیر نحوه استفاده از این سیستم را برای شما به نمایش در می آورد.

from pgmagick.api import Image
img = Image((300, 200))
img.annotate('Hello World', angle=45)
img.write('helloworld45.png')

این کد Hello world را به شکل زیر به نمایش میگذلرد.


Pycairo

Pycairo را میتوان یه شکل مجموعه ای از اتصالات پایتون برای کتابخانه دو بعدی Cairo نام برد. این کتابخانه یکی از کتابخانه های پایتون است که با استفاده از آن به طراحی وکتور میپردازند و Pycairo کمک میکند که قابلیت های Cairo در پایتون نیز قابل استفاده باشد. نمونه ای از برنامه ای با استفاده از Pycairo به شکل زیر است.

#!/usr/bin/python
'''
ZetCode PyCairo tutorial
In this program, we connect all mouse
clicks with a line.
Author: Jan Bodnar
Website: zetcode.com
'''
from gi.repository import Gtk, Gdk
import cairo
class MouseButtons:
LEFT_BUTTON = 1
RIGHT_BUTTON = 3
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.darea = Gtk.DrawingArea()
self.darea.connect(&quotdraw&quot, self.on_draw)
self.darea.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.add(self.darea)
self.coords = []
self.darea.connect(&quotbutton-press-event&quot, self.on_button_press)
self.set_title(&quotLines&quot)
self.resize(300, 200)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect(&quotdelete-event&quot, Gtk.main_quit)
self.show_all()
def on_draw(self, wid, cr):
cr.set_source_rgb(0, 0, 0)
cr.set_line_width(0.5)
for i in self.coords:
for j in self.coords:
cr.move_to(i[0], i[1])
cr.line_to(j[0], j[1])
cr.stroke()
del self.coords[:]
def on_button_press(self, w, e):
if e.type == Gdk.EventType.BUTTON_PRESS \
and e.button == MouseButtons.LEFT_BUTTON:
self.coords.append([e.x, e.y])
if e.type == Gdk.EventType.BUTTON_PRESS \
and e.button == MouseButtons.RIGHT_BUTTON:
self.darea.queue_draw()
def main():
app = Example()
Gtk.main()
if __name__ == &quot__main__&quot:
main()

با استفاده از کد بالا میتوانیم وکتور زیر را طراحی کنیم.


با استفاده از همه فریمورک ها، پکیج ها و همچنین کتابخانه های بالا میتوان کار پردازش تصویر با پایتون را شروع کرد و بنا به نوع کار خود از یکی از آن ها استفاده کنیم.

پیشنهاد می‌کنیم قبل از ورود به بحث پردازش تصویر در پایتون ، به برنامه نویسی با این زبان مسلط بشین. دوره آموزش رایگان پایتون در سایت دانشجویار میتونه بهتون کمک کنه. برای مشاهده و دریافت رایگان روی لینک زیر کلیک کنید:
https://dnjy.ir/W4N3


پایتونpythonنکات پایتونپردازش تصویربرنامه نویسی
شاید از این پست‌ها خوشتان بیاید