cancel
Showing results for 
Search instead for 
Did you mean: 

How to detect Glass with vl53l8cx histogram data

kgb
Associate III

Hello everyone, 

I am currently working with the VL53L8CX sensor to explore solutions for a specific use case. In particular, I am trying to detect glass material using the CNH (Compacted Normalized Histogram) data—more about this can be found under the topic: Artificial Intelligence Enabler .

From my research, I understand that the sensor generates a total of 121 histogram bins. However, due to constraints such as memory limitations, only 18 or 24 bins can be accessed, depending on the configuration used.

I started by configuring the sensor to retrieve the first 24 bins and set the subsample value to "1", so that each bin corresponds to a single zone. Then, I attempted to determine the first significant return using an interpolation algorithm I found online. According to the documentation, I multiplied the resulting value by 3.75 to obtain the actual distance.

def interpolate_bin(signal_values, ambient_light_level):
    for i in range(len(signal_values) - 1):
        if signal_values[i] < ambient_light_level <= signal_values[i + 1]:
            interpolated_bin = i +1+ (ambient_light_level - signal_values[i]) / (signal_values[i + 1] - signal_values[i])
            return interpolated_bin
    return None



However, when I hold a glass material in front of the sensor and apply this method, I don’t get convincing results. Therefore, I would like to know if I might be missing some details, or perhaps I am using the wrong method to compute the First Significant Bin (FSB).

