{"id":4512,"date":"2021-08-05T09:01:17","date_gmt":"2021-08-05T07:01:17","guid":{"rendered":"http:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/?p=4512"},"modified":"2022-09-07T10:56:38","modified_gmt":"2022-09-07T08:56:38","slug":"recognizing-seam-errors-and-breaks-with-a-residual-neural-network","status":"publish","type":"post","link":"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/2021\/08\/05\/recognizing-seam-errors-and-breaks-with-a-residual-neural-network\/","title":{"rendered":"Recognizing Seam Errors and Breaks with a residual neural network"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p>Once a year we offer a class called <em>Forschungsprojekt Industrie 4.0<\/em> (Research Project 4.0), which is a practical assignment to enrolled of students. This time the goal of the assignment was to create a quality control system which recognizes errors and breaks on textile seams. Six students enrolled to this specific assignment.<\/p>\n\n\n\n<p>In Figure 1 you see on the left upper side a sewing machine from top. We reconstructed a lamp holder to a camera holder by attaching a usb-camera (1 on Figure 1) on it. Then we moved the usb-camera right next to the sewing machine (2 on Figure 1) to have a camera view of the sewed textile. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"616\" height=\"414\" src=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/Setup.png\" alt=\"\" class=\"wp-image-4515\" srcset=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/Setup.png 616w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/Setup-300x202.png 300w\" sizes=\"auto, (max-width: 616px) 100vw, 616px\" \/><figcaption>Figure 1: Setup<\/figcaption><\/figure>\n\n\n\n<p>While the sewing maching is running, the usb-camera takes images from the textile with its sewed string. The image is sent to a quality control system, which should recognize errors and breaks. So the assignment for the students here is to create a quality control system.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Preparations<\/h2>\n\n\n\n<p>We decided to use a neural network to recognize breaks and errors from the images made by the usb-camera. For training the neural network the students needed to gather numerous training images.<\/p>\n\n\n\n<p>For this purpose the students cut textile stripes which can bee seen in Figure 2. Then the students sewed seams along the textile stripes, which were recorded by the usb-camera system at the same time. The videos were saved as mp4 files. <\/p>\n\n\n\n<p>Only in rare cases sewing machines produce errors or breaks. This is why we had to generate the errors and breaks ourselves. <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"338\" height=\"447\" src=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/stripes.png\" alt=\"\" class=\"wp-image-4517\" srcset=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/stripes.png 338w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/stripes-227x300.png 227w\" sizes=\"auto, (max-width: 338px) 100vw, 338px\" \/><figcaption>Figure 2: Stripes<\/figcaption><\/figure>\n<\/div>\n\n\n<p>In Figure 3 you can see how we create errors and breaks. The left picture shows how an error is produced by sticking a scissor under the seam which widening the string. The right picture shows how we produce a break by cutting the string with the scissor. Therefore we have already two categories to distinguish: &#8220;errors&#8221; and &#8220;breaks&#8221;. We call them from now on attributes. There are two more attributes to come.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/prepare.png\" alt=\"\" class=\"wp-image-4518\" width=\"362\" height=\"240\" srcset=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/prepare.png 454w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/prepare-300x199.png 300w\" sizes=\"auto, (max-width: 362px) 100vw, 362px\" \/><figcaption>Figure 3: Prepare errors and breaks<\/figcaption><\/figure>\n<\/div>\n\n\n<p>On sewing machines you can set up the distances for the stitches. Possible values are e.g. 2mm or 4mm. We decided to use exactly these values to distinguish and defined for this the attribute &#8220;length&#8221;. If the attribute &#8220;length&#8221; is true, than we have a stitch distance of 2mm, if false we have a stitch distance of 4mm.<\/p>\n\n\n\n<p>The last attribute we call &#8220;good&#8221;. This simply means that we recognize the image on the seam as a good seam. If attribute &#8220;good&#8221; is set to false, there is a problem with an &#8220;error&#8221;, &#8220;break&#8221; or &#8220;length&#8221;. This makes it altogether four attributes: &#8220;good&#8221;, &#8220;error&#8221;, &#8220;break&#8221; and &#8220;length&#8221;.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"123\" src=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/seams-1-1024x123.png\" alt=\"\" class=\"wp-image-4568\" srcset=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/seams-1-1024x123.png 1024w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/seams-1-300x36.png 300w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/seams-1-768x92.png 768w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/seams-1.png 1251w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Figure 4: Images of categories &#8220;good&#8221;, &#8220;error&#8221;, &#8220;break&#8221; and &#8220;length&#8221;<\/figcaption><\/figure>\n\n\n\n<p>In Figure 4 you find images for each attribute. On the left you find a &#8220;good&#8221; image. The second left picture you see an &#8220;error&#8221;. On the second right picture you find a &#8220;break&#8221;.  The right most picture shows a length distance of 4mm, which is an attribute with value false.<\/p>\n\n\n\n<p>You might have noticed that the picture on the right and the picture on the second right have the same stitch distance. So the second left picture has a &#8220;break&#8221;, and a stitch distance of 4mm. This means the classifications are not exclusive. An image from the usb-camera can show all attributes as true, all attributes as false or any other combination.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating labeled data<\/h2>\n\n\n\n<p>In Figure 1 you can see the setup of the sewing machine and the attached usb-camera. We used this setup to create videos while sewing strings on the textile stripes (Figure 2). Since error&#8217;s and break&#8217;s do not happen very often, we prepared the textile stripes  with the scissor, and created new videos from them. Our goal was to have balanced data, which means that there is a good proportion of error&#8217;s and break&#8217;s in our data set. We are not describing the code to create video&#8217;s here. You could actually use any cell phone for this task.<\/p>\n\n\n\n<p>However we want to show how we have labeled the training images resulting from the videos. The code below sets the <em>basepath<\/em> and assigns the video file path to the variable <em>video<\/em>. We will also use a file called <em>data.csv<\/em> to store the values (true or false) for each attribute on every image. It is basically a lookup table. The filename is assigned to <em>datacsv.<\/em><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">basepath = r\"...\"\nvideos = \"videos\"\nvideopath = os.path.join(basepath,videos)\n\nvideonamepre = \"video_2021_05_05_09_34_57__2mm_Kombi\"\nvideonameext = \"mp4\"\ndatacsv = videonamepre + \"_data.csv\"\nvideoname = videonamepre + \".\" + videonameext\nvideo = os.path.join(videopath,videoname)<\/pre>\n\n\n\n<p>The list <em>picnames<\/em> contains the names of the attributes, see code below. The list <em>picstats<\/em> contain the true or false values of the attributes for one image. The variable <em>picpath<\/em> is the path of the directory where we store our images extracted from the videos.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">picnames = [\"good\",\"error\",\"break\",\"length\"]\npicstats = [False, False, False, False]\n\npicspath = os.path.join(basepath, \"pics\")<\/pre>\n\n\n\n<p>The function <em>save_entry<\/em> is shown below. It uses the python library <em>pandas<\/em> to append the classification information (parameters <em>name<\/em>, <em>picnames<\/em> and <em>picstats<\/em>) to a data.csv file. First it creates a data frame, then it reads in an already existing <em>data.csv<\/em> file and loads in its content into the list <em>datalist<\/em>. Finally it moves the <em>name<\/em>, <em>picnames<\/em> and <em>picstats<\/em> values into the dictionary <em>dataitem <\/em>and appends it to <em>datalist<\/em>.  The function <em>save_entry<\/em> converts <em>datalist<\/em> into a pandas data frame and saves the content into the <em>data.csv<\/em> file.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def save_entry(name, picnames, picstats):\n\n    arr = os.listdir(picspath)\n      \n    df = pd.DataFrame()\n    datalist = []\n    \n        \n    if os.path.isfile(os.path.join(os.path.join(picspath,datacsv))):\n\n        df=pd.read_csv(os.path.join(os.path.join(picspath,datacsv)))\n        for index, row in df.iterrows():\n            datalist.append({'name': row['name'], picnames[0]: row[picnames[0]], picnames[1]: row[picnames[1]], picnames[2]: row[picnames[2]], picnames[3]: row[picnames[3]]})\n            \n    dataitem ={'name': name, picnames[0]: picstats[0], picnames[1]: picstats[1], picnames[2]: picstats[2], picnames[3]: picstats[3]}\n    datalist.append(dataitem)\n    df = pd.DataFrame(datalist) \n    df.to_csv(os.path.join(picspath, datacsv))\n    \n    return<\/pre>\n\n\n\n<p>The code below is a small application to label the images from the previously created videos. First it opens a video named <em>video<\/em> with OpenCV&#8217;s constructor VideoCapture. The code runs into a loop to process each single image of the video. The OpenCV&#8217;s function <em>waitKey<\/em> stops the code execution until a key is pressed. In case the user presses the key &#8220;n&#8221;, the code reads in the next image of the video. It then puts text with classification information onto the image and displays it. Basically it displays the keys g for &#8220;good&#8221;, e for &#8220;error&#8221;, b for &#8220;break&#8221;, l for &#8220;length&#8221; and their attribute values. The user can press the keys<em> g, e, b, l <\/em>to toggle the attribute values. If the user presses the <em>s <\/em>key, then the application saves the attribute values with the function <em>save_entry <\/em>into the <em>data.csv<\/em> file.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">count = 0\ncap = cv2.VideoCapture(video)\n\nif cap.isOpened():\n    ret, frame = cap.read() \n\nwhile(cap.isOpened()):\n\n    if ret == True:\n        framedis = frame.copy()\n        framedis = cv2.putText(framedis, str(count), (5, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,255,255), 2, cv2.LINE_AA)\n        framedis = cv2.putText(framedis, str(count), (5, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,0,0), 1, cv2.LINE_AA)\n        framedis = cv2.putText(framedis, picnames[0] + \" (g) \" + str(picstats[0]), (35, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,255,255), 3, cv2.LINE_AA)\n        framedis = cv2.putText(framedis, picnames[0] + \" (g) \" + str(picstats[0]), (35, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,0,0), 1, cv2.LINE_AA)\n        framedis = cv2.putText(framedis, picnames[1] + \" (e) \" + str(picstats[1]), (35, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,255,255), 3, cv2.LINE_AA)\n        framedis = cv2.putText(framedis, picnames[1] + \" (e) \" + str(picstats[1]), (35, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,0,0), 1, cv2.LINE_AA)\n        framedis = cv2.putText(framedis, picnames[2] + \" (b) \" + str(picstats[2]), (35, 45), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,255,255), 3, cv2.LINE_AA)\n        framedis = cv2.putText(framedis, picnames[2] + \" (b) \" + str(picstats[2]), (35, 45), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,0,0), 1, cv2.LINE_AA)\n        framedis = cv2.putText(framedis, picnames[3] + \" (l) \" + str(picstats[3]), (35, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,255,255), 3, cv2.LINE_AA)\n        framedis = cv2.putText(framedis, picnames[3] + \" (l) \" + str(picstats[3]), (35, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,0,0), 1, cv2.LINE_AA)\n        \n        cv2.imshow('frame',framedis)\n    else:\n        break\n        \n    key = cv2.waitKey(0) &amp; 0xFF\n    if key == ord('q'):\n        break\n    if key == ord('n'):\n        ret, frame = cap.read()\n        count += 1\n    if key == ord('g'):\n        if picstats[0] == True:\n            picstats[0] = False\n        else:\n            picstats[0] = True\n    if key == ord('e'):\n        if picstats[1] == True:\n            picstats[1] = False\n        else:\n            picstats[1] = True\n    if key == ord('b'):\n        if picstats[2] == True:\n            picstats[2] = False\n        else:\n            picstats[2] = True\n    if key == ord('l'):\n        if picstats[3] == True:\n            picstats[3] = False\n        else:\n            picstats[3] = True\n            \n            \n    if key == ord('s'):\n        save_entry(videonamepre+f\"_{count}.png\", picnames, picstats)\n        cv2.imwrite(os.path.join(picspath, videonamepre+f\"_{count}.png\"),frame)       \n            \ncap.release()\ncv2.destroyAllWindows()<\/pre>\n\n\n\n<p>In Figure 5 you can see how the application displays the current image of a video. The user can change the attribute values by pressing one of the described keys to save each image into the<em> data.csv<\/em> file.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/labeling.png\" alt=\"\" class=\"wp-image-4525\" width=\"305\" height=\"339\" srcset=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/labeling.png 519w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/labeling-270x300.png 270w\" sizes=\"auto, (max-width: 305px) 100vw, 305px\" \/><figcaption>Figure 5: Labeling<\/figcaption><\/figure>\n<\/div>\n\n\n<p>The students working on this project created around 15000 images and saved their attribute values with this application into csv files.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Preprocessing the data<\/h2>\n\n\n\n<p>For training we need to have both, training data and validation data, which we want to separate. We do this by creating two files with lookup tables, a <em>train.csv<\/em> file and a <em>valid.csv<\/em> file. Both files have the image file name, the path location and its attribute values. Also we take a small number of images and assign them to a test data file which we call <em>test.csv<\/em>. <\/p>\n\n\n\n<p>The function <em>createcsv<\/em> below is creating a new csv file with a csv file name as parameter (<em>datacsv<\/em>) from a list <em>piclist<\/em>. The list <em>piclist <\/em>contains a list of image filenames and its attribute values.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def createcsv(datacsv, targetpath, piclist):\n\n    datalist = []\n    \n    df = pd.DataFrame()\n    \n    for item in piclist:\n        datalist.append({'name': item[0], classes[0]: item[1][classes[0]], classes[1]: item[1][classes[1]], classes[2]: item[1][classes[2]], classes[3]: item[1][classes[3]]})\n\n    df = pd.DataFrame(datalist) \n    df.to_csv(os.path.join(targetpath, datacsv))<\/pre>\n\n\n\n<p>The code below opens all csv files one by one located inside the<em> fullpathpic<\/em> directory . It reads each files content and moves it into a pandas dataframe <em>df<\/em>. The code iterates through the content and moves each entry into the list <em>picnamelist<\/em>. So each entry of <em>picnamelist<\/em> contains a full path and filename of the image and its attribute values. <\/p>\n\n\n\n<p>The list <em>picnamelist<\/em> is then shuffled with<em> random<\/em>&#8216;s<em> shuffle<\/em> function and 20 percent of  <em>picnamelist<\/em> elements are moved into <em>validlist<\/em>. The code moves another two percent of <em>picnamelist<\/em> elements into <em>testlist<\/em>, and finally the code moves the remaining content into <em>trainlist<\/em> (around 78 percent). The code subsequently calls the function <em>createcsv<\/em> with <em>validlist<\/em>, <em>testlist<\/em> and <em>trainlist<\/em> as parameters to store lookup tables into <em>valid.csv<\/em>, <em>test.csv<\/em> and <em>train.csv<\/em>. <\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">picnamelist = []\nattributelist = []\nvalidlist = []\ntestlist = []\ntrainlist = []\n\nfor file in os.listdir(fullpathpic ):\n    if file.endswith(\".csv\"):\n        f = open(os.path.join(os.path.join(fullpathpic,file)), \"r\")\n        df = pd.read_csv(f, index_col = 0) \n        \n        for index, row in df.iterrows():\n            picname = row[\"name\"]\n            \n            if os.path.isfile(os.path.join(fullpathpic, picname)):\n                picnamelist.append([os.path.join(fullpathpic, picname), row])\n\n\nrandom.shuffle(picnamelist)\n\nnum = 20*len(picnamelist) \/\/ 100\nvalidlist = picnamelist[:num]\ntestlist = picnamelist[num:num+num\/\/10]\ntrainlist =  picnamelist[num+num\/\/10:]\n\ncreatecsv(\"valid.csv\", basepath, validlist)\ncreatecsv(\"test.csv\", basepath, testlist)\ncreatecsv(\"train.csv\", basepath, trainlist)<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Training<\/h2>\n\n\n\n<p>The code below defines the <em>basepath<\/em>, and the location of  the lookup tables for the training, validation and testing data (<em>traincsv, validcsv<\/em> and <em>testcsv<\/em>). The code stores the model into the path <em>model_path<\/em>.  The model filename is <em>modelsaved<\/em>. The list <em>classes<\/em> contains the attributes, like <em>picnames<\/em> above (note that the code below and above are different code files).<\/p>\n\n\n\n<p>The pandas <em>read_csv<\/em> function returns the length of the training data and validation data and assigns them to<em> lentrain<\/em> and<em> lenvalid<\/em>.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">basepath = r\"...\"\ntraincsv = os.path.join(basepath, \"train.csv\")\nvalidcsv = os.path.join(basepath, 'valid.csv')\ntestcsv = os.path.join(basepath, 'test.csv')\nmodel_path = os.path.join(basepath, 'models')\n\nclasses = [\"good\",\"error\",\"break\",\"length\"]\n\nnow = datetime.datetime.now()\n\nmodelsaved = \"model.h5\"\n\nlentrain = pd.read_csv(traincsv, index_col = 0).shape[0]\nlenvalid = pd.read_csv(validcsv, index_col = 0).shape[0]<\/pre>\n\n\n\n<p>We have described the function <em>generatebatchdata<\/em> previously<a href=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/2020\/08\/15\/image-processing-for-an-autonomous-guided-vehicle-in-a-learn-factory\/\"> here<\/a>, so we dont go too much into details. As parameter we use the previously created lookup tables and open them with pandas <em>read_csv<\/em> function. The code moves the filenames into the list <em>filenames<\/em> and its attribute values into the list <em>classnumbers<\/em>. During training the number of elements (<em>batchsize <\/em>elements) are taken from both lists <em>filenames<\/em> and <em>classnumbers<\/em>, then the images are opened with OpenCV&#8217;s function <em>imread<\/em> and returned to the training process. Around 70 percent of the images are augmented by its brightness and contrast with OpenCV&#8217;s <em>convertScaleAbs<\/em> function.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def generatebatchdata(batchsize, datacsv, classes):\n\n    filenames = []\n    classnumbers = []\n    \n    df = pd.read_csv(datacsv, index_col = 0) \n        \n    for index, row in df.iterrows():\n        filenames.append(row[\"name\"])\n        classnumbers.append([int(row[classes[0]]), int(row[classes[1]]), int(row[classes[2]]), int(row[classes[3]])])\n           \n    while True:\n        batchstart = 0\n        batchend = batchsize    \n        \n        while batchstart &lt; len(filenames):\n            \n            imagelist = []\n            classlist = []\n            \n            limit = min(batchend, len(filenames))\n\n            for i in range(batchstart, limit):\n                img = np.zeros((dim[0], dim[1],3), 'uint8')\n                img = cv2.resize(cv2.imread(filenames[i],cv2.IMREAD_COLOR ), dim, interpolation = cv2.INTER_AREA)\n                if random.random() &gt; 0.3:\n                    alpha = 0.8 + 0.4*random.random()\n                    beta = int(random.random()*15)\n                    img = cv2.convertScaleAbs(img, alpha=alpha, beta=beta)\n\n                imagelist.append(img)\n                classlist.append(classnumbers[i])\n\n\n            train_data = np.array(imagelist, dtype=np.float32)\n            train_data -= train_data.mean()\n            train_data \/= train_data.std()\n            train_class= np.array(classlist, dtype=np.float32)\n\n            yield (train_data,train_class)    \n\n            batchstart += batchsize   \n            batchend += batchsize\n<\/pre>\n\n\n\n<p>We instantiate two functions from <em>generatebatchdata<\/em>: <em>generator_train<\/em> and <em>generator_valid<\/em>. We have set the batchsizes for training to 20, and for validation to one.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">batchsizetrain = 20\nbatchsizevalid = 1\n\ngenerator_train = generatebatchdata(batchsizetrain, traincsv , classes)\ngenerator_valid = generatebatchdata(batchsizevalid, validcsv, classes)<\/pre>\n\n\n\n<p>The functions<em> relu_bn<\/em>, <em>residual_block<\/em>, and <em>create_res_net<\/em>  in the code below create a residual neural network (RNN). Dorian Lazar supplied the code on github and it can be found <a rel=\"noreferrer noopener\" href=\"https:\/\/gist.github.com\/lazuxd\/d7aaba284123bf3340e723701e381e6e\" target=\"_blank\">here<\/a>. We are not going to much into details, but the <em>create_res_net<\/em> is implementing a RNN from elements as shown as in Figure 6. You see here one residual block element in case the <em>downsample<\/em> parameter was set to true. The function<em> create_res_net<\/em> is appending several such blocks into one complete RNN.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"266\" height=\"327\" src=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/residual_block.png\" alt=\"\" class=\"wp-image-4641\" srcset=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/residual_block.png 266w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/residual_block-244x300.png 244w\" sizes=\"auto, (max-width: 266px) 100vw, 266px\" \/><figcaption>Figure 6: Residual Block<\/figcaption><\/figure>\n<\/div>\n\n\n<p>We slightly modified the code at the end of the function <em>create_res_net<\/em>. As an output we have a dense layer with four neurons, which is the length of the list<em> classes <\/em>(also of the number of attributes we use). The last activation function we use the <em>sigmoid<\/em> function. Using the <em>sigmoid<\/em> function is common practice for mulit-label classfication problems in combination with the <em>binary_crossentropy<\/em> loss function.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def relu_bn(inputs: Tensor) -&gt; Tensor:\n    relu = ReLU()(inputs)\n    bn = BatchNormalization()(relu)\n    return bn\n\ndef residual_block(x: Tensor, downsample: bool, filters: int, kernel_size: int = 3) -&gt; Tensor:\n    y = Conv2D(kernel_size=kernel_size,\n               strides= (1 if not downsample else 2),\n               filters=filters,\n               padding=\"same\")(x)\n    y = relu_bn(y)\n    y = Conv2D(kernel_size=kernel_size,\n               strides=1,\n               filters=filters,\n               padding=\"same\")(y)\n\n    if downsample:\n        x = Conv2D(kernel_size=1,\n                   strides=2,\n                   filters=filters,\n                   padding=\"same\")(x)\n\n    out = Add()([x, y])\n    out = relu_bn(out)\n    return out\n\ndef create_res_net():\n    \n    inputs = Input(shape=(dim[0], dim[1], 3))\n    num_filters = 64\n    \n    t = BatchNormalization()(inputs)\n    t = Conv2D(kernel_size=3,\n               strides=1,\n               filters=num_filters,\n               padding=\"same\")(t)\n    t = relu_bn(t)\n    \n    num_blocks_list = [2, 4, 2]\n    for i in range(len(num_blocks_list)):\n        num_blocks = num_blocks_list[i]\n        for j in range(num_blocks):\n            t = residual_block(t, downsample=(j==0 and i!=0), filters=num_filters)\n        num_filters *= 2\n    \n    t = AveragePooling2D(4)(t)\n    t = Flatten()(t)\n    outputs = Dense(len(classes), activation='sigmoid')(t)\n    \n    model = Model(inputs, outputs)\n\n    return model<\/pre>\n\n\n\n<p>The code below creates a model with the function<em> create_res_net <\/em>and assigns it to the variable<em> model<\/em>. The variable <em>model<\/em> is compiled with the<em> binary_crossentropy<\/em> loss function. The summary method shows the structure of the model.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">model = create_res_net()  \nmodel.compile(Adam(lr=.00001), loss=\"binary_crossentropy\", metrics=['accuracy'])\nmodel.summary()<\/pre>\n\n\n\n<p>For training and validation you need to specify the number of steps: <em>steptrainimages<\/em> and <em>stepsvalidimages<\/em>. We can calculate them by dividing the number of training images with the batchsize for training and the number of validation images with the batchsize for validation.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">stepstrainimages = lentrain\/\/batchsizetrain\nstepsvalidimages = lenvalid\/\/batchsizevalid<\/pre>\n\n\n\n<p>Below, the code trains the model with its<em> fit<\/em> method. Parameters are the generators <em>generator_train<\/em> and <em>generator_valid<\/em>. Also the number of steps (<em>stepstrainimages<\/em> and <em>stepsvalidimages<\/em>) need to be given. After execution, the<em> fit<\/em> function returns its history content into variables. They can be used to create a checkpoint <em>modelsaved<\/em> with details on <em>loss<\/em>, <em>valid_loss<\/em>, <em>accuracy<\/em> and <em>val_accuracy<\/em>. By looking at the model&#8217;s checkpoint name, we can see an indication of of quality of the training. The code saves the checkpoint with its<em> save_weights<\/em> method.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">hist = model.fit(generator_train,steps_per_epoch=stepstrainimages, epochs=10, validation_data=generator_valid, validation_steps=stepsvalidimages)\n\ntl=hist.history['loss'][-1]\nvl=hist.history['val_loss'][-1]\nta=hist.history['accuracy'][-1]\nva=hist.history['val_accuracy'][-1]\n\nmodelsaved = f\"model_{net}_{tl:.2f}_{vl:.2f}_{ta:1.3f}_{va:1.3f}.h5\"\nmodel.save_weights(os.path.join(model_path,modelsaved))<\/pre>\n\n\n\n<p>After training we compared the training losses with the validation losses. We find, that the training losses are smaller compared to the validation losses which indicates an overfitting. In the result section below we show the prediction results with testing data.   <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Result<\/h2>\n\n\n\n<p>We created not only a lookup table for training data and validation data, but also for testing data in the file <em>test.csv<\/em>. We set aside for this around 300 images. Then we ran the 300 images through the prediction method of <em>model<\/em> and compared the results with the attribute values in the lookup table. We are not showing here the code. Below you find the percentages of correct answers for the attributes &#8220;good&#8221;, &#8220;error&#8221;, &#8220;break&#8221; and &#8220;length&#8221;. We see that the &#8220;length&#8221; exceeds 100 percent correctness (all images were predicted concerning attribute &#8220;length&#8221; correctly). While &#8220;error&#8221; and &#8220;break&#8221; have a prediction accuracy of around 97 percent. Only the &#8220;good&#8221; category has less accuracy. We explain this, because student&#8217;s decision for &#8220;good&#8221; during labeling could be very error prone. Six students can label the attribute &#8220;good&#8221; very  subjectively. While the decision to label an attribute &#8220;error&#8221;, &#8220;break&#8221; and &#8220;length&#8221; is much clearer to make.  <\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Good Accuracy: 82.70270270270271 \nError Accuracy: 97.2972972972973 \nBreak Accuracy: 97.02702702702702 \nLength Accuracy: 100.<\/pre>\n\n\n\n<p>To do further validation on the test images, we created heatmaps. A heatmap can point out the pixels, which leads to the neural network&#8217;s classification decision. We have described the code for creating heatmaps<a href=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/2021\/08\/\"> here<\/a>, so we will not show it in this post anymore.<\/p>\n\n\n\n<p>Figure 7 shows four heatsmaps. The left most picture shows correctly an error in blue. This means that the model predicts correctly the pixels around the seam error which led to the right classification decision. The same is true for the second left picture. We have here a break, and the heatmap showing correctly the pixels of the break in green.  Since we have multi-label classification, there are cases where we have an error and a break at the same time. The second right picture is showing this case. Also here the decision was made correctly with pixels around the breaks in green and around the error in blue. The most right picture shows the pixels in red, leading to the length decision. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"249\" src=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/GradCAM-1024x249.png\" alt=\"\" class=\"wp-image-4528\" srcset=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/GradCAM-1024x249.png 1024w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/GradCAM-300x73.png 300w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/GradCAM-768x187.png 768w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/GradCAM.png 1310w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Figure 7: Heatmaps<\/figcaption><\/figure>\n\n\n\n<p>One last remark. The <em>generatebatchdata<\/em> function we described above did some data augmentation by changing the brightness and the contrast of 70 percent of all images. Actually we did even more data augmentation, which was not shown here. The 15000 images were doubled to 30000 images by modifying them in the following way. We took a portion of the upper image out and appended this portion to the bottom part of the image. You find in Figure 8 an illustration how we did this. These are simple OpenCV functions, so we leave out the code here.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"669\" height=\"247\" src=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/augment.png\" alt=\"\" class=\"wp-image-4519\" srcset=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/augment.png 669w, https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/files\/2021\/08\/augment-300x111.png 300w\" sizes=\"auto, (max-width: 669px) 100vw, 669px\" \/><figcaption>Figure 8: Data Augmentation<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Acknowledgement<\/h2>\n\n\n\n<p>Special thanks to the class of Summer Semester 2021&nbsp;<em>Forschungsprojekt Industrie 4.0<\/em>&nbsp;providing 15000 images for the training data used for the neural network training. We appreciate this very much, and we know how much effort you have put into this.<\/p>\n\n\n\n<p>Also special thanks to the University of Applied Science Albstadt-Sigmaringen for hosting the learn-factory and providing the appliances to enable this research.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Once a year we offer a class called Forschungsprojekt Industrie 4.0 (Research Project 4.0), which is a practical assignment to enrolled of students. This time the goal of the assignment was to create a quality control system which recognizes errors and breaks on textile seams. Six students enrolled to this specific assignment. In Figure &hellip; <a href=\"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/2021\/08\/05\/recognizing-seam-errors-and-breaks-with-a-residual-neural-network\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Recognizing Seam Errors and Breaks with a residual neural network<\/span><\/a><\/p>\n","protected":false},"author":24,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[4,3,5,8,9],"class_list":["post-4512","post","type-post","status-publish","format-standard","hentry","category-allgemein","tag-ai","tag-deep-learning","tag-ki","tag-residual-neural-network","tag-rnn"],"_links":{"self":[{"href":"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/wp-json\/wp\/v2\/posts\/4512","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/wp-json\/wp\/v2\/users\/24"}],"replies":[{"embeddable":true,"href":"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/wp-json\/wp\/v2\/comments?post=4512"}],"version-history":[{"count":297,"href":"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/wp-json\/wp\/v2\/posts\/4512\/revisions"}],"predecessor-version":[{"id":4828,"href":"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/wp-json\/wp\/v2\/posts\/4512\/revisions\/4828"}],"wp:attachment":[{"href":"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/wp-json\/wp\/v2\/media?parent=4512"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/wp-json\/wp\/v2\/categories?post=4512"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www3.hs-albsig.de\/wordpress\/point2pointmotion\/wp-json\/wp\/v2\/tags?post=4512"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}