Image-processing script used to detect marking dots, follow their displacement and estimate lateral deformation during traction tests.
clear; close all; clc; % =====================================================================% Image_treatment_dots.m v7 (white sample, TWO dark dots)%% Suit la DISTANCE entre deux points marques sur la jauge -> allongement% REEL du materiau (insensible au glissement). H_mm = distance points,% D_mm = diametre a mi-distance, nu = -eps_lat/eps_axial.% Sauvegarde le MEME .mat que les autres scripts image.%% -----------------------------------------------------------------% CHANGEMENT v6 -> v7 (corrige la detection sur images SOMBRES) :% v6 : seuil FIXE (dot_dark_frac * luminosite). Sur images peu eclairees% les points gris passaient sous le bruit -> 0/6/1 points detectes,% distance erratique, allongement parfois NEGATIF (E corrige absurde).% v7 : seuil ADAPTATIF par image. On isole d'abord la colonne, puis on% prend les pixels les plus SOMBRES de l'interieur (percentile bas)% comme candidats-points, on enleve le bruit (ouverture morpho), et% on garde les 2 plus GROS blobs proches de l'axe. Beaucoup plus% robuste a un eclairage faible/variable.% -> verifie sur 0,1molEchantillon2 : 60/61 images, distance% croissante et monotone.%% Necessite l'Image Processing Toolbox (bwconncomp/regionprops/imopen).% A EDITER : base_folder, folder_name, scale ; au besoin dot_pct.% ===================================================================== base_folder = 'C:\Users\imran\OneDrive\Documents\MATLAB\potato\0,2mol';folder_name = '0,2molEchantillon2\';scale = 0.02239; % mm/pixel -- re-deriver si distance/zoom change band_frac = 0.075; % demi-largeur bande centrale (fraction largeur image)row_lo = 0.40; row_hi = 0.88;min_contrast = 25;edge_margin = 0.20; % fraction de largeur ignoree de chaque bord (evite le fond)dot_pct = 2.5; % les pixels < ce percentile (interieur) = candidats-points % (monter a ~4 si points peu visibles ; baisser si trop de bruit)dot_min_area = 8; % aire mini d'un point (px)dot_max_area = 2500; % aire maxi (px) -- rejette grosse ombreaxis_tol = 0.35; % un point doit etre a < axis_tol*largeur_bande de l'axemin_sep_frac = 0.30; % les 2 points separes de > min_sep_frac * hauteur jaugedebug_first = true; % entoure les points sur la 1ere image (VERIFIER) % ---------------------------------------------------------------------path = [base_folder '\RAW_DATA\' folder_name];files = dir([path '*.jpg']);if isempty(files), error('Aucune image .jpg dans %s', path); end[~,order] = sort({files.name}); files = files(order);n = numel(files); t = nan(n,1); H_mm = nan(n,1); D_mm = nan(n,1);xc_anchor = []; for k = 1:n name = files(k).name; I = imread([path name]); if size(I,3)==3, G = double(rgb2gray(I)); else, G = double(I); end [Himg,Wimg] = size(G); xc = round(Wimg/2); dx = round(Wimg*band_frac); x0 = max(1,xc-dx); band = G(:, x0:min(Wimg,xc+dx)); bw_px = size(band,2); if isempty(xc_anchor), xc_anchor = bw_px/2; end r0s = max(1, round(row_lo*Himg)); r1s = min(Himg, round(row_hi*Himg)); % --- 1) colonne SAMPLE : bords par ligne, suite continue --- yj=[];L=[];R=[];cs=[]; for r = r0s:r1s prof = band(r,:); pk = max(prof); bg = prctile(prof,20); if pk-bg < min_contrast, continue; end half = 0.5*(pk+bg); above = prof>half; if ~any(above), continue; end dd=diff([false,above,false]); st=find(dd==1); en=find(dd==-1)-1; ctr=(st+en)/2; [dist,wi]=min(abs(ctr-xc_anchor)); if dist>0.5*bw_px || en(wi)<=st(wi), continue; end yj(end+1)=r; L(end+1)=st(wi); R(end+1)=en(wi); cs(end+1)=ctr(wi); %#ok<AGROW> end if numel(yj)<30, warning('%s : echantillon non trouve', name); continue; end yj=yj(:);L=L(:);R=R(:);cs=cs(:); sp=find(diff(yj)>5); ss=[1;sp+1]; ee=[sp;numel(yj)]; [~,bi]=max(ee-ss); ix=ss(bi):ee(bi); yj=yj(ix);L=L(ix);R=R(ix);cs=cs(ix); top=yj(1); bot=yj(end); gauge_h=bot-top; xc_anchor = median(cs); % --- 2) interieur du sample + valeurs --- interior = false(size(band)); vals=[]; for j=1:numel(yj) w=R(j)-L(j); m=round(edge_margin*w); a=L(j)+m; b=R(j)-m; if b>a, interior(yj(j),a:b)=true; vals(end+1:end+(b-a+1))=band(yj(j),a:b); end %#ok<AGROW> end if numel(vals)<50, continue; end % --- 3) POINTS = pixels les plus sombres (seuil ADAPTATIF par percentile) --- thr = prctile(vals, dot_pct); BWdot = (band <= thr) & interior; BWdot = imopen(BWdot, strel('disk',1)); % enleve le bruit 1px CC = bwconncomp(BWdot); rp = regionprops(CC,'Centroid','Area'); cents=[]; areas=[]; for i=1:numel(rp) if rp(i).Area<dot_min_area || rp(i).Area>dot_max_area, continue; end if abs(rp(i).Centroid(1)-xc_anchor) > axis_tol*bw_px, continue; end cents=[cents; rp(i).Centroid]; areas=[areas; rp(i).Area]; %#ok<AGROW> end if size(cents,1)<2 warning('%s : moins de 2 points (monter dot_pct ?)', name); continue; end % garder les 2 plus GROS (vrais points > mouchetures), puis ordonner par y [~,is]=sort(areas,'descend'); keep=cents(is(1:min(3,numel(is))),:); [~,iy]=sort(keep(:,2)); cTop=keep(iy(1),:); cBot=keep(iy(end),:); dot_dist = hypot(cBot(1)-cTop(1), cBot(2)-cTop(2)); if dot_dist < min_sep_frac*gauge_h warning('%s : 2 points trop proches (%.0f px) -> ignore', name, dot_dist); continue; end % --- diametre sous-pixel a mi-distance --- ymid = min(max(round(mean([cTop(2) cBot(2)])), top), bot); prof = band(ymid,:); pk=max(prof); bg=prctile(prof,20); half=0.5*(pk+bg); above=prof>half; dd=diff([false,above,false]); st=find(dd==1); en=find(dd==-1)-1; ctr=(st+en)/2; [~,wi]=min(abs(ctr-xc_anchor)); l=st(wi); rr=en(wi); if l>1, xl=l-1+(half-prof(l-1))/(prof(l)-prof(l-1)+1e-9); else, xl=l; end if rr<numel(prof), xr=rr+(half-prof(rr))/(prof(rr+1)-prof(rr)-1e-9); else, xr=rr; end width_px = xr-xl; t(k) = str2double(name(1:2))*3600 + str2double(name(3:4))*60 + str2double(name(5:6)); H_mm(k) = dot_dist * scale; D_mm(k) = width_px * scale; if debug_first figure('Name','VERIF points (1ere image)'); imshow(uint8(band)); hold on; plot(cTop(1),cTop(2),'go','MarkerSize',16,'LineWidth',2); plot(cBot(1),cBot(2),'go','MarkerSize',16,'LineWidth',2); plot([cTop(1) cBot(1)],[cTop(2) cBot(2)],'g-'); plot([xl xr],[ymid ymid],'c-','LineWidth',2); title('VERIFIER : 2 cercles verts = vos points ; cyan = diametre'); hold off; debug_first=false; endend ok = ~isnan(t); t=t(ok); H_mm=H_mm(ok); D_mm=D_mm(ok);if numel(t)<3, error('Trop peu de frames exploitables (%d).', numel(t)); endt = t - t(1); eps_axial = (H_mm - H_mm(1)) / H_mm(1);eps_lat = (D_mm - D_mm(1)) / D_mm(1);pN = polyfit(eps_axial, eps_lat, 1); nu = -pN(1); figure;subplot(2,1,1); plot(t,H_mm,'-o'); grid on;xlabel('temps (s)'); ylabel('distance points (mm)'); title('Allongement reel (entre points)');subplot(2,1,2); plot(t,D_mm,'-o'); grid on;xlabel('temps (s)'); ylabel('diametre (mm)'); title('Diametre sous-pixel'); figure;plot(eps_axial, eps_lat, '+r'); hold on;plot(eps_axial, polyval(pN,eps_axial), '-k','LineWidth',1.5); hold off; grid on;xlabel('\epsilon_{axial} (points)'); ylabel('\epsilon_{lateral}');title(sprintf('Poisson \\nu = %.3f', nu)); fprintf('\n=== Image (suivi de points, v7) ===\n');fprintf('frames exploitees = %d / %d\n', numel(t), n);fprintf('distance initiale points = %.2f mm | diametre initial = %.2f mm\n', H_mm(1), D_mm(1));fprintf('allongement reel final = %.2f mm -> deformation axiale = %.3f\n', H_mm(end)-H_mm(1), eps_axial(end));fprintf('variation diametre = %+.1f %% | nu = %.3f\n', (D_mm(end)/D_mm(1)-1)*100, nu); if D_mm(end) >= D_mm(1) warning('Diametre non decroissant -> Poisson NON FIABLE.');elseif nu<0 || nu>0.5 warning('nu=%.3f hors [0;0.5] -> a verifier.', nu);else fprintf('-> diametre decroissant et nu dans [0;0.5] : POISSON PLAUSIBLE.\n');endif any(diff(H_mm) < -0.05*H_mm(1)) warning('Distance points non monotone -> un point a saute, verifier la detection.');else fprintf('-> distance points monotone : allongement/glissement FIABLE.\n');end save([base_folder '/META_DATA_TOLE/' folder_name 'width_temps.mat'], ... 't','H_mm','D_mm','eps_axial','eps_lat','nu','scale','-mat');