Le paramètre de position de l'image est implicite dans l'énoncé, il vaut initiallement (0,0) (en bas et à gauche, enfin normalement), et va changer par la suite. On imagine donc écrite une fonction do_vers_arbre img i j k dont la mission est de transformer la sous-image carrée de coté k positionnée dans img en (i,j) en un arbre. L'appel initial est alors :

let image_vers_arbre k img = do_vers_arbre img 0 0 k

Et voici do_vers_arbre :

let rec do_vers_arbre img i j k =
  if k <= 1 then Feuille img.(i).(j)
  else
    let
 k2 = k/2 in (* Je gagne 8 parenthèses *)
    let
 c1 = do_vers_arbre img i (j+k2) k2
    and c2 = do_vers_arbre img (i+k2) (j+k2) k2
    and c3 = do_vers_arbre img i j k2
    and c4 = do_vers_arbre img (i+k2) j k2 in
    match
 c1,c2,c3,c4 with
    | Feuille n1, Feuille n2, Feuille n3, Feuille n4
        when n1 = n2 && n2 = n3 && n3 = n4 -> c1
    | _,_,_,_ ->  Noeud (c1,c2,c3,c4)

On remarque toujours au sujet du filtrage :

Sur un autre plan, le paramètre img de do_vers_arbre ne change pas. On aurait donc pu économiser un argument à do_vers_arbre en utilisant une fonction locale :

let image_vers_arbre k img  =
  let rec do_vers_arbre i j k =
    if k <= 1 then Feuille img.(i).(j)
    else
      let
 k2 = k/2 in
      let
 c1 = do_vers_arbre img i (j+k2) k2
      ... in
  do_vers_arbre 0 0 k

C'est affaire de goût et de nombre de paramètres, essentiellement. (Noter quand même les aventures du paramètre k dans la version avec fonction locale obtenue par copier-coller).