Below is my VL53L8CX code:

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  //UartComm_Restart();

  //__________MY CODE ___________
  Reset_Sensor();
  p_dev.platform.address = VL53LMZ_DEFAULT_I2C_ADDRESS;




 if (status != VL53LMZ_STATUS_OK){
 		printf("vl53lmz_init failed : %d\n");
 		return status;
 	}

	/* (Mandatory) Initialise the VL53LMZ sensor */
	status = vl53lmz_init(&p_dev);
	if(status)
	{
		printf("VL53LMZ ULD Loading failed\n");
		return status;
	}

	printf("VL53LMZ ULD ready ! (Version : %s)\n",
			VL53LMZ_API_REVISION);

	status = vl53lmz_set_resolution(&p_dev, 16);
	status |= vl53lmz_set_ranging_mode(&p_dev, VL53LMZ_RANGING_MODE_AUTONOMOUS);
	status |= vl53lmz_set_ranging_frequency_hz(&p_dev, 5);
	status |= vl53lmz_set_integration_time_ms(&p_dev, 100);
	if(status)
	{
		printf("ERROR - Failed basic configuration sequence, status=%u\n", status);
		return status;
	}

	status = vl53lmz_cnh_init_config( &cnh_config,
											0,		/* StartBin 	*/
											24,		/* NumBins   	*/
											1);	/* SubSample 	*/

	if (status != VL53LMZ_STATUS_OK){
			printf("VL53LMZ CNH init config failed\n");
			return status;
		}

	status = vl53lmz_cnh_create_agg_map( &cnh_config,
												16,				/* Resolution. Must match value used in vl53lmz_set_resolution() */
												0,				/* StartX 		*/
												0,				/* StartY 		*/
												1,				/* MergeX		*/
												1,				/* MergeY		*/
												4,				/* Cols			*/
												4);			/* Rows 		*/

	if (status != VL53LMZ_STATUS_OK){
			printf("VL53LMZ CNH set aggregate map failed\n");
			return status;
		}

	status = vl53lmz_cnh_calc_required_memory( &cnh_config, &cnh_data_size );
	if (status != VL53LMZ_STATUS_OK){
		printf("VL53LMZ CNH calc required memory failed\n");
		if (cnh_data_size < 0){
			printf("Required memory is too high : %d. Maximum is %d!\n", (int)-cnh_data_size, (int)VL53LMZ_CNH_MAX_DATA_BYTES);
		}
		return status;
	}


	/* Send this CNH configuration to the sensor.										*/
	status = vl53lmz_cnh_send_config(&p_dev,&cnh_config);
	if (status != VL53LMZ_STATUS_OK){
		printf("VL53LMZ CNH send config failed\n");
		return status;
	}

	/* First create the standard data upload(output) configuration. 						*/
	status = vl53lmz_create_output_config(&p_dev);
	if (status != VL53LMZ_STATUS_OK){
		printf("VL53LMZ CNH create output config failed\n");
		return status;
	}

	/* Next, add the CNH data block, sized correctly for the configuration we are using. */
	union Block_header cnh_data_bh;
	cnh_data_bh.idx = VL53LMZ_CNH_DATA_IDX;
	cnh_data_bh.type = 4;
	cnh_data_bh.size = cnh_data_size / 4;
	status = vl53lmz_add_output_block(&p_dev, cnh_data_bh.bytes);
	if (status != VL53LMZ_STATUS_OK){
		printf("VL53LMZ CNH add output block failed\n");
		return status;
	}

	status = vl53lmz_send_output_config_and_start(&p_dev);

	   	printf("Started ranging\n");



  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
	   	while (1)

	   	{

	   		   		// __________________ MY CODE _______________

	   		   	    status = vl53lmz_check_data_ready(&p_dev, &isReady);
	   		   	    if (isReady)
	   		   	    {


	   		   	        vl53lmz_get_ranging_data(&p_dev, &Results);
	   		   		    wait_for_dma_to_complete();

	   		   	        printf("Print data no : %3u\n", p_dev.streamcount);

									  for (int i = 0; i < 16; i++) {
									if (Results.target_status  [VL53LMZ_NB_TARGET_PER_ZONE * i] != 0) {
									printf("Zone : %3d, Status : %3u, Distance : %4d mm\n", i,
									Results.target_status[VL53LMZ_NB_TARGET_PER_ZONE * i],
									Results.distance_mm[VL53LMZ_NB_TARGET_PER_ZONE * i]);
									}
	   		   	              else {
	   		   	                  // Wenn keine Ziele erkannt wurden
	   		   	                  printf("Zone %d: No target detected.\r\n", i);
	   		   	              }
	   		   	  }




	   		   	        status = vl53lmz_results_extract_block(&p_dev, VL53LMZ_CNH_DATA_IDX, (uint8_t *)cnh_data_buffer, cnh_data_size);
	   		   	        if (status != VL53LMZ_STATUS_OK)
	   		   	        {
	   		   	            printf("ERROR at %s(%d) : vl53lmz_results_extract_block failed : %d\n", __func__, __LINE__, status);
	   		   	            return status;
	   		   	        }

	   		   	        for (agg_id = 0; agg_id < cnh_config.nb_of_aggregates; agg_id++)
	   		   	        {
	   		   	            vl53lmz_cnh_get_block_addresses(&cnh_config,
	   		   	                                            agg_id,
	   		   	                                            cnh_data_buffer,
	   		   	                                            &(p_hist), &(p_hist_scaler),
	   		   	                                            &(p_ambient), &(p_ambient_scaler));

	   		   	            amb_value = ((float)*p_ambient) / (2 << *p_ambient_scaler);

	   		   	            printf("Agg, %2d, Ambient, % .1f, Bins, ", agg_id, amb_value);

	   		   	            for (bin_num = 0; bin_num < cnh_config.feature_length; bin_num++)
	   		   	            {
	   		   	                bin_value = ((float)p_hist[bin_num]) / (2 << p_hist_scaler[bin_num]);
	   		   	                printf("% .1f, ", bin_value);
	   		   	            }

	   		   	            printf("\n");
	   		   	        }


	   		   	    }

	   		   	    // Wait a few ms to avoid too high polling
	   		   	   WaitMs(&(p_dev.platform), 10);

	   		   	}
}


Here is an example of my CNH data when I hold a glass sheet approximately 37 cm in front of the sensor. However, when I apply the method described above, I get a result between 34 cm and 35 cm—which is clearly inaccurate.

