|
本帖最后由 点点&木木 于 2019-4-11 23:29 编辑 项目介绍 在我们在织物印花厂的培训期间,我们注意到常规织物GSM测量方法浪费了相当多的织物(GSM(克/平方米)表示织物的质量),并且织物工业还使用各种机器和技术来保持织物的GSM在制造和印刷过程中保持一致。 织物GSM测量的常规方法包括切割面积为0.01平方厘米的小圆形织物并使用精密天平测量其重量。这是在拉幅过程中特别完成的,其中根据输入和输出GSM控制机器的过量供给,最后通过PYNQ计算机视觉和BNN叠加的放大图像确定纱线支数和每平方米克数。
这个项目中使用的东西 硬件组件 96Boards Ultra96 × 1 USB显微镜相机 × 1 SparkFun FTDI基本突破 - 3.3V × 1 STM32 F103C8T6 × 1 SparkFun步进电机驱动板A4988 × 4 铝线性杆 × 6 OpenBuilds NEMA 17步进电机 × 4 OpenBuilds GT2 2mm正时皮带线轴(50米) × 1 OpenBuilds GT2(2毫米)同步皮带轮 - 20齿 × 3 OpenBuilds 5mm * 8mm弹性联轴器 × 1 8mm直线轴承 × 10 OpenBuilds 8mm公制Acme导螺杆 × 1 50厘米x 50厘米胶合板 × 1 OpenBuilds微型限位开关 × 2 Sn04电感式传感器 × 1 手动工具和制造机器 3D打印机 - Tevo Tarantula 烙铁(通用) 热风枪
传统结构GSM测量设备 该项目旨在用基于光学的方法取代这种传统方法。目前,该项目的范围仅限于单色浅色(白色)织物与采用平纹斜纹编织的织物。(算法也可以应用于具有相当精确度的针织面料) 在Ultra96上设置PYNQ 对于任何希望以最少的开发和调试时间实现硬件加速应用程序的人来说,PYNQ是一个很好的选择。目前PYNQ支持包括Ultra96在内的多个主板。所以,如果您碰巧拥有Ultra96或类似的主板,我认真地建议您尝试使用PYNQ。(根据我的经验,任何支持Python的东西都非常容易使用) 下载后,您可以使用PYNQ重新刷新电路板附带的SD卡。如果您在格式化卡时遇到问题,此视频将显示如何使用DISKPART for Windows执行此操作。 安装后,您应该能够使用其WiFi接入点(pynq_ <电路板的mac地址>)访问Ultra96。您可以通过从您喜欢的浏览器访问192.168.2.1:9090来打开Jupyter Notebook ,并立即开始开发。 提示: 登录密码是'xilinx' 您可以使用smb访问文件系统。如果你在Windows上,只需键入//192.168.2.1/xilinx即可。 有关更全面的指南,请查看http://pynq.readthedocs.io/en/v2.3/ 获得PYNQ计算机视觉库 PYNQ的计算机视觉库提供了多种覆盖,用于加速硬件中的OpenCV功能。目前支持Filter2D和扩张操作 或者简单地说,在Jupyter笔记本中打开一个终端并输入, sudo pip3 install --upgrade git+https://github.com/Xilinx/PYNQ-ComputerVision.git你完成了 获得PYNQ:BNN叠加 PYNQ越来越好。Xilinx的优秀人员还提供了一些覆盖,允许我们在硬件中运行量化神经网络!您可以在他们的GitHub页面上阅读更多相关信息。(在路上,我们将尝试训练我们自己的小型神经网络,以识别织物结构,使校准更容易。) sudo pip3 install git+https://github.com/Xilinx/BNN-PYNQ.git (on PYNQ v2.3) 现在我们已经安装了所有这些花哨的覆盖层,让我们开始干活。 分离机织物的经纱和纬纱,我们决定使用USB显微镜相机来获得织物的放大图像。
USB显微镜相机 通过他,我们能够获得令人惊讶的良好的织物图像。下面的图片显示了织物在放大时的样子。
织物特写 机织织物由在水平和垂直方向上延伸的纱线组成。那么有什么比使用可靠的索尔过滤器更好的方式来分离它们!Sobel算子用于识别喷射图像的边缘(称为图像强度函数,其中每个像素仅给出从最小强度到最大值的值)。我们可以通过将原始图像与Sobel X和Y内核进行卷积来生成经线和纬线的子图像。 使用这些梯度,可以通过将它们的梯度与它们的邻居的梯度进行比较来找到形成边缘的像素。
Sobel内核 Sobel滤波器 - 使用Filter2D Overlay在Ultra96硬件上实现 在我们开始使用硬件加速过滤器2D之前,我们需要导入必要的叠加层。让我们打开一个新的Notebook并开始开发算法。安装计算机视觉库时,笔记本中提供了使用叠加层时必须遵循的语法和结构。 提示:自己运行提供的笔记本,看看与软件相比这些操作有多快! 提示2 - 如果访问摄像机时出错,请执行Kernel> Restart and Run All。 import cv2 #NOTE: This needs to be loaded first# Load filter2D + dilate overlay# Load filter2D + dilate overlay from pynq import Bitstreambs = Bitstream("/usr/local/lib/python3.6/dist-packages/pynq_cv/overlays/xv2Filter2DDilate.bit")bs.download()import pynq_cv.overlays.xv2Filter2DDilate as xv2 # Load xlnk memory mangager from pynq import XlnkXlnk.set_allocator_library('/usr/local/lib/python3.6/dist-packages/pynq_cv/overlays/xv2Filter2DDilate.so')mem_manager = Xlnk() 我们还需要从我们的网络摄像头捕获。 camera = cv2.VideoCapture(0)width = 640 height = 480 camera.set(cv2.CAP_PROP_FRAME_WIDTH,width)camera.set(cv2.CAP_PROP_FRAME_HEIGHT,height) 您还需要此功能才能在Jupyter中显示图像。稍后我们将把这个项目与Flask集成在一起,所以我们在初始阶段只需要这个,只是为了检查事情是否正常运行。 import IPythondef imshow(img): returnValue, buffer = cv2.imencode('.jpg', img) IPython.display.display(IPython.display.Image(data=buffer.tobytes())) 让我们将内核设置为numpy数组。 sobelx = np.ones((480,640),np.uint8)sobely = np.ones((480,640),np.uint8)blur_frame = np.ones((480,640),np.uint8)#These must be 3x3 since filter2d overlay supports a 3x3 kernel size kernel_sobelx = np.array([[1.0,0.0,-1.0],[2.0,0.0,-2.0],[1.0,0.0,-1.0]],np.float32)#Sobel Xkernel_sobely = np.array([[1.0,2.0,1.0],[0.0,0.0,0.0],[-1.0,-2.0,-1.0]],np.float32)#Sobel Ykernel_sharp = np.array([[-1.0,-1.0,-1.0],[-1.0,32.0,-1.0],[-1.0,-1.0,-1.0]],np.float32)#sharpkernelb = np.array([[1/16.0,1/8.0,1/16.0],[1/8.0,1/4.0,1/8.0],[1/16.0,1/8.0,1/16.0]],np.float32)#blurkernelVoid = np.zeros(0) 您将需要这些来运行您的硬件功能。 xFin= mem_manager.cma_array((height,width),np.uint8)xFbuf= mem_manager.cma_array((height,width),np.uint8) xFout= mem_manager.cma_array((height,width),np.uint8) blur = mem_manager.cma_array((height,width),np.uint8) 让我们开始应用sobel滤波器。 # Flush webcam buffers (needed when rerunning notebook)for _ in range(5): ret, frame_in = camera.read()# Read in a frame ret, img = camera.read() if ret: gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #Convert color image from webcam to grayscale image xFin[:] = gray[:] # load grayscale image to frame in buffer xv2.filter2D(xFin, -1, kernelb, xFout, borderType=cv2.BORDER_CONSTANT) #See how easy it is to call the overlay! blur_frame[:] = xFout[:] imshow(blur_frame) #Display gaussian blurred image blur[:] = xFout[:] xv2.filter2D(blur, -1, kernel_sobelx, xFbuf, borderType=cv2.BORDER_CONSTANT) #convolve with sobel x filter xv2.filter2D(xFbuf, -1, kernelVoid, xFout, borderType=cv2.BORDER_CONSTANT) sobelx[:] = xFout[:] xv2.filter2D(blur, -1, kernel_sobely, xFbuf, borderType=cv2.BORDER_CONSTANT) #convolve with sobel y filter xv2.filter2D(xFbuf, -1, kernelVoid, xFout, borderType=cv2.BORDER_CONSTANT) sobely[:] = xFout[:] imshow(sobelx) imshow(sobely) else: print("Error reading frame from camera.")
高斯模糊。 由于我们需要强烈的模糊来去除纱线的细小部分,因此通过使用相机的焦距调整,输入图像将会略微失焦。
Sobel X.
索贝尔Y. 通过一些额外的形态转换和阈值处理,我们可以轻松地将经纱和纬纱作为白色条纹在黑色背景上获得。 由于我们需要获得经纱和纬纱的数量,我们不得不使用一些耗时的软件功能。(回想一下我们使用上面的代码获得的sobelx和sobely图像) weftcount = 0 warparea = 0 weftarea = 0 draw_warp = 1 draw_weft = 1 warp_perimeter_thresh = 800 weft_perimeter_thresh = 800 ############ This part should come after obtaining sobelx and y images########## if draw_warp: im2x, contoursx, hierarchyx = cv2.findContours(sobelx,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) for cnt in contoursx: perimeter = cv2.arcLength(cnt,True) if perimeter > warp_perimeter_thresh: cv2.drawContours(img, cnt, -1, (204,50,153), 2) warpcount = warpcount+1 warparea = +cv2.contourArea(cnt) if draw_weft: im2y, contoursy, hierarchyy = cv2.findContours(sobely,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) for cnt in contoursy: perimeter = cv2.arcLength(cnt,True) if perimeter > weft_perimeter_thresh: cv2.drawContours(img, cnt, -1, (0,0,255), 2) weftcount = weftcount+1 weftarea = +cv2.contourArea(cnt) mem = "Warp Yarns : "+str(warpcount) mem1 = "Warp Pixel Area : "+str(warparea)+ " pixels" mem2 = "Weft Yarns : "+str(weftcount)mem3 = "Weft Pixel Area : "+str(weftarea)+ " pixels" areatot = (warparea+weftarea) mem4 = "Totalized Pixel Area : "+str(areatot)+ " pixels" font = cv2.FONT_HERSHEY_SIMPLEX cv2.putText(img,mem,(5,370), font, 1,(0,255,0),2,cv2.LINE_AA) cv2.putText(img,mem1,(5,400), font, 1,(255,100,100),2,cv2.LINE_AA) cv2.putText(img,mem2,(5,430), font, 1,(0,255,0),2,cv2.LINE_AA) cv2.putText(img,mem3,(5,460), font, 1,(255,100,100),2,cv2.LINE_AA) cv2.putText(img,mem4,(5,30), font, 1,(255,0,0),2,cv2.LINE_AA) imshow(img) 这部分是资源密集型的,但我们的帧速率已经限制在25FPS(由于它是一个USB摄像头)所以它不会对我们产生太大影响。
绘制轮廓 到目前为止,我们已经获得了经纱和纬纱数量(我们可以直接使用它们来获得纱线密度,即每单位面积纱线的数量),以及纱线的总计像素数。由于无法在不知道给定织物类型的每像素质量的情况下从像素数量估计纱线质量,因此我们可以使用具有已知GSM的织物样品进行简单校准。 举个例子。上面显示的织物样品是110GSM。让我们将其结合起来校准我们的算法来测量这种结构类型的GSM。 gsm = (areatot/27747)*110mem5 = "Predicted GSM : "+str(gsm)+ " GSM"cv2.putText(img,mem5,(5,60), font, 1,(255,0,0),2,cv2.LINE_AA) 让我们再次运行代码并提供相同结构的不同部分的图片。
图9:预测的GSM 预测的GSM接近但可以观察到一点偏差。校准后,我们必须确保传感器和织物之间的距离在移动到不同区域时不会改变。这促使我们开发了本文后半部分所示的XY cartecean滑块系统。 针织面料测试 针织物的织物结构更加复杂,以便以这种方式捕获。但是,如下所示,我们可以在一侧捕获交织的纱线簇(称为环)。
尝试针织物的纱线检测 因此,我们可以建立这些纱线簇和质量之间的关系。为此,我们需要将算法更改为仅查找垂直边。 回想一下draw_warp和draw_weft控件变量。我们可以将其中一个设置为零。 使用PYNQ:BNN使我们的算法更智能 如果我们的算法可以在校准过程中识别织物和针织物,它可以很容易地适应不同的织物结构。让我们利用PYNQ BNN叠加来更快地推动我们的推理。 (在Ultra96上安装BNN后,请务必查看示例笔记本以熟悉自己) 为了在我们的数据集上训练提供的模型,我们需要转移到我们相对更强大的计算机上。(我在Linux上做了这个)所以克隆PYNQ:BNN repo到你的电脑。 您将需要按照以上说明这里来安装Theano,千层面,Pylearn2和其他一些所需的软件包。由于我们的数据集非常小,我们现在不需要CUDA进行GPU加速,但如果您的数据集越来越大,您可能需要它。 安装pylearn2之后,记得通过键入来设置pylearn2数据集的路径变量, export PYLEARN2_VIEWER_COMMAND="eog --new-instance"export PYLEARN2_DATA_PATH=/YOURPATHTOHERE/pylearn2/datasets 在你的终端。 我们现在将尝试为我们的模型生成一个MNIST数据集以进行训练。首先,我们需要获得一些编织和针织面料结构的照片。 您可以使用末尾附带的phototaker.py代码从网络摄像头拍摄多张灰度图像。 获取照片后,我们可以使用这个神奇的工具生成MNIST数据。 将repo克隆到PC中,将图像复制到培训和测试目录中。 根据标签数量编辑batches.meta.txt。 在终端上键入./resize-script.sh,将所有图像的大小调整为28x28,然后运行convert-images-to-mnist-format.py生成数据集。 提示:您将获得4个文件。
MNIST测试和培训数据 gskielian的工具将生成训练图像集和labesl作为train-images-idx3-ubyte和train-labels-idx1-ubyte,但是你必须将火车重命名为t10k,因为这是训练脚本所寻找的。 然后,您必须转到pylearn2 /pyearn2 / datasets并编辑mnist.py文件,并更改训练和测试数据的大小,时期数和批量大小以适合您的数据集。 调整完所有内容后,您可以转到BNN目录并运行mnist.py
培训 这将生成一个名为mnist_parameters.npz的文件。您需要使用mnist-gen-weights-W1A1或W1A2.py生成比特流 成功生成后,将生成的文件放在/usr/local/lib/python3.6/dist-packages/bnn/params/newmodel/lfcW1A1/ 我们可以使用LFC-BNN_MNIST_Webcam.ipynb 笔记本测试它。 hw_classifier = bnn.LfcClassifier(bnn.NETWORK_LFCW1A1,"newmodel",bnn.RUNTIME_HW) import cv2 from PIL import Image as PIL_Image from PIL import ImageEnhance from PIL import ImageOps# says we capture an image from a webcam import numpy as np import math from scipy import misc from array import * cap = cv2.VideoCapture(0) _ , cv2_im = cap.read() cv2_im = cv2.cvtColor(cv2_im,cv2.COLOR_BGR2RGB) img = PIL_Image.fromarray(cv2_im).convert("L") Image enhancement contr = ImageEnhance.Contrast(img)img = contr.enhance(3) # The enhancement values (contrast and brightness) bright = ImageEnhance.Brightness(img) # depends on backgroud, external lightsetc img = bright.enhance(4.0) #Adding a border for future cropping img = ImageOps.expand(img,border=80,fill='white') inverted = ImageOps.invert(img) box = inverted.getbbox() img_new = img.crop(box) width, height = img_new.size ratio = min((28./height), (28./width)) background = PIL_Image.new('RGB', (28,28), (255,255,255)) if(height == width): img_new = img_new.resize((28,28)) elif(height>width): img_new = img_new.resize((int(width*ratio),28)) background.paste(img_new, (int((28-img_new.size[0])/2),int((28-img_new.size[1])/2))) else: img_new = img_new.resize((28, int(height*ratio))) background.paste(img_new, (int((28-img_new.size[0])/2),int((28-img_new.size[1])/2))) background img_data=np.asarray(background) img_data = img_data[:,:,0] misc.imsave('/home/xilinx/img_webcam_mnist.png', img_data) #Resize the image and invert it (white on black) smallimg = ImageOps.invert(img_load) smallimg = smallimg.rotate(0) data_image = array('B') pixel = smallimg.load() for x in range(0,28): for y in range(0,28): if(pixel[y,x] == 255): data_image.append(255) else: data_image.append(1) # Setting up the header of the MNIST format file - Required as the hardware is designed for MNIST dataset hexval = "{0:#0{1}x}".format(1,6) header = array('B') header.extend([0,0,8,1,0,0]) header.append(int('0x'+hexval[2:][:2],16)) header.append(int('0x'+hexval[2:][2:],16)) header.extend([0,0,0,28,0,0,0,28]) header[3] = 3 # Changing MSB for image data (0x00000803) data_image = header + data_image output_file = open('/home/xilinx/img_webcam_mnist_processed', 'wb') data_image.tofile(output_file) output_file.close() class_out = hw_classifier.classify_mnist("/home/xilinx/img_webcam_mnist_processed") print("Class name: {0}".format(hw_classifier.class_name(class_out))) 针织布料图像的输出,在训练时放置在班级名称1下。 推断耗时7.00微秒分类率:每秒142857.14张图像 班级名称:1 我们的模型能够成功地确定织物结构,并且使用硬件进行推理非常快! 制作XYZ滑块 制作该XYZ滑动机构是为了从织物的不同区域取出多个样品而不在校准后改变相机和传感器之间的距离。它是三维印刷零件和金属零件的混合物。附加了3D可打印文件。
滑块机制 使用EAGLE设计PCB,使用A4988步进电机驱动器控制4个步进电机。这些驱动程序使用STM32 F103C8T6 MCU进行控制。MCU根据Ultra96板通过Serial发送的数据控制每个轴的绝对位置。附有PCB Gerber文件和STM32-Arduino代码。(转到页面末尾!) 您还需要下载Arduino 的AccelStepper库。 MCU期望绝对位置用','作为分隔符,*用于表示字符串结尾。我们可以轻松生成一个字符串并使用pySerial通过串行通信方式发送它。
焊接PCB 使用Flask创建Web界面 现在我们的代码没有问题,让我们把所有东西放在一起。你可以使用Flask为你的图像处理应用程序快速生成一个漂亮的webapp。最棒的是,Ultra96板预装了Flask!你可以找到一个很好的例子,说明如何在GitHub上使用Flask和OpenCV。 通过一点Bootstrap和Javascript魔术,您可以创建一个真正响应的Web GUI。
界面 结果 基于Sobel过滤器的纱线检测在来自3种不同织物样品的30张图像上进行。以下图像显示了所用样品的类型。
用于精度测试的织物样品 通过将相机放置在织物的不同部分上获得相同织物的不同样品。对每个样品在10张这样的照片上运行纱线检测算法。结果表明,织物的精度<94%,针织物的垂直环结构精度为90%。(根据要求提供表格结果)
纱线检测结果 使用纱线像素区域确定GSM 使用具有107GSM的标称GSM的50个不同的织物样品(每个在不同点处的4个帧,总共200个图像)用于该测试。列表显示全范围误差(最大读数为250GSM)。下图描绘了读数相对于实际值的变化。
与标称值比较的测量结果 可以观察到相当大的偏差,但这在织物工业中是可接受的。然而,这可以通过增强算法进一步最小化。 结论 Ultra96主板是初学者和专家的理想选择。它与PYNQ的兼容性使其达到了一个全新的水平。如果您对Xilinx提供的令人惊叹的计算机视觉和BNN库不满意,您可以使用Vivado编写自己的“用户层”,并在硬件上运行某些部分(甚至整个程序)。 在硬件层中运行卷积(For Sobel,Smoothing,Erosion,Blurs)等操作可以大大减少算法的运行时间。然而,在该应用中,帧速率受到资源密集型轮廓检测和相机的最大帧速率(24fps)的限制。 希望Avnet和Xilinx为PYNQ和Ultra96创建越来越多的资源和库,使开发人员能够快速测试他们的想法,而无需经历从头开始构建所有内容的麻烦。 |
STM32
超强工具——STM32CubeMX 你会用吗?
集结出发! STM32全国研讨会系列之一:ST智能门铃中国首秀
关于STM32启动文件的几个小问题
【银杏科技ARM+FPGA双核心应用】STM32H7系列35——USB_VCP_FS
【银杏科技ARM+FPGA双核心应用】STM32H7系列28——USB_HID
粉丝分享 | 图说CRC原理应用及STM32硬件CRC外设
STM32L151进入低功耗,并由RTC唤醒的故事
[转]stm32控制NFC模块(PN532)源码(P2P,模拟卡,读写卡等
STM32G070RB+LVGL移植
微信公众号
手机版