Overcoming NSP Noise is an independent project investigating the efficacy of machine learning on images taken on cameras under high thermal load. The project has two main parts, one is establishing an automated image capture system, and two is to generate a high quantity of images using this automated setup for the training of a noisy-image-trained neural network.
Christopher Bruinsma and Yuhao Zhu at Horizon Research, University of Rochester
Images will be captured using a FLIR BlackFly USB3 camera which has thermal-noise induced using a heat gun from Wagner.
In order to safely heat the camera, Python code is used which relies on the Spinnaker SDK to monitor the camera temperature which is limited by the camera to less than 100°C. The heat gun is also automated around the temperature of the camera. This is achieved through the use of Python-Kasa which will power on and off the heat-gun by turning on and off a Smart-Plug
Dependencies
Python3.8
sys
asyncio : pip install asyncio
tensorflow : pip install tensorflow
poetry : pip install poetry
json
PySpin : pip install spinnaker_python-2.6.0.156-cp38-cp38-macosx_10_14_x86_64.whl
kasa : pip install kasa
keras : pip install keras
time
graphviz : pip install graphviz
pydot : pip install pydot
numpy : pip install numpy
PIL
cv2 : pip install cv2
Under the alias of HeatTrigger.py this code adds on to the code written by the Spinnaker SDK Trigger.py
This was added to dynamically access the camera temperature : GetCameraTemperature(cam)
def GetCameraTemperature(cam):
if cam.DeviceTemperature.GetAccessMode() == PySpin.RO:
return float(cam.DeviceTemperature.ToString())
I have added as well: Heat(cam,GoalTemperature)
which is run inside of Capture(cam,temp)
(below)
# Does the temperature sensing during the loops.
def Heat(cam, GoalTemperature):
# Get Temperature of Camera
Temp = GetCameraTemperature(cam)
# Heating
if Temp > GoalTemperature + 2.5:
return False
else:
asyncio.run(HeatGun.On())
"""
Continue Heating unitl goal temperature is achieved
"""
TempBar = Bar('Heating',fill='█',index=Temp,max=GoalTemperature)
while Temp < GoalTemperature:
Temp = GetCameraTemperature(cam)
time.sleep(2)
TempBar.next(BarProgress(GetCameraTemperature(cam),Temp))
TempBar.finish()
asyncio.run(HeatGun.Off())
return True
def Capture(cam,temp):
"""
This function acts as the body of the example; please see NodeMapInfo example
for more in-depth comments on setting up cameras.
:param cam: Camera to run on.
:type cam: CameraPtr
:return: True if successful, False otherwise.
:rtype: bool
"""
try:
result = True
# Retrieve TL device nodemap and print device information
nodemap_tldevice = cam.GetTLDeviceNodeMap()
cam.Init()
# Retrieve GenICam nodemap
nodemap = cam.GetNodeMap()
# Configure trigger
if configure_trigger(cam) is False:
return False
result &= AutoExposure(cam)
if(Heat(cam,temp)):
# Acquire images
result &= acquire_images(cam, nodemap, nodemap_tldevice)
# Reset trigger
result &= reset_trigger(nodemap)
cam.DeInit()
result = True
else:
print('\
Camera is %d°C your temperature was %s°C, please allow the camera to cool and try again.'
% (GetCameraTemperature(cam),temp))
cam.DeInit()
return False
except PySpin.SpinnakerException as ex:
print('Error: %s' % ex)
result = False
return result
Heat Gun Automation
class HeatGun:
async def On():
print("Heat Gun Power On")
HeatGun = s.SmartPlug('192.168.0.1')
await HeatGun.turn_on()
async def Off():
print("Heat Gun Power Off")
HeatGun = s.SmartPlug('192.168.0.1')
await HeatGun.turn_off()
The heat testing is done using a loop in the main()
method.
for i, cam in enumerate(cam_list):
try:
if(Capture(cam,int(argv))):
print('Capture Completed.')
except PySpin.SpinnakerException as ex:
print('Capture Failed.')
break
del cam # It's about the little things in programming...
$ Python3.8 HeatTrigger.py temperature
The numbering relies on the Accuracy.json
which stores the number of captures after each capture.
In the terminal it looks like the following:
Cold to Hot
$ Python3.8 HeatTrigger.py 48
...
Heat Gun Power On
Heating |████████████████████████████████| 48/48
Heat Gun Power Off
Acquiring images...
Image saved at sample-18255214-2913-48.png
Image saved at sample-18255214-2914-48.png
Image saved at sample-18255214-2915-48.png
Image saved at sample-18255214-2916-48.png
Image saved at sample-18255214-2917-49.png
Trigger mode disabled...
Capture Completed.
Over Temperature
$ Python3.8 HeatTrigger.py 25
...
Camera is 49°C your temperature was 25°C, please allow the camera to cool and try again.
Capture Failed.
The main metric measured for images in this project is the Signal-To-Noise ratio of these images. This is done using SNR.py
Due to the nature of image processing of noisy images, max-pooling will likely be used alongside some kind of edge detection algoritm. This aspect very much remains in the research stage, but as of right now the goal is to train a Convolution Neural Network to identify cups of coffee that are either hot or iced. This implementation relies on TensorFlow.Keras.
This machine-learning model relies heavily on the ResNet model and has 34 layers which use the implementation of residual being added back.
$ IgnoresThermal.py
and $ NoThermal.py
Identify cups of coffee as either iced or hot. This will be done using a variety of coffee cups from the on-campus Starbucks here at the University that contain hot or iced coffee. These are contained within the Training_Data
and CleanTestImages
folders.
I have done the following to create a neural network that uses data augmentation to virtually increase the sample size, as well as varying sized convolution kernels, batch normalization, making more dense the layers of the network and finally dropping layers out at each iteration to help train the network of more key characteristics.
""" Citations:
Cites: https://towardsdatascience.com/an-overview-of-resnet-and-its-variants-5281e2f56035
"""
def Block(x,size):
x = layers.Activation("sigmoid")(x)
x = layers.Conv2D(size, 3, strides=2, padding="same")(x)
return layers.BatchNormalization()(x)
def SixtyFour(x):
for i in range(0,3):
x = Block(x,64)
previous_block_activation = x
residual = layers.Conv2D(64, 3, strides=2, padding="same")(
previous_block_activation
)
x = Block(x,64)
x = layers.add([x, residual]) # Add back residual
previous_block_activation = x
x = layers.Dropout(0.3)(x)
return x
def OneTwentyEight(x):
for i in range(0,4):
x = Block(x,128)
previous_block_activation = x
residual = layers.Conv2D(128, 3, strides=2, padding="same")(
previous_block_activation
)
x = Block(x,128)
x = layers.add([x, residual]) # Add back residual
previous_block_activation = x
x = layers.Dropout(0.3)(x)
return x
def TwoFiftySix(x):
for i in range(0,6):
x = Block(x,256)
previous_block_activation = x
residual = layers.Conv2D(256, 3, strides=2, padding="same")(
previous_block_activation
)
x = Block(x,256)
x = layers.add([x, residual]) # Add back residual
previous_block_activation = x
x = layers.Dropout(0.3)(x)
return x
def FiveTwelve(x):
for i in range(0,3):
x = Block(x,512)
previous_block_activation = x
residual = layers.Conv2D(512, 3, strides=2, padding="same")(
previous_block_activation
)
x = Block(x,512)
x = layers.add([x, residual]) # Add back residual
previous_block_activation = x
x = layers.Dropout(0.3)(x)
return x
"""
Assembling the model
"""
def NModel(input_shape, num_classes):
inputs = keras.Input(shape=input_shape)
# Image augmentation block
# Entry block
"""
This is a modified ResNet Model
"""
x = data_augmentation(inputs)
x = layers.Rescaling(1.0 / 255)(x)
x = layers.Conv2D(64, 7, strides=2, padding="same")(x)
x = (lambda x: FiveTwelve(TwoFiftySix(OneTwentyEight(SixtyFour(x)))))(x)
x = layers.Rescaling(1.0 / 2)(x)
x = (lambda x: FiveTwelve(TwoFiftySix(OneTwentyEight(SixtyFour(x)))))(x)
x = layers.GlobalAveragePooling2D()(x)
if num_classes == 2:
activation = "sigmoid"
units = 1
else:
activation = "softmax"
units = num_classes
x = layers.Dropout(0.5)(x)
outputs = layers.Dense(units, activation=activation)(x)
return keras.Model(inputs, outputs)
2022-03-03 00:26:01.678284: I tensorflow/core/platform/cpu_feature_guard.cc:151] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
Found 599 files belonging to 2 classes.
Using 119 files for validation.
Please Enter 1 for compile 2 for test and 3 for both or and key to close
2
Current accuracy is 0.537177:
You have selected test
Images of hot coffee
This image is 83.03 percent hot coffee and this image is 16.97 percent iced coffee.
This image is hot coffee
This image is 59.49 percent hot coffee and this image is 40.51 percent iced coffee.
This image is hot coffee
Images of iced coffee
This image is 16.03 percent hot coffee and this image is 83.97 percent iced coffee.
This image is cold coffee
This image is 41.25 percent hot coffee and this image is 58.75 percent iced coffee.
This image is cold coffee
Current final accuracy is 0.550496:
Too soon to be determined. Images only number 600 and overfitting is quite a risk.
Dirty Pixels: Towards End-to-End Image Processing and Perception Diamond Et. Al.
Dynamic Temperature Management of Near-Sensor Processing for Energy-Efficient High-Fidelity Imaging. Kodukula Et Al.
Feng, Vincent. 2017. “An Overview of ResNet and its Variants | by Vincent Feng.” Towards Data Science. https://towardsdatascience.com/an-overview-of-resnet-and-its-variants-5281e2f56035.
FLIR. (n.d.). Spinnaker-SDKVersion (Trigger.py). Spinnaker SDK. Retrieved from https://www.flir.com/products/spinnaker-sdk/.
FLIR. (n.d.). Spinnaker-SDKVersion (Exposure_QuickSpin.py). Spinnaker SDK. Retrieved from https://www.flir.com/products/spinnaker-sdk/.
FLIR. (n.d.). Spinnaker-SDKVersion (ImageFormatControl_QuickSpin.py). Spinnaker SDK. Retrieved from https://www.flir.com/products/spinnaker-sdk/.
FLIR Integrated Imaging Solutions, Inc. (n.d.). PySpinDoc.
François Chollet, Team, K. (n.d.). Keras documentation: Image Classification From Scratch. Keras. Retrieved February 22, 2022, from https://keras.io/examples/vision/image_classification_from_scratch/
n.d. python-kasa — python-kasa documentation. Accessed April 5, 2022. https://python-kasa.readthedocs.io/en/latest/.
University of Rochester and Yuhao Zhu. n.d. “Lecture 8: Image Sensing and Sensor Design II.” Fall 2021. Rochester, New York. Accessed March, 2022. https://www.cs.rochester.edu/courses/572/fall2020/decks/sensor-2.pd