measurements2 = [
{"Agg": 0, "Ambient":  3.1, "Bins":[ -3.1, -3.1, -3.1, -3.1, -3.1, -3.1, -3.1, -1.4, -0.3,  26.8,  83.1,  105.6,  116.3,  122.1,  122.9,  125.4,  123.1,  73.9,  36.0,  22.9,  18.6,  16.3,  15.3,  12.6]},
{"Agg": 1, "Ambient":  3.6, "Bins":[ -3.6, -3.6, -3.6, -3.6, -3.6, -3.6, -3.6, -0.6,  3.8,  58.5,  108.8,  136.7,  147.8,  155.7,  159.4,  159.8,  137.5,  71.5,  36.0,  21.5,  12.6,  7.9,  5.9,  5.1 ]},
{"Agg": 2, "Ambient":  3.9, "Bins":[-3.9, -3.9, -3.9, -3.9, -3.9, -3.9, -2.4,  0.2,  5.9,  57.0,  103.1,  124.9,  140.1,  146.6,  152.9,  151.9,  126.5,  71.3,  40.0,  23.4,  13.6,  8.1,  5.6,  3.6]},
{"Agg": 3, "Ambient":  3.3, "Bins":[ -3.3, -3.3, -3.3, -3.3, -3.3, -3.3, -3.3, -1.0,  1.4,  13.8,  52.6,  81.1,  99.5,  109.1,  112.9,  115.5,  113.4,  87.0,  50.5,  29.7,  18.0,  10.3,  6.8,  3.6]},  
{"Agg": 4, "Ambient":  4.5, "Bins":[ -4.5, -4.5, -4.5, -4.5, -4.5, -4.5, -4.5, -2.1,  1.9,  69.1,  137.8,  168.6,  181.0,  181.0,  181.4,  185.5,  162.3,  72.5,  28.1,  14.7,  8.5,  5.6,  4.0,  3.3]}, 
{"Agg": 5, "Ambient":  4.8, "Bins":[ -4.8, -4.8, -4.8, -4.8, -4.8, -4.8, -4.3, -1.6,  15.3,  141.9,  219.9,  256.7,  277.3,  272.9,  282.1,  274.0,  209.6,  81.4,  36.1,  17.5,  9.4,  6.6,  4.5,  4.2]}, 
{"Agg": 6, "Ambient":  4.9, "Bins":[ -4.9, -4.9, -4.9, -4.9, -4.9, -4.9, -2.5, -0.8,  16.5,  112.9,  175.8,  203.8,  217.5,  224.1,  223.2,  222.5,  166.6,  70.9,  31.5,  15.7,  9.4,  7.0,  5.2,  4.6]}, 
{"Agg": 7, "Ambient":  4.6, "Bins":[ -4.6, -4.6, -4.6, -4.6, -4.6, -4.6, -3.6, -0.9, -0.3,  34.1,  99.6,  134.6,  154.5,  158.8,  158.4,  158.0,  152.7,  94.3,  38.2,  17.1,  9.5,  6.1,  3.8,  3.3]}, 
{"Agg": 8, "Ambient": 4.7, "Bins":[ -4.7, -4.7, -4.7, -4.7, -4.7, -4.7, -4.5, -1.7, -0.8,  51.4,  119.6,  152.1,  161.1,  170.0,  170.2,  172.1,  153.9,  78.4,  29.1,  13.4,  7.4,  3.8,  2.8,  2.4]}, 
{"Agg": 9, "Ambient": 5.0, "Bins":[ -5.0, -5.0, -5.0, -5.0, -5.0, -5.0, -3.2, -1.0,  11.6,  107.0,  166.8,  195.9,  207.2,  213.8,  214.7,  213.7,  159.8,  65.6,  27.3,  13.4,  7.3,  4.9,  4.1,  3.2]}, 
{"Agg": 10, "Ambient":  5.1, "Bins":[ -5.1, -5.1, -5.1, -5.1, -5.1, -5.1, -2.8, -0.6,  8.6,  89.5,  150.3,  177.8,  189.4,  192.4,  191.4,  193.0,  151.9,  65.4,  27.6,  14.1,  7.7,  4.8,  3.6,  3.2]}, 
{"Agg": 11, "Ambient":  4.8, "Bins":[ -4.8, -4.8, -4.8, -4.8, -4.8, -4.8, -3.3, -0.8, -0.3,  22.7,  85.2,  119.6,  135.8,  141.8,  144.6,  144.7,  142.4,  94.7,  40.1,  18.0,  8.2,  4.8,  3.8,  3.1]}, 
{"Agg": 12, "Ambient":  5.2, "Bins":[ -5.2, -5.2, -5.2, -5.2, -5.2, -5.2, -4.0, -1.1,  0.2,  13.8,  72.1,  114.3,  131.4,  139.2,  138.8,  141.4,  135.3,  103.6,  44.3,  17.6,  8.9,  4.5,  2.7,  2.0]}]


I’m wondering if I might be missing a step in the CNH processing or if there’s a better approach to accurately detect transparent materials like glass using this sensor. Any insights, suggestions, or corrections would be greatly appreciated!

4 REPLIES 4
RogerM
ST Employee

If you require to get best range accuracy from features extracted from histogram data you need to apply additional correction factors on each zone based on calibration data read from the device. The standard zone based target range data already incorporates these correction factors, but the CNH data does not.

Code in the Python scripts package - "STSW-IMG043_PY - v1.0.0-r13748" shows how these correction factors should be applied.

A top level script core_examples/cnh_feature_extract.py shows how to read data from a MZ-AI_EVK data log, extract a feature from the CNH data(rising edge position) and convert it to a range in mm.

Within the utils/cnh_lib.py package the class BinToRangeConverter converts between a <bin-position-within-a-histogram> to a <range-in-mm>.

The offset_comp parameter contains offsets that are to be applied to ranges derived from each zone. This parameter should be set to the values read from the sensor, viewable in GUI as cnh__offset_comp. The values in this array are measured during device production testing and programmed into on-board NVM storage. They compensate for small zone-to-zone differences in electrical timing across the SPAD array.

The scale_comp parameter is used to convert from radial to perpendicular ranges from the sensor. This parameter should be set to the values taken from cnh__rad2perp_scale_comp. This correction is only required if ranges in terms of perpendicular distance are required.

RogerM
ST Employee

With regards to detection of glass...
Glass is highly specular surface so when you point the ToF at glass you tend to see a very strong return signal just a very small area of the glass where the normal of the glass is directly towards the ToF sensor. You should be able to see this easily if you display "signal-per-spad" in th GUI while pointing the sensor at glass. On the zone(s) where you see this strong return signal you should be able to extract the range to the glass from the histogram data. Possibly on zones around this specular peak zone you might also be able to get a range but the signal will much much lower, possible by factor of 10 to 100 less !

kgb
Associate III

Hallo @RogerM ,

Thanks for your reply. I may have forgotten to mention that I’m using a Linux PC, so I don’t have the option to run the MZAI_EVK software directly. I had initially tried to log the output data from the MZ-AI_Kit_r13933  myself, but it turned out to be quite complicated, and I didn’t want to spend too much time on that part—so I decided to proceed step by step.

That said, I’ll first try to see if the provided Python scripts could help me, although I suspect most of them were designed to work with data already logged from the MZAI_EVK.

RogerM
ST Employee

So inspection of the Python scripts should help, but you will need to look into the source code of the STM32 firmware that is used with the GUI to see how the cnh__offset_comp and cnh__rad2perp_scale_comp are read and pre-process by the firmware.  I can point out where to look...

In source file  apps_layer/mz_ai_main.c

g_AdditionalResults.agg_offset_mm  is cnh__offset_comp
----------------------------------
Read during device configuration.
mz_ai_main - Lines 439 to 447

Values later modified by small amounts from frame to frame based on ref_residual value. This compensates for temperate changes on the device.
mz_ai_main - Lines 783 and 602.


g_AdditionalResults.agg_rad2perp_scale_grid is cnh__rad2perp_scale_com
-------------------------------------------
Read once after first frame of data received.
mz_ai_main - Lines 759 to 764
Data has 12 fractional bits => divide integer value by 4096 to get the correct actual value (maximum value = 0.9999